""" 斗地主游戏逻辑 牌型识别、牌组管理、规则验证 """ import random from typing import List, Dict, Optional, Any from dataclasses import dataclass, asdict from enum import Enum class CardType(Enum): """牌型枚举""" SINGLE = "single" # 单张 PAIR = "pair" # 对子 TRIPLE = "triple" # 三张 TRIPLE_WITH_SINGLE = "triple_with_single" # 三带一 TRIPLE_WITH_PAIR = "triple_with_pair" # 三带二 STRAIGHT = "straight" # 顺子 DOUBLE_STRAIGHT = "double_straight" # 连对 TRIPLE_STRAIGHT = "triple_straight" # 飞机 QUAD_WITH_TWO = "quad_with_two" # 四带二 BOMB = "bomb" # 炸弹 JOKER_BOMB = "joker_bomb" # 王炸 PASS = "pass" # 不出 @dataclass class Card: """扑克牌""" id: int rank: str suit: str value: int is_joker: bool = False def to_dict(self) -> Dict: return asdict(self) def __repr__(self): return f"{self.suit}{self.rank}" class Deck: """牌组管理""" SUITS = ['♠', '♥', '♣', '♦'] RANKS = ['3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A', '2'] @classmethod def create_full_deck(cls) -> List[Card]: """创建完整牌组(54张)""" deck = [] card_id = 0 # 52张普通牌 for rank in cls.RANKS: for suit in cls.SUITS: value = cls.RANKS.index(rank) + 3 # 3=3, ..., A=14, 2=15 deck.append(Card( id=card_id, rank=rank, suit=suit, value=value, is_joker=False )) card_id += 1 # 小王 deck.append(Card( id=card_id, rank='小王', suit='🃏', value=16, is_joker=True )) card_id += 1 # 大王 deck.append(Card( id=card_id, rank='大王', suit='🃏', value=17, is_joker=True )) return deck @classmethod def shuffle(cls, deck: List[Card]) -> List[Card]: """洗牌""" shuffled = deck.copy() random.shuffle(shuffled) return shuffled @classmethod def sort_cards(cls, cards: List[Card]) -> List[Card]: """按牌值排序""" return sorted(cards, key=lambda c: c.value) def identify_card_type(cards: List[Card]) -> Optional[Dict[str, Any]]: """ 识别牌型 返回: {type: str, value: int, length: int, name: str, cards: List[Card]} 注意: type 返回字符串而非 CardType 枚举,便于 JSON 序列化 """ if not cards or len(cards) == 0: return None n = len(cards) sorted_cards = Deck.sort_cards(cards) values = [c.value for c in sorted_cards] # 统计每个牌值的数量 value_count = {} for v in values: value_count[v] = value_count.get(v, 0) + 1 counts = sorted(value_count.values(), reverse=True) unique_values = sorted(value_count.keys()) # 王炸 if n == 2 and all(c.is_joker for c in cards): return { 'type': 'joker_bomb', 'value': 100, 'length': 2, 'name': '王炸', 'cards': cards } # 单张 if n == 1: return { 'type': 'single', 'value': values[0], 'length': 1, 'name': f'单张{sorted_cards[0].rank}', 'cards': cards } # 对子 if n == 2 and counts == [2]: return { 'type': 'pair', 'value': values[0], 'length': 2, 'name': f'对子{sorted_cards[0].rank}', 'cards': cards } # 三张 if n == 3 and counts == [3]: return { 'type': 'triple', 'value': values[0], 'length': 3, 'name': f'三张{sorted_cards[0].rank}', 'cards': cards } # 三带一 if n == 4 and counts == [3, 1]: triple_value = [v for v, c in value_count.items() if c == 3][0] return { 'type': 'triple_with_single', 'value': triple_value, 'length': 4, 'name': f'三带一', 'cards': cards } # 三带二 if n == 5 and counts == [3, 2]: triple_value = [v for v, c in value_count.items() if c == 3][0] return { 'type': 'triple_with_pair', 'value': triple_value, 'length': 5, 'name': f'三带二', 'cards': cards } # 炸弹 if n == 4 and counts == [4]: return { 'type': 'bomb', 'value': values[0], 'length': 4, 'name': f'炸弹{sorted_cards[0].rank}', 'cards': cards } # 顺子 (>=5张连续,不能含2和王) if n >= 5 and max(values) <= 14: # A=14 if len(unique_values) == n and unique_values[-1] - unique_values[0] == n - 1 and counts == [1] * n: return { 'type': 'straight', 'value': unique_values[0], 'length': n, 'name': f'{n}连顺', 'cards': cards } # 连对 (>=3对连续) if n >= 6 and n % 2 == 0 and counts == [2] * (n // 2): pair_values = [v for v, c in value_count.items() if c == 2] if len(pair_values) == n // 2 and max(pair_values) <= 14: if pair_values[-1] - pair_values[0] == len(pair_values) - 1: return { 'type': 'double_straight', 'value': pair_values[0], 'length': n // 2, 'name': f'{n // 2}连对', 'cards': cards } # 飞机 (>=2组连续三张) if n >= 6 and n % 3 == 0: triple_values = [v for v, c in value_count.items() if c == 3] if len(triple_values) == n // 3 and max(triple_values) <= 14: if triple_values[-1] - triple_values[0] == len(triple_values) - 1: return { 'type': 'triple_straight', 'value': triple_values[0], 'length': n // 3, 'name': f'{n // 3}连三张', 'cards': cards } # 四带二 if n == 6 and counts == [4, 1, 1]: quad_value = [v for v, c in value_count.items() if c == 4][0] return { 'type': 'quad_with_two', 'value': quad_value, 'length': 6, 'name': '四带二', 'cards': cards } if n == 8 and counts == [4, 2, 2]: quad_value = [v for v, c in value_count.items() if c == 4][0] return { 'type': 'quad_with_two', 'value': quad_value, 'length': 8, 'name': '四带两对', 'cards': cards } return None def can_beat(desktop_type: Optional[Dict], new_type: Dict) -> bool: """ 判断新牌型能否管上桌面牌型 type字段现在是字符串而非CardType枚举 """ if not desktop_type: return True new_type_str = new_type['type'] desktop_type_str = desktop_type['type'] # 王炸最大 if new_type_str == 'joker_bomb': return True if desktop_type_str == 'joker_bomb': return False # 炸弹 > 非炸弹 if new_type_str == 'bomb' and desktop_type_str != 'bomb': return True if desktop_type_str == 'bomb' and new_type_str != 'bomb': return False # 同类型比较 if new_type_str != desktop_type_str: return False # 连牌数量必须相同 if new_type.get('length') != desktop_type.get('length'): return False # 比较牌值 return new_type['value'] > desktop_type['value'] def evaluate_hand_strength(cards: List[Card]) -> int: """ 评估手牌强度(用于叫地主决策) 返回分数,越高表示手牌越好 """ score = 0 # 大牌加分 for card in cards: if card.value >= 16: # 王 score += 4 elif card.value == 15: # 2 score += 3 elif card.value == 14: # A score += 2 elif card.value == 13: # K score += 1 # 炸弹加分 value_count = {} for c in cards: value_count[c.value] = value_count.get(c.value, 0) + 1 for count in value_count.values(): if count == 4: score += 6 # 王炸 jokers = [c for c in cards if c.is_joker] if len(jokers) == 2: score += 10 return score def cards_to_dicts(cards: List[Card]) -> List[Dict]: """转换为字典列表""" return [c.to_dict() for c in cards] def find_cards_by_ids(all_cards: List[Card], card_ids: List[int]) -> List[Card]: """通过ID查找牌""" card_map = {c.id: c for c in all_cards} return [card_map.get(cid) for cid in card_ids if cid in card_map] # 导出 CARD_TYPES = CardType