142 lines
6.0 KiB
Python
142 lines
6.0 KiB
Python
from collections import Counter
|
||
from src.engine.mahjong.hand import Hand
|
||
from src.engine.mahjong.mahjong_tile import MahjongTile
|
||
from src.engine.mahjong.meld import Meld
|
||
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)] # 每个玩家的明牌列表,存储 Meld 对象
|
||
# 剩余的牌堆
|
||
self.deck = [MahjongTile(suit, value) for suit in ["条", "筒", "万"] for value in range(1, 10)] * 4 # 108张牌
|
||
# 当前玩家索引
|
||
self.current_player = 0
|
||
# 底分
|
||
self.bottom_score = 1
|
||
# 玩家分数
|
||
self.scores = [100, 100, 100, 100]
|
||
# 剩余牌数量
|
||
self.remaining_tiles = len(self.deck)
|
||
# 胜利玩家列表
|
||
self.winners = []
|
||
# 记录每个玩家的抓牌次数
|
||
self.draw_counts = [0] * 4
|
||
# 缺门信息
|
||
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 print_game_state(self, player_index: int):
|
||
"""
|
||
打印指定玩家的手牌、暗牌、明牌和缺门信息。
|
||
|
||
:param player_index: 要打印的玩家索引(0-3)。
|
||
"""
|
||
hand = self.hands[player_index]
|
||
melds = self.melds[player_index]
|
||
missing_suit = self.missing_suits[player_index]
|
||
|
||
# 打印日志,所有信息在一行
|
||
logger.info(
|
||
f"玩家索引: {player_index}, 手牌: {hand}, 明牌: {melds}, 总牌数: {len(hand.tiles) + sum(meld.count for meld in melds)}, 缺门: {missing_suit}"
|
||
)
|
||
|
||
def can_win(self, hand: Hand, melds: list[Meld], missing_suit: str):
|
||
"""
|
||
判断玩家是否能胡牌。
|
||
:param hand: 玩家手牌(Hand 对象)。
|
||
:param melds: 玩家已明牌的列表(Meld 对象列表)。
|
||
: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, pairs_found=False):
|
||
"""
|
||
尝试将剩余牌分组,必须满足 n * AAA + m * ABC + DD。
|
||
"""
|
||
# 如果没有剩余牌,检查是否已经找到对子
|
||
if not remaining_tiles:
|
||
return pairs_found # 必须存在一个对子
|
||
|
||
# 尝试找到一个对子
|
||
if not pairs_found:
|
||
tile_counter = Counter(remaining_tiles)
|
||
for tile, count in tile_counter.items():
|
||
if count >= 2: # 找到一个对子
|
||
temp_tiles = remaining_tiles[:]
|
||
temp_tiles.remove(tile)
|
||
temp_tiles.remove(tile)
|
||
if try_win(temp_tiles, pairs_found=True):
|
||
return True
|
||
|
||
# 尝试找到一个合法组(AAA 或 ABC)
|
||
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, pairs_found):
|
||
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):
|
||
logger.info("仍有缺门花色,不能胡牌")
|
||
return False # 仍有缺门的花色,不能胡牌
|
||
|
||
# **第二步:分离明牌(杠、碰)和暗牌**
|
||
# 提取明牌(碰和杠)的组数
|
||
groups_from_melds = 0
|
||
for meld in melds:
|
||
if meld.type == "碰":
|
||
groups_from_melds += 1 # 碰牌构成 1 组
|
||
elif meld.type == "杠":
|
||
groups_from_melds += 1 # 杠牌也构成 1 组
|
||
|
||
# 获取所有暗牌
|
||
remaining_tiles = hand.tiles[:]
|
||
logger.info(f"暗牌: {remaining_tiles}, 明牌: {melds}, 已构成的组数: {groups_from_melds}")
|
||
|
||
# **第三步:检查暗牌是否满足分组条件**
|
||
return try_win(remaining_tiles, pairs_found=False) and groups_from_melds >= 0 |