mjAi/src/engine/chengdu_mahjong_state.py

104 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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 # 如果没有找到符合条件的组合,则不能胡牌