diff --git a/src/engine/fan_type.py b/src/engine/fan_type.py index b86828c..feabaa1 100644 --- a/src/engine/fan_type.py +++ b/src/engine/fan_type.py @@ -1,4 +1,4 @@ -from src.engine.utils import try_win +from src.engine.utils import try_win,is_terminal_tile def is_basic_win(hand): # 将手牌转换为列表并按花色和数值排序 @@ -12,10 +12,7 @@ def is_basic_win(hand): def is_cleared(hand, melds): - """ - 检查手牌和明牌是否符合清一色的番型。 - """ - # 获取手牌和明牌的所有牌 + # 合并所有牌(手牌和明牌) all_tiles = hand.tiles[:] for meld in melds: if meld.is_triplet(): @@ -28,16 +25,48 @@ def is_cleared(hand, melds): if len(suits) != 1: return 0 # 不是清一色 - # 检查杠的数量 + # 计算杠的数量 gang_count = sum(1 for meld in melds if meld.is_kong()) - # 根据杠的数量和是否符合基本胡规则确定番数 - sorted_tiles = sorted(hand.tiles, key=lambda t: (t.suit, t.value)) - if try_win(sorted_tiles): # 检查是否符合基本胡(四坎牌加一对将) + # 检查是否符合基本胡规则(四坎牌加一对将) + if try_win(hand.tiles): if gang_count == 0: return 2 # 素清 elif gang_count == 1: return 3 # 极品 elif gang_count >= 2: return 4 # 极中极 - return 0 \ No newline at end of file + return 0 + + +def calculate_terminal_fan(hand, melds): + """ + 计算带幺九番型,并返回对应番数。 + """ + # 合并所有牌(手牌和明牌) + all_tiles = hand.tiles[:] + for meld in melds: + if meld.is_triplet(): + all_tiles.extend([meld.tile] * 3) + elif meld.is_kong(): + all_tiles.extend([meld.tile] * 4) + + # 检查是否同时包含 1 和 9 + contains_one = any(tile.value == 1 for tile in all_tiles) + contains_nine = any(tile.value == 9 for tile in all_tiles) + if not (contains_one and contains_nine): + return 0 # 不符合带幺九 + + # 检查是否符合花色限制 + suits = {tile.suit for tile in all_tiles} + if len(suits) > 2: + return 0 # 不符合花色要求 + + # 检查是否符合基本胡规则(四坎牌加一对将) + sorted_tiles = sorted(all_tiles, key=lambda t: (t.suit, t.value)) + if try_win(sorted_tiles): + return 3 # 带幺九 + + return 0 + + diff --git a/src/engine/utils.py b/src/engine/utils.py index cea81c1..d7e73be 100644 --- a/src/engine/utils.py +++ b/src/engine/utils.py @@ -1,51 +1,54 @@ +from collections import Counter + + def is_valid_group(tiles): """ - 判断是否是合法的刻子(AAA)或顺子(ABC)。 + 检查是否是合法组(AAA 或 ABC)。 """ if len(tiles) != 3: return False tiles.sort(key=lambda t: (t.suit, t.value)) # 按花色和数值排序 - # 判断是否为刻子 - if tiles[0].value == tiles[1].value == tiles[2].value and \ - tiles[0].suit == tiles[1].suit == tiles[2].suit: - return True - # 判断是否为顺子 - if tiles[0].value + 1 == tiles[1].value and \ - tiles[1].value + 1 == tiles[2].value and \ - tiles[0].suit == tiles[1].suit == tiles[2].suit: - return True - return False + 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, groups_formed=0, has_pair=False): +def try_win(remaining_tiles, pairs_found=False): """ - 尝试将剩余牌分组为合法的刻子或顺子,并检查是否有一对将牌。 + 尝试将剩余牌分组,必须满足 n * AAA + m * ABC + DD。 """ - # 如果没有剩余牌,检查是否形成了四坎牌且有一对将牌 + # 如果没有剩余牌,检查是否已经找到对子 if not remaining_tiles: - return groups_formed == 4 and has_pair + return pairs_found # 必须存在一个对子 - # 检查是否可以找到一对将牌 - if not has_pair: # 如果还没有找到将牌 - for i in range(len(remaining_tiles) - 1): - if remaining_tiles[i] == remaining_tiles[i + 1]: # 找到对子 - temp_tiles = remaining_tiles[:i] + remaining_tiles[i + 2:] # 移除对子 - if try_win(temp_tiles, groups_formed, has_pair=True): + # 尝试找到一个对子 + 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): - temp_tiles = ( - remaining_tiles[:i] - + remaining_tiles[i + 1:j] - + remaining_tiles[j + 1:k] - + remaining_tiles[k + 1:] - ) - if try_win(temp_tiles, groups_formed + 1, has_pair): + 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 + + +def is_terminal_tile(tile): + """ + 检查单张牌是否是幺九牌(1 或 9)。 + """ + return tile.value in {1, 9} diff --git a/tests/test_fan_type.py b/tests/test_fan_type.py index 3824bef..3d596c6 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 +from src.engine.fan_type import is_basic_win,is_cleared,calculate_terminal_fan from src.engine.meld import Meld def test_is_basic_win(): @@ -80,3 +80,24 @@ def test_is_cleared_with_one_gang(): assert is_cleared(hand, melds) == 3, "测试失败:极品应为 3 番" print("测试通过:极品") +def test_calculate_terminal_fan(): + """测试带幺九番型""" + + # 示例1:基本带幺九 + hand = Hand() + hand.add_tile(MahjongTile("条", 1)) + hand.add_tile(MahjongTile("条", 2)) + hand.add_tile(MahjongTile("条", 3)) + hand.add_tile(MahjongTile("条", 7)) + hand.add_tile(MahjongTile("条", 8)) + hand.add_tile(MahjongTile("条", 9)) + hand.add_tile(MahjongTile("筒", 1)) + hand.add_tile(MahjongTile("筒", 1)) + hand.add_tile(MahjongTile("筒", 1)) + hand.add_tile(MahjongTile("筒", 9)) + hand.add_tile(MahjongTile("筒", 9)) + hand.add_tile(MahjongTile("筒", 9)) + hand.add_tile(MahjongTile("条", 5)) + hand.add_tile(MahjongTile("条", 5)) + melds = [] + assert calculate_terminal_fan(hand, melds) == 3, "测试失败:基本带幺九应为 3 番" \ No newline at end of file