Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

339 lines
9.1 KiB
Python

"""
斗地主游戏逻辑
牌型识别、牌组管理、规则验证
"""
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