예제 #1
0
    def test_find_dealer_tile_to_discard(self):
        dealer = 2
        dora = self._string_to_136_tile(honors='3')
        table = Table()
        table.init_round(0, 0, 0, dora, dealer, [])

        tiles = self._string_to_136_array(sou='2234678', pin='34', man='45789')
        table.player.init_hand(tiles)

        table.add_discarded_tile(1, self._string_to_136_tile(man='4'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(man='5'), False)

        table.add_discarded_tile(2, self._string_to_136_tile(man='8'), False)
        table.add_discarded_tile(2, self._string_to_136_tile(man='9'), False)

        table.add_called_riichi(1)
        table.add_called_riichi(2)

        # for this test we don't need temporary_safe_tiles
        table.get_player(1).temporary_safe_tiles = []
        table.get_player(2).temporary_safe_tiles = []

        result = table.player.discard_tile()
        # second player is a dealer, let's fold against him
        self.assertEqual(self._to_string([result]), '9m')

        tiles = self._string_to_136_array(sou='234567', pin='348', man='234', honors='23')
        table.player.init_hand(tiles)

        result = table.player.discard_tile()
        # there is no safe tiles against dealer, so let's fold against other players
        self.assertEqual(table.player.ai.in_defence, True)
        self.assertEqual(self._to_string([result]), '4m')
예제 #2
0
    def test_should_go_for_defence_and_good_hand_with_drawn_tile(self):
        """
        When we have 14 tiles in hand and someone declared a riichi
        """
        table = Table()
        table.has_aka_dora = True

        tiles = self._string_to_136_array(sou='2223457899', honors='666')
        table.player.init_hand(tiles)
        table.player.draw_tile(self._string_to_136_tile(man='8'))
        table.player.add_called_meld(self._make_meld(Meld.PON, sou='222'))
        table.player.add_called_meld(self._make_meld(Meld.PON, honors='666'))

        self.assertEqual(table.player.ai.defence.should_go_to_defence_mode(),
                         False)

        table.add_called_riichi(3)

        results, shanten = table.player.ai.calculate_outs(
            table.player.tiles, table.player.closed_hand,
            table.player.open_hand_34_tiles)
        selected_tile = table.player.ai.process_discard_options_and_select_tile_to_discard(
            results, shanten)

        self.assertEqual(
            table.player.ai.defence.should_go_to_defence_mode(selected_tile),
            False)
        result = table.player.discard_tile()
        self.assertEqual(self._to_string([result]), '8m')
    def test_detect_enemy_tempai_and_riichi(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_riichi(1)

        self.assertEqual(EnemyAnalyzer(table.get_player(1)).in_tempai, True)
        self.assertEqual(EnemyAnalyzer(table.get_player(1)).is_threatening, True)
예제 #4
0
    def test_dont_call_kan_in_defence_mode(self):
        table = Table()

        tiles = self._string_to_136_array(man='12589', sou='111459', pin='12')
        table.player.init_hand(tiles)

        table.add_called_riichi(1)

        tile = self._string_to_136_tile(sou='1')
        self.assertEqual(table.player.should_call_kan(tile, False), None)
예제 #5
0
    def test_detect_enemy_tempai_and_riichi(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_riichi(1)

        self.assertEqual(EnemyAnalyzer(table.get_player(1)).in_tempai, True)
        self.assertEqual(
            EnemyAnalyzer(table.get_player(1)).is_threatening, True)
예제 #6
0
    def test_not_open_hand_in_defence_mode(self):
        table = Table()
        player = table.player

        tiles = self._string_to_136_array(sou='12368', pin='2358', honors='4455')
        player.init_hand(tiles)

        table.add_called_riichi(1)

        tile = self._string_to_136_tile(honors='5')
        meld, _ = player.try_to_call_meld(tile, False)
        self.assertEqual(meld, None)
예제 #7
0
    def test_try_to_discard_not_needed_tiles_first_in_defence_mode(self):
        table = Table()

        tiles = self._string_to_136_array(sou='2345678', pin='789', man='55', honors='12')
        table.player.init_hand(tiles)

        table.add_discarded_tile(1, self._string_to_136_tile(man='5'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(honors='1'), False)

        table.add_called_riichi(1)

        result = table.player.discard_tile()

        self.assertEqual(self._to_string([result]), '1z')
예제 #8
0
    def test_find_suji_tiles_to_discard_for_one_player(self):
        table = Table()

        tiles = self._string_to_136_array(sou='2345678', pin='12789', man='55')
        table.player.init_hand(tiles)

        table.add_discarded_tile(1, self._string_to_136_tile(man='2'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(man='8'), False)

        table.add_called_riichi(1)

        result = table.player.discard_tile()

        self.assertEqual(self._to_string([result]), '5m')
예제 #9
0
    def test_call_riichi_with_bad_wait_against_other_player_riichi(self):
        table = Table()
        table.count_of_remaining_tiles = 60
        table.player.scores = 25000

        tiles = self._string_to_136_array(sou='11223', pin='234678', man='55')
        table.player.init_hand(tiles)
        table.player.draw_tile(self._string_to_136_tile(man='9'))

        table.add_called_riichi(3)

        discard = table.player.discard_tile()
        self.assertEqual(self._to_string([discard]), '9m')
        self.assertEqual(table.player.ai.in_defence, True)
        self.assertEqual(table.player.can_call_riichi(), False)
예제 #10
0
    def test_find_common_suji_tiles_to_discard_for_multiple_players(self):
        table = Table()

        tiles = self._string_to_136_array(sou='2345678', pin='12789', man='55')
        table.player.init_hand(tiles)

        table.add_discarded_tile(1, self._string_to_136_tile(pin='4'), False)
        table.add_discarded_tile(2, self._string_to_136_tile(pin='4'), False)

        table.add_called_riichi(1)
        table.add_called_riichi(2)

        result = table.player.discard_tile()

        self.assertEqual(self._to_string([result]), '1p')
예제 #11
0
    def test_mark_dora_as_dangerous_tile_for_suji(self):
        table = Table()
        table.add_dora_indicator(self._string_to_136_tile(man='8'))

        tiles = self._string_to_136_array(man='112235', pin='4557788')
        table.player.init_hand(tiles)
        # 9 man is dora!
        table.player.draw_tile(self._string_to_136_tile(man='9'))

        table.add_discarded_tile(1, self._string_to_136_tile(man='6'), False)
        table.add_called_riichi(1)

        result = table.player.discard_tile()

        self.assertEqual(self._to_string([result]), '3m')
예제 #12
0
    def test_priority_of_players_safe_tiles(self):
        table = Table()

        tiles = self._string_to_136_array(man='789', pin='2789', sou='23789', honors='1')
        table.player.init_hand(tiles)
        table.player.draw_tile(self._string_to_136_tile(sou='1'))

        table.add_discarded_tile(1, self._string_to_136_tile(sou='7'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(honors='1'), False)
        table.add_called_riichi(1)
        table.add_discarded_tile(2, self._string_to_136_tile(honors='1'), False)

        result = table.player.discard_tile()

        self.assertEqual(self._to_string([result]), '1z')
예제 #13
0
    def test_try_to_discard_less_valuable_tiles_first_in_defence_mode(self):
        table = Table()

        tiles = self._string_to_136_array(sou='234678', pin='6789', man='55', honors='13')
        table.player.init_hand(tiles)

        table.add_discarded_tile(1, self._string_to_136_tile(pin='7'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(sou='2'), False)

        table.add_called_riichi(1)

        result = table.player.discard_tile()

        # discard of 2s will do less damage to our hand shape than 7p discard
        self.assertEqual(table.player.ai.in_defence, True)
        self.assertEqual(self._to_string([result]), '2s')
예제 #14
0
    def test_should_go_for_defence_and_good_hand(self):
        """
        When we have 13 tiles in hand and someone declared a riichi
        """
        table = Table()
        table.set_players_names_and_ranks([
            {
                "name": "a",
                "rank": 1500
            },
            {
                "name": "b",
                "rank": 1500
            },
            {
                "name": "c",
                "rank": 1500
            },
            {
                "name": "d",
                "rank": 1500
            },
        ])
        table.set_players_scores([250, 250, 250, 250])

        tiles = self._string_to_136_array(sou='234678', pin='34789', man='77')
        table.player.init_hand(tiles)
        table.player.draw_tile(self._string_to_136_tile(man='6'))
        # discard here to reinit shanten number in AI
        table.player.discard_tile()

        self.assertEqual(table.player.ai.defence.should_go_to_defence_mode(),
                         False)

        table.add_called_riichi(3)

        # our hand is in tempai, but it is really cheap
        self.assertEqual(table.player.ai.defence.should_go_to_defence_mode(),
                         False)

        table.add_dora_indicator(self._string_to_136_tile(man='4'))
        table.add_dora_indicator(self._string_to_136_tile(pin='3'))

        # our hand in tempai, and it has a cost, so let's push it
        self.assertEqual(table.player.ai.defence.should_go_to_defence_mode(),
                         False)
    def test_find_impossible_waits_and_honor_tiles(self):
        table = Table()

        tiles = self._string_to_136_array(honors='1133')
        table.player.init_hand(tiles)

        table.add_discarded_tile(1, self._string_to_136_tile(honors='1'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(honors='3'), False)

        table.add_called_riichi(2)

        table.player.ai.defence.hand_34 = self._to_34_array(table.player.tiles)
        table.player.ai.defence.closed_hand_34 = self._to_34_array(table.player.closed_hand)
        result = table.player.ai.defence.impossible_wait.find_tiles_to_discard([])

        # dora is not safe against tanki wait, so let's hold it
        self.assertEqual([x.value for x in result], [EAST, WEST])
예제 #16
0
    def test_should_go_for_defence_and_bad_hand(self):
        """
        When we have 13 tiles in hand and someone declared a riichi
        """
        table = Table()

        tiles = self._string_to_136_array(sou='1259', pin='12348', honors='3456')
        table.player.init_hand(tiles)
        table.player.draw_tile(self._string_to_136_tile(man='6'))
        # discard here to reinit shanten number in AI
        table.player.discard_tile()

        self.assertEqual(table.player.ai.defence.should_go_to_defence_mode(), False)

        table.add_called_riichi(3)

        # our hand is pretty bad, there is no sense to push it against riichi
        self.assertEqual(table.player.ai.defence.should_go_to_defence_mode(), True)
예제 #17
0
    def test_add_safe_tile_after_discard(self):
        table = Table()
        table.add_called_riichi(3)

        table.add_discarded_tile(1, self._string_to_136_tile(man='3'), False)
        table.add_discarded_tile(0, self._string_to_136_tile(man='4'), False)

        self.assertEqual(len(table.get_player(1).discards), 1)
        self.assertEqual(len(table.get_player(2).discards), 0)
        self.assertEqual(len(table.get_player(3).discards), 0)

        self.assertEqual(len(table.get_player(1).safe_tiles), 1)
        self.assertEqual(len(table.get_player(2).safe_tiles), 0)
        self.assertEqual(len(table.get_player(3).safe_tiles), 2)

        # "2" is 3 man
        self.assertEqual(table.get_player(1).safe_tiles, [2])
        self.assertEqual(table.get_player(3).safe_tiles, [2, 3])
예제 #18
0
    def test_dont_discard_safe_tiles_when_call_riichi(self):
        table = Table()
        table.count_of_remaining_tiles = 70
        table.player.scores = 2000

        tiles = self._string_to_136_array(sou='12356789', pin='22678')
        table.player.init_hand(tiles)
        table.player.draw_tile(self._string_to_136_tile(honors='1'))
        table.player.discard_tile()
        table.player.draw_tile(self._string_to_136_tile(honors='1'))

        table.add_discarded_tile(1, self._string_to_136_tile(sou='1'), False)
        table.add_called_riichi(1)

        result = table.player.discard_tile()

        self.assertEqual(table.player.can_call_riichi(), True)
        self.assertEqual(self._to_string([result]), '1z')
예제 #19
0
    def test_should_go_for_defence_and_good_hand_with_drawn_tile(self):
        """
        When we have 14 tiles in hand and someone declared a riichi
        """
        table = Table()
        table.has_aka_dora = True

        tiles = self._string_to_136_array(sou='2223457899', honors='666')
        table.player.init_hand(tiles)
        table.player.draw_tile(self._string_to_136_tile(man='8'))
        table.player.add_called_meld(self._make_meld(Meld.PON, sou='222'))
        table.player.add_called_meld(self._make_meld(Meld.PON, honors='666'))

        self.assertEqual(table.player.ai.defence.should_go_to_defence_mode(), False)

        table.add_called_riichi(3)

        result = table.player.discard_tile()
        self.assertEqual(self._to_string([result]), '8m')
    def test_find_impossible_waits_and_honor_tiles(self):
        table = Table()

        tiles = self._string_to_136_array(honors='1133', man='123', sou='456', pin='999')
        table.player.init_hand(tiles)

        table.player.add_called_meld(self._make_meld(Meld.CHI, man='123'))
        table.player.add_called_meld(self._make_meld(Meld.CHI, sou='456'))
        table.player.add_called_meld(self._make_meld(Meld.PON, pin='999'))

        table.add_discarded_tile(1, self._string_to_136_tile(honors='1'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(honors='3'), False)

        table.add_called_riichi(2)

        table.player.ai.defence.hand_34 = self._to_34_array(table.player.tiles)
        table.player.ai.defence.closed_hand_34 = self._to_34_array(table.player.closed_hand)
        result = table.player.ai.defence.impossible_wait.find_tiles_to_discard([])

        # dora is not safe against tanki wait, so let's hold it
        self.assertEqual([x.value for x in result], [EAST, WEST])
예제 #21
0
    def test_find_common_safe_tile_to_discard(self):
        table = Table()

        tiles = self._string_to_136_array(sou='2456', pin='234478', man='2336')
        table.player.init_hand(tiles)

        table.add_discarded_tile(1, self._string_to_136_tile(sou='6'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False)

        table.add_discarded_tile(2, self._string_to_136_tile(pin='5'), False)
        table.add_discarded_tile(2, self._string_to_136_tile(sou='6'), False)

        table.add_called_riichi(1)
        table.add_called_riichi(2)

        # for this test we don't need temporary_safe_tiles
        table.get_player(1).temporary_safe_tiles = []
        table.get_player(2).temporary_safe_tiles = []

        result = table.player.discard_tile()

        self.assertEqual(self._to_string([result]), '6s')
예제 #22
0
    def test_find_tile_to_discard_when_no_safe_tile(self):
        dealer = 2
        dora = self._string_to_136_tile(honors='3')
        table = Table()
        table.init_round(0, 0, 0, dora, dealer, [])

        tiles = self._string_to_136_array(sou='2234555', pin='11', man='11113')
        table.player.init_hand(tiles)

        table.add_discarded_tile(1, self._string_to_136_tile(man='8'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(man='9'), False)

        table.add_discarded_tile(2, self._string_to_136_tile(man='8'), False)
        table.add_discarded_tile(2, self._string_to_136_tile(man='9'), False)

        table.add_called_riichi(1)
        table.add_called_riichi(2)

        # for this test we don't need temporary_safe_tiles
        table.get_player(1).temporary_safe_tiles = []
        table.get_player(2).temporary_safe_tiles = []

        result = table.player.discard_tile()
        # No safe tile to discard. So should discard 5s
        self.assertEqual(self._to_string([result]), '1m')

        tiles = self._string_to_136_array(sou='2234556', pin='11', man='34567')
        table.player.init_hand(tiles)
        result = table.player.discard_tile()
        # No safe tile to discard. So should discard 1p
        self.assertEqual(self._to_string([result]), '1p')

        tiles = self._string_to_136_array(sou='2345678', pin='19', man='34567')
        table.player.init_hand(tiles)
        result = table.player.discard_tile()
        # No safe tile to discard. So should discard 1p
        self.assertEqual(self._to_string([result]), '1p')
    def test_should_go_for_defence_and_good_hand(self):
        """
        When we have 13 tiles in hand and someone declared a riichi
        """
        table = Table()

        tiles = self._string_to_136_array(sou='234678', pin='34789', man='77')
        table.player.init_hand(tiles)
        table.player.draw_tile(self._string_to_136_tile(man='6'))
        # discard here to reinit shanten number in AI
        table.player.discard_tile()

        self.assertEqual(table.player.ai.defence.should_go_to_defence_mode(), False)

        table.add_called_riichi(3)

        # our hand is in tempai, but it is really cheap
        self.assertEqual(table.player.ai.defence.should_go_to_defence_mode(), True)

        table.add_dora_indicator(self._string_to_136_tile(man='4'))
        table.add_dora_indicator(self._string_to_136_tile(pin='3'))

        # our hand in tempai, and it has a cost, so let's push it
        self.assertEqual(table.player.ai.defence.should_go_to_defence_mode(), False)
예제 #24
0
    def test_find_impossible_waits_and_kabe_technique(self):
        table = Table()
        tiles = self._string_to_136_array(pin='11122777799', man='999')
        table.player.init_hand(tiles)

        table.player.add_called_meld(self._make_meld(Meld.PON, man='999'))

        table.add_discarded_tile(1, self._string_to_136_tile(pin='2'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(pin='2'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(pin='9'), False)

        table.add_called_riichi(2)

        table.player.ai.defence.hand_34 = self._to_34_array(table.player.tiles)
        table.player.ai.defence.closed_hand_34 = self._to_34_array(table.player.closed_hand)
        result = table.player.ai.defence.kabe.find_tiles_to_discard([])

        self.assertEqual(self._to_string([x.value * 4 for x in result]), '19p')

        table = Table()
        tiles = self._string_to_136_array(pin='33337777', man='888999')
        table.player.init_hand(tiles)

        table.player.add_called_meld(self._make_meld(Meld.PON, man='888'))
        table.player.add_called_meld(self._make_meld(Meld.PON, man='999'))

        table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False)

        table.add_called_riichi(2)

        table.player.ai.defence.hand_34 = self._to_34_array(table.player.tiles)
        table.player.ai.defence.closed_hand_34 = self._to_34_array(table.player.closed_hand)
        result = table.player.ai.defence.kabe.find_tiles_to_discard([])

        self.assertEqual(self._to_string([x.value * 4 for x in result]), '5p')

        table = Table()
        tiles = self._string_to_136_array(pin='33334446666', man='999')
        table.player.init_hand(tiles)

        table.player.add_called_meld(self._make_meld(Meld.PON, man='999'))

        table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False)
        table.add_discarded_tile(1, self._string_to_136_tile(pin='5'), False)

        table.add_called_riichi(2)

        table.player.ai.defence.hand_34 = self._to_34_array(table.player.tiles)
        table.player.ai.defence.closed_hand_34 = self._to_34_array(table.player.closed_hand)
        result = table.player.ai.defence.kabe.find_tiles_to_discard([])

        self.assertEqual(self._to_string([x.value * 4 for x in result]), '45p')
예제 #25
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])))
예제 #26
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])))
    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])))