Beispiel #1
0
    def test_auth_message(self):
        decoder = TenhouDecoder()
        message = '<HELO uname="%4E%6F%4E%61%6D%65" auth="20160318-54ebe070" ratingscale=""/>'

        result = decoder.parse_auth_string(message)

        self.assertEqual(result, '20160318-54ebe070')
def test_parse_initial_scores():
    decoder = TenhouDecoder()
    message = (
        '<INIT seed="0,2,3,0,1,126" ten="240,260,270,280" oya="3" hai="30,67,44,21,133,123,87,69,36,34,94,4,128"/>'
    )
    values = decoder.parse_initial_values(message)
    assert values["scores"] == [240, 260, 270, 280]
Beispiel #3
0
    def test_parse_called_chi(self):
        decoder = TenhouDecoder()
        meld = decoder.parse_meld('<N who="3" m="27031" />')

        self.assertEqual(meld.who, 3)
        self.assertEqual(meld.type, Meld.CHI)
        self.assertEqual(meld.tiles, [42, 44, 51])
    def test_parse_initial_scores(self):
        decoder = TenhouDecoder()
        message = '<INIT seed="0,2,3,0,1,126" ten="240,260,270,280" oya="3" ' \
                  'hai="30,67,44,21,133,123,87,69,36,34,94,4,128"/>'
        values = decoder.parse_initial_values(message)

        self.assertEqual(values['scores'], [240, 260, 270, 280])
Beispiel #5
0
    def test_parse_initial_hand(self):
        decoder = TenhouDecoder()
        message = '<INIT seed="0,2,3,0,1,126" ten="250,250,250,250" oya="3" ' \
                  'hai="30,67,44,21,133,123,87,69,36,34,94,4,128"/>'
        tiles = decoder.parse_initial_hand(message)

        self.assertEqual(len(tiles), 13)
Beispiel #6
0
    def test_parse_called_pon(self):
        decoder = TenhouDecoder()
        meld = decoder.parse_meld('<N who="3" m="34314" />')

        self.assertEqual(meld.who, 3)
        self.assertEqual(meld.type, Meld.PON)
        self.assertEqual(meld.tiles, [89, 90, 91])
    def test_parse_initial_hand(self):
        decoder = TenhouDecoder()
        message = '<INIT seed="0,2,3,0,1,126" ten="250,250,250,250" oya="3" ' \
                  'hai="30,67,44,21,133,123,87,69,36,34,94,4,128"/>'
        tiles = decoder.parse_initial_hand(message)

        self.assertEqual(len(tiles), 13)
def test_parse_names_and_ranks():
    decoder = TenhouDecoder()
    message = (
        '<un n0="%4e%6f%4e%61%6d%65" n1="%6f%32%6f%32" n2="%73%68%69%6d%6d%6d%6d%6d"'
        ' n3="%e5%b7%9d%e6%b5%b7%e8%80%81" dan="0,7,12,1" '
        'rate="1500.00,1421.91,1790.94,1532.23" sx="m,m,m,m"/>')
    values = decoder.parse_names_and_ranks(message)
    assert values[0] == {
        "seat": 0,
        "name": "NoName",
        "rank": TenhouDecoder.RANKS[0]
    }
    assert values[1] == {
        "seat": 1,
        "name": "o2o2",
        "rank": TenhouDecoder.RANKS[7]
    }
    assert values[2] == {
        "seat": 2,
        "name": "shimmmmm",
        "rank": TenhouDecoder.RANKS[12]
    }
    assert values[3] == {
        "seat": 3,
        "name": "川海老",
        "rank": TenhouDecoder.RANKS[1]
    }
def test_auth_message():
    decoder = TenhouDecoder()
    message = """<HELO uname="%4E%6F%4E%61%6D%65"
                       PF4="9,45,1290.90,-5184.0,69,95,129,157,71,4303,831,830,33,1761"/>"""

    rating_string, _ = decoder.parse_hello_string(message)
    assert rating_string == "9,45,1290.90,-5184.0,69,95,129,157,71,4303,831,830,33,1761"
