parent
bf1c5116be
commit
27da1caf44
|
|
@ -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]}")
|
||||
|
|
@ -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
|
||||
|
|
@ -40,7 +40,7 @@ def is_cleared(hand, melds):
|
|||
return 0
|
||||
|
||||
|
||||
def calculate_terminal_fan(hand, melds):
|
||||
def is_terminal_fan(hand, melds):
|
||||
"""
|
||||
计算带幺九番型,并返回对应番数。
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
"""测试七对番型"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue