diff --git a/src/engine/chengdu_mahjong_engine.py b/src/engine/chengdu_mahjong_engine.py index 4d73824..2eea05f 100644 --- a/src/engine/chengdu_mahjong_engine.py +++ b/src/engine/chengdu_mahjong_engine.py @@ -2,7 +2,7 @@ import random from loguru import logger -from .game_state import ChengduMahjongState +from .chengdu_mahjong_state import ChengduMahjongState class ChengduMahjongEngine: diff --git a/src/engine/game_state.py b/src/engine/chengdu_mahjong_state.py similarity index 61% rename from src/engine/game_state.py rename to src/engine/chengdu_mahjong_state.py index 5583202..05d8913 100644 --- a/src/engine/game_state.py +++ b/src/engine/chengdu_mahjong_state.py @@ -1,7 +1,7 @@ from collections import Counter from .hand import Hand from .mahjong_tile import MahjongTile - +from loguru import logger class ChengduMahjongState: def __init__(self): @@ -40,28 +40,33 @@ class ChengduMahjongState: raise ValueError("缺门设置无效") self.missing_suits[player] = missing_suit - def can_win(self, hand: Hand): + def can_win(self, hand: Hand, missing_suit: str): """ - 判断是否满足胡牌条件:四组(顺子或刻子)+ 一对将。 + 判断玩家是否能胡牌。 + :param hand: 玩家手牌(Hand 对象)。 + :param missing_suit: 玩家设置的缺门花色。 + :return: True 表示能胡牌,False 表示不能胡牌。 """ + def is_valid_group(tiles): """ - 判断是否为合法的顺子或刻子。 + 检查是否是合法组(AAA 或 ABC)。 """ if len(tiles) != 3: return False - tiles.sort(key=lambda t: (t.suit, t.value)) - return (tiles[0] == tiles[1] == tiles[2]) or \ - (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) + tiles.sort(key=lambda t: (t.suit, t.value)) # 按花色和数值排序 + 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, depth=0): """ - 递归检查是否可以将剩余牌分为合法组合。 + 尝试将剩余牌分组,必须满足 n * AAA + m * ABC。 """ if not remaining_tiles: - return depth == 4 # 必须分成四组 - + 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)): @@ -73,18 +78,27 @@ class ChengduMahjongState: return True return False - # 统计每张牌的数量 + # **第一步:检查花色限制** + suits = {tile.suit for tile in hand.tiles} + if len(suits) > 2: + logger.info("花色超过两种,不能胡牌") + return False # 花色超过两种,不能胡牌 + + # 检查是否打完缺门的花色 + if any(tile.suit == missing_suit for tile in hand.tiles): + return False # 仍有缺门的花色,不能胡牌 + + # **第二步:寻找对子并分组** + # 找到所有对子(至少两张相同的牌) tile_counter = Counter(hand.tiles) pairs = [tile for tile, count in tile_counter.items() if count >= 2] + # 遍历所有对子,尝试用剩余牌分组 for pair in pairs: - # 移除一对将牌 temp_tiles = hand.tiles[:] - temp_tiles.remove(pair) - temp_tiles.remove(pair) - - # 检查剩余牌是否能分组 - if try_win(temp_tiles): + temp_tiles.remove(pair) # 移除一张将牌 + temp_tiles.remove(pair) # 再移除一张将牌 + if try_win(temp_tiles): # 检查是否可以分组 return True - return False \ No newline at end of file + return False # 如果没有找到符合条件的组合,则不能胡牌 \ No newline at end of file diff --git a/tests/test_chengdu_majiang_engine.py b/tests/test_chengdu_majiang_engine.py index d259595..5586d33 100644 --- a/tests/test_chengdu_majiang_engine.py +++ b/tests/test_chengdu_majiang_engine.py @@ -27,7 +27,7 @@ def test_discard_tile(): def test_set_missing_suit(): - from src.engine.game_state import ChengduMahjongState + from src.engine.chengdu_mahjong_state import ChengduMahjongState state = ChengduMahjongState() player = 0 @@ -41,7 +41,7 @@ def test_set_missing_suit(): def test_can_win(): - from src.engine.game_state import ChengduMahjongState + from src.engine.chengdu_mahjong_state import ChengduMahjongState state = ChengduMahjongState() hand = [0] * 108 diff --git a/tests/test_game_status.py b/tests/test_game_status.py new file mode 100644 index 0000000..ae526f4 --- /dev/null +++ b/tests/test_game_status.py @@ -0,0 +1,98 @@ +from src.engine.chengdu_mahjong_state import ChengduMahjongState +from src.engine.hand import Hand +from src.engine.mahjong_tile import MahjongTile + + +def test_set_missing_suit(): + """测试设置缺门功能""" + state = ChengduMahjongState() + state.set_missing_suit(0, "条") + assert state.missing_suits[0] == "条", "测试失败:缺门设置为 '条' 后未正确更新" + state.set_missing_suit(1, "筒") + assert state.missing_suits[1] == "筒", "测试失败:缺门设置为 '筒' 后未正确更新" + state.set_missing_suit(2, "万") + assert state.missing_suits[2] == "万", "测试失败:缺门设置为 '万' 后未正确更新" + + try: + state.set_missing_suit(0, "花") + except ValueError: + print("测试通过:设置无效缺门 '花' 抛出异常") + else: + raise AssertionError("测试失败:设置无效缺门 '花' 未抛出异常") + + +def test_can_win_with_sequence_and_pair(): + 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("条", 4)) + hand.add_tile(MahjongTile("筒", 5)) + hand.add_tile(MahjongTile("筒", 6)) + hand.add_tile(MahjongTile("筒", 7)) + hand.add_tile(MahjongTile("万", 1)) + hand.add_tile(MahjongTile("万", 2)) + hand.add_tile(MahjongTile("万", 3)) + hand.add_tile(MahjongTile("筒", 8)) + hand.add_tile(MahjongTile("筒", 8)) + state = ChengduMahjongState() + state.hands[0] = hand + assert state.can_win(hand), "测试失败:顺子和对子应该可以胡牌" + print("测试通过:顺子和对子可以胡牌") + + +def test_can_win_with_triplets_and_pair(): + """测试刻子和对子胡牌""" + hand = Hand() + hand.add_tile(MahjongTile("条", 1)) + hand.add_tile(MahjongTile("条", 2)) + hand.add_tile(MahjongTile("条", 3)) + hand.add_tile(MahjongTile("条", 1)) + hand.add_tile(MahjongTile("条", 2)) + hand.add_tile(MahjongTile("条", 3)) + hand.add_tile(MahjongTile("条", 1)) + hand.add_tile(MahjongTile("条", 2)) + hand.add_tile(MahjongTile("条", 3)) + hand.add_tile(MahjongTile("筒", 3)) + hand.add_tile(MahjongTile("筒", 3)) + hand.add_tile(MahjongTile("筒", 3)) + hand.add_tile(MahjongTile("筒", 4)) + hand.add_tile(MahjongTile("筒", 4)) + state = ChengduMahjongState() + state.hands[0] = hand + print(f"\n,state.hand[0]: {state.hands[0]}") + assert state.can_win(state.hands[0]) == True, "测试失败:刻子和对子应该可以胡牌" + + +def test_can_win_with_mixed_groups(): + """测试顺子、刻子和对子混合胡牌""" + 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("筒", 4)) + hand.add_tile(MahjongTile("筒", 4)) + hand.add_tile(MahjongTile("万", 7)) + hand.add_tile(MahjongTile("万", 8)) + hand.add_tile(MahjongTile("万", 9)) + hand.add_tile(MahjongTile("筒", 6)) + hand.add_tile(MahjongTile("筒", 6)) + state = ChengduMahjongState() + assert state.can_win(hand), "测试失败:顺子、刻子和对子混合应该可以胡牌" + + +def test_cannot_win(): + """测试不能胡牌""" + 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)) + state = ChengduMahjongState() + assert not state.can_win(hand), "测试失败:当前手牌不应可以胡牌" + +