class TenhouLogReproducer(object):
    """
    The way to debug bot decisions that it made in real tenhou.net games
    """
    def __init__(self, mjlog_file=None, log_url=None, stop_tag=None):
        if log_url:
            log_id, player_position, needed_round = self._parse_url(log_url)
            log_content = self._download_log_content(log_id)
        elif mjlog_file:
            with open(mjlog_file, encoding="utf8") as f:
                log_id = mjlog_file.split("/")[-1].split(".")[0]
                player_position = 0  # tw: seat
                needed_round = 1  # ts: round
                log_content = f.read()
        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()

        # ADD: to get results of all rounds
        self.rounds = rounds
        # ADD: to extract features to be saved
        self.extract_features = ExtractFeatures()

    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)))

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

        table = Table()
        previous_tag = ""
        score = 1
        is_valid_sample = False
        for n, tag in enumerate(self.round_content):
            if dry_run:
                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_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(',')
                    ],
                ]

                # DEL: we can't only initialize the main player, we must initialize
                # other players as well.
                #table.player.init_hand(hands[self.player_position])

                # ADD: initialize all players on the table
                table.players[0].init_hand(hands[self.player_position])
                table.players[1].init_hand(hands[(self.player_position + 1) %
                                                 4])
                table.players[2].init_hand(hands[(self.player_position + 2) %
                                                 4])
                table.players[3].init_hand(hands[(self.player_position + 3) %
                                                 4])

                # ADD: when restart a new game, we need to reinitialize the config
                self.extract_features.__init__()

            # We must deal with ALL players.
            #if player_draw_regex.match(tag) and 'UN' not in tag:
            if draw_regex.match(tag) and 'UN' not in tag:
                tile = self.decoder.parse_tile(tag)

                # CHG: we must deal with ALL players
                #table.player.draw_tile(tile)
                if "T" in tag:
                    table.players[0].draw_tile(tile)
                elif "U" in tag:
                    table.players[1].draw_tile(tile)
                elif "V" in tag:
                    table.players[2].draw_tile(tile)
                elif "W" in tag:
                    table.players[3].draw_tile(tile)
                    #print("After draw `W`:", table.players[3].tiles)

            if discard_regex.match(tag) and 'DORA' not in tag:
                tile = self.decoder.parse_tile(tag)
                player_sign = tag.upper()[1]

                # TODO: I don't know why the author wrote the code as below, the
                # player_seat won't work if we use self._normalize_position. This
                # might be a tricky part, and we need to review it later.
                #player_seat = self._normalize_position(self.player_position, discard_tags.index(player_sign))

                # Temporally solution to modify the player_seat
                player_seat = (discard_tags.index(player_sign) +
                               self.player_position) % 4
                #print("updated player seat:",player_seat)

                if player_seat == 0:
                    table.players[player_seat].discard_tile(
                        DiscardOption(table.players[player_seat], tile // 4, 0,
                                      [], 0))
                else:
                    # ADD: we must take care of ALL players
                    tile_to_discard = tile

                    is_tsumogiri = tile_to_discard == table.players[
                        player_seat].last_draw
                    # it is important to use table method,
                    # to recalculate revealed tiles and etc.
                    table.add_discarded_tile(player_seat, tile_to_discard,
                                             is_tsumogiri)

                    #print("seat:",player_seat)
                    #print("tiles:", TilesConverter.to_one_line_string(table.players[player_seat].tiles), " discard?:", TilesConverter.to_one_line_string([tile_to_discard]))
                    table.players[player_seat].tiles.remove(tile_to_discard)

                    # DEL
                    #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)
                # Again, we change the player_seat here
                player_seat = (meld.who + self.player_position) % 4
                table.add_called_meld(player_seat, meld)

                #if player_seat == 0:
                # CHG: we need to handle ALL players here
                if True:
                    # 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.players[player_seat].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)

            # This part is to extract the features that will be used to train
            # our model.
            try:
                next_tag = self.round_content[n + 1]
            except IndexError:
                next_tag = ""
            if '<AGARI' in next_tag:
                who_regex = re.compile("who=\"\d+\"")
                fromWho_regex = re.compile("fromWho=\"\d+\"")
                sc_regex = "sc=\"[+-]?\d+,[+-]?\d+,[+-]?\d+,[+-]?\d+,[+-]?\d+,[+-]?\d+,[+-]?\d+,[+-]?\d+\""
                score_regex = re.compile(sc_regex)
                who = int(
                    who_regex.search(next_tag).group(0).replace(
                        '"', '').split("=")[1])
                fromWho = int(
                    fromWho_regex.search(next_tag).group(0).replace(
                        '"', '').split("=")[1])
                scores = [
                    float(s)
                    for s in score_regex.search(next_tag).group(0).replace(
                        '"', '').split("=")[1].split(",")
                ]
                score = scores[fromWho * 2 + 1]
                player_seat, features = self.execute_extraction(tag, table)

                #                # tsumo is not a valid sample for our training.
                #                if (who!=fromWho): # not tsumo (lose the score to winner, score<0)
                #                    if (features is not None) and (player_seat is not None) and (score<0):
                #                        # The first element before ";" is table_info, therefore player_info starts
                #                        # from index 1, and we put who+1 here.
                #                        self.feature_to_logger(features, who+1, score)
                #                        score = 1

                # tsumo is a valid sample for our training
                if (who == fromWho):  # tsumo (win the score, score>0)
                    if (features
                            is not None) and (player_seat
                                              is not None) and (score > 0):
                        self.feature_to_logger(features, who + 1, score)
                        score = -1

            else:
                player_seat, features = self.execute_extraction(tag, table)

        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 feature_to_logger(self, features, player_seat, score):
        features_list = features.split(";")
        assert len(features_list) == 6, "<D> Features format incorrect!"
        table_info = features_list[0]
        player_info = features_list[player_seat]
        logger2.info(table_info + ";" + player_info + ";" + str(score))

    def execute_extraction(self, tag, table):
        """
        D/E/F/G are for discards
        T/U/V/W are for draws
        """
        if ('<T' in tag) or ('<D' in tag):
            features = self.extract_features.get_scores_features(table)
            return 1, features
        if ('<U' in tag) or ('<E' in tag):
            features = self.extract_features.get_scores_features(table)
            return 2, features
        if ('<V' in tag) or ('<F' in tag):
            features = self.extract_features.get_scores_features(table)
            return 3, features
        if ('<W' in tag) or ('<G' in tag):
            features = self.extract_features.get_scores_features(table)
            return 4, features
        return None, None


