# hand_utils.py from typing import List, Tuple from collections import Counter SUITS = ['\u2665', '\u2663', '\u2660', '\u2666'] # Hearts, Clubs, Spades, Diamonds RANKS = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'] RANK_VALUES = {rank: i for i, rank in enumerate(RANKS, 2)} class Card: def __init__(self, rank: str, suit: str): self.rank = rank.strip().upper() self.suit = suit.strip().capitalize() def __repr__(self): return f"{self.rank} of {self.suit}" def __eq__(self, other): return isinstance(other, Card) and self.rank == other.rank and self.suit == other.suit # return str(self) == str(other) # Alternative comparison method # Other utility functions for hand evaluation def hand_rank_name(rank: int) -> str: names = { 9: "Royal Flush", 8: "Straight Flush", 7: "Four of a Kind", 6: "Full House", 5: "Flush", 4: "Straight", 3: "Three of a Kind", 2: "Two Pair", 1: "One Pair", 0: "High Card" } return names.get(rank, "Unknown Hand") def get_rank_counts(ranks: List[int]) -> Counter: return Counter(ranks) def is_flush(suits: List[str]) -> bool: return len(set(suits)) == 1 def is_straight(ranks: List[int]) -> bool: sorted_ranks = sorted(ranks, reverse=True) return all(sorted_ranks[i] - 1 == sorted_ranks[i + 1] for i in range(len(sorted_ranks) - 1)) def evaluate_hand(hand: List[Card]) -> Tuple[int, List[int]]: if len(hand) != 5: raise ValueError("Hand must contain exactly 5 cards.") ranks = sorted([RANK_VALUES[card.rank] for card in hand], reverse=True) suits = [card.suit for card in hand] rank_counts = get_rank_counts(ranks) # Handle Ace-low straight if ranks == [14, 5, 4, 3, 2]: ranks = [5, 4, 3, 2, 1] straight = True else: straight = is_straight(ranks) flush = is_flush(suits) if flush and ranks == [14, 13, 12, 11, 10]: return (9, hand_rank_name(9), ranks) # Royal Flush if flush and straight: return (8, hand_rank_name(8), ranks) # Straight Flush if 4 in rank_counts.values(): four = max(rank for rank, count in rank_counts.items() if count == 4) kicker = max(rank for rank in ranks if rank != four) return (7, hand_rank_name(7), [four]*4 + [kicker]) # Four of a Kind if 3 in rank_counts.values() and 2 in rank_counts.values(): three = max(rank for rank, count in rank_counts.items() if count == 3) pair = max(rank for rank, count in rank_counts.items() if count == 2) return (6, hand_rank_name(6), [three]*3 + [pair]*2) # Full House if flush: return (5, hand_rank_name(5), ranks) # Flush if straight: return (4, hand_rank_name(4), ranks) # Straight if 3 in rank_counts.values(): three = max(rank for rank, count in rank_counts.items() if count == 3) kickers = sorted([rank for rank in ranks if rank != three], reverse=True) return (3, hand_rank_name(3), [three]*3 + kickers) # Three of a Kind if list(rank_counts.values()).count(2) == 2: pairs = sorted([rank for rank, count in rank_counts.items() if count == 2], reverse=True) kicker = max(rank for rank in ranks if rank not in pairs) return (2, hand_rank_name(2), pairs*2 + [kicker]) # Two Pair if 2 in rank_counts.values(): pair = max(rank for rank, count in rank_counts.items() if count == 2) kickers = sorted([rank for rank in ranks if rank != pair], reverse=True) return (1, hand_rank_name(1), [pair]*2 + kickers) # One Pair return (0, hand_rank_name(0), ranks) # High Card