def test_decode_new_dora_indicator():
    decoder = TenhouDecoder()
    message = '<DORA hai="125" />'

    result = decoder.parse_dora_indicator(message)

    assert result == 125
Beispiel #11
0
    def test_parse_called_chakan(self):
        decoder = TenhouDecoder()
        meld = decoder.parse_meld('<N who="3" m="18547" />')

        self.assertEqual(meld.who, 3)
        self.assertEqual(meld.type, Meld.CHAKAN)
        self.assertEqual(meld.tiles, [48, 49, 50, 51])
def test_parse_initial_hand():
    decoder = TenhouDecoder()
    message = (
        '<INIT seed="0,2,3,0,1,126" ten="250,250,250,250" oya="3" hai="30,67,44,21,133,123,87,69,36,34,94,4,128"/>'
    )
    tiles = decoder.parse_initial_hand(message)
    assert len(tiles) == 13
Beispiel #13
0
    def test_parse_called_kan(self):
        decoder = TenhouDecoder()
        meld = decoder.parse_meld('<N who="3" m="13825" />')

        self.assertEqual(meld.who, 3)
        self.assertEqual(meld.type, Meld.KAN)
        self.assertEqual(meld.tiles, [52, 53, 54, 55])
Beispiel #14
0
    def test_decode_new_dora_indicator(self):
        decoder = TenhouDecoder()
        message = '<DORA hai="125" />'

        result = decoder.parse_dora_indicator(message)

        self.assertEqual(result, 125)
Beispiel #15
0
    def test_parse_initial_scores(self):
        decoder = TenhouDecoder()
        message = '<INIT seed="0,2,3,0,1,126" ten="240,260,270,280" oya="3" ' \
                  'hai="30,67,44,21,133,123,87,69,36,34,94,4,128"/>'
        values = decoder.parse_initial_values(message)

        self.assertEqual(values['scores'], [240, 260, 270, 280])
    def test_decode_new_dora_indicator(self):
        decoder = TenhouDecoder()
        message = '<DORA hai="125" />'

        result = decoder.parse_dora_indicator(message)

        self.assertEqual(result, 125)
def test_parse_called_pon():
    decoder = TenhouDecoder()
    meld = decoder.parse_meld('<N who="3" m="34314" />')

    assert meld.who == 3
    assert meld.type == MeldPrint.PON
    assert meld.opened is True
    assert meld.tiles == [89, 90, 91]
    def test_generate_auth_token(self):
        client = TenhouDecoder()

        string = '20160318-54ebe070'
        self.assertEqual(client.generate_auth_token(string), '20160318-72b5ba21')

        string = '20160319-5b859bb3'
        self.assertEqual(client.generate_auth_token(string), '20160319-9bc528f3')
Beispiel #19
0
    def test_generate_auth_token(self):
        client = TenhouDecoder()

        string = '20160318-54ebe070'
        self.assertEqual(client.generate_auth_token(string), '20160318-72b5ba21')

        string = '20160319-5b859bb3'
        self.assertEqual(client.generate_auth_token(string), '20160319-9bc528f3')
def test_parse_called_chi():
    decoder = TenhouDecoder()
    meld = decoder.parse_meld('<N who="3" m="27031" />')

    assert meld.who == 3
    assert meld.type == MeldPrint.CHI
    assert meld.opened is True
    assert meld.tiles == [42, 44, 51]
    def test_parse_called_chi(self):
        decoder = TenhouDecoder()
        meld = decoder.parse_meld('<N who="3" m="27031" />')

        self.assertEqual(meld.who, 3)
        self.assertEqual(meld.type, Meld.CHI)
        self.assertEqual(meld.opened, True)
        self.assertEqual(meld.tiles, [42, 44, 51])
    def test_parse_log_link(self):
        decoder = TenhouDecoder()
        message = '<TAIKYOKU oya="1" log="2016031911gm-0001-0000-381f693b"/>'

        game_id, position = decoder.parse_log_link(message)

        self.assertEqual(game_id, '2016031911gm-0001-0000-381f693b')
        self.assertEqual(position, 3)
    def test_parse_called_chakan(self):
        decoder = TenhouDecoder()
        meld = decoder.parse_meld('<N who="3" m="18547" />')

        self.assertEqual(meld.who, 3)
        self.assertEqual(meld.type, Meld.CHANKAN)
        self.assertEqual(meld.opened, True)
        self.assertEqual(meld.tiles, [48, 49, 50, 51])
def test_parse_called_chakan():
    decoder = TenhouDecoder()
    meld = decoder.parse_meld('<N who="3" m="18547" />')

    assert meld.who == 3
    assert meld.type == MeldPrint.SHOUMINKAN
    assert meld.opened is True
    assert meld.tiles == [48, 49, 50, 51]
    def test_parse_called_pon(self):
        decoder = TenhouDecoder()
        meld = decoder.parse_meld('<N who="3" m="34314" />')

        self.assertEqual(meld.who, 3)
        self.assertEqual(meld.type, Meld.PON)
        self.assertEqual(meld.opened, True)
        self.assertEqual(meld.tiles, [89, 90, 91])
Beispiel #26
0
    def test_parse_log_link(self):
        decoder = TenhouDecoder()
        message = '<TAIKYOKU oya="1" log="2016031911gm-0001-0000-381f693b"/>'

        game_id, position = decoder.parse_log_link(message)

        self.assertEqual(game_id, '2016031911gm-0001-0000-381f693b')
        self.assertEqual(position, 3)
    def test_parse_called_opened_kan(self):
        decoder = TenhouDecoder()
        meld = decoder.parse_meld('<N who="3" m="13825" />')

        self.assertEqual(meld.who, 3)
        self.assertEqual(meld.from_who, 0)
        self.assertEqual(meld.type, Meld.KAN)
        self.assertEqual(meld.opened, True)
        self.assertEqual(meld.tiles, [52, 53, 54, 55])
    def test_is_match_discard(self):
        decoder = TenhouDecoder()

        self.assertTrue(decoder.is_discarded_tile_message('<e107/>'))
        self.assertTrue(decoder.is_discarded_tile_message('<F107/>'))
        self.assertTrue(decoder.is_discarded_tile_message('<g107/>'))

        self.assertFalse(decoder.is_discarded_tile_message('<GO type="9" lobby="0" gpid=""/>'))
        self.assertFalse(decoder.is_discarded_tile_message('<FURITEN show="1" />'))
    def test_parse_called_closed_kan(self):
        decoder = TenhouDecoder()
        meld = decoder.parse_meld('<N who="0" m="15872" />')

        self.assertEqual(meld.who, 0)
        self.assertEqual(meld.from_who, 0)
        self.assertEqual(meld.type, Meld.KAN)
        self.assertEqual(meld.opened, False)
        self.assertEqual(meld.tiles, [60, 61, 62, 63])
    def test_parse_called_closed_kan(self):
        decoder = TenhouDecoder()
        meld = decoder.parse_meld('<N who="0" m="15872" />')

        self.assertEqual(meld.who, 0)
        self.assertEqual(meld.from_who, 0)
        self.assertEqual(meld.type, Meld.KAN)
        self.assertEqual(meld.opened, False)
        self.assertEqual(meld.tiles, [60, 61, 62, 63])
    def __init__(self, log_url, stop_tag=None):
        log_id, player_position, needed_round = self._parse_url(log_url)
        log_content = self._download_log_content(log_id)
        rounds = self._parse_rounds(log_content)

        self.player_position = player_position
        self.round_content = rounds[needed_round]
        self.stop_tag = stop_tag
        self.decoder = TenhouDecoder()
def test_parse_called_opened_kan():
    decoder = TenhouDecoder()
    meld = decoder.parse_meld('<N who="3" m="13825" />')

    assert meld.who == 3
    assert meld.from_who == 0
    assert meld.type == MeldPrint.KAN
    assert meld.opened is True
    assert meld.tiles == [52, 53, 54, 55]
def test_parse_called_closed_kan():
    decoder = TenhouDecoder()
    meld = decoder.parse_meld('<N who="0" m="15872" />')

    assert meld.who == 0
    assert meld.from_who == 0
    assert meld.type == MeldPrint.KAN
    assert meld.opened is False
    assert meld.tiles == [60, 61, 62, 63]
    def test_auth_message(self):
        decoder = TenhouDecoder()
        message = """<HELO uname="%4E%6F%4E%61%6D%65"
                           auth="20160318-54ebe070" ratingscale=""
                           PF4="9,45,1290.90,-5184.0,69,95,129,157,71,4303,831,830,33,1761"/>"""

        auth_string, rating_string, _ = decoder.parse_hello_string(message)

        self.assertEqual(auth_string, '20160318-54ebe070')
        self.assertEqual(rating_string, '9,45,1290.90,-5184.0,69,95,129,157,71,4303,831,830,33,1761')
Beispiel #35
0
    def test_parse_names_and_ranks(self):
        decoder = TenhouDecoder()
        message = '<un n0="%4e%6f%4e%61%6d%65" n1="%6f%32%6f%32" n2="%73%68%69%6d%6d%6d%6d%6d"' \
                  ' n3="%e5%b7%9d%e6%b5%b7%e8%80%81" dan="0,7,12,1" ' \
                  'rate="1500.00,1421.91,1790.94,1532.23" sx="m,m,m,m"/>'
        values = decoder.parse_names_and_ranks(message)

        self.assertEqual(values[0], {'name': 'NoName', 'rank': TenhouDecoder.RANKS[0]})
        self.assertEqual(values[1], {'name': 'o2o2', 'rank': TenhouDecoder.RANKS[7]})
        self.assertEqual(values[2], {'name': 'shimmmmm', 'rank': TenhouDecoder.RANKS[12]})
        self.assertEqual(values[3], {'name': u'川海老', 'rank': TenhouDecoder.RANKS[1]})
Beispiel #36
0
    def test_parse_initial_round_values(self):
        decoder = TenhouDecoder()
        message = '<INIT seed="0,2,3,0,1,126" ten="250,250,250,250" oya="3" ' \
                  'hai="30,67,44,21,133,123,87,69,36,34,94,4,128"/>'

        values = decoder.parse_initial_values(message)
        self.assertEqual(values['round_number'], 0)
        self.assertEqual(values['count_of_honba_sticks'], 2)
        self.assertEqual(values['count_of_riichi_sticks'], 3)
        self.assertEqual(values['dora_indicator'], 126)
        self.assertEqual(values['dealer'], 3)
    def test_parse_names_and_ranks(self):
        decoder = TenhouDecoder()
        message = '<un n0="%4e%6f%4e%61%6d%65" n1="%6f%32%6f%32" n2="%73%68%69%6d%6d%6d%6d%6d"' \
                  ' n3="%e5%b7%9d%e6%b5%b7%e8%80%81" dan="0,7,12,1" ' \
                  'rate="1500.00,1421.91,1790.94,1532.23" sx="m,m,m,m"/>'
        values = decoder.parse_names_and_ranks(message)

        self.assertEqual(values[0], {'name': 'NoName', 'rank': TenhouDecoder.RANKS[0]})
        self.assertEqual(values[1], {'name': 'o2o2', 'rank': TenhouDecoder.RANKS[7]})
        self.assertEqual(values[2], {'name': 'shimmmmm', 'rank': TenhouDecoder.RANKS[12]})
        self.assertEqual(values[3], {'name': u'川海老', 'rank': TenhouDecoder.RANKS[1]})
