"""hand.py: the cards. A card is two letters, rank and suit: "As" is the ace of spades, "Td" the ten of diamonds. A hand's strength is a tuple of numbers, and Python compares tuples from the left. """ import secrets from collections import Counter from itertools import combinations RANKS = "23456789TJQKA" SUITS = "shdc" NAMES = ["high card", "a pair", "two pair", "three of a kind", "a straight", "a flush", "a full house", "four of a kind", "a straight flush"] def shuffled_deck(): """A new deck, shuffled with the system's secure random numbers: random.shuffle can be predicted.""" deck = [rank + suit for rank in RANKS for suit in SUITS] secrets.SystemRandom().shuffle(deck) return deck def strength(cards): """The strength of exactly five cards: (category, then the ranks that break a tie).""" ranks = sorted((RANKS.index(card[0]) for card in cards), reverse=True) # The biggest group first, then the highest rank: a full house reads (three's rank, pair's rank). groups = sorted(Counter(ranks).items(), key=lambda group: (-group[1], -group[0])) shape = [n for _, n in groups] order = [rank for rank, _ in groups] flush = all(card[1] == cards[0][1] for card in cards) high = -1 # the top card of a straight if len(groups) == 5: if ranks[0] - ranks[4] == 4: high = ranks[0] elif ranks == [12, 3, 2, 1, 0]: # A-2-3-4-5: the ace counts low, the five is top high = 3 if flush and high >= 0: return (8, high) if shape == [4, 1]: return (7, *order) if shape == [3, 2]: return (6, *order) if flush: return (5, *ranks) if high >= 0: return (4, high) if shape == [3, 1, 1]: return (3, *order) if shape == [2, 2, 1]: return (2, *order) if shape == [2, 1, 1, 1]: return (1, *order) return (0, *ranks) def best_hand(cards): """The best five of a player's two cards and the board: (score, name).""" score = max(strength(five) for five in combinations(cards, 5)) return score, NAMES[score[0]]