from collections import Counter from .hand import Hand from .mahjong_tile import MahjongTile from loguru import logger class ChengduMahjongState: def __init__(self): # 每个玩家的手牌 self.hands = [Hand() for _ in range(4)] # 每个玩家的手牌由 Hand 类表示 # 每个玩家的打出的牌 self.discards = [[] for _ in range(4)] # 每个玩家的弃牌列表 # 每个玩家的明牌(碰、杠) self.melds = [[] for _ in range(4)] # 剩余的牌堆 self.deck = [MahjongTile(suit, value) for suit in ["条", "筒", "万"] for value in range(1, 10)] * 4 # 108张牌 # 当前玩家索引 self.current_player = 0 # 玩家分数 self.scores = [100, 100, 100, 100] # 剩余牌数量 self.remaining_tiles = len(self.deck) # 胜利玩家列表 self.winners = [] # 缺门信息 self.missing_suits = [None] * 4 # 每个玩家的缺门("条"、"筒" 或 "万") def set_missing_suit(self, player, missing_suit): """ 设置玩家的缺门信息。 参数: - player: 玩家索引(0-3)。 - missing_suit: 玩家选择的缺门("条"、"筒" 或 "万")。 异常: - ValueError: 如果缺门设置无效。 """ valid_suits = ["条", "筒", "万"] if missing_suit not in valid_suits: raise ValueError("缺门设置无效") self.missing_suits[player] = missing_suit def can_win(self, hand: Hand, missing_suit: str): """ 判断玩家是否能胡牌。 :param hand: 玩家手牌(Hand 对象)。 :param missing_suit: 玩家设置的缺门花色。 :return: True 表示能胡牌,False 表示不能胡牌。 """ def is_valid_group(tiles): """ 检查是否是合法组(AAA 或 ABC)。 """ if len(tiles) != 3: return False tiles.sort(key=lambda t: (t.suit, t.value)) # 按花色和数值排序 return (tiles[0].value == tiles[1].value == tiles[2].value and # AAA tiles[0].suit == tiles[1].suit == tiles[2].suit) or \ (tiles[0].value + 1 == tiles[1].value and # ABC tiles[1].value + 1 == tiles[2].value and tiles[0].suit == tiles[1].suit == tiles[2].suit) def try_win(remaining_tiles, depth=0): """ 尝试将剩余牌分组,必须满足 n * AAA + m * ABC。 """ if not remaining_tiles: return True # 所有牌成功分组 for i in range(len(remaining_tiles)): for j in range(i + 1, len(remaining_tiles)): for k in range(j + 1, len(remaining_tiles)): group = [remaining_tiles[i], remaining_tiles[j], remaining_tiles[k]] if is_valid_group(group): next_tiles = remaining_tiles[:i] + remaining_tiles[i + 1:j] + \ remaining_tiles[j + 1:k] + remaining_tiles[k + 1:] if try_win(next_tiles, depth + 1): return True return False # **第一步:检查花色限制** suits = {tile.suit for tile in hand.tiles} if len(suits) > 2: logger.info("花色超过两种,不能胡牌") return False # 花色超过两种,不能胡牌 # 检查是否打完缺门的花色 if any(tile.suit == missing_suit for tile in hand.tiles): return False # 仍有缺门的花色,不能胡牌 # **第二步:寻找对子并分组** # 找到所有对子(至少两张相同的牌) tile_counter = Counter(hand.tiles) pairs = [tile for tile, count in tile_counter.items() if count >= 2] # 遍历所有对子,尝试用剩余牌分组 for pair in pairs: temp_tiles = hand.tiles[:] temp_tiles.remove(pair) # 移除一张将牌 temp_tiles.remove(pair) # 再移除一张将牌 if try_win(temp_tiles): # 检查是否可以分组 return True return False # 如果没有找到符合条件的组合,则不能胡牌