def test_parse_initial_round_values():
    decoder = TenhouDecoder()
    message = (
        '<INIT seed="0,2,3,0,1,126" ten="250,250,250,250" oya="3" hai="30,67,44,21,133,123,87,69,36,34,94,4,128"/>'
    )
    values = decoder.parse_initial_values(message)
    assert values["round_wind_number"] == 0
    assert values["count_of_honba_sticks"] == 2
    assert values["count_of_riichi_sticks"] == 3
    assert values["dora_indicator"] == 126
    assert values["dealer"] == 3
    def test_parse_initial_round_values(self):
        decoder = TenhouDecoder()
        message = '<INIT seed="0,2,3,0,1,126" ten="250,250,250,250" oya="3" ' \
                  'hai="30,67,44,21,133,123,87,69,36,34,94,4,128"/>'

        values = decoder.parse_initial_values(message)
        self.assertEqual(values['round_wind_number'], 0)
        self.assertEqual(values['count_of_honba_sticks'], 2)
        self.assertEqual(values['count_of_riichi_sticks'], 3)
        self.assertEqual(values['dora_indicator'], 126)
        self.assertEqual(values['dealer'], 3)
    def test_auth_message(self):
        decoder = TenhouDecoder()
        message = """<HELO uname="%4E%6F%4E%61%6D%65"
                           auth="20160318-54ebe070" ratingscale=""
                           PF4="9,45,1290.90,-5184.0,69,95,129,157,71,4303,831,830,33,1761"/>"""

        auth_string, rating_string, _ = decoder.parse_hello_string(message)

        self.assertEqual(auth_string, '20160318-54ebe070')
        self.assertEqual(
            rating_string,
            '9,45,1290.90,-5184.0,69,95,129,157,71,4303,831,830,33,1761')
Beispiel #41
0
    def __init__(self, log_id, file_path, logger):
        self.decoder = TenhouDecoder()
        self.logger = logger

        if log_id:
            log_content = self._download_log_content(log_id)
        elif file_path:
            with open(file_path, "r") as f:
                log_content = f.read()
        else:
            raise AssertionError("log id or file path should be specified")

        self.rounds = self._parse_rounds(log_content)
Beispiel #42
0
    def test_parse_final_scores_and_uma(self):
        decoder = TenhouDecoder()
        message = '<agari ba="0,0" hai="12,13,41,46,51,78,80,84,98,101,105" m="51243" ' \
                  'machi="101" ten="30,1000,0" yaku="20,1" dorahai="89" who="2" fromwho="1" ' \
                  'sc="225,0,240,-10,378,10,157,0" owari="225,-17.0,230,3.0,388,48.0,157,-34.0" />'
        values = decoder.parse_final_scores_and_uma(message)

        self.assertEqual(values['scores'], [225, 230, 388, 157])
        self.assertEqual(values['uma'], [-17, 3, 48, -34])

        message = '<ryuukyoku ten="30,1000,0" sc="225,0,240,-10,378,10,157,0" ' \
                  'owari="225,-17.0,230,3.0,388,48.0,157,-34.0" />'
        values = decoder.parse_final_scores_and_uma(message)

        self.assertEqual(values['scores'], [225, 230, 388, 157])
        self.assertEqual(values['uma'], [-17, 3, 48, -34])
    def test_parse_final_scores_and_uma(self):
        decoder = TenhouDecoder()
        message = '<agari ba="0,0" hai="12,13,41,46,51,78,80,84,98,101,105" m="51243" ' \
                  'machi="101" ten="30,1000,0" yaku="20,1" dorahai="89" who="2" fromwho="1" ' \
                  'sc="225,0,240,-10,378,10,157,0" owari="225,-17.0,230,3.0,388,48.0,157,-34.0" />'
        values = decoder.parse_final_scores_and_uma(message)

        self.assertEqual(values['scores'], [225, 230, 388, 157])
        self.assertEqual(values['uma'], [-17, 3, 48, -34])

        message = '<ryuukyoku ten="30,1000,0" sc="225,0,240,-10,378,10,157,0" ' \
                  'owari="225,-17.0,230,3.0,388,48.0,157,-34.0" />'
        values = decoder.parse_final_scores_and_uma(message)

        self.assertEqual(values['scores'], [225, 230, 388, 157])
        self.assertEqual(values['uma'], [-17, 3, 48, -34])