#    def execute_extraction(self, tag, score, table, to_logger):
#        if '<D' in tag:
#            #features = self.extract_features.get_is_waiting_features(table)
#            #features = self.extract_features.get_waiting_tiles_features(table)
#            features = self.extract_features.get_scores_features(score, table)
#            if (features is not None) and to_logger:
#                features_list = features.split(";")
#                assert len(features_list)==6, "<D> Features format incorrect!"
#                score_info = features_list[0]
#                player_info = features_list[1]
#                logger2.info(score_info + ";" + player_info)
#
#        if '<E' in tag:
#            #features = self.extract_features.get_is_waiting_features(table)
#            #features = self.extract_features.get_waiting_tiles_features(table)
#            features = self.extract_features.get_scores_features(score, table)
#            if (features is not None) and to_logger:
#                features_list = features.split(";")
#                assert len(features_list)==6, "<E> Features format incorrect!"
#                score_info = features_list[0]
#                player_info = features_list[2]
#                logger2.info(score_info + ";" + player_info)
#
#        if '<F' in tag:
#            #features = self.extract_features.get_is_waiting_features(table)
#            #features = self.extract_features.get_waiting_tiles_features(table)
#            features = self.extract_features.get_scores_features(score, table)
#            if (features is not None) and to_logger:
#                features_list = features.split(";")
#                assert len(features_list)==6, "<F> Features format incorrect!"
#                score_info = features_list[0]
#                player_info = features_list[3]
#                logger2.info(score_info + ";" + player_info)
#
#        if '<G' in tag:
#            #features = self.extract_features.get_is_waiting_features(table)
#            #features = self.extract_features.get_waiting_tiles_features(table)
#            features = self.extract_features.get_scores_features(score, table)
#            if (features is not None) and to_logger:
#                features_list = features.split(";")
#                assert len(features_list)==6, "<G> Features format incorrect!"
#                score_info = features_list[0]
#                player_info = features_list[4]
#                logger2.info(score_info + ";" + player_info)

    def reproduce_all(self, dry_run=False):
        for r in self.rounds:
            self.round_content = r
            self.reproduce(dry_run=dry_run)
            print("--------------------------------------\n")

    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_number = '', 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_number = int(item[1])
        return log_id, player, round_number

    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:]
