From 27da1caf442795fdd9c576cd304f8dafe51be951 Mon Sep 17 00:00:00 2001 From: wsy182 <2392948297@qq.com> Date: Sun, 1 Dec 2024 19:29:42 +0800 Subject: [PATCH] 1 1 --- src/engine/actions.py | 99 +++++++++++++++++++++----------- src/engine/calculate_fan.py | 110 ++++++++++-------------------------- src/engine/fan_type.py | 2 +- tests/test_fan_type.py | 4 +- 4 files changed, 101 insertions(+), 114 deletions(-) diff --git a/src/engine/actions.py b/src/engine/actions.py index 61a82da..48423f3 100644 --- a/src/engine/actions.py +++ b/src/engine/actions.py @@ -2,6 +2,7 @@ import random as random_module from loguru import logger +from src.engine.calculate_fan import calculate_fan from src.engine.mahjong_tile import MahjongTile @@ -233,20 +234,23 @@ def handle_peng(self, player, tile): """ 处理玩家碰牌逻辑并更新出牌顺序。 """ + if not isinstance(tile, MahjongTile): + logger.error(f"玩家 {player} 碰牌的牌无效: {tile}") + return False + if self.state.hands[player].tile_count[tile] < 2: logger.error(f"玩家 {player} 无法碰牌: {tile}") return False - # 减少两张牌 - self.state.hands[player].tile_count[tile] -= 2 - self.state.melds[player].append(("碰", tile)) + # 更新状态 + self._update_meld(player, tile, "碰", count=2) logger.info(f"玩家 {player} 碰了牌: {tile}。当前明牌: {self.state.melds[player]}") - return True + def get_player_discard_choice(self, player): """ 模拟获取玩家打牌的选择。 @@ -269,24 +273,26 @@ def handle_gang(self, player, tile, mode): :param mode: 杠牌的类型 ("ming" 或 "an") :return: True 如果操作成功,否则 False """ + if not isinstance(tile, MahjongTile): + logger.error(f"玩家 {player} 杠牌的牌无效: {tile}") + return False + base_score = self.state.bottom_score # 底分 if mode == "ming": # 明杠逻辑 if self.state.hands[player].tile_count[tile] < 3: logger.error(f"玩家 {player} 无法明杠: {tile}") - return None + return False - # 减少牌数量并更新明牌 - self.state.hands[player].tile_count[tile] -= 3 - self.state.melds[player].append(("杠", tile)) - logger.info(f"玩家 {player} 明杠了牌: {tile}") + # 更新状态 + self._update_meld(player, tile, "杠", count=3) # 明杠分数计算 - gang_score = base_score * 2 # 明杠的分数倍率 + gang_score = base_score * 2 for i in range(4): if i != player: - self.state.scores[i] -= gang_score # 每个其他玩家扣分 - self.state.scores[player] += gang_score * 3 # 杠牌玩家得总分 + self.state.scores[i] -= gang_score + self.state.scores[player] += gang_score * 3 logger.info(f"玩家 {player} 明杠,总分变化: +{gang_score * 3},其他玩家每人扣分: -{gang_score}") return True @@ -296,21 +302,28 @@ def handle_gang(self, player, tile, mode): logger.error(f"玩家 {player} 无法暗杠: {tile}") return False - # 减少牌数量并更新暗牌 - self.state.hands[player].tile_count[tile] -= 4 - self.state.melds[player].append(("an_gang", tile)) - logger.info(f"玩家 {player} 暗杠了牌: {tile}") + # 更新状态 + update_meld(player, tile, "杠", count=4) # 暗杠分数计算 - gang_score = base_score * 4 # 暗杠的分数倍率 + gang_score = base_score * 4 for i in range(4): if i != player: - self.state.scores[i] -= gang_score # 每个其他玩家扣分 - self.state.scores[player] += gang_score * 3 # 杠牌玩家得总分 + self.state.scores[i] -= gang_score + self.state.scores[player] += gang_score * 3 logger.info(f"玩家 {player} 暗杠,总分变化: +{gang_score * 3},其他玩家每人扣分: -{gang_score}") return True +def update_meld(self, player, tile, meld_type, count): + """ + 更新玩家的明牌状态,并移除相应的牌。 + """ + self.state.hands[player].tile_count[tile] -= count + self.state.melds[player].append((meld_type, tile)) + logger.info(f"玩家 {player} 更新明牌: {meld_type} {tile},当前明牌: {self.state.melds[player]}") + + def random_discard_tile(engine): """ @@ -417,27 +430,40 @@ def select_discard_tile(self, player): def handle_win(self, player, current_player, tile): """ - 处理胡牌逻辑,包括判断地胡,计算番数和分数。 + 处理胡牌逻辑,包括动态计算番数和分数。 :param player: 胡牌玩家索引 - :param current_player: 打出牌的玩家索引 + :param current_player: 打出牌的玩家索引(若自摸为 None) :param tile: 胡牌的那张牌 """ - logger.info(f"玩家 {player} 胡牌!胡的牌是: {tile}") + logger.info(f"玩家 {player} 胡牌!胡的牌是: {tile if tile else '自摸'}") # 判断是否地胡 is_dihu = self.state.draw_counts[player] == 0 and player != self.state.current_player - if is_dihu: - logger.info(f"玩家 {player} 地胡!") - else: - logger.info(f"玩家 {player} 不是地胡") + is_self_draw = current_player is None - # 计算分数 - fan_count = 5 # 按你逻辑固定番数为5 - base_score = self.state.bottom_score # 底分 + # 动态计算番数 + fan_count = calculate_fan( + hand=self.state.hands[player], + melds=self.state.melds[player], + is_self_draw=is_self_draw, + winning_tile=tile, + conditions={"is_tian_hu": False, "is_di_hu": is_dihu} + ) + + # 分数计算 + base_score = self.state.bottom_score win_score = base_score * (2 ** fan_count) - self.state.scores[player] += win_score * 3 # 胡牌玩家得总分 - self.state.scores[current_player] -= win_score # 点炮玩家扣除分数 + if is_self_draw: + # 自摸结算 + for i in range(4): + if i != player: + self.state.scores[i] -= win_score + self.state.scores[player] += win_score * 3 + else: + # 点炮结算 + self.state.scores[player] += win_score * 3 + self.state.scores[current_player] -= win_score # 更新赢家状态 self.state.winners.append(player) @@ -446,6 +472,15 @@ def handle_win(self, player, current_player, tile): # 输出日志 logger.info(f"玩家 {player} 胡牌类型: {'地胡' if is_dihu else '普通胡牌'}") logger.info(f"玩家 {player} 总番数: {fan_count}") - logger.info(f"玩家 {current_player} 点炮,扣分: {win_score}") + logger.info(f"玩家 {current_player if current_player is not None else '所有其他玩家'} 扣分: {win_score}") logger.info(f"玩家 {player} 加分: {win_score * 3}") logger.info(f"当前分数: {self.state.scores}") + + +def _update_meld(self, player, tile, meld_type, count): + """ + 更新玩家的明牌状态,并移除相应的牌。 + """ + self.state.hands[player].tile_count[tile] -= count + self.state.melds[player].append((meld_type, tile)) + logger.info(f"玩家 {player} 更新明牌: {meld_type} {tile},当前明牌: {self.state.melds[player]}") \ No newline at end of file diff --git a/src/engine/calculate_fan.py b/src/engine/calculate_fan.py index 532e327..d61ef99 100644 --- a/src/engine/calculate_fan.py +++ b/src/engine/calculate_fan.py @@ -1,92 +1,44 @@ -def calculate_fan(hand, melds, is_self_draw, is_cleared, conditions): +from src.engine.fan_type import is_terminal_fan,is_cleared,is_full_request,is_seven_pairs,is_basic_win,is_dragon_seven_pairs +from loguru import logger + +def calculate_fan(hand, melds, is_self_draw, winning_tile, conditions): """ - 动态计算番数,优先处理高番数。 + 动态计算番数,根据现有的番型规则。 参数: - hand: 当前胡牌的手牌(Hand 对象)。 - melds: 玩家已明牌的列表(Meld 对象列表)。 - is_self_draw: 是否自摸。 - - is_cleared: 是否清一色。 - - conditions: 字典,包含特殊胡牌条件,如 {"is_seven_pairs": True, "is_tian_hu": True}。 + - winning_tile: 胡的那张牌(MahjongTile 对象)。 + - conditions: 字典,包含特殊胡牌条件,如 {"is_tian_hu": True}。 返回: - - fan: 最大番数。 + - int: 最大番数。 """ - fan = 0 # 默认番数 + fan = 0 # 初始化番数 - # 定义番数规则(按优先级从高到低排序) - rules = { - "tian_hu": lambda: 12 if conditions.get("is_tian_hu", False) else 0, # 天胡 - "di_hu": lambda: 12 if conditions.get("is_di_hu", False) else 0, # 地胡 - "dragon_seven_pairs": lambda: 12 if is_dragon_seven_pairs(hand) else 0, # 龙七对 - "clear_seven_pairs": lambda: 12 if is_cleared and is_seven_pairs(hand) else 0, # 清七对 - "full_request": lambda: 6 if is_full_request(hand, melds) else 0, # 全求人 - "pure_cleared": lambda: 4 if is_cleared and len(melds) >= 2 else 0, # 极品清一色 - "seven_pairs": lambda: 2 if is_seven_pairs(hand) else 0, # 七对 - "big_pairs": lambda: 2 if is_big_pairs(hand) else 0, # 大对子 - "small_pairs": lambda: 2 if is_small_pairs(hand) else 0, # 小七对 - "golden_hook": lambda: 1 if is_golden_hook(hand) else 0, # 金钩吊 - "gang_flower": lambda: 1 if conditions.get("is_gang_flower", False) else 0, # 杠上开花 - "rob_gang": lambda: 1 if conditions.get("is_rob_gang", False) else 0, # 抢杠胡 - "under_the_sea": lambda: 1 if conditions.get("is_under_the_sea", False) else 0, # 海底捞月 - "plain_win": lambda: 1 if not any(conditions.values()) else 0 # 平胡 - } + # 定义番型规则(按优先级从高到低排序) + rules = [ + ("tian_hu", lambda: 12 if conditions.get("is_tian_hu", False) else 0), # 天胡 + ("di_hu", lambda: 12 if conditions.get("is_di_hu", False) else 0), # 地胡 + ("dragon_seven_pairs", lambda: is_dragon_seven_pairs(hand, melds)[0]), # 龙七对 + ("seven_pairs", lambda: is_seven_pairs(hand)), # 七对 + ("full_request", lambda: is_full_request(hand, melds, winning_tile)), # 全求人 + ("cleared", lambda: is_cleared(hand, melds)), # 清一色 + ("terminal_fan", lambda: is_terminal_fan(hand, melds)), # 带幺九 + ("plain_win", lambda: is_basic_win(hand)), # 平胡 + ] - # 逐一应用规则 - for rule, func in rules.items(): - result = func() - if result > fan: - fan = result # 选择当前最大番数 + # 逐一应用规则,取最大番数 + for rule, func in rules: + current_fan = func() + if current_fan > fan: + fan = current_fan + logger.debug(f"应用番型规则 {rule}: {current_fan} 番") + + # 特殊条件(例如自摸加番) + if is_self_draw: + fan += 1 # 自摸额外加 1 番 + logger.debug("自摸加 1 番") return fan - - -# 辅助函数实现 -def is_seven_pairs(hand): - """检查是否是七对(七个对子)。""" - from collections import Counter - counter = Counter(hand.tiles) - return sum(1 for count in counter.values() if count == 2) == 7 - - -def is_dragon_seven_pairs(hand): - """检查是否是龙七对(七对中有一对是三张)。""" - from collections import Counter - counter = Counter(hand.tiles) - pairs = sum(1 for count in counter.values() if count == 2) - triplets = sum(1 for count in counter.values() if count == 3) - return pairs == 6 and triplets == 1 - - -def is_big_pairs(hand): - """检查是否是大对子(四坎牌和一对将,坎牌为刻子)。""" - from collections import Counter - counter = Counter(hand.tiles) - triplets = sum(1 for count in counter.values() if count == 3) - pairs = sum(1 for count in counter.values() if count == 2) - return triplets == 4 and pairs == 1 - - -def is_golden_hook(hand): - """检查是否是金钩吊(只剩一张牌)。""" - return len(hand.tiles) == 1 - - -def is_full_request(hand, melds): - """检查是否是全求人(所有牌通过碰、杠完成)。""" - return all(tile_count == 0 for tile_count in hand.tiles) and len(melds) > 0 - - -def is_cleared(hand, melds): - """检查是否是清一色(所有牌同一种花色)。""" - all_tiles = hand.tiles + [meld.tile for meld in melds] - suits = {tile.suit for tile in all_tiles} - return len(suits) == 1 -def is_small_pairs(hand): - """ - 检查是否是小七对(六对加一对)。 - """ - from collections import Counter - counter = Counter(hand.tiles) - pairs = sum(1 for count in counter.values() if count == 2) - return pairs == 6 \ No newline at end of file diff --git a/src/engine/fan_type.py b/src/engine/fan_type.py index 7130ddb..5dacefd 100644 --- a/src/engine/fan_type.py +++ b/src/engine/fan_type.py @@ -40,7 +40,7 @@ def is_cleared(hand, melds): return 0 -def calculate_terminal_fan(hand, melds): +def is_terminal_fan(hand, melds): """ 计算带幺九番型,并返回对应番数。 """ diff --git a/tests/test_fan_type.py b/tests/test_fan_type.py index 5e2fbe5..a89fe64 100644 --- a/tests/test_fan_type.py +++ b/tests/test_fan_type.py @@ -1,6 +1,6 @@ from src.engine.hand import Hand from src.engine.mahjong_tile import MahjongTile -from src.engine.fan_type import is_basic_win,is_cleared,calculate_terminal_fan,is_seven_pairs,is_full_request,is_dragon_seven_pairs +from src.engine.fan_type import is_basic_win,is_cleared,is_terminal_fan,is_seven_pairs,is_full_request,is_dragon_seven_pairs from src.engine.meld import Meld def test_is_basic_win(): @@ -100,7 +100,7 @@ def test_calculate_terminal_fan(): hand.add_tile(MahjongTile("条", 5)) hand.add_tile(MahjongTile("条", 5)) melds = [] - assert calculate_terminal_fan(hand, melds) == 3, "测试失败:基本带幺九应为 3 番" + assert is_terminal_fan(hand, melds) == 3, "测试失败:基本带幺九应为 3 番" def test_is_seven_pairs(): """测试七对番型"""