def test_parse_final_scores_and_uma():
    decoder = TenhouDecoder()
    message = (
        '<agari ba="0,0" hai="12,13,41,46,51,78,80,84,98,101,105" m="51243" '
        'machi="101" ten="30,1000,0" yaku="20,1" dorahai="89" who="2" fromwho="1" '
        'sc="225,0,240,-10,378,10,157,0" owari="225,-17.0,230,3.0,388,48.0,157,-34.0" />'
    )
    values = decoder.parse_final_scores_and_uma(message)
    assert values["scores"] == [225, 230, 388, 157]
    assert values["uma"] == [-17, 3, 48, -34]

    message = (
        '<ryuukyoku ten="30,1000,0" sc="225,0,240,-10,378,10,157,0" owari="225,-17.0,230,3.0,388,48.0,157,-34.0" />'
    )
    values = decoder.parse_final_scores_and_uma(message)
    assert values["scores"] == [225, 230, 388, 157]
    assert values["uma"] == [-17, 3, 48, -34]
def test_reconnection_information():
    message = ('<REINIT seed="0,0,1,4,3,59" ten="250,250,250,240" oya="0" '
               'hai="1,2,4,13,17,20,46,47,53,71,76,81,85" '
               'm2="41515" '
               'kawa0="120,28,128,131,18,75,74,27,69,130,64" '
               'kawa1="117,121,123,129,103,72,83,125,62,84" '
               'kawa2="33,114,122,107,31,105,78,9,68,73,38" '
               'kawa3="115,0,126,87,24,25,106,255,70,3,119"/>')
    decoder = TenhouDecoder()
    result = decoder.parse_table_state_after_reconnection(message)
    assert len(result) == 4

    assert len(result[0]["discards"]) == 11
    # one additional tile from meld
    assert len(result[1]["discards"]) == 11
    assert len(result[2]["discards"]) == 11
    assert len(result[3]["discards"]) == 10
    assert len(result[2]["melds"]) == 1
    def __init__(self, log_url, stop_tag=None):
        log_id, player_position, needed_round = self._parse_url(log_url)
        log_content = self._download_log_content(log_id)
        rounds = self._parse_rounds(log_content)

        self.player_position = player_position
        self.round_content = rounds[needed_round]
        self.stop_tag = stop_tag
        self.decoder = TenhouDecoder()
    def test_reconnection_information(self):
        message = '<REINIT seed="0,0,1,4,3,59" ten="250,250,250,240" oya="0" ' \
                  'hai="1,2,4,13,17,20,46,47,53,71,76,81,85" ' \
                  'm2="41515" ' \
                  'kawa0="120,28,128,131,18,75,74,27,69,130,64" ' \
                  'kawa1="117,121,123,129,103,72,83,125,62,84" ' \
                  'kawa2="33,114,122,107,31,105,78,9,68,73,38" ' \
                  'kawa3="115,0,126,87,24,25,106,255,70,3,119"/>'
        decoder = TenhouDecoder()
        result = decoder.parse_table_state_after_reconnection(message)
        self.assertEqual(len(result), 4)

        self.assertEqual(len(result[0]['discards']), 11)
        # one additional tile from meld
        self.assertEqual(len(result[1]['discards']), 11)
        self.assertEqual(len(result[2]['discards']), 11)
        self.assertEqual(len(result[3]['discards']), 10)

        self.assertEqual(len(result[2]['melds']), 1)
    def test_parse_tile(self):
        decoder = TenhouDecoder()

        tile = decoder.parse_tile('<t23/>')
        self.assertEqual(tile, 23)

        tile = decoder.parse_tile('<e24/>')
        self.assertEqual(tile, 24)

        tile = decoder.parse_tile('<f25/>')
        self.assertEqual(tile, 25)

        tile = decoder.parse_tile('<g26/>')
        self.assertEqual(tile, 26)

        tile = decoder.parse_tile('<f23 t="4"/>')
        self.assertEqual(tile, 23)
