568 lines
18 KiB
Python
568 lines
18 KiB
Python
"""
|
|
山东保皇游戏
|
|
经典的 5 人扑克游戏
|
|
"""
|
|
|
|
import random
|
|
from enum import Enum
|
|
from typing import List, Dict, Tuple, Optional
|
|
|
|
|
|
class Suit(Enum):
|
|
"""花色枚举"""
|
|
SPADES = "♠" # 黑桃
|
|
HEARTS = "♥" # 红心
|
|
CLUBS = "♣" # 梅花
|
|
DIAMONDS = "♦" # 方块
|
|
JOKER = "🃏" # 王
|
|
|
|
|
|
class CardValue(Enum):
|
|
"""牌值枚举"""
|
|
THREE = 3
|
|
FOUR = 4
|
|
FIVE = 5
|
|
SIX = 6
|
|
SEVEN = 7
|
|
EIGHT = 8
|
|
NINE = 9
|
|
TEN = 10
|
|
JACK = 11
|
|
QUEEN = 12
|
|
KING = 13
|
|
ACE = 14
|
|
TWO = 15
|
|
SMALL_JOKER = 16 # 小王
|
|
BIG_JOKER = 17 # 大王
|
|
EMPEROR = 18 # 皇帝牌(带标记的大王)
|
|
GUARD = 19 # 侍卫牌(带标记的小王)
|
|
|
|
|
|
class Card:
|
|
"""扑克牌类"""
|
|
def __init__(self, value: CardValue, suit: Suit = None):
|
|
self.value = value
|
|
self.suit = suit
|
|
self.display = self._get_display()
|
|
|
|
def _get_display(self) -> str:
|
|
"""获取牌的显示文本"""
|
|
value_map = {
|
|
CardValue.THREE: '3',
|
|
CardValue.FOUR: '4',
|
|
CardValue.FIVE: '5',
|
|
CardValue.SIX: '6',
|
|
CardValue.SEVEN: '7',
|
|
CardValue.EIGHT: '8',
|
|
CardValue.NINE: '9',
|
|
CardValue.TEN: '10',
|
|
CardValue.JACK: 'J',
|
|
CardValue.QUEEN: 'Q',
|
|
CardValue.KING: 'K',
|
|
CardValue.ACE: 'A',
|
|
CardValue.TWO: '2',
|
|
CardValue.SMALL_JOKER: '小王',
|
|
CardValue.BIG_JOKER: '大王',
|
|
CardValue.EMPEROR: '皇',
|
|
CardValue.GUARD: '卫',
|
|
}
|
|
|
|
if self.value in [CardValue.SMALL_JOKER, CardValue.BIG_JOKER,
|
|
CardValue.EMPEROR, CardValue.GUARD]:
|
|
return value_map[self.value]
|
|
|
|
suit_str = self.suit.value if self.suit else ''
|
|
return f"{suit_str}{value_map[self.value]}"
|
|
|
|
def __str__(self):
|
|
return self.display
|
|
|
|
def __repr__(self):
|
|
return self.display
|
|
|
|
def power(self) -> int:
|
|
"""获取牌力值"""
|
|
# 侍卫牌 > 皇帝牌 > 大王 > 小王 > 2 > A > ... > 3
|
|
power_map = {
|
|
CardValue.GUARD: 22,
|
|
CardValue.EMPEROR: 21,
|
|
CardValue.BIG_JOKER: 20,
|
|
CardValue.SMALL_JOKER: 19,
|
|
CardValue.TWO: 18,
|
|
CardValue.ACE: 17,
|
|
CardValue.KING: 16,
|
|
CardValue.QUEEN: 15,
|
|
CardValue.JACK: 14,
|
|
CardValue.TEN: 13,
|
|
CardValue.NINE: 12,
|
|
CardValue.EIGHT: 11,
|
|
CardValue.SEVEN: 10,
|
|
CardValue.SIX: 9,
|
|
CardValue.FIVE: 8,
|
|
CardValue.FOUR: 7,
|
|
CardValue.THREE: 6,
|
|
}
|
|
return power_map.get(self.value, 0)
|
|
|
|
|
|
class CardType(Enum):
|
|
"""牌型枚举"""
|
|
SINGLE = "单张"
|
|
PAIR = "对子"
|
|
TRIPLE = "三张"
|
|
BOMB = "炸弹"
|
|
SEQUENCE = "顺子"
|
|
PASS = "不要"
|
|
|
|
|
|
class Hand:
|
|
"""手牌类"""
|
|
def __init__(self):
|
|
self.cards: List[Card] = []
|
|
|
|
def add_card(self, card: Card):
|
|
"""添加牌"""
|
|
self.cards.append(card)
|
|
|
|
def remove_cards(self, cards: List[Card]):
|
|
"""移除牌"""
|
|
for card in cards:
|
|
if card in self.cards:
|
|
self.cards.remove(card)
|
|
|
|
def sort(self):
|
|
"""按牌力排序"""
|
|
self.cards.sort(key=lambda c: c.power(), reverse=True)
|
|
|
|
def is_empty(self) -> bool:
|
|
"""是否为空"""
|
|
return len(self.cards) == 0
|
|
|
|
def card_count(self) -> int:
|
|
"""牌的数量"""
|
|
return len(self.cards)
|
|
|
|
def get_display(self) -> str:
|
|
"""获取手牌显示"""
|
|
return ' '.join([str(c) for c in self.cards])
|
|
|
|
|
|
class Player:
|
|
"""玩家类"""
|
|
def __init__(self, name: str, player_id: int):
|
|
self.name = name
|
|
self.player_id = player_id
|
|
self.hand = Hand()
|
|
self.role: str = "" # "皇帝", "侍卫", "革命党"
|
|
self.is_emperor = False
|
|
self.is_guard = False
|
|
self.finished_position: int = 0 # 完成位置(第几个出完牌)
|
|
|
|
def __str__(self):
|
|
return f"{self.name}({self.role})"
|
|
|
|
|
|
class GameRule:
|
|
"""游戏规则类"""
|
|
|
|
@staticmethod
|
|
def get_card_type(cards: List[Card]) -> Tuple[CardType, int]:
|
|
"""
|
|
判断牌型
|
|
|
|
Returns:
|
|
(牌型,牌力值)
|
|
"""
|
|
if not cards:
|
|
return (CardType.PASS, 0)
|
|
|
|
count = len(cards)
|
|
|
|
# 单张
|
|
if count == 1:
|
|
return (CardType.SINGLE, cards[0].power())
|
|
|
|
# 对子
|
|
if count == 2 and cards[0].value == cards[1].value:
|
|
return (CardType.PAIR, cards[0].power())
|
|
|
|
# 三张
|
|
if count == 3 and all(c.value == cards[0].value for c in cards):
|
|
return (CardType.TRIPLE, cards[0].power())
|
|
|
|
# 炸弹(4 张相同)
|
|
if count == 4 and all(c.value == cards[0].value for c in cards):
|
|
return (CardType.BOMB, cards[0].power() + 100) # 炸弹牌力加 100
|
|
|
|
# 顺子(至少 5 张连续)
|
|
if count >= 5:
|
|
values = sorted([c.power() for c in cards])
|
|
is_sequence = all(values[i+1] - values[i] == 1 for i in range(len(values)-1))
|
|
if is_sequence:
|
|
return (CardType.SEQUENCE, values[0])
|
|
|
|
return (CardType.PASS, 0)
|
|
|
|
@staticmethod
|
|
def can_beat(played_cards: List[Card], new_cards: List[Card]) -> bool:
|
|
"""
|
|
判断是否能打过
|
|
|
|
Args:
|
|
played_cards: 已出的牌
|
|
new_cards: 新出的牌
|
|
|
|
Returns:
|
|
True 表示可以打过
|
|
"""
|
|
if not played_cards:
|
|
return True # 首家出牌
|
|
|
|
played_type, played_power = GameRule.get_card_type(played_cards)
|
|
new_type, new_power = GameRule.get_card_type(new_cards)
|
|
|
|
if new_type == CardType.PASS:
|
|
return False
|
|
|
|
# 牌数必须相同
|
|
if len(played_cards) != len(new_cards):
|
|
return False
|
|
|
|
# 炸弹可以打非炸弹
|
|
if new_type == CardType.BOMB and played_type != CardType.BOMB:
|
|
return True
|
|
|
|
# 同牌型比较牌力
|
|
if new_type == played_type:
|
|
return new_power > played_power
|
|
|
|
return False
|
|
|
|
|
|
class BaohuangGame:
|
|
"""保皇游戏类"""
|
|
|
|
def __init__(self):
|
|
"""初始化游戏"""
|
|
self.players: List[Player] = []
|
|
self.deck: List[Card] = []
|
|
self.current_player_idx: int = 0
|
|
self.last_played_cards: List[Card] = []
|
|
self.last_played_player_idx: int = -1
|
|
self.game_started = False
|
|
self.game_over = False
|
|
self.emperor_idx: int = -1
|
|
self.guard_idx: int = -1
|
|
|
|
# 创建牌堆(2 副牌)
|
|
self._create_deck()
|
|
|
|
def _create_deck(self):
|
|
"""创建牌堆(2 副牌,共 108 张)"""
|
|
self.deck = []
|
|
|
|
# 普通牌(2 副)
|
|
for _ in range(2):
|
|
for suit in [Suit.SPADES, Suit.HEARTS, Suit.CLUBS, Suit.DIAMONDS]:
|
|
for value in [CardValue.THREE, CardValue.FOUR, CardValue.FIVE,
|
|
CardValue.SIX, CardValue.SEVEN, CardValue.EIGHT,
|
|
CardValue.NINE, CardValue.TEN, CardValue.JACK,
|
|
CardValue.QUEEN, CardValue.KING, CardValue.ACE,
|
|
CardValue.TWO]:
|
|
self.deck.append(Card(value, suit))
|
|
|
|
# 小王(2 张,其中 1 张是侍卫牌)
|
|
self.deck.append(Card(CardValue.SMALL_JOKER))
|
|
self.deck.append(Card(CardValue.GUARD)) # 侍卫牌
|
|
|
|
# 大王(2 张,其中 1 张是皇帝牌)
|
|
self.deck.append(Card(CardValue.BIG_JOKER))
|
|
self.deck.append(Card(CardValue.EMPEROR)) # 皇帝牌
|
|
|
|
print(f"🃏 牌堆创建完成,共{len(self.deck)}张牌")
|
|
|
|
def setup_players(self, player_names: List[str] = None):
|
|
"""
|
|
设置玩家
|
|
|
|
Args:
|
|
player_names: 玩家名字列表(默认 5 人)
|
|
"""
|
|
if player_names is None:
|
|
player_names = [f"玩家{i+1}" for i in range(5)]
|
|
elif len(player_names) != 5:
|
|
print("❌ 保皇需要 5 人游戏")
|
|
return
|
|
|
|
self.players = []
|
|
for i, name in enumerate(player_names):
|
|
player = Player(name, i)
|
|
self.players.append(player)
|
|
|
|
print(f"👥 玩家设置完成:{', '.join([p.name for p in self.players])}")
|
|
|
|
def deal_cards(self):
|
|
"""发牌"""
|
|
print("\n🔄 开始发牌...")
|
|
|
|
# 洗牌
|
|
random.shuffle(self.deck)
|
|
|
|
# 发牌(每人 21 张,剩余 3 张底牌)
|
|
card_idx = 0
|
|
for player in self.players:
|
|
for _ in range(21):
|
|
if card_idx < len(self.deck):
|
|
player.hand.add_card(self.deck[card_idx])
|
|
card_idx += 1
|
|
|
|
# 剩余 3 张作为底牌(给皇帝)
|
|
self.base_cards = self.deck[card_idx:]
|
|
print(f"📦 底牌:{len(self.base_cards)}张")
|
|
|
|
# 排序手牌
|
|
for player in self.players:
|
|
player.hand.sort()
|
|
|
|
print("✅ 发牌完成")
|
|
|
|
def identify_roles(self):
|
|
"""
|
|
确定身份
|
|
抓到皇帝牌的是皇帝,抓到侍卫牌的是侍卫
|
|
"""
|
|
print("\n🎭 确定身份...")
|
|
|
|
for player in self.players:
|
|
for card in player.hand.cards:
|
|
if card.value == CardValue.EMPEROR:
|
|
player.is_emperor = True
|
|
player.role = "皇帝"
|
|
self.emperor_idx = player.player_id
|
|
print(f"👑 {player.name} 是皇帝(持有皇帝牌)")
|
|
|
|
if card.value == CardValue.GUARD:
|
|
player.is_guard = True
|
|
player.role = "侍卫"
|
|
self.guard_idx = player.player_id
|
|
print(f"🛡️ {player.name} 是侍卫(持有侍卫牌)")
|
|
|
|
# 设置革命党
|
|
for player in self.players:
|
|
if not player.is_emperor and not player.is_guard:
|
|
player.role = "革命党"
|
|
|
|
# 皇帝获得底牌
|
|
if self.emperor_idx >= 0 and hasattr(self, 'base_cards'):
|
|
emperor = self.players[self.emperor_idx]
|
|
for card in self.base_cards:
|
|
emperor.hand.add_card(card)
|
|
emperor.hand.sort()
|
|
print(f"\n📦 皇帝{emperor.name}获得底牌")
|
|
|
|
def get_current_player(self) -> Player:
|
|
"""获取当前玩家"""
|
|
return self.players[self.current_player_idx]
|
|
|
|
def play_cards(self, player_idx: int, cards: List[Card]) -> bool:
|
|
"""
|
|
出牌
|
|
|
|
Args:
|
|
player_idx: 玩家索引
|
|
cards: 要出的牌
|
|
|
|
Returns:
|
|
True 表示出牌成功
|
|
"""
|
|
player = self.players[player_idx]
|
|
|
|
# 检查是否是该玩家出牌
|
|
if player_idx != self.current_player_idx:
|
|
print(f"❌ 还没到{player.name}出牌")
|
|
return False
|
|
|
|
# 检查手牌中是否有这些牌
|
|
for card in cards:
|
|
if card not in player.hand.cards:
|
|
print(f"❌ {player.name}没有这张牌:{card}")
|
|
return False
|
|
|
|
# 检查是否能打过上家
|
|
if self.last_played_cards and self.last_played_player_idx != player_idx:
|
|
if not GameRule.can_beat(self.last_played_cards, cards):
|
|
print(f"❌ {cards}打不过{self.last_played_cards}")
|
|
return False
|
|
|
|
# 出牌
|
|
player.hand.remove_cards(cards)
|
|
self.last_played_cards = cards
|
|
self.last_played_player_idx = player_idx
|
|
|
|
print(f"🎴 {player.name} 出牌:{' '.join([str(c) for c in cards])}")
|
|
|
|
# 检查是否出完
|
|
if player.hand.is_empty():
|
|
player.finished_position = len([p for p in self.players if p.finished_position > 0]) + 1
|
|
print(f"🎉 {player.name} 出完牌了!第{player.finished_position}名")
|
|
|
|
# 检查游戏是否结束
|
|
if player.finished_position >= 3: # 前 3 名出完就结束
|
|
self.game_over = True
|
|
|
|
# 下一个玩家
|
|
self.current_player_idx = (self.current_player_idx + 1) % len(self.players)
|
|
|
|
# 跳过已出完牌的玩家
|
|
while self.players[self.current_player_idx].hand.is_empty():
|
|
self.current_player_idx = (self.current_player_idx + 1) % len(self.players)
|
|
|
|
# 如果是新的回合,重置出牌
|
|
if self.current_player_idx == self.last_played_player_idx:
|
|
self.last_played_cards = []
|
|
self.last_played_player_idx = -1
|
|
|
|
return True
|
|
|
|
def pass_turn(self, player_idx: int):
|
|
"""
|
|
过牌
|
|
|
|
Args:
|
|
player_idx: 玩家索引
|
|
"""
|
|
player = self.players[player_idx]
|
|
|
|
if player_idx != self.current_player_idx:
|
|
print(f"❌ 还没到{player.name}出牌")
|
|
return
|
|
|
|
if not self.last_played_cards or self.last_played_player_idx == player_idx:
|
|
print(f"❌ {player.name}不能过牌(是首家)")
|
|
return
|
|
|
|
print(f"⏭️ {player.name} 过牌")
|
|
|
|
# 下一个玩家
|
|
self.current_player_idx = (self.current_player_idx + 1) % len(self.players)
|
|
|
|
# 跳过已出完牌的玩家
|
|
while self.players[self.current_player_idx].hand.is_empty():
|
|
self.current_player_idx = (self.current_player_idx + 1) % len(self.players)
|
|
|
|
def check_winner(self) -> str:
|
|
"""
|
|
检查胜负
|
|
|
|
Returns:
|
|
获胜方
|
|
"""
|
|
if not self.game_over:
|
|
return ""
|
|
|
|
# 统计皇帝方和革命党的名次
|
|
emperor_team_positions = []
|
|
revolutionary_positions = []
|
|
|
|
for player in self.players:
|
|
if player.is_emperor or player.is_guard:
|
|
emperor_team_positions.append(player.finished_position)
|
|
else:
|
|
revolutionary_positions.append(player.finished_position)
|
|
|
|
# 皇帝方获胜条件:皇帝和侍卫都进前 3,或者皇帝第 1 且侍卫不是最后
|
|
emperor_wins = False
|
|
|
|
if all(pos <= 3 for pos in emperor_team_positions):
|
|
emperor_wins = True
|
|
elif 1 in emperor_team_positions and 5 not in emperor_team_positions:
|
|
emperor_wins = True
|
|
|
|
if emperor_wins:
|
|
return "皇帝方获胜"
|
|
else:
|
|
return "革命党获胜"
|
|
|
|
def display_game_state(self):
|
|
"""显示游戏状态"""
|
|
print("\n" + "=" * 60)
|
|
print("🎮 游戏状态")
|
|
print("=" * 60)
|
|
|
|
for i, player in enumerate(self.players):
|
|
marker = "👑" if player.is_emperor else ("🛡️" if player.is_guard else "")
|
|
finished = f"(第{player.finished_position}名)" if player.finished_position > 0 else ""
|
|
print(f"{i+1}. {marker}{player.name} [{player.role}] - 剩余{player.hand.card_count()}张 {finished}")
|
|
|
|
if self.last_played_cards and self.last_played_player_idx >= 0:
|
|
last_player = self.players[self.last_played_player_idx]
|
|
print(f"\n🎴 上家出牌:{last_player.name} - {' '.join([str(c) for c in self.last_played_cards])}")
|
|
|
|
current = self.get_current_player()
|
|
print(f"\n➡️ 当前出牌:{current.name}")
|
|
print("=" * 60)
|
|
|
|
def run(self):
|
|
"""运行游戏主流程"""
|
|
try:
|
|
print("\n" + "=" * 60)
|
|
print("🃏 山东保皇 🃏")
|
|
print("=" * 60)
|
|
|
|
# 设置玩家
|
|
self.setup_players()
|
|
|
|
# 发牌
|
|
self.deal_cards()
|
|
|
|
# 确定身份
|
|
self.identify_roles()
|
|
|
|
# 游戏主循环
|
|
self.game_started = True
|
|
round_num = 0
|
|
|
|
while not self.game_over:
|
|
round_num += 1
|
|
print(f"\n{'='*60}")
|
|
print(f"第 {round_num} 轮")
|
|
print(f"{'='*60}")
|
|
|
|
self.display_game_state()
|
|
|
|
# 简单的 AI 出牌(示例)
|
|
current_player = self.get_current_player()
|
|
|
|
# 模拟出牌(实际应该由玩家输入)
|
|
if current_player.hand.cards:
|
|
# 出最小的单张
|
|
card_to_play = [current_player.hand.cards[-1]]
|
|
self.play_cards(current_player.player_id, card_to_play)
|
|
else:
|
|
self.pass_turn(current_player.player_id)
|
|
|
|
# 游戏结束
|
|
print("\n" + "=" * 60)
|
|
print("🎊 游戏结束!")
|
|
print("=" * 60)
|
|
|
|
for player in self.players:
|
|
print(f"{player.name}: 第{player.finished_position}名")
|
|
|
|
winner = self.check_winner()
|
|
print(f"\n🏆 {winner}")
|
|
print("=" * 60)
|
|
|
|
except KeyboardInterrupt:
|
|
print("\n\n游戏被中断!再见!👋\n")
|
|
|
|
|
|
def main():
|
|
"""主函数"""
|
|
game = BaohuangGame()
|
|
game.run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|