Files
poker_task1/shortdeck/hand_evaluator.py
2025-09-24 16:54:52 +08:00

103 lines
4.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)