class TenhouLogReproducer(object):
    """
    The way to debug bot decisions that it made in real tenhou.net games
    """

    def __init__(self, log_url, stop_tag=None):
        log_id, player_position, needed_round = self._parse_url(log_url)
        log_content = self._download_log_content(log_id)
        rounds = self._parse_rounds(log_content)

        self.player_position = player_position
        self.round_content = rounds[needed_round]
        self.stop_tag = stop_tag
        self.decoder = TenhouDecoder()

    def reproduce(self, dry_run=False):
        draw_tags = ['T', 'U', 'V', 'W']
        discard_tags = ['D', 'E', 'F', 'G']

        player_draw = draw_tags[self.player_position]

        player_draw_regex = re.compile('^<[{}]+\d*'.format(''.join(player_draw)))
        discard_regex = re.compile('^<[{}]+\d*'.format(''.join(discard_tags)))

        table = Table()
        for tag in self.round_content:
            if player_draw_regex.match(tag) and 'UN' not in tag:
                print('Player draw')
                tile = self.decoder.parse_tile(tag)
                table.player.draw_tile(tile)

            if dry_run:
                if self._is_draw(tag):
                    print('<-', TilesConverter.to_one_line_string([self._parse_tile(tag)]), tag)
                elif self._is_discard(tag):
                    print('->', TilesConverter.to_one_line_string([self._parse_tile(tag)]), tag)
                elif self._is_init_tag(tag):
                    hands = {
                        0: [int(x) for x in self._get_attribute_content(tag, 'hai0').split(',')],
                        1: [int(x) for x in self._get_attribute_content(tag, 'hai1').split(',')],
                        2: [int(x) for x in self._get_attribute_content(tag, 'hai2').split(',')],
                        3: [int(x) for x in self._get_attribute_content(tag, 'hai3').split(',')],
                    }
                    print('Initial hand:', TilesConverter.to_one_line_string(hands[self.player_position]))
                else:
                    print(tag)

            if not dry_run and tag == self.stop_tag:
                break

            if 'INIT' in tag:
                values = self.decoder.parse_initial_values(tag)

                shifted_scores = []
                for x in range(0, 4):
                    shifted_scores.append(values['scores'][self._normalize_position(x, self.player_position)])

                table.init_round(
                    values['round_wind_number'],
                    values['count_of_honba_sticks'],
                    values['count_of_riichi_sticks'],
                    values['dora_indicator'],
                    self._normalize_position(self.player_position, values['dealer']),
                    shifted_scores,
                )

                hands = [
                    [int(x) for x in self.decoder.get_attribute_content(tag, 'hai0').split(',')],
                    [int(x) for x in self.decoder.get_attribute_content(tag, 'hai1').split(',')],
                    [int(x) for x in self.decoder.get_attribute_content(tag, 'hai2').split(',')],
                    [int(x) for x in self.decoder.get_attribute_content(tag, 'hai3').split(',')],
                ]

                table.player.init_hand(hands[self.player_position])

            if discard_regex.match(tag) and 'DORA' not in tag:
                tile = self.decoder.parse_tile(tag)
                player_sign = tag.upper()[1]
                player_seat = self._normalize_position(self.player_position, discard_tags.index(player_sign))

                if player_seat == 0:
                    table.player.discard_tile(tile)
                else:
                    table.add_discarded_tile(player_seat, tile, False)

            if '<N who=' in tag:
                meld = self.decoder.parse_meld(tag)
                player_seat = self._normalize_position(self.player_position, meld.who)
                table.add_called_meld(player_seat, meld)

                if player_seat == 0:
                    # we had to delete called tile from hand
                    # to have correct tiles count in the hand
                    if meld.type != Meld.KAN and meld.type != Meld.CHANKAN:
                        table.player.draw_tile(meld.called_tile)

            if '<REACH' in tag and 'step="1"' in tag:
                who_called_riichi = self._normalize_position(self.player_position,
                                                             self.decoder.parse_who_called_riichi(tag))
                table.add_called_riichi(who_called_riichi)

        if not dry_run:
            tile = self.decoder.parse_tile(self.stop_tag)
            print('Hand: {}'.format(table.player.format_hand_for_print(tile)))

            # to rebuild all caches
            table.player.draw_tile(tile)
            tile = table.player.discard_tile()

            # real run, you can stop debugger here
            table.player.draw_tile(tile)
            tile = table.player.discard_tile()

            print('Discard: {}'.format(TilesConverter.to_one_line_string([tile])))

    def _normalize_position(self, who, from_who):
        positions = [0, 1, 2, 3]
        return positions[who - from_who]

    def _parse_url(self, log_url):
        temp = log_url.split('?')[1].split('&')
        log_id, player, round_wind = '', 0, 0
        for item in temp:
            item = item.split('=')
            if 'log' == item[0]:
                log_id = item[1]
            if 'tw' == item[0]:
                player = int(item[1])
            if 'ts' == item[0]:
                round_wind = int(item[1])
        return log_id, player, round_wind

    def _download_log_content(self, log_id):
        """
        Check the log file, and if it is not there download it from tenhou.net
        :param log_id:
        :return:
        """
        temp_folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'logs')
        if not os.path.exists(temp_folder):
            os.mkdir(temp_folder)

        log_file = os.path.join(temp_folder, log_id)
        if os.path.exists(log_file):
            with open(log_file, 'r') as f:
                return f.read()
        else:
            url = 'http://e.mjv.jp/0/log/?{0}'.format(log_id)
            response = requests.get(url)

            with open(log_file, 'w') as f:
                f.write(response.text)

            return response.text

    def _parse_rounds(self, log_content):
        """
        Build list of round tags
        :param log_content:
        :return:
        """
        rounds = []

        game_round = []
        tag_start = 0
        tag = None
        for x in range(0, len(log_content)):
            if log_content[x] == '>':
                tag = log_content[tag_start:x + 1]
                tag_start = x + 1

            # not useful tags
            if tag and ('mjloggm' in tag or 'TAIKYOKU' in tag):
                tag = None

            # new round was started
            if tag and 'INIT' in tag:
                rounds.append(game_round)
                game_round = []

            # the end of the game
            if tag and 'owari' in tag:
                rounds.append(game_round)

            if tag:
                # to save some memory we can remove not needed information from logs
                if 'INIT' in tag:
                    # we dont need seed information
                    find = re.compile(r'shuffle="[^"]*"')
                    tag = find.sub('', tag)

                # add processed tag to the round
                game_round.append(tag)
                tag = None

        return rounds[1:]

    def _is_discard(self, tag):
        skip_tags = ['<GO', '<FURITEN', '<DORA']
        if any([x in tag for x in skip_tags]):
            return False

        match_discard = re.match(r"^<[defgDEFG]+\d*", tag)
        if match_discard:
            return True

        return False

    def _is_draw(self, tag):
        match_discard = re.match(r"^<[tuvwTUVW]+\d*", tag)
        if match_discard:
            return True

        return False

    def _parse_tile(self, tag):
        result = re.match(r'^<[defgtuvwDEFGTUVW]+\d*', tag).group()
        return int(result[2:])

    def _is_init_tag(self, tag):
        return tag and 'INIT' in tag

    def _get_attribute_content(self, tag, attribute_name):
        result = re.findall(r'{}="([^"]*)"'.format(attribute_name), tag)
        return result and result[0] or None
    def test_parse_who_called_riichi(self):
        decoder = TenhouDecoder()

        who = decoder.parse_who_called_riichi('<REACH who="2" ten="255,216,261,258" step="2"/>')
        self.assertEqual(who, 2)