def test_is_threatening_and_dora_pon(): table = Table() threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 0 enemy_seat = 2 table.add_called_meld(enemy_seat, make_meld(MeldPrint.PON, man="333")) table.player.round_step = 7 table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="1"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="5"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="8"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(pin="9"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="4"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="7"), False) # simple pon it is no threat threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 0 # dora pon is threat table.add_dora_indicator(string_to_136_tile(man="2")) threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 1 assert threatening_players[0].enemy.seat == enemy_seat assert threatening_players[0].threat_reason[ "id"] == EnemyDanger.THREAT_OPEN_HAND_AND_MULTIPLE_DORA["id"] assert threatening_players[0].get_assumed_hand_cost( string_to_136_tile(man="2")) == 8000
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_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_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_threatening_riichi_player_with_yakuhai_kan(): table = Table() enemy_seat = 2 table.round_wind_number = 1 enemy_called_riichi_helper(table, enemy_seat) table.add_called_meld( enemy_seat, make_meld(MeldPrint.KAN, is_open=False, honors="1111")) # non dealer 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")) == 8000
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_is_threatening_and_atodzuke(): table = Table() threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 0 table.add_dora_indicator(string_to_136_tile(honors="5")) enemy_seat = 2 table.add_called_meld(enemy_seat, make_meld(MeldPrint.CHI, man="234")) table.add_called_meld(enemy_seat, make_meld(MeldPrint.PON, sou="333")) table.add_called_meld(enemy_seat, make_meld(MeldPrint.KAN, pin="9999")) table.player.round_step = 5 table.add_discarded_tile(enemy_seat, string_to_136_tile(honors="1"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(honors="4"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="8"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(pin="9"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="1"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="6"), False) # atodzuke with 3 melds is a threat threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 1 assert threatening_players[0].enemy.seat == enemy_seat assert threatening_players[0].threat_reason[ "id"] == EnemyDanger.THREAT_OPEN_HAND_UNKNOWN_COST["id"] assert threatening_players[0].get_assumed_hand_cost( string_to_136_tile(honors="5")) == 2000 assert threatening_players[0].get_assumed_hand_cost( string_to_136_tile(honors="6")) == 8000 for tile_136 in range(0, 136): bonus_danger = threatening_players[0].threat_reason.get( "active_yaku")[0].get_bonus_danger(tile_136, 1) if not is_honor(tile_136 // 4): assert not bonus_danger elif ((tile_136 // 4 == string_to_34_tile(honors="1")) or (tile_136 // 4 == string_to_34_tile(honors="3")) or (tile_136 // 4 == string_to_34_tile(honors="5")) or (tile_136 // 4 == string_to_34_tile(honors="6")) or (tile_136 // 4 == string_to_34_tile(honors="7"))): assert bonus_danger else: assert not bonus_danger
def test_threatening_riichi_player_with_kan(): table = Table() enemy_seat = 2 enemy_called_riichi_helper(table, enemy_seat) table.add_called_meld(enemy_seat, make_meld(MeldPrint.KAN, is_open=False, man="3333")) # non dealer 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")) == 5200 # dealer threatening_player.enemy.dealer_seat = enemy_seat assert threatening_player.get_assumed_hand_cost( string_to_136_tile(man="2")) == 7700
def test_crash_when_tyring_to_discard_with_open_hand(): """ Bot crashed when tried to discard tile from hand 266m4444z + 1z [111z, 789m] This test is checking that there are no crashes in such situations anymore """ table = Table() player = table.player tiles = string_to_136_array(man="266789", honors="1114444") player.init_hand(tiles) # we manually reveal one tile to emulate the fact that we saw it when it was discarded table._add_revealed_tile(string_to_136_tile(honors="1")) table.add_called_meld(player.seat, make_meld(MeldPrint.PON, honors="111")) table.add_called_meld(player.seat, make_meld(MeldPrint.CHI, man="789")) # it is important for crash to take fourth 4z tile = string_to_136_array(honors="1111")[3] player.draw_tile(tile) discard = player.discard_tile() assert discard is not None
def test_is_threatening_and_honitsu_hand(): table = Table() table.add_dora_indicator(string_to_136_tile(pin="1")) threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 0 enemy_seat = 1 table.add_called_meld(enemy_seat, make_meld(MeldPrint.PON, honors="444")) table.add_called_meld(enemy_seat, make_meld(MeldPrint.CHI, pin="123")) table.add_called_meld(enemy_seat, make_meld(MeldPrint.CHI, pin="345")) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="1"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="5"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="8"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="9"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="1"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="1"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(pin="1"), False) threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 1 assert threatening_players[0].threat_reason[ "id"] == EnemyDanger.THREAT_EXPENSIVE_OPEN_HAND["id"] assert threatening_players[0].get_assumed_hand_cost( string_to_136_tile(pin="4")) == 3900 assert threatening_players[0].get_assumed_hand_cost( string_to_136_tile(pin="2")) == 8000 assert HonitsuAnalyzer.id in [ x.id for x in threatening_players[0].threat_reason["active_yaku"] ] honitsu_analyzer = [ x for x in threatening_players[0].threat_reason["active_yaku"] if x.id == HonitsuAnalyzer.id ][0] for tile_136 in range(0, 136): bonus_danger = honitsu_analyzer.get_bonus_danger(tile_136, 1) if is_honor(tile_136 // 4): assert bonus_danger else: assert not bonus_danger
def test_threatening_riichi_player_with_kan_aka(): table = Table() enemy_seat = 2 table.add_called_riichi_step_one(enemy_seat) table.has_aka_dora = True table.add_called_meld(enemy_seat, make_meld(MeldPrint.KAN, is_open=False, man="5505")) # non dealer 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(sou="2")) == 8000 # dealer threatening_player.enemy.dealer_seat = enemy_seat assert threatening_player.get_assumed_hand_cost( string_to_136_tile(sou="2")) == 12000
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_tile_danger_against_tanyao_threat(): table = Table() player = table.player enemy_seat = 2 table.add_called_meld(enemy_seat, make_meld(MeldPrint.PON, pin="234")) table.add_called_meld(enemy_seat, make_meld(MeldPrint.CHI, sou="333")) table.player.round_step = 2 table.add_dora_indicator(string_to_136_tile(pin="1")) table.add_dora_indicator(string_to_136_tile(pin="2")) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="1"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="5"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="8"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(pin="9"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="4"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="3"), False) threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 1 assert threatening_players[0].enemy.seat == enemy_seat tiles = string_to_136_array(man="11134", pin="1569", honors="2555") tile = string_to_136_tile(sou="4") player.init_hand(tiles) player.draw_tile(tile) _assert_discard(player, enemy_seat, TileDanger.SAFE_AGAINST_THREATENING_HAND, man="1") _assert_discard(player, enemy_seat, TileDanger.SAFE_AGAINST_THREATENING_HAND, pin="9") _assert_discard(player, enemy_seat, TileDanger.SAFE_AGAINST_THREATENING_HAND, honors="2") _assert_discard_not_equal(player, enemy_seat, TileDanger.SAFE_AGAINST_THREATENING_HAND, pin="5")
def test_detect_enemy_tempai_and_opened_sets(self): table = Table() self.assertEqual(EnemyAnalyzer(table.get_player(1)).in_tempai, False) self.assertEqual(EnemyAnalyzer(table.get_player(1)).is_threatening, False) table.add_called_meld(1, self._make_meld(Meld.CHI, sou='567')) table.add_called_meld(1, self._make_meld(Meld.CHI, pin='123')) table.add_called_meld(1, self._make_meld(Meld.CHI, man='345')) table.add_called_meld(1, self._make_meld(Meld.PON, man='777')) self.assertEqual(EnemyAnalyzer(table.get_player(1)).in_tempai, True) self.assertEqual(EnemyAnalyzer(table.get_player(1)).is_threatening, False) table.dora_indicators = [self._string_to_136_tile(man='6')] # enemy opened the pon of dor, so better to fold against him self.assertEqual(EnemyAnalyzer(table.get_player(1)).in_tempai, True) self.assertEqual(EnemyAnalyzer(table.get_player(1)).is_threatening, True)
def test_is_threatening_and_two_open_yakuhai_melds(): table = Table() threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 0 # south player enemy_seat = 1 # south round table.round_wind_number = 4 table.add_called_meld(enemy_seat, make_meld(MeldPrint.PON, honors="222")) table.add_called_meld(enemy_seat, make_meld(MeldPrint.CHI, man="123")) table.player.round_step = 2 table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="1"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="5"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="8"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(pin="9"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="4"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="3"), False) # double wind is not enough threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 0 # with one dora in enemy melds we can start think about threat # it will be 3 han table.add_dora_indicator(string_to_136_tile(man="1")) threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 1 assert threatening_players[0].enemy.seat == enemy_seat assert threatening_players[0].threat_reason[ "id"] == EnemyDanger.THREAT_EXPENSIVE_OPEN_HAND["id"] assert threatening_players[0].get_assumed_hand_cost( string_to_136_tile(man="8")) == 3900 for tile_136 in range(0, 136): bonus_danger = threatening_players[0].threat_reason.get( "active_yaku")[0].get_bonus_danger(tile_136, 1) assert not bonus_danger
def test_is_threatening_and_toitoi_melds(): table = Table() threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 0 enemy_seat = 2 table.player.round_step = 2 table.add_called_meld(enemy_seat, make_meld(MeldPrint.PON, pin="222")) table.add_called_meld(enemy_seat, make_meld(MeldPrint.PON, honors="444")) table.add_called_meld(enemy_seat, make_meld(MeldPrint.PON, sou="999")) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="1"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="5"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="8"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(pin="9"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="4"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="3"), False) table.add_dora_indicator(string_to_136_tile(pin="1")) threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 1 assert threatening_players[0].enemy.seat == enemy_seat assert threatening_players[0].threat_reason[ "id"] == EnemyDanger.THREAT_EXPENSIVE_OPEN_HAND["id"] assert threatening_players[0].get_assumed_hand_cost( string_to_136_tile(man="2")) == 8000
def test_get_more_yakuhai_sets_in_hand(self): table = Table() tiles = self._string_to_136_array(sou='1378', pin='67', man='68', honors='5566') table.player.init_hand(tiles) tile = self._string_to_136_tile(honors='5') meld, discard_option = table.player.try_to_call_meld(tile, False) self.assertNotEqual(meld, None, "Should call meld in this situation.") table.add_called_meld(0, meld) table.player.tiles.append(tile) table.player.discard_tile(discard_option) tile = self._string_to_136_tile(honors='6') meld, _ = table.player.try_to_call_meld(tile, False) self.assertNotEqual(meld, None) table = Table() tiles = self._string_to_136_array(sou='234', pin='788', man='567', honors='5566') table.player.init_hand(tiles) tile = self._string_to_136_tile(honors='5') meld, discard_option = table.player.try_to_call_meld(tile, False) self.assertNotEqual(meld, None) table.add_called_meld(0, meld) table.player.tiles.append(tile) table.player.discard_tile(discard_option) tile = self._string_to_136_tile(honors='6') meld, _ = table.player.try_to_call_meld(tile, False) self.assertEqual(meld, None)
def test_detect_enemy_tempai_and_opened_sets(self): table = Table() self.assertEqual(EnemyAnalyzer(table.get_player(1)).in_tempai, False) self.assertEqual( EnemyAnalyzer(table.get_player(1)).is_threatening, False) table.add_called_meld(1, self._make_meld(Meld.CHI, sou='567')) table.add_called_meld(1, self._make_meld(Meld.CHI, pin='123')) table.add_called_meld(1, self._make_meld(Meld.CHI, man='345')) table.add_called_meld(1, self._make_meld(Meld.PON, man='777')) self.assertEqual(EnemyAnalyzer(table.get_player(1)).in_tempai, True) self.assertEqual( EnemyAnalyzer(table.get_player(1)).is_threatening, False) table.dora_indicators = [self._string_to_136_tile(man='6')] # enemy opened the pon of dor, so better to fold against him self.assertEqual(EnemyAnalyzer(table.get_player(1)).in_tempai, True) self.assertEqual( EnemyAnalyzer(table.get_player(1)).is_threatening, True)
def test_threatening_riichi_player_with_dora_kan(): table = Table() enemy_seat = 2 enemy_called_riichi_helper(table, enemy_seat) table.add_dora_indicator(string_to_136_tile(man="2")) table.add_called_meld(enemy_seat, make_meld(MeldPrint.KAN, is_open=False, man="3333")) # we have to do it manually in test # normally tenhou client would do that table._add_revealed_tile(string_to_136_tile(man="3")) # non dealer 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")) == 12000 # dealer threatening_player.enemy.dealer_seat = enemy_seat assert threatening_player.get_assumed_hand_cost( string_to_136_tile(man="2")) == 18000
def test_is_threatening_and_two_open_tanyao_melds(): table = Table() threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 0 enemy_seat = 2 table.add_called_meld(enemy_seat, make_meld(MeldPrint.PON, pin="234")) table.add_called_meld(enemy_seat, make_meld(MeldPrint.CHI, sou="333")) table.player.round_step = 2 table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="1"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="5"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="8"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(pin="9"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="4"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="3"), False) # tanyao without doras is not threat threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 0 # and now it is threat table.add_dora_indicator(string_to_136_tile(pin="1")) table.add_dora_indicator(string_to_136_tile(pin="2")) threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 1 assert threatening_players[0].enemy.seat == enemy_seat assert threatening_players[0].threat_reason[ "id"] == EnemyDanger.THREAT_EXPENSIVE_OPEN_HAND["id"] assert threatening_players[0].get_assumed_hand_cost( string_to_136_tile(man="2")) == 3900 for tile_136 in range(0, 136): bonus_danger = threatening_players[0].threat_reason.get( "active_yaku")[0].get_bonus_danger(tile_136, 1) assert not bonus_danger
def test_crash_when_tyring_to_open_meld(): """ Bot crashed when tried to calculate meld possibility with hand 7m333789s + 3s [222z, 123p] This test is checking that there are no crashes in such situations anymore """ # checking a few similar hands here # #1 table = Table() # dora here to activate honitsu strategy table.add_dora_indicator(string_to_136_tile(sou="9")) player = table.player tiles = string_to_136_array(sou="1112345678", honors="447") player.init_hand(tiles) tile = string_to_136_array(sou="1111")[3] meld, _ = player.try_to_call_meld(tile, False) assert meld is not None # #2 table = Table() # dora here to activate honitsu strategy table.add_dora_indicator(string_to_136_tile(sou="9")) player = table.player tiles = string_to_136_array(sou="11123456789", honors="47") player.init_hand(tiles) tile = string_to_136_array(sou="1111")[3] meld, _ = player.try_to_call_meld(tile, False) assert meld is not None # #3 table = Table() # dora here to activate honitsu strategy table.add_dora_indicator(string_to_136_tile(sou="9")) player = table.player tiles = string_to_136_array(sou="111234567", honors="4444") player.init_hand(tiles) table.add_called_meld(player.seat, make_meld(MeldPrint.PON, honors="444")) tile = string_to_136_array(sou="1111")[3] meld, _ = player.try_to_call_meld(tile, False) assert meld is not None # #4 - the original one table = Table() # dora here to activate yakuhai strategy table.add_dora_indicator(string_to_136_tile(sou="2")) player = table.player tiles = string_to_136_array(man="7", pin="123", sou="333789", honors="111") player.init_hand(tiles) table.add_called_meld(player.seat, make_meld(MeldPrint.PON, honors="111")) table.add_called_meld(player.seat, make_meld(MeldPrint.CHI, pin="123")) # it is important for crash to take fourth 3s (with index 83) tile = string_to_136_array(sou="3333")[3] meld, _ = player.try_to_call_meld(tile, False) assert meld is None
def test_closed_kan_and_riichi(self): table = Table() table.count_of_remaining_tiles = 60 player = table.player player.scores = 25000 kan_tiles = self._string_to_136_array(pin='7777') tiles = self._string_to_136_array(pin='568', sou='1235788') + kan_tiles[:3] player.init_hand(tiles) # +3 to avoid tile duplication of 7 pin tile = kan_tiles[3] player.draw_tile(tile) kan_type = player.should_call_kan(tile, False) self.assertEqual(kan_type, Meld.KAN) meld = Meld() meld.type = Meld.KAN meld.tiles = kan_tiles meld.called_tile = tile meld.who = 0 meld.from_who = 0 meld.opened = False # replacement from the dead wall player.draw_tile(self._string_to_136_tile(pin='4')) table.add_called_meld(meld.who, meld) discard = player.discard_tile() self.assertEqual(self._to_string([discard]), '8p') self.assertEqual(player.can_call_riichi(), True) # with closed kan we can't call riichi player.melds[0].opened = True self.assertEqual(player.can_call_riichi(), False)
def test_choose_best_wait_with_melds(): table = Table() player = table.player table.has_aka_dora = False tiles = string_to_136_array(sou="3499222555123") player.init_hand(tiles) meld = make_meld(MeldPrint.PON, sou="222") table.add_called_meld(0, meld) # 123s, we can't automatically chose correct index for fourth 2s meld = make_meld(MeldPrint.CHI, tiles=[72, 79, 80]) table.add_called_meld(0, meld) meld = make_meld(MeldPrint.PON, sou="555") table.add_called_meld(0, meld) player.draw_tile(string_to_136_tile(sou="4")) discarded_tile, _ = player.discard_tile() # double-pairs wait becomes better, because it has 4 tiles to wait for # against just 1 in ryanmen assert tiles_to_string([discarded_tile]) == "3s"
def test_tile_danger_against_toitoi_threat(): table = Table() table.add_dora_indicator(string_to_136_tile(pin="1")) player = table.player enemy_seat = 1 table.add_called_meld(enemy_seat, make_meld(MeldPrint.PON, pin="222")) table.add_called_meld(enemy_seat, make_meld(MeldPrint.PON, honors="444")) table.add_called_meld(enemy_seat, make_meld(MeldPrint.PON, sou="999")) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="1"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="5"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="8"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(pin="9"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(sou="4"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="3"), False) table.add_dora_indicator(string_to_136_tile(pin="1")) threatening_players = table.player.ai.defence.get_threatening_players() assert len(threatening_players) == 1 assert threatening_players[0].enemy.seat == enemy_seat # let's make 2 man impossible to wait in toitoi table.add_discarded_tile(enemy_seat, string_to_136_tile(man="2"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="2"), False) table.add_discarded_tile(enemy_seat, string_to_136_tile(man="2"), False) tiles = string_to_136_array(man="11134", pin="1569", honors="2555") tile = string_to_136_tile(man="2") player.init_hand(tiles) player.draw_tile(tile) _assert_discard(player, enemy_seat, TileDanger.SAFE_AGAINST_THREATENING_HAND, man="2")
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 reproduce(self, player, wind, honba, needed_tile, action, tile_number_to_stop): player_position = self._find_player_position(player) round_content = self._find_needed_round(wind, honba) draw_tags = ["T", "U", "V", "W"] discard_tags = ["D", "E", "F", "G"] player_draw = draw_tags[player_position] player_draw_regex = re.compile(r"^<[{}]+\d*".format( "".join(player_draw))) discard_regex = re.compile(r"^<[{}]+\d*".format("".join(discard_tags))) draw_regex = re.compile(r"^<[{}]+\d*".format("".join(draw_tags))) last_draws = {0: None, 1: None, 2: None, 3: None} table = Table() # TODO get this info from log content table.has_aka_dora = True table.has_open_tanyao = True table.player.init_logger(self.logger) players = {} for round_item in self.rounds: for tag in round_item: if "<UN" in tag: players_temp = self.decoder.parse_names_and_ranks(tag) if players_temp: for x in players_temp: players[x["seat"]] = x draw_tile_seen_number = 0 enemy_discard_seen_number = 0 for tag in round_content: if player_draw_regex.match(tag) and "UN" not in tag: tile = self.decoder.parse_tile(tag) table.count_of_remaining_tiles -= 1 # is it time to stop reproducing? found_tile = TilesConverter.to_one_line_string( [tile]) == needed_tile if action == "draw" and found_tile: draw_tile_seen_number += 1 if draw_tile_seen_number == tile_number_to_stop: self.logger.info("Stop on player draw") discard_result = None with_riichi = False table.player.draw_tile(tile) table.player.should_call_kan( tile, open_kan=False, from_riichi=table.player.in_riichi) if not table.player.in_riichi: discard_result = table.player.discard_tile() with_riichi = table.player.can_call_riichi() return discard_result, with_riichi table.player.draw_tile(tile) 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( player_position, x)]) table.init_round( values["round_wind_number"], values["count_of_honba_sticks"], values["count_of_riichi_sticks"], values["dora_indicator"], self._normalize_position(values["dealer"], player_position), 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[player_position]) table.player.name = players[player_position]["name"] self.logger.info("Init round info") self.logger.info(table.player.name) self.logger.info(f"Scores: {table.player.scores}") self.logger.info( f"Wind: {DISPLAY_WINDS[table.player.player_wind]}") if "DORA hai" in tag: table.add_dora_indicator( int(self._get_attribute_content(tag, "hai"))) if draw_regex.match(tag) and "UN" not in tag: tile = self.decoder.parse_tile(tag) player_sign = tag.upper()[1] player_seat = self._normalize_position( draw_tags.index(player_sign), player_position) last_draws[player_seat] = tile 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( discard_tags.index(player_sign), player_position) if player_seat == 0: table.player.discard_tile(tile, force_tsumogiri=True) else: # is it time to stop? found_tile = TilesConverter.to_one_line_string( [tile]) == needed_tile is_kamicha_discard = player_seat == 3 if action == "enemy_discard" and found_tile: enemy_discard_seen_number += 1 if enemy_discard_seen_number == tile_number_to_stop: self.logger.info("Stop on enemy discard") self._rebuild_bot_shanten_cache(table.player) table.player.should_call_kan(tile, open_kan=True, from_riichi=False) return table.player.try_to_call_meld( tile, is_kamicha_discard) is_tsumogiri = last_draws[player_seat] == tile table.add_discarded_tile(player_seat, tile, is_tsumogiri=is_tsumogiri) if "<N who=" in tag: meld = self.decoder.parse_meld(tag) player_seat = self._normalize_position(meld.who, player_position) table.add_called_meld(player_seat, meld) if player_seat == 0: if meld.type != MeldPrint.KAN and meld.type != MeldPrint.SHOUMINKAN: table.player.draw_tile(meld.called_tile) if "<REACH" in tag and 'step="1"' in tag: who_called_riichi = self._normalize_position( self.decoder.parse_who_called_riichi(tag), player_position) table.add_called_riichi_step_one(who_called_riichi) if "<REACH" in tag and 'step="2"' in tag: who_called_riichi = self._normalize_position( self.decoder.parse_who_called_riichi(tag), player_position) table.add_called_riichi_step_two(who_called_riichi)
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 reproduce(self, dry_run=True, verbose=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(self.params) total = defaultdict(int) results = defaultdict(int) for i, r in enumerate(self.rounds): print("Round:", i) print() for tag in r: if dry_run: #print(tag) pass 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_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 player_draw_regex.match(tag) and 'UN' not in tag: tile = self.decoder.parse_tile(tag) table.player.draw_tile(tile) 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: # TODO: add player's state, river, melds, and reach timepoint current_hand = TilesConverter.to_one_line_string( table.player.tiles) choice = table.player.ai.discard_tile(None) table.player.discard_tile(tile) match = int(tile == choice) total["TOTAL"] += 1 results["TOTAL"] += match total[table.player.play_state] += 1 results[table.player.play_state] += match if verbose: print("Hand:", current_hand) print("AI's Choice:", TilesConverter.to_one_line_string([choice])) print("MP's Choice:", TilesConverter.to_one_line_string(([tile]))) print("AI's State:", table.player.play_state) print("Same:", tile == choice) print() 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) # TODO: add reach time point if dry_run: print(total, results) return total, results 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])))