Files
2026-05-11 15:46:30 +08:00

92 lines
2.8 KiB
Python

from __future__ import annotations
from collections import Counter
from dataclasses import dataclass
from itertools import combinations
from texas_holdem.cards import Card
CATEGORY_NAMES = {
8: "straight_flush",
7: "four_of_a_kind",
6: "full_house",
5: "flush",
4: "straight",
3: "three_of_a_kind",
2: "two_pair",
1: "pair",
0: "high_card",
}
@dataclass(frozen=True, order=True, slots=True)
class HandValue:
category: int
ranks: tuple[int, ...]
@property
def name(self) -> str:
return CATEGORY_NAMES[self.category]
def to_dict(self) -> dict[str, object]:
return {"category": self.category, "name": self.name, "ranks": list(self.ranks)}
def evaluate(cards: list[Card]) -> HandValue:
if len(cards) < 5:
raise ValueError("at least five cards are required")
return max(_evaluate_five(list(combo)) for combo in combinations(cards, 5))
def _evaluate_five(cards: list[Card]) -> HandValue:
ranks = sorted((card.rank for card in cards), reverse=True)
counts = Counter(ranks)
groups = sorted(counts.items(), key=lambda item: (item[1], item[0]), reverse=True)
is_flush = len({card.suit for card in cards}) == 1
straight_high = _straight_high(ranks)
if is_flush and straight_high is not None:
return HandValue(8, (straight_high,))
if groups[0][1] == 4:
quad_rank = groups[0][0]
kicker = max(rank for rank in ranks if rank != quad_rank)
return HandValue(7, (quad_rank, kicker))
if groups[0][1] == 3 and groups[1][1] == 2:
return HandValue(6, (groups[0][0], groups[1][0]))
if is_flush:
return HandValue(5, tuple(ranks))
if straight_high is not None:
return HandValue(4, (straight_high,))
if groups[0][1] == 3:
trip_rank = groups[0][0]
kickers = sorted((rank for rank in ranks if rank != trip_rank), reverse=True)
return HandValue(3, (trip_rank, *kickers))
if groups[0][1] == 2 and groups[1][1] == 2:
pair_ranks = sorted((rank for rank, count in counts.items() if count == 2), reverse=True)
kicker = max(rank for rank in ranks if rank not in pair_ranks)
return HandValue(2, (*pair_ranks, kicker))
if groups[0][1] == 2:
pair_rank = groups[0][0]
kickers = sorted((rank for rank in ranks if rank != pair_rank), reverse=True)
return HandValue(1, (pair_rank, *kickers))
return HandValue(0, tuple(ranks))
def _straight_high(ranks: list[int]) -> int | None:
unique = sorted(set(ranks), reverse=True)
if {14, 5, 4, 3, 2}.issubset(unique):
unique.append(1)
for index in range(0, len(unique) - 4):
window = unique[index : index + 5]
if window[0] - window[4] == 4 and len(set(window)) == 5:
return 5 if window == [5, 4, 3, 2, 1] else window[0]
return None