From 0db865854ee063f9d6b81e98461865847efc054d Mon Sep 17 00:00:00 2001 From: wsy182 <2392948297@qq.com> Date: Sat, 30 Nov 2024 14:08:42 +0800 Subject: [PATCH] 1 1 --- configs/log_config.py | 4 ++ src/engine/actions.py | 18 ++++++++ src/engine/chengdu_mahjong_engine.py | 63 +++++++++++++++++++++------- src/engine/game_state.py | 45 +++++++++++++++++--- src/engine/utils.py | 24 ++++++++++- 5 files changed, 132 insertions(+), 22 deletions(-) create mode 100644 configs/log_config.py diff --git a/configs/log_config.py b/configs/log_config.py new file mode 100644 index 0000000..75e4700 --- /dev/null +++ b/configs/log_config.py @@ -0,0 +1,4 @@ +from loguru import logger + +# 配置日志 +logger.add("mahjong_ai_{time}.log", rotation="10 MB", level="DEBUG", format="{time} {level} {message}") diff --git a/src/engine/actions.py b/src/engine/actions.py index e4a683b..881b352 100644 --- a/src/engine/actions.py +++ b/src/engine/actions.py @@ -1,3 +1,7 @@ +from loguru import logger +from utils import get_tile_name # 确保 get_tile_name 已在 utils.py 中定义并导入 + + def draw_tile(state): """ 当前玩家摸牌的动作逻辑。 @@ -12,10 +16,17 @@ def draw_tile(state): - ValueError: 如果牌堆已经空了(流局条件)。 """ if state.remaining_tiles == 0: + logger.warning("牌堆已空,无法摸牌!") raise ValueError("牌堆已空") # 牌堆为空时不能摸牌 + tile = state.deck.pop(0) # 从牌堆顶取出一张牌 state.remaining_tiles -= 1 # 更新牌堆剩余数量 state.hands[state.current_player][tile] += 1 # 将摸到的牌添加到当前玩家的手牌中 + + tile_name = get_tile_name(tile) # 获取牌的具体名称 + logger.info( + f"玩家 {state.current_player} 摸到一张牌: {tile_name}(索引 {tile})。剩余牌堆数量: {state.remaining_tiles}" + ) return tile @@ -35,6 +46,13 @@ def discard_tile(state, tile): - ValueError: 如果当前玩家的手牌中没有这张牌。 """ if state.hands[state.current_player][tile] == 0: + logger.error(f"玩家 {state.current_player} 尝试打出不存在的牌: 索引 {tile}") raise ValueError("玩家没有这张牌") # 防止打出不存在的牌 + state.hands[state.current_player][tile] -= 1 # 从手牌中移除该牌 state.discards[state.current_player].append(tile) # 将牌添加到牌河 + + tile_name = get_tile_name(tile) # 获取牌的具体名称 + logger.info( + f"玩家 {state.current_player} 打出一张牌: {tile_name}(索引 {tile})。当前牌河: {[get_tile_name(t) for t in state.discards[state.current_player]]}" + ) diff --git a/src/engine/chengdu_mahjong_engine.py b/src/engine/chengdu_mahjong_engine.py index 30ea267..50fea54 100644 --- a/src/engine/chengdu_mahjong_engine.py +++ b/src/engine/chengdu_mahjong_engine.py @@ -1,4 +1,6 @@ from .game_state import ChengduMahjongState +from .utils import get_tile_name # 确保 utils 中有 get_tile_name 定义 +from loguru import logger class ChengduMahjongEngine: @@ -7,47 +9,78 @@ class ChengduMahjongEngine: self.game_over = False def draw_tile(self): - # 当前玩家摸牌 + """ + 当前玩家摸牌逻辑,记录牌的详细信息和游戏状态。 + """ if self.state.remaining_tiles == 0: + logger.warning("牌堆已空,游戏结束!") self.game_over = True return "牌堆已空" - tile = self.state.deck.pop(0) - self.state.remaining_tiles -= 1 - self.state.hands[self.state.current_player][tile] += 1 + + tile = self.state.deck.pop(0) # 从牌堆中取出一张牌 + self.state.remaining_tiles -= 1 # 更新剩余牌数 + self.state.hands[self.state.current_player][tile] += 1 # 加入当前玩家手牌 + + tile_name = get_tile_name(tile) # 获取具体的牌名 + logger.info( + f"玩家 {self.state.current_player} 摸到一张牌: {tile_name}(索引 {tile})。剩余牌堆数量: {self.state.remaining_tiles}" + ) return tile def discard_tile(self, tile): - # 当前玩家打牌 + """ + 当前玩家打牌逻辑,记录打出的牌和当前牌河信息。 + """ if self.state.hands[self.state.current_player][tile] == 0: - raise ValueError("当前玩家没有这张牌") - self.state.hands[self.state.current_player][tile] -= 1 - self.state.discards[self.state.current_player].append(tile) + logger.error(f"玩家 {self.state.current_player} 尝试打出不存在的牌: 索引 {tile}") + raise ValueError("玩家没有这张牌") + + self.state.hands[self.state.current_player][tile] -= 1 # 从手牌中移除 + self.state.discards[self.state.current_player].append(tile) # 加入牌河 + + tile_name = get_tile_name(tile) # 获取具体的牌名 + logger.info( + f"玩家 {self.state.current_player} 打出一张牌: {tile_name}(索引 {tile})。当前牌河: {[get_tile_name(t) for t in self.state.discards[self.state.current_player]]}" + ) def peng(self, tile): - # 碰牌逻辑 + """ + 当前玩家碰牌逻辑,记录碰牌操作和手牌状态。 + """ player = self.state.current_player if self.state.hands[player][tile] < 2: + logger.error(f"玩家 {player} 尝试碰牌失败: {get_tile_name(tile)}(索引 {tile})") raise ValueError("碰牌条件不满足") - self.state.hands[player][tile] -= 2 - self.state.melds[player].append(("peng", tile)) + + self.state.hands[player][tile] -= 2 # 减去两张牌 + self.state.melds[player].append(("peng", tile)) # 加入明牌列表 + + tile_name = get_tile_name(tile) + logger.info(f"玩家 {player} 碰了一张牌: {tile_name}(索引 {tile})。当前明牌: {self.state.melds[player]}") def gang(self, tile, mode="ming"): - # 杠牌逻辑 + """ + 当前玩家杠牌逻辑,记录杠牌类型和状态更新。 + """ player = self.state.current_player + tile_name = get_tile_name(tile) + if mode == "ming" and self.state.hands[player][tile] == 3: self.state.hands[player][tile] -= 3 self.state.melds[player].append(("ming_gang", tile)) + logger.info(f"玩家 {player} 明杠: {tile_name}(索引 {tile})") elif mode == "an" and self.state.hands[player][tile] == 4: self.state.hands[player][tile] -= 4 self.state.melds[player].append(("an_gang", tile)) + logger.info(f"玩家 {player} 暗杠: {tile_name}(索引 {tile})") else: + logger.error(f"玩家 {player} 尝试杠牌失败: {tile_name}(索引 {tile}),条件不满足") raise ValueError("杠牌条件不满足") def check_blood_battle(self): """ - 检查游戏是否流局或血战结束。 - 如果满足结束条件,设置 self.game_over 为 True。 + 检查游戏是否流局或血战结束,记录状态。 """ if len(self.state.winners) >= 3 or self.state.remaining_tiles == 0: - print(f"游戏结束,赢家列表: {self.state.winners}") + logger.info(f"游戏结束,赢家列表: {self.state.winners}") self.game_over = True diff --git a/src/engine/game_state.py b/src/engine/game_state.py index a5ef062..4751cb2 100644 --- a/src/engine/game_state.py +++ b/src/engine/game_state.py @@ -1,4 +1,6 @@ -from utils import get_suit +from utils import get_suit, get_tile_name # 确保 get_tile_name 方法已经定义 +from loguru import logger + class ChengduMahjongState: def __init__(self): @@ -16,21 +18,44 @@ class ChengduMahjongState: self.remaining_tiles = 108 # 胜利玩家列表 self.winners = [] + # 缺门信息 + self.missing_suits = [None] * 4 # 每个玩家的缺门("条"、"筒" 或 "万") - def set_missing_suit(player, missing_suit): - # 确定玩家的缺门 + def set_missing_suit(self, player, missing_suit): + """ + 设置玩家的缺门信息。 + + 参数: + - player: 玩家索引(0-3)。 + - missing_suit: 玩家选择的缺门("条"、"筒" 或 "万")。 + + 异常: + - ValueError: 如果缺门设置无效。 + """ valid_suits = ["条", "筒", "万"] if missing_suit not in valid_suits: + logger.error(f"玩家 {player} 尝试设置无效的缺门: {missing_suit}") raise ValueError("缺门设置无效") - player.missing_suit = missing_suit - def can_win(hand): - # 判断是否满足胡牌条件(四组+一对) + self.missing_suits[player] = missing_suit + logger.info(f"玩家 {player} 设置缺门为: {missing_suit}") + + def can_win(self, hand): + """ + 判断玩家的手牌是否满足胡牌条件。 + + 参数: + - hand: 玩家当前的手牌(长度为108的列表)。 + + 返回: + - bool: 是否满足胡牌条件。 + """ from collections import Counter # 判断缺一门 suits = [get_suit(tile) for tile, count in enumerate(hand) if count > 0] if len(set(suits)) > 2: + logger.info(f"手牌花色: {set(suits)},未满足缺一门条件") return False # 不满足缺一门 # 判断是否满足四组+一对 @@ -41,9 +66,17 @@ class ChengduMahjongState: counter = Counter(hand) pairs = [tile for tile, count in counter.items() if count >= 2] + for pair in pairs: temp_hand = hand[:] temp_hand[pair] -= 2 + + # 记录尝试的对子 + logger.debug(f"尝试对子: {get_tile_name(pair)}(索引 {pair})") + if is_valid_group(temp_hand): + logger.info(f"满足胡牌条件的对子: {get_tile_name(pair)}(索引 {pair})") return True + + logger.info("未找到满足胡牌条件的组合") return False diff --git a/src/engine/utils.py b/src/engine/utils.py index 12e8d61..5377dd3 100644 --- a/src/engine/utils.py +++ b/src/engine/utils.py @@ -2,6 +2,28 @@ def get_suit(tile_index): """ 根据牌的索引返回花色。 条:索引 0-35,筒:索引 36-71,万:索引 72-107 + + 参数: + - tile_index: 牌的索引(0-107)。 + + 返回: + - 花色字符串: "条"、"筒" 或 "万"。 """ suits = ["条", "筒", "万"] - return suits[tile_index // 36] \ No newline at end of file + return suits[tile_index // 36] + + +def get_tile_name(tile_index): + """ + 根据牌的索引返回具体的牌(花色和数字)。 + + 参数: + - tile_index: 牌的索引(0-107)。 + + 返回: + - 具体牌的字符串: 例如 "1条"、"9筒"、"5万"。 + """ + suits = ["条", "筒", "万"] + suit = suits[tile_index // 36] # 根据索引获取花色 + number = (tile_index % 36) // 4 + 1 # 计算具体数字(1-9) + return f"{number}{suit}"