95 lines
3.6 KiB
Python
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
|
|
|