Projects/hand_utils.py

95 lines
3.6 KiB
Python

# 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