Example #1
0
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
Example #2
0
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
Example #3
0
    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')
Example #5
0
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)
Example #7
0
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
Example #8
0
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
Example #9
0
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
Example #10
0
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
Example #11
0
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
Example #12
0
    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)
Example #13
0
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)
Example #15
0
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
Example #16
0
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
Example #17
0
    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)
Example #18
0
    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_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')
Example #20
0
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
Example #21
0
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
Example #22
0
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
Example #23
0
    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)
Example #24
0
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"
Example #25
0
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])))
Example #27
0
    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)
Example #28
0
    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])))
Example #29
0
    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])))