def test_atodzuke_keep_yakuhai_wait(self): # make sure yakuhai strategy is activated by adding 3 doras to the hand table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='9')) tiles = self._string_to_136_array(man='11144', sou='567', pin='567', honors='77') player.init_hand(tiles) meld = self._make_meld(Meld.PON, man='111') player.add_called_meld(meld) # two of 4 man tiles are already out, so it would seem our wait is worse, but we know # we must keep two pairs in order to be atodzuke tempai table.add_discarded_tile(1, self._string_to_136_tile(man='4'), False) table.add_discarded_tile(1, self._string_to_136_tile(man='4'), False) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) player.draw_tile(self._string_to_136_tile(man='2')) discarded_tile = player.discard_tile() self.assertEqual(self._to_string([discarded_tile]), '2m')
def test_keep_only_yakuhai_pair(self): # make sure yakuhai strategy is activated by adding 3 doras to the hand table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='9')) table.add_dora_indicator(self._string_to_136_tile(man='3')) table.add_discarded_tile(1, self._string_to_136_tile(honors='7'), False) tiles = self._string_to_136_array(man='11144', sou='567', pin='156', honors='77') player.init_hand(tiles) meld = self._make_meld(Meld.PON, man='111') player.add_called_meld(meld) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) player.draw_tile(self._string_to_136_tile(pin='1')) discarded_tile = player.discard_tile() self.assertNotEqual(self._to_string([discarded_tile]), '7z')
def test_tile_danger_early_discard_early(): enemy_seat = 1 table = Table() table.has_aka_dora = True player = table.player tiles = string_to_136_array(man="11345", pin="11289", honors="5", sou="19") tile = string_to_136_tile(man="9") player.init_hand(tiles) player.draw_tile(tile) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="2"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="5"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(pin="7"), True) table.add_discarded_tile(enemy_seat, string_to_136_tile(pin="1"), True) enemy_called_riichi_helper(table, enemy_seat, string_to_136_tile(pin="4")) # too early to judge about early discards _assert_discard_not_equal(player, enemy_seat, TileDanger.BONUS_EARLY_28, sou="1") _assert_discard_not_equal(player, enemy_seat, TileDanger.BONUS_EARLY_5, sou="9") _assert_discard_not_equal(player, enemy_seat, TileDanger.BONUS_EARLY_37, pin="8") _assert_discard_not_equal(player, enemy_seat, TileDanger.BONUS_EARLY_37, pin="9")
def _create_table(enemy_seat, discards, riichi_tile): table = Table() table.has_aka_dora = True for discard in discards: table.add_discarded_tile(0, discard, False) enemy_called_riichi_helper(table, enemy_seat, riichi_tile) return table
def test_find_dealer_tile_to_discard(self): dealer = 2 dora = self._string_to_136_tile(honors='3') table = Table() table.init_round(0, 0, 0, dora, dealer, []) tiles = self._string_to_136_array(sou='2234678', pin='34', man='45789') table.player.init_hand(tiles) table.add_discarded_tile(1, self._string_to_136_tile(man='4'), False) table.add_discarded_tile(1, self._string_to_136_tile(man='5'), False) table.add_discarded_tile(2, self._string_to_136_tile(man='8'), False) table.add_discarded_tile(2, self._string_to_136_tile(man='9'), False) table.add_called_riichi(1) table.add_called_riichi(2) # for this test we don't need temporary_safe_tiles table.get_player(1).temporary_safe_tiles = [] table.get_player(2).temporary_safe_tiles = [] result = table.player.discard_tile() # second player is a dealer, let's fold against him self.assertEqual(self._to_string([result]), '9m') tiles = self._string_to_136_array(sou='234567', pin='348', man='234', honors='23') table.player.init_hand(tiles) result = table.player.discard_tile() # there is no safe tiles against dealer, so let's fold against other players self.assertEqual(table.player.ai.in_defence, True) self.assertEqual(self._to_string([result]), '4m')
def test_defence_against_honitsu_second_case(self): table = Table() tiles = self._string_to_136_array(sou='4', pin='2223456', man='678', honors='66') table.player.init_hand(tiles) table.add_called_meld(1, self._make_meld(Meld.CHI, sou='789')) table.add_called_meld(1, self._make_meld(Meld.PON, honors='444')) table.add_called_meld(1, self._make_meld(Meld.PON, honors='222')) table.add_discarded_tile(1, self._string_to_136_tile(man='2'), False) table.add_discarded_tile(1, self._string_to_136_tile(man='8'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='1'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='3'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='4'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='6'), False) table.add_discarded_tile(1, self._string_to_136_tile(sou='7'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='9'), False) table.add_discarded_tile(1, self._string_to_136_tile(honors='5'), False) table.add_discarded_tile(1, self._string_to_136_tile(honors='7'), False) table.player.draw_tile(self._string_to_136_tile(sou='9')) result = table.player.discard_tile() self.assertEqual(self._to_string([result]), '3p')
def test_open_hand_and_once_discarded_tile(): """ If we have valuable pair in the hand, this tile was discarded once and we have 1+ shanten let's open on this valuable pair """ table = Table() strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, table.player) tiles = string_to_136_array(sou="678", pin="14689", man="456", honors="77") table.player.init_hand(tiles) # we don't activate strategy yet assert strategy.should_activate_strategy(table.player.tiles) is False # let's skip first yakuhai early in the game tile = string_to_136_tile(honors="7") meld, _ = table.player.try_to_call_meld(tile, True) assert meld is None # now one is out table.add_discarded_tile(1, tile, False) meld, _ = table.player.try_to_call_meld(tile, True) assert meld is not None assert tiles_to_string(meld.tiles) == "777z" # but we don't need to open hand for atodzuke here tile = string_to_136_tile(pin="7") meld, _ = table.player.try_to_call_meld(tile, True) assert meld is None
def test_dont_open_bad_hand_if_there_are_expensive_threat(): table = Table() table.add_dora_indicator(string_to_136_tile(man="4")) player = table.player player.round_step = 10 table.has_open_tanyao = True table.has_aka_dora = True enemy_seat = 1 table.add_called_riichi_step_one(enemy_seat) table.add_discarded_tile(enemy_seat, string_to_136_tile(honors="4"), True) tiles = string_to_136_array(sou="226", pin="2469", man="3344", honors="4") + [FIVE_RED_MAN] player.init_hand(tiles) # cheap enemy tempai, but this meld is garbage, let's not push tile = string_to_136_array(man="4444")[2] meld, _ = player.try_to_call_meld(tile, True) assert meld is None # cheap enemy tempai, and good chi, let's take this meld tile = string_to_136_tile(man="2") meld, _ = player.try_to_call_meld(tile, True) assert meld is not None table.add_called_meld( enemy_seat, make_meld(MeldPrint.KAN, is_open=False, honors="1111")) # enemy hand is more expensive now (12000) # in this case let's not open this hand tile = string_to_136_tile(man="2") meld, _ = player.try_to_call_meld(tile, True) assert meld is None
def test_calculate_second_level_ukeire(self): """ There was a bug with 2356 form and second level ukeire """ table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='2')) table.add_discarded_tile(1, self._string_to_136_tile(man='2'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='3'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='3'), False) tiles = self._string_to_136_array(man='34678', pin='2356', sou='4467') tile = self._string_to_136_tile(sou='8') player.init_hand(tiles) player.draw_tile(tile) discard_options, _ = player.ai.hand_builder.find_discard_options( player.tiles, player.closed_hand, player.melds ) tile = self._string_to_136_tile(man='4') discard_option = [x for x in discard_options if x.tile_to_discard == tile // 4][0] player.ai.hand_builder.calculate_second_level_ukeire(discard_option, player.tiles, player.melds) self.assertEqual(discard_option.ukeire_second, 108) tile = self._string_to_136_tile(man='3') discard_option = [x for x in discard_options if x.tile_to_discard == tile // 4][0] player.ai.hand_builder.calculate_second_level_ukeire(discard_option, player.tiles, player.melds) self.assertEqual(discard_option.ukeire_second, 108) tile = self._string_to_136_tile(pin='2') discard_option = [x for x in discard_options if x.tile_to_discard == tile // 4][0] player.ai.hand_builder.calculate_second_level_ukeire(discard_option, player.tiles, player.melds) self.assertEqual(discard_option.ukeire_second, 96) tile = self._string_to_136_tile(pin='3') discard_option = [x for x in discard_options if x.tile_to_discard == tile // 4][0] player.ai.hand_builder.calculate_second_level_ukeire(discard_option, player.tiles, player.melds) self.assertEqual(discard_option.ukeire_second, 96) tile = self._string_to_136_tile(pin='5') discard_option = [x for x in discard_options if x.tile_to_discard == tile // 4][0] player.ai.hand_builder.calculate_second_level_ukeire(discard_option, player.tiles, player.melds) self.assertEqual(discard_option.ukeire_second, 96) tile = self._string_to_136_tile(pin='6') discard_option = [x for x in discard_options if x.tile_to_discard == tile // 4][0] player.ai.hand_builder.calculate_second_level_ukeire(discard_option, player.tiles, player.melds) self.assertEqual(discard_option.ukeire_second, 96)
def test_threatening_riichi_player_and_not_early_hand_bonus(): table = Table() enemy_seat = 2 discards = string_to_136_array(sou="111122") for discard in discards: table.add_discarded_tile(enemy_seat, discard, False) enemy_called_riichi_helper(table, enemy_seat) # +1 scale for riichi on 6+ turn threatening_player = table.player.ai.defence.get_threatening_players()[0] assert threatening_player.enemy.seat == enemy_seat assert threatening_player.get_assumed_hand_cost( string_to_136_tile(man="2")) == 3900
def _choose_tanki_with_kabe_helper(self, tiles, kabe_tiles, tile_to_draw, tile_to_discard_str): table = Table() player = table.player player.round_step = 2 player.dealer_seat = 3 for tile in kabe_tiles: for _ in range(0, 4): table.add_discarded_tile(1, tile, False) player.init_hand(tiles) player.draw_tile(tile_to_draw) discarded_tile = player.discard_tile() self.assertEqual(self._to_string([discarded_tile]), tile_to_discard_str)
def test_defence_against_honitsu_first_case(self): table = Table() tiles = self._string_to_136_array(sou='22', pin='222367899', man='45', honors='1') table.player.init_hand(tiles) table.add_called_meld(1, self._make_meld(Meld.CHI, pin='567')) table.add_called_meld(1, self._make_meld(Meld.CHI, pin='123')) table.add_called_meld(1, self._make_meld(Meld.CHI, pin='345')) table.add_discarded_tile(1, self._string_to_136_tile(sou='6'), False) table.add_discarded_tile(1, self._string_to_136_tile(sou='6'), False) table.add_discarded_tile(1, self._string_to_136_tile(sou='8'), False) table.add_discarded_tile(1, self._string_to_136_tile(sou='9'), False) table.add_discarded_tile(1, self._string_to_136_tile(man='1'), False) table.add_discarded_tile(1, self._string_to_136_tile(man='1'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False) result = table.player.discard_tile() # we can't discard pin and honor tiles against honitsu self.assertEqual(self._to_string([result]), '2s')
def test_try_to_discard_not_needed_tiles_first_in_defence_mode(self): table = Table() tiles = self._string_to_136_array(sou='2345678', pin='789', man='55', honors='12') table.player.init_hand(tiles) table.add_discarded_tile(1, self._string_to_136_tile(man='5'), False) table.add_discarded_tile(1, self._string_to_136_tile(honors='1'), False) table.add_called_riichi(1) result = table.player.discard_tile() self.assertEqual(self._to_string([result]), '1z')
def test_calculate_our_hand_cost(): table = Table() player = table.player enemy_seat = 2 table.add_called_riichi_step_one(enemy_seat) table.add_discarded_tile(enemy_seat, string_to_136_tile(pin="9"), True) tiles = string_to_136_array(sou="234678", pin="23478", man="22") tile = string_to_136_tile(honors="1") player.init_hand(tiles) player.draw_tile(tile) discard_option = find_discard_option(player, honors="1") assert discard_option.danger.weighted_cost == 6128
def test_find_suji_tiles_to_discard_for_one_player(self): table = Table() tiles = self._string_to_136_array(sou='2345678', pin='12789', man='55') table.player.init_hand(tiles) table.add_discarded_tile(1, self._string_to_136_tile(man='2'), False) table.add_discarded_tile(1, self._string_to_136_tile(man='8'), False) table.add_called_riichi(1) result = table.player.discard_tile() self.assertEqual(self._to_string([result]), '5m')
def test_temporary_safe_tiles(self): table = Table() table.add_discarded_tile(1, self._string_to_136_tile(man='4'), False) self.assertEqual(table.get_player(1).temporary_safe_tiles, []) self.assertEqual(table.get_player(2).temporary_safe_tiles, [3]) self.assertEqual(table.get_player(3).temporary_safe_tiles, [3]) table.add_discarded_tile(2, self._string_to_136_tile(man='5'), False) self.assertEqual(table.get_player(1).temporary_safe_tiles, [4]) self.assertEqual(table.get_player(2).temporary_safe_tiles, []) self.assertEqual(table.get_player(3).temporary_safe_tiles, [3, 4]) table.add_discarded_tile(3, self._string_to_136_tile(man='6'), False) self.assertEqual(table.get_player(1).temporary_safe_tiles, [4, 5]) self.assertEqual(table.get_player(2).temporary_safe_tiles, [5]) self.assertEqual(table.get_player(3).temporary_safe_tiles, []) table.add_discarded_tile(1, self._string_to_136_tile(man='7'), False) self.assertEqual(table.get_player(1).temporary_safe_tiles, []) self.assertEqual(table.get_player(2).temporary_safe_tiles, [5, 6]) self.assertEqual(table.get_player(3).temporary_safe_tiles, [6])
def test_mark_dora_as_dangerous_tile_for_suji(self): table = Table() table.add_dora_indicator(self._string_to_136_tile(man='8')) tiles = self._string_to_136_array(man='112235', pin='4557788') table.player.init_hand(tiles) # 9 man is dora! table.player.draw_tile(self._string_to_136_tile(man='9')) table.add_discarded_tile(1, self._string_to_136_tile(man='6'), False) table.add_called_riichi(1) result = table.player.discard_tile() self.assertEqual(self._to_string([result]), '3m')
def test_find_common_suji_tiles_to_discard_for_multiple_players(self): table = Table() tiles = self._string_to_136_array(sou='2345678', pin='12789', man='55') table.player.init_hand(tiles) table.add_discarded_tile(1, self._string_to_136_tile(pin='4'), False) table.add_discarded_tile(2, self._string_to_136_tile(pin='4'), False) table.add_called_riichi(1) table.add_called_riichi(2) result = table.player.discard_tile() self.assertEqual(self._to_string([result]), '1p')
def test_dont_activate_strategy_if_we_dont_have_enough_tiles_in_the_wall(): table = Table() strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, table.player) table.dora_indicators.append(string_to_136_tile(honors="7")) tiles = string_to_136_array(man="59", sou="1235", pin="12789", honors="55") table.player.init_hand(tiles) assert strategy.should_activate_strategy(table.player.tiles) is True table.add_discarded_tile(3, string_to_136_tile(honors="5"), False) table.add_discarded_tile(3, string_to_136_tile(honors="5"), False) # we can't complete yakuhai, because there is not enough honor tiles assert strategy.should_activate_strategy(table.player.tiles) is False
def _choose_tanki_with_kabe_helper(tiles, kabe_tiles, tile_to_draw, tile_to_discard_str): table = Table() player = table.player player.round_step = 2 player.dealer_seat = 3 for tile in kabe_tiles: for _ in range(0, 4): table.add_discarded_tile(1, tile, False) player.init_hand(tiles) player.draw_tile(tile_to_draw) discarded_tile, _ = player.discard_tile() assert tiles_to_string([discarded_tile]) == tile_to_discard_str
def test_discard_tile_based_on_second_level_ukeire_and_cost(self): table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='2')) table.add_discarded_tile(1, self._string_to_136_tile(man='2'), False) tiles = self._string_to_136_array(man='34678', pin='2356', sou='4467') tile = self._string_to_136_tile(sou='8') player.init_hand(tiles) player.draw_tile(tile) discarded_tile = player.discard_tile() discard_correct = self._to_string([discarded_tile]) == '2p' or self._to_string([discarded_tile]) == '3p' self.assertEqual(discard_correct, True)
def _choose_furiten_over_karaten_helper(self, tiles, furiten_tile, karaten_tile, tile_to_draw, tile_to_discard_str): table = Table() player = table.player player.round_step = 2 player.dealer_seat = 3 player.init_hand(tiles) player.add_discarded_tile(Tile(furiten_tile, True)) for _ in range(0, 3): table.add_discarded_tile(1, karaten_tile, False) player.draw_tile(tile_to_draw) discarded_tile = player.discard_tile() self.assertEqual(self._to_string([discarded_tile]), tile_to_discard_str)
def test_try_to_discard_less_valuable_tiles_first_in_defence_mode(self): table = Table() tiles = self._string_to_136_array(sou='234678', pin='6789', man='55', honors='13') table.player.init_hand(tiles) table.add_discarded_tile(1, self._string_to_136_tile(pin='7'), False) table.add_discarded_tile(1, self._string_to_136_tile(sou='2'), False) table.add_called_riichi(1) result = table.player.discard_tile() # discard of 2s will do less damage to our hand shape than 7p discard self.assertEqual(table.player.ai.in_defence, True) self.assertEqual(self._to_string([result]), '2s')
def _choose_furiten_over_karaten_helper(tiles, furiten_tile, karaten_tile, tile_to_draw, tile_to_discard_str): table = Table() player = table.player player.round_step = 2 player.dealer_seat = 3 player.init_hand(tiles) player.add_discarded_tile(Tile(furiten_tile, True)) for _ in range(0, 3): table.add_discarded_tile(1, karaten_tile, False) player.draw_tile(tile_to_draw) discarded_tile, _ = player.discard_tile() assert tiles_to_string([discarded_tile]) == tile_to_discard_str
def test_find_impossible_waits_and_honor_tiles(self): table = Table() tiles = self._string_to_136_array(honors='1133') table.player.init_hand(tiles) table.add_discarded_tile(1, self._string_to_136_tile(honors='1'), False) table.add_discarded_tile(1, self._string_to_136_tile(honors='3'), False) table.add_called_riichi(2) table.player.ai.defence.hand_34 = self._to_34_array(table.player.tiles) table.player.ai.defence.closed_hand_34 = self._to_34_array(table.player.closed_hand) result = table.player.ai.defence.impossible_wait.find_tiles_to_discard([]) # dora is not safe against tanki wait, so let's hold it self.assertEqual([x.value for x in result], [EAST, WEST])
def test_discard_tile_based_on_second_level_ukeire_and_cost(): table = Table() player = table.player table.add_dora_indicator(string_to_136_tile(man="2")) table.add_discarded_tile(1, string_to_136_tile(man="2"), False) tiles = string_to_136_array(man="34678", pin="2356", sou="4467") tile = string_to_136_tile(sou="8") player.init_hand(tiles) player.draw_tile(tile) discarded_tile, _ = player.discard_tile() discard_correct = tiles_to_string( [discarded_tile]) == "2p" or tiles_to_string([discarded_tile]) == "3p" assert discard_correct is True
def test_dont_discard_safe_tiles_when_call_riichi(self): table = Table() table.count_of_remaining_tiles = 70 table.player.scores = 2000 tiles = self._string_to_136_array(sou='12356789', pin='22678') table.player.init_hand(tiles) table.player.draw_tile(self._string_to_136_tile(honors='1')) table.player.discard_tile() table.player.draw_tile(self._string_to_136_tile(honors='1')) table.add_discarded_tile(1, self._string_to_136_tile(sou='1'), False) table.add_called_riichi(1) result = table.player.discard_tile() self.assertEqual(table.player.can_call_riichi(), True) self.assertEqual(self._to_string([result]), '1z')
def test_add_safe_tile_after_discard(self): table = Table() table.add_called_riichi(3) table.add_discarded_tile(1, self._string_to_136_tile(man='3'), False) table.add_discarded_tile(0, self._string_to_136_tile(man='4'), False) self.assertEqual(len(table.get_player(1).discards), 1) self.assertEqual(len(table.get_player(2).discards), 0) self.assertEqual(len(table.get_player(3).discards), 0) self.assertEqual(len(table.get_player(1).safe_tiles), 1) self.assertEqual(len(table.get_player(2).safe_tiles), 0) self.assertEqual(len(table.get_player(3).safe_tiles), 2) # "2" is 3 man self.assertEqual(table.get_player(1).safe_tiles, [2]) self.assertEqual(table.get_player(3).safe_tiles, [2, 3])
def _avoid_furiten_helper(self, tiles, furiten_tile, other_tile, tile_to_draw, tile_to_discard_str): table = Table() player = table.player player.round_step = 2 player.dealer_seat = 3 player.init_hand(tiles) player.add_discarded_tile(Tile(furiten_tile, True)) for _ in range(0, 2): table.add_discarded_tile(1, other_tile, False) player.draw_tile(tile_to_draw) discarded_tile = player.discard_tile() self.assertEqual(self._to_string([discarded_tile]), tile_to_discard_str)
def test_discard_not_needed_winds(): table = Table() player = table.player player.scores = 25000 table.count_of_remaining_tiles = 100 tiles = string_to_136_array(man="24", pin="4", sou="12344668", honors="36") player.init_hand(tiles) player.draw_tile(string_to_136_tile(sou="5")) table.add_discarded_tile(1, string_to_136_tile(honors="3"), False) table.add_discarded_tile(1, string_to_136_tile(honors="3"), False) table.add_discarded_tile(1, string_to_136_tile(honors="3"), False) tile_to_discard = player.discard_tile() # west was discarded three times, we don't need it assert tiles_to_string([tile_to_discard]) == "3z"
def test_discard_not_needed_winds(self): table = Table() player = table.player player.scores = 25000 table.count_of_remaining_tiles = 100 tiles = self._string_to_136_array(man='24', pin='4', sou='12344668', honors='36') player.init_hand(tiles) player.draw_tile(self._string_to_136_tile(sou='5')) table.add_discarded_tile(1, self._string_to_136_tile(honors='3'), False) table.add_discarded_tile(1, self._string_to_136_tile(honors='3'), False) table.add_discarded_tile(1, self._string_to_136_tile(honors='3'), False) tile_to_discard = player.discard_tile() # west was discarded three times, we don't need it self.assertEqual(self._to_string([tile_to_discard]), '3z')
def test_discard_tile_based_on_second_level_ukeire_and_cost(self): table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='2')) table.add_discarded_tile(1, self._string_to_136_tile(man='2'), False) tiles = self._string_to_136_array(man='34678', pin='2356', sou='4467') tile = self._string_to_136_tile(sou='8') player.init_hand(tiles) player.draw_tile(tile) discarded_tile = player.discard_tile() discard_correct = self._to_string([ discarded_tile ]) == '2p' or self._to_string([discarded_tile]) == '3p' self.assertEqual(discard_correct, True)
def test_get_common_tempai_and_0_ukeire_crash(): """ Checks that we don't have crash anymore when bot tried to open hand with 0 ukeire :return: """ table = Table() table.add_discarded_tile(1, string_to_136_tile(sou="1"), True) table.add_discarded_tile(1, string_to_136_tile(sou="1"), True) table.add_discarded_tile(1, string_to_136_tile(man="1"), True) table.add_discarded_tile(1, string_to_136_tile(man="1"), True) tiles = string_to_136_array(man="11999", sou="116", pin="99", honors="333") table.player.init_hand(tiles) tile = string_to_136_tile(pin="9") meld, _ = table.player.try_to_call_meld(tile, False) # no ukeire, no reason to open hand assert meld is None
def test_threatening_riichi_player_and_not_visible_dora(): table = Table() enemy_seat = 2 table.add_dora_indicator(string_to_136_tile(sou="2")) discards = string_to_136_array(sou="3333222") for discard in discards: table.add_discarded_tile(enemy_seat, discard, False) enemy_called_riichi_helper(table, enemy_seat) # +1 scale for riichi on 6+ turn threatening_player = table.player.ai.defence.get_threatening_players()[0] assert threatening_player.enemy.seat == enemy_seat assert threatening_player.get_assumed_hand_cost( string_to_136_tile(man="2")) == 3900 # on dora discard, enemy hand will be on average more expensive assert threatening_player.get_assumed_hand_cost( string_to_136_tile(sou="3")) == 5200
def test_find_impossible_waits_and_honor_tiles(self): table = Table() tiles = self._string_to_136_array(honors='1133', man='123', sou='456', pin='999') table.player.init_hand(tiles) table.player.add_called_meld(self._make_meld(Meld.CHI, man='123')) table.player.add_called_meld(self._make_meld(Meld.CHI, sou='456')) table.player.add_called_meld(self._make_meld(Meld.PON, pin='999')) table.add_discarded_tile(1, self._string_to_136_tile(honors='1'), False) table.add_discarded_tile(1, self._string_to_136_tile(honors='3'), False) table.add_called_riichi(2) table.player.ai.defence.hand_34 = self._to_34_array(table.player.tiles) table.player.ai.defence.closed_hand_34 = self._to_34_array(table.player.closed_hand) result = table.player.ai.defence.impossible_wait.find_tiles_to_discard([]) # dora is not safe against tanki wait, so let's hold it self.assertEqual([x.value for x in result], [EAST, WEST])
def test_keep_only_yakuhai_pair(self): # make sure yakuhai strategy is activated by adding 3 doras to the hand table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='9')) table.add_dora_indicator(self._string_to_136_tile(man='3')) table.add_discarded_tile(1, self._string_to_136_tile(honors='7'), False) tiles = self._string_to_136_array(man='11144', sou='567', pin='156', honors='77') player.init_hand(tiles) meld = self._make_meld(Meld.PON, man='111') player.add_called_meld(meld) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) player.draw_tile(self._string_to_136_tile(pin='1')) discarded_tile = player.discard_tile() self.assertNotEqual(self._to_string([discarded_tile]), '7z')
def test_atodzuke_choose_hidden_syanpon(self): # make sure yakuhai strategy is activated by adding 3 doras to the hand table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='9')) tiles = self._string_to_136_array(man='111678', sou='56678', honors='77') player.init_hand(tiles) meld = self._make_meld(Meld.PON, man='111') player.add_called_meld(meld) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) for _ in range(0, 4): table.add_discarded_tile(1, self._string_to_136_tile(sou='9'), False) player.draw_tile(self._string_to_136_tile(man='6')) discarded_tile = player.discard_tile() self.assertNotEqual(self._to_string([discarded_tile]), '6m')
def test_find_common_safe_tile_to_discard(self): table = Table() tiles = self._string_to_136_array(sou='2456', pin='234478', man='2336') table.player.init_hand(tiles) table.add_discarded_tile(1, self._string_to_136_tile(sou='6'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False) table.add_discarded_tile(2, self._string_to_136_tile(pin='5'), False) table.add_discarded_tile(2, self._string_to_136_tile(sou='6'), False) table.add_called_riichi(1) table.add_called_riichi(2) # for this test we don't need temporary_safe_tiles table.get_player(1).temporary_safe_tiles = [] table.get_player(2).temporary_safe_tiles = [] result = table.player.discard_tile() self.assertEqual(self._to_string([result]), '6s')
def test_atodzuke_keep_yakuhai_wait(self): # make sure yakuhai strategy is activated by adding 3 doras to the hand table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='9')) tiles = self._string_to_136_array(man='11144', sou='567', pin='567', honors='77') player.init_hand(tiles) meld = self._make_meld(Meld.PON, man='111') player.add_called_meld(meld) # two of 4 man tiles are already out, so it would seem our wait is worse, but we know # we must keep two pairs in order to be atodzuke tempai table.add_discarded_tile(1, self._string_to_136_tile(man='4'), False) table.add_discarded_tile(1, self._string_to_136_tile(man='4'), False) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) player.draw_tile(self._string_to_136_tile(man='2')) discarded_tile = player.discard_tile() self.assertEqual(self._to_string([discarded_tile]), '2m')
def test_try_to_detect_honitsu_hand(self): table = Table() table.add_called_meld(1, self._make_meld(Meld.CHI, pin='567')) table.add_called_meld(1, self._make_meld(Meld.CHI, pin='123')) table.add_called_meld(1, self._make_meld(Meld.CHI, pin='345')) table.add_discarded_tile(1, self._string_to_136_tile(sou='1'), False) table.add_discarded_tile(1, self._string_to_136_tile(sou='5'), False) table.add_discarded_tile(1, self._string_to_136_tile(sou='8'), False) table.add_discarded_tile(1, self._string_to_136_tile(sou='9'), False) table.add_discarded_tile(1, self._string_to_136_tile(man='1'), False) table.add_discarded_tile(1, self._string_to_136_tile(man='1'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='1'), False) self.assertEqual(EnemyAnalyzer(table.get_player(1)).is_threatening, True) self.assertEqual(EnemyAnalyzer(table.get_player(1)).chosen_suit, is_pin)
def test_defence_against_honitsu_first_case(self): table = Table() tiles = self._string_to_136_array(sou='22', pin='222367899', man='45', honors='1') table.player.init_hand(tiles) table.add_called_meld(1, self._make_meld(Meld.CHI, pin='567')) table.add_called_meld(1, self._make_meld(Meld.CHI, pin='123')) table.add_called_meld(1, self._make_meld(Meld.CHI, pin='345')) table.add_discarded_tile(1, self._string_to_136_tile(sou='6'), False) table.add_discarded_tile(1, self._string_to_136_tile(sou='6'), False) table.add_discarded_tile(1, self._string_to_136_tile(sou='8'), False) table.add_discarded_tile(1, self._string_to_136_tile(sou='9'), False) table.add_discarded_tile(1, self._string_to_136_tile(man='1'), False) table.add_discarded_tile(1, self._string_to_136_tile(man='1'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False) result = table.player.discard_tile() # we can't discard pin and honor tiles against honitsu self.assertEqual(self._to_string([result]), '2s')
def test_priority_of_players_safe_tiles(self): table = Table() tiles = self._string_to_136_array(man='789', pin='2789', sou='23789', honors='1') table.player.init_hand(tiles) table.player.draw_tile(self._string_to_136_tile(sou='1')) table.add_discarded_tile(1, self._string_to_136_tile(sou='7'), False) table.add_discarded_tile(1, self._string_to_136_tile(honors='1'), False) table.add_called_riichi(1) table.add_discarded_tile(2, self._string_to_136_tile(honors='1'), False) result = table.player.discard_tile() self.assertEqual(self._to_string([result]), '1z')
class CallRiichiTestCase(unittest.TestCase, TestMixin): def _make_table(self, dora_indicators=None): self.table = Table() self.table.count_of_remaining_tiles = 60 self.player = self.table.player self.player.scores = 25000 # with that we don't have daburi anymore self.player.round_step = 1 if dora_indicators: for x in dora_indicators: self.table.add_dora_indicator(x) def test_dont_call_riichi_with_yaku_and_central_tanki_wait(self): self._make_table() tiles = self._string_to_136_array(sou='234567', pin='234567', man='4') self.player.init_hand(tiles) self.player.draw_tile(self._string_to_136_tile(man='5')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), False) def test_dont_call_riichi_expensive_damaten_with_yaku(self): self._make_table(dora_indicators=[ self._string_to_136_tile(man='7'), self._string_to_136_tile(man='5'), self._string_to_136_tile(sou='1'), ]) # tanyao pinfu sanshoku dora 4 - this is damaten baiman, let's not riichi it tiles = self._string_to_136_array(man='67888', sou='678', pin='34678') self.player.init_hand(tiles) self.player.draw_tile(self._string_to_136_tile(honors='3')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), False) # let's test lots of doras hand, tanyao dora 8, also damaten baiman tiles = self._string_to_136_array(man='666888', sou='22', pin='34678') self.player.init_hand(tiles) self.player.draw_tile(self._string_to_136_tile(honors='3')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), False) # chuuren tiles = self._string_to_136_array(man='1112345678999') self.player.init_hand(tiles) self.player.draw_tile(self._string_to_136_tile(honors='3')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), False) def test_riichi_expensive_hand_without_yaku(self): self._make_table(dora_indicators=[ self._string_to_136_tile(man='1'), self._string_to_136_tile(sou='1'), self._string_to_136_tile(pin='1') ]) tiles = self._string_to_136_array(man='222', sou='22278', pin='22789') self.player.init_hand(tiles) self.player.draw_tile(self._string_to_136_tile(honors='3')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), True) def test_riichi_tanki_honor_without_yaku(self): self._make_table( dora_indicators=[ self._string_to_136_tile(man='2'), self._string_to_136_tile(sou='6') ] ) tiles = self._string_to_136_array(man='345678', sou='789', pin='123', honors='2') self.player.init_hand(tiles) self.player.draw_tile(self._string_to_136_tile(honors='3')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), True) def test_riichi_tanki_honor_chiitoitsu(self): self._make_table() tiles = self._string_to_136_array(man='22336688', sou='99', pin='99', honors='2') self.player.init_hand(tiles) self.player.draw_tile(self._string_to_136_tile(honors='3')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), True) def test_always_call_daburi(self): self._make_table() self.player.round_step = 0 tiles = self._string_to_136_array(sou='234567', pin='234567', man='4') self.player.init_hand(tiles) self.player.draw_tile(self._string_to_136_tile(man='5')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), True) def test_dont_call_karaten_tanki_riichi(self): self._make_table() tiles = self._string_to_136_array(man='22336688', sou='99', pin='99', honors='2') self.player.init_hand(tiles) for _ in range(0, 3): self.table.add_discarded_tile(1, self._string_to_136_tile(honors='2'), False) self.table.add_discarded_tile(1, self._string_to_136_tile(honors='3'), False) self.player.draw_tile(self._string_to_136_tile(honors='3')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), False) def test_dont_call_karaten_ryanmen_riichi(self): self._make_table(dora_indicators=[ self._string_to_136_tile(man='1'), self._string_to_136_tile(sou='1'), self._string_to_136_tile(pin='1') ]) tiles = self._string_to_136_array(man='222', sou='22278', pin='22789') self.player.init_hand(tiles) for _ in range(0, 4): self.table.add_discarded_tile(1, self._string_to_136_tile(sou='6'), False) self.table.add_discarded_tile(1, self._string_to_136_tile(sou='9'), False) self.player.draw_tile(self._string_to_136_tile(honors='3')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), False) def test_call_riichi_penchan_with_suji(self): self._make_table(dora_indicators=[ self._string_to_136_tile(pin='1'), ]) tiles = self._string_to_136_array(sou='11223', pin='234567', man='66') self.player.init_hand(tiles) self.player.draw_tile(self._string_to_136_tile(sou='6')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), True) def test_call_riichi_tanki_with_kabe(self): self._make_table(dora_indicators=[ self._string_to_136_tile(pin='1'), ]) for _ in range(0, 3): self.table.add_discarded_tile(1, self._string_to_136_tile(honors='1'), False) for _ in range(0, 4): self.table.add_discarded_tile(1, self._string_to_136_tile(sou='8'), False) tiles = self._string_to_136_array(sou='1119', pin='234567', man='666') self.player.init_hand(tiles) self.player.draw_tile(self._string_to_136_tile(honors='1')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), True) def test_call_riichi_chiitoitsu_with_suji(self): self._make_table(dora_indicators=[ self._string_to_136_tile(man='1'), ]) for _ in range(0, 3): self.table.add_discarded_tile(1, self._string_to_136_tile(honors='3'), False) tiles = self._string_to_136_array(man='22336688', sou='9', pin='99', honors='22') self.player.init_hand(tiles) self.player.add_discarded_tile(Tile(self._string_to_136_tile(sou='6'), True)) self.player.draw_tile(self._string_to_136_tile(honors='3')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), True) def test_dont_call_riichi_chiitoitsu_bad_wait(self): self._make_table(dora_indicators=[ self._string_to_136_tile(man='1'), ]) for _ in range(0, 3): self.table.add_discarded_tile(1, self._string_to_136_tile(honors='3'), False) tiles = self._string_to_136_array(man='22336688', sou='4', pin='99', honors='22') self.player.init_hand(tiles) self.player.draw_tile(self._string_to_136_tile(honors='3')) self.player.discard_tile() self.assertEqual(self.player.can_call_riichi(), False)
class YakuhaiStrategyTestCase(unittest.TestCase, TestMixin): def setUp(self): self.table = Table() self.player = self.table.player self.player.dealer_seat = 3 def test_should_activate_strategy(self): strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, self.player) tiles = self._string_to_136_array(sou='12355689', man='89', honors='123') self.player.init_hand(tiles) self.assertEqual(strategy.should_activate_strategy(self.player.tiles), False) self.table.dora_indicators.append(self._string_to_136_tile(honors='7')) tiles = self._string_to_136_array(sou='12355689', man='899', honors='55') self.player.init_hand(tiles) self.assertEqual(strategy.should_activate_strategy(self.player.tiles), True) # with chitoitsu-like hand we don't need to go for yakuhai tiles = self._string_to_136_array(sou='1235566', man='8899', honors='66') self.player.init_hand(tiles) self.assertEqual(strategy.should_activate_strategy(self.player.tiles), False) # don't count tile discarded by other player as our pair tiles = self._string_to_136_array(sou='12355689', man='899', honors='25') self.player.init_hand(tiles) tiles = self._string_to_136_array(sou='12355689', man='899', honors='255') self.assertEqual(strategy.should_activate_strategy(tiles), False) def test_dont_activate_strategy_if_we_dont_have_enough_tiles_in_the_wall(self): strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, self.player) self.table.dora_indicators.append(self._string_to_136_tile(honors='7')) tiles = self._string_to_136_array(man='59', sou='1235', pin='12789', honors='55') self.player.init_hand(tiles) self.assertEqual(strategy.should_activate_strategy(self.player.tiles), True) self.table.add_discarded_tile(3, self._string_to_136_tile(honors='5'), False) self.table.add_discarded_tile(3, self._string_to_136_tile(honors='5'), False) # we can't complete yakuhai, because there is not enough honor tiles self.assertEqual(strategy.should_activate_strategy(self.player.tiles), False) def test_suitable_tiles(self): strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, self.player) # for yakuhai we can use any tile for tile in range(0, 136): self.assertEqual(strategy.is_tile_suitable(tile), True) def test_force_yakuhai_pair_waiting_for_tempai_hand(self): """ If hand shanten = 1 don't open hand except the situation where is we have tempai on yakuhai tile after open set """ self.table.dora_indicators.append(self._string_to_136_tile(man='3')) tiles = self._string_to_136_array(sou='123', pin='678', man='34468', honors='66') self.player.init_hand(tiles) # we will not get tempai on yakuhai pair with this hand, so let's skip this call tile = self._string_to_136_tile(man='5') meld, _ = self.player.try_to_call_meld(tile, False) self.assertEqual(meld, None) # but here we will have atodzuke tempai tile = self._string_to_136_tile(man='7') meld, _ = self.player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) self.assertEqual(meld.type, Meld.CHI) self.assertEqual(self._to_string(meld.tiles), '678m') self.table = Table() self.player = self.table.player # we can open hand in that case self.table.dora_indicators.append(self._string_to_136_tile(sou='5')) tiles = self._string_to_136_array(man='44556', sou='366789', honors='77') self.player.init_hand(tiles) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, self.player) self.assertEqual(strategy.should_activate_strategy(self.player.tiles), True) tile = self._string_to_136_tile(honors='7') meld, _ = self.player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) self.assertEqual(self._to_string(meld.tiles), '777z') def test_tempai_without_yaku(self): tiles = self._string_to_136_array(sou='678', pin='12355', man='456', honors='77') self.player.init_hand(tiles) tile = self._string_to_136_tile(pin='5') self.player.draw_tile(tile) meld = self._make_meld(Meld.CHI, sou='678') self.player.add_called_meld(meld) discard = self.player.discard_tile() self.assertNotEqual(self._to_string([discard]), '7z') self.assertNotEqual(self._to_string([discard]), '5p') def test_wrong_shanten_improvements_detection(self): """ With hand 2345s1p11z bot wanted to open set on 2s, so after opened set we will get 25s1p11z it is not correct logic, because we ruined our hand :return: """ tiles = self._string_to_136_array(sou='2345999', honors='114446') self.player.init_hand(tiles) meld = self._make_meld(Meld.PON, sou='999') self.player.add_called_meld(meld) meld = self._make_meld(Meld.PON, honors='444') self.player.add_called_meld(meld) tile = self._string_to_136_tile(sou='2') meld, _ = self.table.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) def test_open_hand_with_doras_in_the_hand(self): """ If we have valuable pair in the hand, and 2+ dora let's open on this valuable pair """ tiles = self._string_to_136_array(man='59', sou='1235', pin='12789', honors='11') self.player.init_hand(tiles) tile = self._string_to_136_tile(honors='1') meld, _ = self.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) # add doras to the hand self.table.dora_indicators.append(self._string_to_136_tile(pin='7')) self.table.dora_indicators.append(self._string_to_136_tile(pin='8')) self.player.init_hand(tiles) # and now we can open hand on the valuable pair tile = self._string_to_136_tile(honors='1') meld, _ = self.player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) # but we don't need to open hand for atodzuke here tile = self._string_to_136_tile(pin='3') meld, _ = self.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) def test_open_hand_with_doras_in_the_hand_and_atodzuke(self): """ If we have valuable pair in the hand, and 2+ dora we can open hand on any tile but only if we have other pair in the hand """ tiles = self._string_to_136_array(man='59', sou='1235', pin='12788', honors='11') self.player.init_hand(tiles) tile = self._string_to_136_tile(pin='3') meld, _ = self.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) # add doras to the hand self.table.dora_indicators.append(self._string_to_136_tile(pin='7')) self.player.init_hand(tiles) # we have other pair in the hand, so we can open atodzuke here tile = self._string_to_136_tile(pin='3') meld, _ = self.player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) def test_open_hand_on_fifth_round_step(self): """ If we have valuable pair in the hand, 1+ dora and 5+ round step let's open on this valuable pair """ tiles = self._string_to_136_array(man='59', sou='1235', pin='12789', honors='11') self.player.init_hand(tiles) tile = self._string_to_136_tile(honors='1') meld, _ = self.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) # add doras to the hand self.table.dora_indicators.append(self._string_to_136_tile(pin='7')) self.player.init_hand(tiles) tile = self._string_to_136_tile(honors='1') meld, _ = self.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) # one discard == one round step self.player.add_discarded_tile(Tile(0, False)) self.player.add_discarded_tile(Tile(0, False)) self.player.add_discarded_tile(Tile(0, False)) self.player.add_discarded_tile(Tile(0, False)) self.player.add_discarded_tile(Tile(0, False)) self.player.add_discarded_tile(Tile(0, False)) self.player.init_hand(tiles) # after 5 round step we can open hand tile = self._string_to_136_tile(honors='1') meld, _ = self.player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) # but we don't need to open hand for atodzuke here tile = self._string_to_136_tile(pin='3') meld, _ = self.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) def test_open_hand_with_two_valuable_pairs(self): """ If we have two valuable pairs in the hand and 1+ dora let's open on one of this valuable pairs """ tiles = self._string_to_136_array(man='159', sou='128', pin='789', honors='5566') self.player.init_hand(tiles) tile = self._string_to_136_tile(honors='5') meld, _ = self.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) # add doras to the hand self.table.dora_indicators.append(self._string_to_136_tile(pin='7')) self.player.init_hand(tiles) tile = self._string_to_136_tile(honors='5') meld, _ = self.player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) tile = self._string_to_136_tile(honors='6') meld, _ = self.player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) # but we don't need to open hand for atodzuke here tile = self._string_to_136_tile(pin='3') meld, _ = self.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) def test_open_hand_and_once_discarded_tile(self): """ If we have valuable pair in the hand, this tile was discarded once and we have 1+ shanten let's open on this valuable pair """ strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, self.player) tiles = self._string_to_136_array(sou='678', pin='14689', man='456', honors='77') self.player.init_hand(tiles) # we don't activate strategy yet self.assertEqual(strategy.should_activate_strategy(self.player.tiles), False) # let's skip first yakuhai early in the game tile = self._string_to_136_tile(honors='7') meld, _ = self.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) # now one is out self.table.add_discarded_tile(1, tile, False) meld, _ = self.player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) self.assertEqual(self._to_string(meld.tiles), '777z') # but we don't need to open hand for atodzuke here tile = self._string_to_136_tile(pin='7') meld, _ = self.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) def test_open_hand_when_yakuhai_already_in_the_hand(self): # make sure yakuhai strategy is activated by adding 3 doras to the hand table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(honors='5')) tiles = self._string_to_136_array(man='46', pin='4679', sou='1348', honors='666') player.init_hand(tiles) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) tile = self._string_to_136_tile(sou='2') meld, _ = player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) def test_always_open_double_east_wind(self): tiles = self._string_to_136_array(man='59', sou='1235', pin='12788', honors='11') self.player.init_hand(tiles) # player is is not east self.player.dealer_seat = 2 self.assertEqual(self.player.player_wind, WEST) self.player.init_hand(tiles) tile = self._string_to_136_tile(honors='1') meld, _ = self.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) # player is is east self.player.dealer_seat = 0 self.assertEqual(self.player.player_wind, EAST) self.player.init_hand(tiles) tile = self._string_to_136_tile(honors='1') meld, _ = self.player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) def test_open_double_south_wind(self): tiles = self._string_to_136_array(man='59', sou='1235', pin='12788', honors='22') self.player.init_hand(tiles) tile = self._string_to_136_tile(honors='2') meld, _ = self.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) # player is south and round is south self.table.round_wind_number = 5 self.player.dealer_seat = 3 self.assertEqual(self.player.player_wind, SOUTH) self.player.init_hand(tiles) tile = self._string_to_136_tile(honors='2') meld, _ = self.player.try_to_call_meld(tile, True) self.assertEqual(meld, None) # add dora in the hand and after that we can open a hand self.table.dora_indicators.append(self._string_to_136_tile(pin='6')) self.player.init_hand(tiles) tile = self._string_to_136_tile(honors='2') meld, _ = self.player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) def test_keep_yakuhai_in_closed_hand(self): tiles = self._string_to_136_array(man='14', sou='15', pin='113347', honors='777') self.player.init_hand(tiles) tile = self._string_to_136_tile(honors='3') self.player.draw_tile(tile) discard = self.player.discard_tile() self.assertNotEqual(self._to_string([discard]), '7z') def test_keep_only_yakuhai_pon(self): # make sure yakuhai strategy is activated by adding 3 doras to the hand table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='9')) table.add_dora_indicator(self._string_to_136_tile(man='3')) tiles = self._string_to_136_array(man='11144', sou='567', pin='56', honors='777') player.init_hand(tiles) meld = self._make_meld(Meld.PON, man='111') player.add_called_meld(meld) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) player.draw_tile(self._string_to_136_tile(man='4')) discarded_tile = player.discard_tile() self.assertNotEqual(self._to_string([discarded_tile]), '7z') def test_keep_only_yakuhai_pair(self): # make sure yakuhai strategy is activated by adding 3 doras to the hand table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='9')) table.add_dora_indicator(self._string_to_136_tile(man='3')) table.add_discarded_tile(1, self._string_to_136_tile(honors='7'), False) tiles = self._string_to_136_array(man='11144', sou='567', pin='156', honors='77') player.init_hand(tiles) meld = self._make_meld(Meld.PON, man='111') player.add_called_meld(meld) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) player.draw_tile(self._string_to_136_tile(pin='1')) discarded_tile = player.discard_tile() self.assertNotEqual(self._to_string([discarded_tile]), '7z') def test_atodzuke_keep_yakuhai_wait(self): # make sure yakuhai strategy is activated by adding 3 doras to the hand table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='9')) tiles = self._string_to_136_array(man='11144', sou='567', pin='567', honors='77') player.init_hand(tiles) meld = self._make_meld(Meld.PON, man='111') player.add_called_meld(meld) # two of 4 man tiles are already out, so it would seem our wait is worse, but we know # we must keep two pairs in order to be atodzuke tempai table.add_discarded_tile(1, self._string_to_136_tile(man='4'), False) table.add_discarded_tile(1, self._string_to_136_tile(man='4'), False) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) player.draw_tile(self._string_to_136_tile(man='2')) discarded_tile = player.discard_tile() self.assertEqual(self._to_string([discarded_tile]), '2m') # issue #98 @unittest.expectedFailure def test_atodzuke_dont_destroy_second_pair(self): # make sure yakuhai strategy is activated by adding 3 doras to the hand table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='9')) tiles = self._string_to_136_array(man='111445', sou='468', pin='56', honors='77') player.init_hand(tiles) meld = self._make_meld(Meld.PON, man='111') player.add_called_meld(meld) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) # 6 man is bad meld, we lose our second pair and so is 4 man tile = self._string_to_136_tile(man='6') meld, _ = player.try_to_call_meld(tile, True) self.assertEqual(meld, None) tile = self._string_to_136_tile(man='4') meld, _ = player.try_to_call_meld(tile, True) self.assertEqual(meld, None) # but if we have backup pair it's ok tiles = self._string_to_136_array(man='111445', sou='468', pin='88', honors='77') player.init_hand(tiles) meld = self._make_meld(Meld.PON, man='111') player.add_called_meld(meld) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) # 6 man is bad meld, we lose our second pair and so is 4 man tile = self._string_to_136_tile(man='6') meld, _ = player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) tile = self._string_to_136_tile(man='4') meld, _ = player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) def test_atodzuke_dont_open_no_yaku_tempai(self): # make sure yakuhai strategy is activated by adding 3 doras to the hand table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='9')) tiles = self._string_to_136_array(man='111445', sou='567', pin='56', honors='77') player.init_hand(tiles) meld = self._make_meld(Meld.PON, man='111') player.add_called_meld(meld) # 6 man is bad meld, we lose our second pair and so is 4 man tile = self._string_to_136_tile(man='6') meld, _ = player.try_to_call_meld(tile, True) self.assertEqual(meld, None) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) tile = self._string_to_136_tile(man='4') meld, _ = player.try_to_call_meld(tile, True) self.assertEqual(meld, None) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) # 7 pin is a good meld, we get to tempai keeping yakuhai wait tile = self._string_to_136_tile(pin='7') meld, _ = player.try_to_call_meld(tile, True) self.assertNotEqual(meld, None) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) def test_atodzuke_choose_hidden_syanpon(self): # make sure yakuhai strategy is activated by adding 3 doras to the hand table = Table() player = table.player table.add_dora_indicator(self._string_to_136_tile(man='9')) tiles = self._string_to_136_array(man='111678', sou='56678', honors='77') player.init_hand(tiles) meld = self._make_meld(Meld.PON, man='111') player.add_called_meld(meld) strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) self.assertEqual(strategy.should_activate_strategy(player.tiles), True) for _ in range(0, 4): table.add_discarded_tile(1, self._string_to_136_tile(sou='9'), False) player.draw_tile(self._string_to_136_tile(man='6')) discarded_tile = player.discard_tile() self.assertNotEqual(self._to_string([discarded_tile]), '6m') def test_tempai_with_open_yakuhai_meld_and_yakuhai_pair_in_the_hand(self): """ there was a bug where bot didn't handle tempai properly with opened yakuhai pon and pair in the hand 56m555p6678s55z + [777z] """ table = Table() player = table.player tiles = self._string_to_136_array(man='56', pin='555', sou='667', honors='55777') player.init_hand(tiles) player.add_called_meld(self._make_meld(Meld.PON, honors='777')) player.draw_tile(self._string_to_136_tile(sou='8')) player.ai.current_strategy = YakuhaiStrategy(BaseStrategy.YAKUHAI, player) discarded_tile = player.discard_tile() self.assertEqual(self._to_string([discarded_tile]), '6s')
def reproduce(self, dry_run=False): draw_tags = ['T', 'U', 'V', 'W'] discard_tags = ['D', 'E', 'F', 'G'] player_draw = draw_tags[self.player_position] player_draw_regex = re.compile('^<[{}]+\d*'.format(''.join(player_draw))) discard_regex = re.compile('^<[{}]+\d*'.format(''.join(discard_tags))) table = Table() for tag in self.round_content: if player_draw_regex.match(tag) and 'UN' not in tag: print('Player draw') tile = self.decoder.parse_tile(tag) table.player.draw_tile(tile) if dry_run: if self._is_draw(tag): print('<-', TilesConverter.to_one_line_string([self._parse_tile(tag)]), tag) elif self._is_discard(tag): print('->', TilesConverter.to_one_line_string([self._parse_tile(tag)]), tag) elif self._is_init_tag(tag): hands = { 0: [int(x) for x in self._get_attribute_content(tag, 'hai0').split(',')], 1: [int(x) for x in self._get_attribute_content(tag, 'hai1').split(',')], 2: [int(x) for x in self._get_attribute_content(tag, 'hai2').split(',')], 3: [int(x) for x in self._get_attribute_content(tag, 'hai3').split(',')], } print('Initial hand:', TilesConverter.to_one_line_string(hands[self.player_position])) else: print(tag) if not dry_run and tag == self.stop_tag: break if 'INIT' in tag: values = self.decoder.parse_initial_values(tag) shifted_scores = [] for x in range(0, 4): shifted_scores.append(values['scores'][self._normalize_position(x, self.player_position)]) table.init_round( values['round_wind_number'], values['count_of_honba_sticks'], values['count_of_riichi_sticks'], values['dora_indicator'], self._normalize_position(self.player_position, values['dealer']), shifted_scores, ) hands = [ [int(x) for x in self.decoder.get_attribute_content(tag, 'hai0').split(',')], [int(x) for x in self.decoder.get_attribute_content(tag, 'hai1').split(',')], [int(x) for x in self.decoder.get_attribute_content(tag, 'hai2').split(',')], [int(x) for x in self.decoder.get_attribute_content(tag, 'hai3').split(',')], ] table.player.init_hand(hands[self.player_position]) if discard_regex.match(tag) and 'DORA' not in tag: tile = self.decoder.parse_tile(tag) player_sign = tag.upper()[1] player_seat = self._normalize_position(self.player_position, discard_tags.index(player_sign)) if player_seat == 0: table.player.discard_tile(tile) else: table.add_discarded_tile(player_seat, tile, False) if '<N who=' in tag: meld = self.decoder.parse_meld(tag) player_seat = self._normalize_position(self.player_position, meld.who) table.add_called_meld(player_seat, meld) if player_seat == 0: # we had to delete called tile from hand # to have correct tiles count in the hand if meld.type != Meld.KAN and meld.type != Meld.CHANKAN: table.player.draw_tile(meld.called_tile) if '<REACH' in tag and 'step="1"' in tag: who_called_riichi = self._normalize_position(self.player_position, self.decoder.parse_who_called_riichi(tag)) table.add_called_riichi(who_called_riichi) if not dry_run: tile = self.decoder.parse_tile(self.stop_tag) print('Hand: {}'.format(table.player.format_hand_for_print(tile))) # to rebuild all caches table.player.draw_tile(tile) tile = table.player.discard_tile() # real run, you can stop debugger here table.player.draw_tile(tile) tile = table.player.discard_tile() print('Discard: {}'.format(TilesConverter.to_one_line_string([tile])))
def test_find_impossible_waits_and_kabe_technique(self): table = Table() tiles = self._string_to_136_array(pin='11122777799', man='999') table.player.init_hand(tiles) table.player.add_called_meld(self._make_meld(Meld.PON, man='999')) table.add_discarded_tile(1, self._string_to_136_tile(pin='2'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='2'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='9'), False) table.add_called_riichi(2) table.player.ai.defence.hand_34 = self._to_34_array(table.player.tiles) table.player.ai.defence.closed_hand_34 = self._to_34_array(table.player.closed_hand) result = table.player.ai.defence.kabe.find_tiles_to_discard([]) self.assertEqual(self._to_string([x.value * 4 for x in result]), '19p') table = Table() tiles = self._string_to_136_array(pin='33337777', man='888999') table.player.init_hand(tiles) table.player.add_called_meld(self._make_meld(Meld.PON, man='888')) table.player.add_called_meld(self._make_meld(Meld.PON, man='999')) table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False) table.add_called_riichi(2) table.player.ai.defence.hand_34 = self._to_34_array(table.player.tiles) table.player.ai.defence.closed_hand_34 = self._to_34_array(table.player.closed_hand) result = table.player.ai.defence.kabe.find_tiles_to_discard([]) self.assertEqual(self._to_string([x.value * 4 for x in result]), '5p') table = Table() tiles = self._string_to_136_array(pin='33334446666', man='999') table.player.init_hand(tiles) table.player.add_called_meld(self._make_meld(Meld.PON, man='999')) table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False) table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False) table.add_called_riichi(2) table.player.ai.defence.hand_34 = self._to_34_array(table.player.tiles) table.player.ai.defence.closed_hand_34 = self._to_34_array(table.player.closed_hand) result = table.player.ai.defence.kabe.find_tiles_to_discard([]) self.assertEqual(self._to_string([x.value * 4 for x in result]), '45p')