diff --git a/src/engine/fan_type.py b/src/engine/fan_type.py new file mode 100644 index 0000000..7278f75 --- /dev/null +++ b/src/engine/fan_type.py @@ -0,0 +1,43 @@ +from src.engine.utils import try_win + +def is_basic_win(hand): + # 将手牌转换为列表并按花色和数值排序 + all_tiles = hand.tiles[:] + all_tiles.sort(key=lambda t: (t.suit, t.value)) + + # 调用递归函数检查是否符合平胡 + if try_win(all_tiles): + return 1 + return 0 + + +def is_cleared(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) + + # 检查是否只有一种花色 + suits = {tile.suit for tile in all_tiles} + if len(suits) != 1: + return 0 # 不是清一色 + + # 检查杠的数量 + gang_count = sum(1 for meld in melds if meld.is_kong()) + + # 根据杠的数量和是否符合基本胡规则确定番数 + sorted_tiles = sorted(all_tiles, key=lambda t: (t.suit, t.value)) + if try_win(sorted_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 diff --git a/src/engine/utils.py b/src/engine/utils.py index c512eda..442a420 100644 --- a/src/engine/utils.py +++ b/src/engine/utils.py @@ -11,9 +11,59 @@ def get_suit(tile_index): else: raise ValueError(f"无效的牌索引: {tile_index}") + def get_tile_name(tile_index): """ 根据牌的索引返回牌名(例如:1条,2筒等)。 """ suit = get_suit(tile_index) return f"{tile_index % 36 + 1}{suit}" + + +def is_valid_group(tiles): + """ + 判断是否是合法的刻子(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 + + +def try_win(remaining_tiles, groups_formed=0): + """ + 尝试将剩余牌分组为合法的刻子或顺子,并检查是否有一对将牌。 + """ + # 如果没有剩余牌,检查是否形成了四坎牌 + if not remaining_tiles: + return groups_formed == 4 + + # 检查是否可以找到一对将牌 + 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): + 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): + 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): + return True + + return False diff --git a/tests/test_calculate_fan.py b/tests/test_calculate_fan.py index e76912f..1bc48a6 100644 --- a/tests/test_calculate_fan.py +++ b/tests/test_calculate_fan.py @@ -1,31 +1,36 @@ import pytest from src.engine.calculate_fan import calculate_fan, is_seven_pairs, is_cleared, is_big_pairs +from src.engine.hand import Hand +from src.engine.mahjong_tile import MahjongTile + # 测试用例 def test_basic_win(): """ 测试平胡(基本胡)计分 """ - hand = [0] * 108 - # 模拟平胡手牌: 四组顺子 + 一对将 - hand[0] = 2 # 将: 两张1条 - hand[3] = 1 # 2条 - hand[4] = 1 # 3条 - hand[5] = 1 # 4条 - hand[10] = 1 # 5条 - hand[11] = 1 # 6条 - hand[12] = 1 # 7条 - hand[20] = 1 # 8条 - hand[21] = 1 # 9条 - hand[22] = 1 # 1筒 - hand[30] = 1 # 2筒 - hand[31] = 1 # 3筒 + hand = Hand() + # 模拟平胡手牌:四组顺子 + 一对将 + hand.add_tile(MahjongTile("筒", 1)) + hand.add_tile(MahjongTile("筒", 1)) # 将 + hand.add_tile(MahjongTile("筒", 2)) + hand.add_tile(MahjongTile("筒", 3)) # 顺子 + hand.add_tile(MahjongTile("筒", 4)) + hand.add_tile(MahjongTile("筒", 5)) + hand.add_tile(MahjongTile("筒", 6)) # 顺子 + hand.add_tile(MahjongTile("筒", 7)) + hand.add_tile(MahjongTile("筒", 7)) + hand.add_tile(MahjongTile("筒", 8)) + hand.add_tile(MahjongTile("筒", 9)) # 顺子 + hand.add_tile(MahjongTile("万", 1)) + hand.add_tile(MahjongTile("万", 2)) + hand.add_tile(MahjongTile("万", 3)) # 顺子 melds = [] conditions = {} - fan = calculate_fan(hand, melds, is_self_draw=False, is_cleared=False, conditions=conditions) + fan = calculate_fan(hand.tiles, melds, is_self_draw=False, is_cleared=False, conditions=conditions) assert fan == 1, f"Expected 1 fan, got {fan}" diff --git a/tests/test_fan_type.py b/tests/test_fan_type.py new file mode 100644 index 0000000..10e8985 --- /dev/null +++ b/tests/test_fan_type.py @@ -0,0 +1,79 @@ +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.meld import Meld + +def test_is_basic_win(): + """ + 测试平胡(基本胡)的逻辑。 + """ + hand = Hand() + # 添加牌到手牌中 + hand.add_tile(MahjongTile("筒", 5)) + hand.add_tile(MahjongTile("筒", 5)) + hand.add_tile(MahjongTile("筒", 1)) + hand.add_tile(MahjongTile("筒", 2)) + hand.add_tile(MahjongTile("筒", 3)) + hand.add_tile(MahjongTile("筒", 4)) + hand.add_tile(MahjongTile("筒", 5)) + hand.add_tile(MahjongTile("筒", 6)) + hand.add_tile(MahjongTile("筒", 7)) + hand.add_tile(MahjongTile("筒", 8)) + hand.add_tile(MahjongTile("筒", 9)) + hand.add_tile(MahjongTile("万", 3)) + hand.add_tile(MahjongTile("万", 4)) + hand.add_tile(MahjongTile("万", 5)) + + # 打印当前手牌 + print(f"测试手牌: {hand}") + + # 调用平胡逻辑函数 + result = is_basic_win(hand) + + # 使用断言验证 + assert result, "测试失败:此手牌应该符合平胡(基本胡)规则" + print("测试通过:平胡(基本胡)逻辑正确") + +def test_is_cleared_basic(): + """测试素清(不带杠的清一色)""" + hand = Hand() + # 添加手牌 + hand.add_tile(MahjongTile("筒", 1)) + hand.add_tile(MahjongTile("筒", 2)) + hand.add_tile(MahjongTile("筒", 3)) + hand.add_tile(MahjongTile("筒", 4)) + hand.add_tile(MahjongTile("筒", 5)) + hand.add_tile(MahjongTile("筒", 6)) + hand.add_tile(MahjongTile("筒", 7)) + hand.add_tile(MahjongTile("筒", 8)) + hand.add_tile(MahjongTile("筒", 9)) + hand.add_tile(MahjongTile("筒", 4)) + hand.add_tile(MahjongTile("筒", 5)) + hand.add_tile(MahjongTile("筒", 6)) + hand.add_tile(MahjongTile("筒", 9)) + hand.add_tile(MahjongTile("筒", 9)) + + melds = [] # 无杠 + assert is_cleared(hand, melds) == 2, "测试失败:素清应为 2 番" + print("测试通过:素清") + +def test_is_cleared_with_one_gang(): + """测试极品(带 1 杠的清一色)""" + hand = Hand() + # 添加手牌 + hand.add_tile(MahjongTile("筒", 1)) + hand.add_tile(MahjongTile("筒", 2)) + hand.add_tile(MahjongTile("筒", 3)) + hand.add_tile(MahjongTile("筒", 4)) + hand.add_tile(MahjongTile("筒", 5)) + hand.add_tile(MahjongTile("筒", 6)) + hand.add_tile(MahjongTile("筒", 4)) + hand.add_tile(MahjongTile("筒", 5)) + hand.add_tile(MahjongTile("筒", 6)) + hand.add_tile(MahjongTile("筒", 9)) + hand.add_tile(MahjongTile("筒", 9)) + + melds = [Meld(MahjongTile("筒", 7), "杠")] # 带 1 杠 + assert is_cleared(hand, melds) == 3, "测试失败:极品应为 3 番" + print("测试通过:极品") +