I added in some poker rules to identify different hands. Still having a problem with two pair.
import collections
import copy
import random
class Card():
"""A playing card that has a rank and suit"""
# These are class variables. They are associated with the class, not instances of the class
suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
short_suit_names = ["C", "D", "H", "S"]
rank_names = ["", "Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]
short_rank_names = ["", "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]
rank_range = range(2, 15)
def __init__(self, rank, suit=None):
# These are instance variables. Each instance has their own rank an suit
if suit is None:
# Assume rank is a card repr. Extract rank and suit
suit = Card.suit_names[Card.short_suit_names.index(rank[-1])]
rank = Card.short_rank_names[2:].index(rank[:-1])+2
self.rank = rank
self.suit = suit
def __lt__(self, other):
"""For sorting and comparing by rank"""
return self.rank < other.rank # self.rank is my rank. other.rank is the rank of another card
def __gt__(self, other):
"""For sorting and comparing by rank"""
return self.rank > other.rank
def __eq__(self, other):
"""For sorting and comparing by rank"""
return self.rank == other.rank
def name(self):
"""Get long name for card"""
return f"{self.rank_names[self.rank]} {self.suit}"
def __repr__(self):
"""Get short name for card"""
return f"{self.short_rank_names[self.rank]}{self.suit[0]}"
class Hand():
"""A list of cards"""
def __init__(self, cards=None):
self.cards = [] if cards is None else cards
def ranks(self):
"""Group cards into ranks. Sort ranks by number of cards in a rank"""
def sort_key(item):
"""Sorting primarily on number of matching cards and secondarily on rank value"""
return (len(item), item[0].rank)
ranks = {}
for card in self.cards:
if card.rank in ranks:
ranks[card.rank].append(card)
else:
ranks[card.rank] = [card]
ranks = list(ranks.values())
ranks.sort(key=sort_key, reverse=True)
return ranks
def suits(self):
"""Group cards into suits. Sort suits by number of cards in a suit"""
suits = {}
for card in self.cards:
if card.suit in suits:
suits[card.suit].append(card)
else:
suits[card.suit] = [card]
suits = list(suits.values())
for suit in suits:
suit.sort(reverse=True)
suits.sort(key=len, reverse=True)
return suits
def __add__(self, other):
"""Create a new hand by combining two existing hands"""
return Hand(self.cards + other.cards)
def __repr__(self):
"""Print cards in hand"""
return ", ".join([str(card) for card in self.cards])
class Deck():
"""Deck of cards"""
def __init__(self, shuffle=False):
self.cards = [Card(rank, suit) for rank in Card.rank_range for suit in Card.suit_names]
if shuffle:
random.shuffle(self.cards)
def __len__(self):
"""Return number of cards in deck"""
return len(self.cards)
def deal(self, count):
"""Deal count cards from top of deck"""
cards = self.cards[:count]
self.cards = self.cards[count:]
return cards
class Rules():
ROYAL_FLUSH = 9
STRAIGHT_FLUSH = 8
FOUR_OF_A_KIND = 7
FULL_HOUSE = 6
FLUSH = 5
STRAIGHT = 4
THREE_OF_A_KIND = 3
TWO_PAIR = 2
ONE_PAIR = 1
HIGH_CARD = 0
HAND_NAMES = [
"High Card",
"One Pair",
"Two Pair",
"Three of a Kind",
"Straight",
"Flush",
"Full House",
"Four of a Kind",
"Straight Flush",
"Royal Flush"
]
@staticmethod
def ofAKind(ranks, count):
"""Return cards if we have four card with the same rank"""
for rank in ranks:
if len(rank) == count:
return rank
return None
@staticmethod
def flush(suits):
"""Return cards if there are 5 of the same suit"""
return suits[0] if len(suits[0]) > 4 else None
@staticmethod
def straight(ranks):
"""Return cards if there are 5 cards in a row"""
def sort_key(item):
"""Sort ranks by rank value"""
return item[0].rank
straight = None
# Sort the ranks by decreasing value
ranks.sort(key=sort_key, reverse=True)
if ranks[0][0].rank == 14:
# If we have aces, treat them as both rank 14 and rank 1
ranks.append([Card(1, card.suit) for card in ranks[0]])
# look for 5 consecutive ranks
for rank in ranks:
if straight is None or straight[-1][0].rank - rank[0].rank > 1:
straight = [rank]
else:
straight.append(rank)
if len(straight) >= 5:
return straight
return None
@staticmethod
def straightFlush(suits):
"""Return cards if there are 5 cards in a row with the same rank"""
if (cards := Rules.flush(suits)) is not None:
return Rules.straight(Hand(cards).ranks())
return None
@staticmethod
def royalFlush(suits):
"""Return cards if there is an ace high straight flush"""
if (cards := Rules.straightFlush(suits)) is not None and cards[0][0].rank == 14:
return cards
return None
@staticmethod
def fullHouse(ranks):
"""Return cards if there is a full house"""
if len(ranks) > 1 and len(ranks[0]) >= 3 and len(ranks[1]) >= 2:
return ranks[0] + ranks[1]
return None
@staticmethod
def twoPair(ranks):
"""Return cards if there are two pairs"""
if len(ranks) > 1 and len(ranks[0]) >= 2 and len(ranks[1]) >= 2:
return ranks[0] + ranks[1]
return None
@staticmethod
def highCard(ranks):
"""Return the high card"""
def sort_key(item):
"""Sort ranks by rank value"""
return item[0].rank
ranks.sort(key=sort_key, reverse=True)
return ranks[0][0].rank
@classmethod
def evaluate(cls, hand):
ranks = hand.ranks()
suits = hand.suits()
high_card = cls.highCard(ranks)
if (cards := cls.royalFlush(suits)):
return cls.ROYAL_FLUSH, cards, high_card
elif (cards := cls.straightFlush(suits)):
return cls.STRAIGHT_FLUSH, cards, high_card
elif (cards := cls.ofAKind(ranks, 4)):
return cls.FOUR_OF_A_KIND, cards, high_card
elif (cards := cls.fullHouse(ranks)):
return cls.FULL_HOUSE, cards, high_card
elif (cards := cls.flush(suits)):
return cls.FLUSH, cards, high_card
elif (cards := cls.straight(ranks)):
return cls.STRAIGHT, cards, high_card
elif (cards := cls.ofAKind(ranks, 3)):
return cls.THREE_OF_A_KIND, cards, high_card
elif (cards := cls.twoPair(ranks)):
return cls.TWO_PAIR, cards, high_card
elif (cards := cls.ofAKind(ranks, 2)):
return cls.ONE_PAIR, cards, high_card
else:
return cls.HIGH_CARD, high_card, high_card
# Test different hands
flop = Hand([Card(card) for card in ["KS", "QS", "JS"]])
print("Royal Flush", Rules.evaluate(Hand([Card(card) for card in ["AC", "AD", "AH", "AS", "10S"]]) + flop))
print("Straight Flush", Rules.evaluate(Hand([Card(card) for card in ["KC", "KD", "JH", "10S", "9S"]]) + flop))
print("Four of a Kind", Rules.evaluate(Hand([Card(card) for card in ["AC", "AD", "AH", "AS", "10H"]]) + flop))
print("Full House", Rules.evaluate(Hand([Card(card) for card in ["KC", "KD", "QH", "5S", "4H"]]) + flop))
print("Flush", Rules.evaluate(Hand([Card(card) for card in ["2S", "4S", "2C", "4C", "5S"]]) + flop))
print("Straight", Rules.evaluate(Hand([Card(card) for card in ["AD", "2D", "3H", "4C", "5C"]]) + flop))
print("Three of a kind", Rules.evaluate(Hand([Card(card) for card in ["AC", "AD", "AH", "5H", "3D"]]) + flop))
print("Two Pair", Rules.evaluate(Hand([Card(card) for card in ["AC", "AD", "2S", "2D", "3D"]]) + flop))
print("One Pair", Rules.evaluate(Hand([Card(card) for card in ["AS", "AD", "3H", "4C", "5C"]]) + flop))