feat: basic function
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
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
|
||||
Reference in New Issue
Block a user