예제 #2
0
class TenhouLogReproducer(object):
    """
    The way to debug bot decisions that it made in real tenhou.net games
    """

    def __init__(self, mjlog_file=None, log_url=None, stop_tag=None):
        if log_url:
            log_id, player_position, needed_round = self._parse_url(log_url)
            log_content = self._download_log_content(log_id)
        elif mjlog_file:
            with open(mjlog_file, encoding="utf8") as f:
                log_id = mjlog_file.split("/")[-1].split(".")[0]
                player_position = 0 # tw: seat
                needed_round = 1 # ts: round
                log_content = f.read()
        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()
        
        
        # ADD: to get results of all rounds
        self.rounds = rounds
        # ADD: to extract features to be saved
        self.extract_features = ExtractFeatures()

    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)))
        
        draw_regex = re.compile('^<[{}]+\d*'.format(''.join(draw_tags)))
        discard_regex = re.compile('^<[{}]+\d*'.format(''.join(discard_tags)))

        table = Table()
        score = 1
        
        skip = False # We neglect those unwanted records
        clean_records = [] # We use a list to store the clean records
        
        for n, tag in enumerate(self.round_content):
            if dry_run:
                if (draw_regex.match(tag) and 'UN' not in tag) or (discard_regex.match(tag) and 'DORA' not in tag):
                    tile = self.decoder.parse_tile(tag)
                    print("%s %s"%(tag, TilesConverter.to_one_line_string([tile])))
                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_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(',')],
                ]
                 
                # DEL: we can't only initialize the main player, we must initialize
                # other players as well.
                #table.player.init_hand(hands[self.player_position])
                
                # ADD: initialize all players on the table
                table.players[0].init_hand(hands[self.player_position])
                table.players[1].init_hand(hands[(self.player_position+1)%4])
                table.players[2].init_hand(hands[(self.player_position+2)%4])
                table.players[3].init_hand(hands[(self.player_position+3)%4])
                
                # ADD: when restart a new game, we need to reinitialize the config
                self.extract_features.__init__()
                # raw records of a new game
                raw_records = []

            # Trigger skip condition after <INIT>, b/c <INIT> has higher priority than skip
            if skip:
                continue

            # We must deal with ALL players.
            #if player_draw_regex.match(tag) and 'UN' not in tag:
            if draw_regex.match(tag) and 'UN' not in tag:
                tile = self.decoder.parse_tile(tag)
                
                # CHG: we must deal with ALL players
                #table.player.draw_tile(tile)
                if "T" in tag:
                    table.players[0].draw_tile(tile)
                elif "U" in tag:
                    table.players[1].draw_tile(tile)
                elif "V" in tag:
                    table.players[2].draw_tile(tile)
                elif "W" in tag:
                    table.players[3].draw_tile(tile)
                    #print("After draw `W`:", table.players[3].tiles)
                
            if discard_regex.match(tag) and 'DORA' not in tag:
                tile = self.decoder.parse_tile(tag)
                
                player_sign = tag.upper()[1]
                
                # TODO: I don't know why the author wrote the code as below, the 
                # player_seat won't work if we use self._normalize_position. This 
                # might be a tricky part, and we need to review it later.
                #player_seat = self._normalize_position(self.player_position, discard_tags.index(player_sign))
                
                # Temporally solution to modify the player_seat
                player_seat = (discard_tags.index(player_sign) + self.player_position)%4

                # Whenever a tile is discarded, the other players will check their
                # hands to see if they could call meld (steal the tile).
                try:
                    next_tag = self.round_content[n+1]
                except:
                    next_tag = ""
                for i in range(4):
                    if i!=player_seat:
                        is_kamicha_discard = (i-1==player_seat)
                        comb = table.players[i].get_possible_melds(tile, is_kamicha_discard)
                        table.players[i].possible_melds = comb   
                        if len(comb)>0: # he can call melds now
                              #print("\nplayer %s closed hand: %s"%(i,[t//4 for t in table.players[i].closed_hand]))
                              print("player %s closed hand: %s"%(i,TilesConverter.to_one_line_string(table.players[i].closed_hand)))
                              print("Tag: %s\nNext tag: %s"%(tag, next_tag))
                              if '<N who=' in next_tag:
                                  meld = self.decoder.parse_meld(next_tag)
                                  # TODO: Obviously the player seat is confusing again. I can't
                                  # get this part fixed. So let's just manually fix it for the moment.
                                  meld.from_who = player_seat
                                  print("%s meld from %s: get %s to form %s(%s): %s\n"%(meld.who, meld.from_who, meld.called_tile, meld.type, meld.opened, meld.tiles))
                                  assert meld.called_tile==tile, "Called tile NOT equal to discarded tile!"
                                  meld_tiles = [t//4 for t in meld.tiles]
                              else:
                                  meld_tiles = []
                              # This part is to extract the features for stealing
                              features = self.extract_features.get_stealing_features(table)
                              # write data to log file
                              # Note: since the first info is table info, player info starts
                              # from 1, so we use (i+1) here.
                              self.feature_to_logger(features, i+1, meld_tiles, tile, comb)
                
                if player_seat == 0:
                    table.players[player_seat].discard_tile(DiscardOption(table.players[player_seat], tile // 4, 0, [], 0))
                else:
                    # ADD: we must take care of ALL players
                    tile_to_discard = tile
            
                    is_tsumogiri = tile_to_discard == table.players[player_seat].last_draw
                    # it is important to use table method,
                    # to recalculate revealed tiles and etc.
                    table.add_discarded_tile(player_seat, tile_to_discard, is_tsumogiri)
                    
                    table.players[player_seat].tiles.remove(tile_to_discard)
            
                # This part is to extract the features we need.    
#                player_seat, features = self.execute_extraction(tag, table)
#                raw_records.append((player_seat, features))

            if '<N who=' in tag:
                meld = self.decoder.parse_meld(tag)
                #player_seat = self._normalize_position(self.player_position, meld.who)
                # Again, we change the player_seat here
                player_seat = (meld.who + self.player_position) % 4
                table.add_called_meld(player_seat, meld)

                #if player_seat == 0:
                # CHG: we need to handle ALL players here    
                if True:
                    # 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.players[player_seat].draw_tile(meld.called_tile)

            # [Joseph]: For old records, there is no 'step' in the tag. We need to take care of it
            if ('<REACH' in tag and 'step="1"' in tag) or ('<REACH' in tag and 'step' not in tag):
                
                # [Joseph] I don't know why he used _normalize_position here, from which I got incorrect
                # positions. Therefore I simply use the parsed result instead.
#                who_called_riichi = self._normalize_position(self.player_position,
#                                                             self.decoder.parse_who_called_riichi(tag))
                who_called_riichi = self.decoder.parse_who_called_riichi(tag)
                
                table.add_called_riichi(who_called_riichi)
                
#                print("\n")
#                print("{} Riichi!".format(who_called_riichi))
#                print("self.player_position: {}".format(self.player_position))
#                print("parse: {}".format(self.decoder.parse_who_called_riichi(tag)))
#                print("\n")
                # We need to delete those unnecessary records for one player mahjong
#                for (player_seat, features) in raw_records:
#                    if player_seat==(who_called_riichi+1):
#                        clean_records.append((player_seat,features))
                
                skip = True # skip all remaining tags untill <INIT>
        
        # Write the records to log        
#        for player_seat, features in clean_records:
#            self.feature_to_logger(features, player_seat)
              
            # This part is to extract the features that will be used to train
            # our model.
#            try:
#                next_tag = self.round_content[n+1]
#            except IndexError:
#                next_tag = ""
#            if '<AGARI' in next_tag:           
#                who_regex = re.compile("who=\"\d+\"")
#                fromWho_regex = re.compile("fromWho=\"\d+\"")           
#                sc_regex = "sc=\"[+-]?\d+,[+-]?\d+,[+-]?\d+,[+-]?\d+,[+-]?\d+,[+-]?\d+,[+-]?\d+,[+-]?\d+\""
#                score_regex = re.compile(sc_regex)
#                machi_regex = re.compile("machi=\"\d+\"")
#                
#                who = int(who_regex.search(next_tag).group(0).replace('"','').split("=")[1])
#                fromWho = int(fromWho_regex.search(next_tag).group(0).replace('"','').split("=")[1])
#                scores = [float(s) for s in score_regex.search(next_tag).group(0).replace('"','').split("=")[1].split(",")]              
#                machi = int(machi_regex.search(next_tag).group(0).replace('"','').split("=")[1])
#                score = scores[fromWho*2+1] 
#                player_seat, features = self.execute_extraction(tag, table)
#                
#                if (who!=fromWho): # tsumo is not a valid sample for our training.                    
#                    if (features is not None) and (player_seat is not None) and (score<0):
#                        # The first element before ";" is table_info, therefor player_info starts
#                        # from index 1, and we put who+1 here.
#                        self.feature_to_logger(features, who+1, machi//4, score)
#                        score = 1
#                    #print("\n{}\n{}\n".format(tag,table.players[who].tiles))
#            else:
#                player_seat, features = self.execute_extraction(tag, table)
         

                   
        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 feature_to_logger(self, features, player_seat, meld_tiles, tile, comb):
        """
        param features:
        """
        features_list = features.split(";")
        assert len(features_list)==6, "<D> Features format incorrect!"
        table_info = features_list[0]
        player_info = features_list[player_seat]
        
        d1, d2 = table_info, player_info
        
        (table_count_of_honba_sticks,
         table_count_of_remaining_tiles,
         table_count_of_riichi_sticks,
         table_round_number,
         table_round_wind,
         table_turns,
         table_dealer_seat,
         table_dora_indicators,
         table_dora_tiles,
         table_revealed_tiles) = ast.literal_eval(d1)
        
        (player_winning_tiles,                   
         player_discarded_tiles,
         player_closed_hand,
         player_dealer_seat,
         player_in_riichi,
         player_is_dealer,
         player_is_open_hand,  
         player_last_draw,            
         player_melds,                
         player_name,
         player_position,
         player_rank,
         player_scores,
         player_seat,
         player_uma) = ast.literal_eval(d2)
        
#        if player_discarded_tiles:
#            player_discard = player_discarded_tiles[-1]
#        else:
#            player_discard = -1
                
        #logger2.info(table_info + ";" + player_info + ";" + str(machi_34) + ";" + str(score))    
#        logger2.info(str(player_last_draw) + ";" + 
#                     str(player_discard) + ";" + 
#                     str(player_closed_hand) + ";" +
#                     str(table_revealed_tiles)
#                     )

        logger2.info(str(meld_tiles) + ";" + 
                     str(tile) + ";" +  # This info might not be used, but let's keep it for checking purpose 
                     str(comb) + ";" +  # This info might not be used, but let's just keep it for checking purpose
                     str(player_closed_hand) + ";" +
                     str(player_melds) + ";" +
                     str(table_revealed_tiles) + ";" + 
                     str(table_turns)
                     )    
        
    def execute_extraction(self, table):
        features = self.extract_features.get_stealing_features(table)             
        return features
     
    
#    def execute_extraction(self, tag, score, table, to_logger):
#        if '<D' in tag:
#            #features = self.extract_features.get_is_waiting_features(table)
#            #features = self.extract_features.get_waiting_tiles_features(table)
#            features = self.extract_features.get_scores_features(score, table)
#            if (features is not None) and to_logger:
#                features_list = features.split(";")
#                assert len(features_list)==6, "<D> Features format incorrect!"
#                score_info = features_list[0]
#                player_info = features_list[1]
#                logger2.info(score_info + ";" + player_info)
#                
#        if '<E' in tag:
#            #features = self.extract_features.get_is_waiting_features(table)
#            #features = self.extract_features.get_waiting_tiles_features(table)
#            features = self.extract_features.get_scores_features(score, table)
#            if (features is not None) and to_logger:
#                features_list = features.split(";")
#                assert len(features_list)==6, "<E> Features format incorrect!"
#                score_info = features_list[0]
#                player_info = features_list[2]
#                logger2.info(score_info + ";" + player_info)
#                
#        if '<F' in tag:
#            #features = self.extract_features.get_is_waiting_features(table)
#            #features = self.extract_features.get_waiting_tiles_features(table)
#            features = self.extract_features.get_scores_features(score, table)
#            if (features is not None) and to_logger:
#                features_list = features.split(";")
#                assert len(features_list)==6, "<F> Features format incorrect!"
#                score_info = features_list[0]
#                player_info = features_list[3]
#                logger2.info(score_info + ";" + player_info)
#               
#        if '<G' in tag:
#            #features = self.extract_features.get_is_waiting_features(table)
#            #features = self.extract_features.get_waiting_tiles_features(table)
#            features = self.extract_features.get_scores_features(score, table)
#            if (features is not None) and to_logger:
#                features_list = features.split(";")
#                assert len(features_list)==6, "<G> Features format incorrect!"
#                score_info = features_list[0]
#                player_info = features_list[4]
#                logger2.info(score_info + ";" + player_info)
                
       
            
    def reproduce_all(self, dry_run=False):
        for r in self.rounds:
            self.round_content = r
            self.reproduce(dry_run=dry_run)
            print("--------------------------------------\n")

    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_number = '', 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_number = int(item[1])
        return log_id, player, round_number

    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:]