Dec-29-2021, 06:49 PM
(This post was last modified: Dec-29-2021, 06:49 PM by deanhystad.)
I added a rules class that uses Hand to determine value. I should flatten the list before returning but I grow weary of this exercise.
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 = copy.deepcopy(ranks) 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""" high_rank = 0 for rank in ranks: high = max(rank[0].rank, high_rank) return high_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"]]) def test(*cards): hand = Hand([Card(card) for card in cards]) + flop result, cards, high_card = Rules.evaluate(hand) print(f"{cards}, {Rules.HAND_NAMES[result]}") test("AC", "AD", "AH", "AS", "10S") test("KC", "KD", "JH", "10S", "9S") test("AC", "AD", "AH", "AS", "10H") test("KC", "KD", "QH", "5S", "4H") test("2S", "4S", "2C", "4C", "5S") test("AD", "2D", "3H", "4C", "5C") test("AC", "AD", "AH", "5H", "3D") test("AC", "AD", "2S", "2D", "3D") test("AS", "AD", "3H", "4C", "5C")
Output:[[AS], [KS], [QS], [JS], [10S]], Royal Flush
[[KS], [QS], [JS], [10S], [9S]], Straight Flush
[AC, AD, AH, AS], Four of a Kind
[KC, KD, KS, QH, QS], Full House
[KS, QS, JS, 5S, 4S, 2S], Flush
[[5C], [4C], [3H], [2D], [AD]], Straight
[AC, AD, AH], Three of a Kind
[AC, AD, 2S, 2D], Two Pair
[AS, AD], One Pair