103 lines
4.8 KiB
Python
103 lines
4.8 KiB
Python
from collections import Counter
|
||
from typing import List, Optional
|
||
from poker.hand_evaluator import HandEvaluator
|
||
from poker.card import Suit, Card, ShortDeckRank
|
||
from .hand_ranking import ShortDeckHandType, ShortDeckHandRanking
|
||
from itertools import combinations
|
||
|
||
|
||
class ShortDeckHandEvaluator(HandEvaluator):
|
||
|
||
@classmethod
|
||
def evaluate_hand(cls, cards: List[Card]) -> ShortDeckHandRanking:
|
||
if len(cards) < 5:
|
||
raise ValueError("Need at least 5 cards to evaluate a hand")
|
||
|
||
best_ranking = None
|
||
for combo in combinations(cards, 5):
|
||
ranking = cls.evaluate_5_cards(list(combo))
|
||
if best_ranking is None or ranking > best_ranking:
|
||
best_ranking = ranking
|
||
|
||
return best_ranking
|
||
|
||
@classmethod
|
||
def evaluate_5_cards(cls, cards: List[Card]) -> ShortDeckHandRanking:
|
||
if len(cards) != 5:
|
||
raise ValueError("Must provide exactly 5 cards")
|
||
|
||
from poker.hand_evaluator import HandEvaluator
|
||
sorted_cards, ranks, suits, rank_counts, count_values, is_flush, _, _ = HandEvaluator._analyze_cards(cards)
|
||
|
||
# 重写顺子判断
|
||
is_straight, straight_high = cls.is_straight(ranks)
|
||
|
||
if is_straight and is_flush:
|
||
if straight_high == ShortDeckRank.ACE:
|
||
return ShortDeckHandRanking(ShortDeckHandType.ROYAL_FLUSH, [straight_high], sorted_cards)
|
||
else:
|
||
return ShortDeckHandRanking(ShortDeckHandType.STRAIGHT_FLUSH, [straight_high], sorted_cards)
|
||
|
||
if count_values == [4, 1]: # 四条
|
||
quad_rank = [rank for rank, count in rank_counts.items() if count == 4][0]
|
||
kicker = [rank for rank, count in rank_counts.items() if count == 1][0]
|
||
return ShortDeckHandRanking(ShortDeckHandType.FOUR_OF_A_KIND, [quad_rank, kicker], sorted_cards)
|
||
|
||
# 关键差异:Flush > Full House
|
||
if is_flush:
|
||
return ShortDeckHandRanking(ShortDeckHandType.FLUSH, ranks, sorted_cards)
|
||
|
||
if count_values == [3, 2]: # 满堂红
|
||
trip_rank = [rank for rank, count in rank_counts.items() if count == 3][0]
|
||
pair_rank = [rank for rank, count in rank_counts.items() if count == 2][0]
|
||
return ShortDeckHandRanking(ShortDeckHandType.FULL_HOUSE, [trip_rank, pair_rank], sorted_cards)
|
||
|
||
if is_straight:
|
||
return ShortDeckHandRanking(ShortDeckHandType.STRAIGHT, [straight_high], sorted_cards)
|
||
|
||
if count_values == [3, 1, 1]: # 三条
|
||
trip_rank = [rank for rank, count in rank_counts.items() if count == 3][0]
|
||
kickers = sorted([rank for rank, count in rank_counts.items() if count == 1],
|
||
key=lambda x: x.numeric_value, reverse=True)
|
||
return ShortDeckHandRanking(ShortDeckHandType.THREE_OF_A_KIND, [trip_rank] + kickers, sorted_cards)
|
||
|
||
if count_values == [2, 2, 1]: # 两对
|
||
pairs = sorted([rank for rank, count in rank_counts.items() if count == 2],
|
||
key=lambda x: x.numeric_value, reverse=True)
|
||
kicker = [rank for rank, count in rank_counts.items() if count == 1][0]
|
||
return ShortDeckHandRanking(ShortDeckHandType.TWO_PAIR, pairs + [kicker], sorted_cards)
|
||
|
||
if count_values == [2, 1, 1, 1]: # 一对
|
||
pair_rank = [rank for rank, count in rank_counts.items() if count == 2][0]
|
||
kickers = sorted([rank for rank, count in rank_counts.items() if count == 1],
|
||
key=lambda x: x.numeric_value, reverse=True)
|
||
return ShortDeckHandRanking(ShortDeckHandType.ONE_PAIR, [pair_rank] + kickers, sorted_cards)
|
||
|
||
return ShortDeckHandRanking(ShortDeckHandType.HIGH_CARD, ranks, sorted_cards)
|
||
|
||
@classmethod
|
||
def is_straight(cls, ranks: List[ShortDeckRank]) -> tuple[bool, Optional[ShortDeckRank]]:
|
||
unique_ranks = sorted(set(rank.numeric_value for rank in ranks))
|
||
|
||
if len(unique_ranks) < 5:
|
||
return False, None
|
||
ace_low_straight = [6, 7, 8, 9, 14] # A(14), 6, 7, 8, 9
|
||
if all(val in unique_ranks for val in ace_low_straight):
|
||
return True, ShortDeckRank.NINE
|
||
|
||
for i in range(len(unique_ranks) - 4):
|
||
if unique_ranks[i+4] - unique_ranks[i] == 4:
|
||
high_value = unique_ranks[i+4]
|
||
high_rank = None
|
||
for rank in ShortDeckRank:
|
||
if rank.numeric_value == high_value:
|
||
high_rank = rank
|
||
break
|
||
return True, high_rank
|
||
|
||
return False, None
|
||
|
||
@classmethod
|
||
def evaluate_from_input(cls, input_str) -> ShortDeckHandRanking:
|
||
cards = Card.parse_short_deck_cards(input_str)
|
||
return cls.evaluate_hand(cards) |