예제 #1
0
    def __init__(self, player):
        super(MainAI, self).__init__(player)

        self.agari = Agari()
        self.shanten = Shanten()
        self.defence = DefenceHandler(player)
        self.hand_divider = HandDivider()
        self.finished_hand = FinishedHand()
        self.previous_shanten = 7
        self.current_strategy = None
        self.waiting = []
        self.in_defence = False
예제 #2
0
class GreedyAII(BaseAI):
    version = '0.0.1'

    def __init__(self, table, player):
        super(GreedyAII, self).__init__(table, player)
        self.shanten = Shanten()

    def discard_tile(self):
        gd_player = GreedyPlayer("Me")
        h = Hand(TilesConverter.to_one_line_string(self.player.tiles))
        t = gd_player.select_best_tile(h)

        tiles = TilesConverter.to_34_array(self.player.tiles)
        shanten = self.shanten.calculate_shanten(tiles)
        if shanten == 0:
            self.player.in_tempai = True

        types = ['m', 'p', 's', 'z']
        if h.test_win():
            return Shanten.AGARI_STATE
        else:
            tile_in_hand = TilesConverter.find_34_tile_in_136_array(
                t.get_number() + (t.get_type() >> 4) * 9 - 1,
                self.player.tiles)
            return tile_in_hand
    def test_shanten_number_and_open_sets(self):
        shanten = Shanten()

        tiles = self._string_to_34_array(sou='44467778', pin='222567')
        open_sets = []
        self.assertEqual(
            shanten.calculate_shanten(tiles, open_sets_34=open_sets),
            Shanten.AGARI_STATE)

        open_sets = [self._string_to_open_34_set(sou='777')]
        self.assertEqual(
            shanten.calculate_shanten(tiles, open_sets_34=open_sets), 0)

        tiles = self._string_to_34_array(sou='23455567', pin='222', man='345')
        open_sets = [
            self._string_to_open_34_set(man='345'),
            self._string_to_open_34_set(sou='555')
        ]
        self.assertEqual(
            shanten.calculate_shanten(tiles, open_sets_34=open_sets), 0)
예제 #4
0
    def test_shanten_number_and_kokushi_musou(self):
        shanten = Shanten()

        tiles = self._string_to_136_array(sou='19',
                                          pin='19',
                                          man='19',
                                          honors='12345677')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         Shanten.AGARI_STATE)

        tiles = self._string_to_136_array(sou='129',
                                          pin='19',
                                          man='19',
                                          honors='1234567')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         0)

        tiles = self._string_to_136_array(sou='129',
                                          pin='129',
                                          man='19',
                                          honors='123456')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         1)

        tiles = self._string_to_136_array(sou='129',
                                          pin='129',
                                          man='129',
                                          honors='12345')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         2)
예제 #5
0
    def __init__(self, player):
        super(MainAI, self).__init__(player)

        # we don't `defense` since our algorithm will defense by evaluating "trade-off" value.
        self.defence = DefenceHandler(player)
        # strategy_type is set to None. We use this BaseStrategy to call meld.
        self.current_strategy = BaseStrategy(None, player)
        # shantan
        self.shanten = Shanten()

        # We load the classifiers and regressors here
        self.clf_is_waiting = pickle.load(
            open(abs_data_path + "/train_model/trained_models/is_waiting.sav",
                 "rb"))
        self.clf_waiting_tile = []
        for n in range(34):
            clf = pickle.load(
                open(
                    abs_data_path +
                    "/train_model/trained_models/waiting_tile_{}.sav".format(
                        n), "rb"))
            self.clf_waiting_tile.append(clf)
        self.rgrs_scores = pickle.load(
            open(abs_data_path + "/train_model/trained_models/scores.sav",
                 "rb"))
        self.rgrs_wfw_scores = pickle.load(
            open(abs_data_path + "/train_model/trained_models/wfw_scores.sav",
                 "rb"))

        # We also load the scalers for regressors
        self.scaler_scores = pickle.load(
            open(
                abs_data_path +
                "/train_model/trained_models/scaler_scores.sav", "rb"))
        self.scaler_wfw_scores = pickle.load(
            open(
                abs_data_path +
                "/train_model/trained_models/scaler_wfw_scores.sav", "rb"))
    def test_shanten_number_and_chitoitsu(self):
        shanten = Shanten()

        tiles = self._string_to_34_array(sou='114477', pin='114477', man='77')
        self.assertEqual(shanten.calculate_shanten(tiles), Shanten.AGARI_STATE)

        tiles = self._string_to_34_array(sou='114477', pin='114477', man='76')
        self.assertEqual(shanten.calculate_shanten(tiles), 0)

        tiles = self._string_to_34_array(sou='114477', pin='114479', man='76')
        self.assertEqual(shanten.calculate_shanten(tiles), 1)

        tiles = self._string_to_34_array(sou='114477',
                                         pin='14479',
                                         man='76',
                                         honors='1')
        self.assertEqual(shanten.calculate_shanten(tiles), 2)
예제 #7
0
class MainAI(BaseAI):
    version = '0.0.6'

    agari = None
    shanten = None
    defence = None

    def __init__(self, table, player):
        super(MainAI, self).__init__(table, player)

        self.agari = Agari()
        self.shanten = Shanten()
        self.defence = Defence(table)

    def discard_tile(self):
        results, shanten = self.calculate_outs()

        if shanten == 0:
            self.player.in_tempai = True

        # we are win!
        if shanten == Shanten.AGARI_STATE:
            return Shanten.AGARI_STATE

        # Disable defence for now
        # if self.defence.go_to_defence_mode():
        #     self.player.in_tempai = False
        #     tile_in_hand = self.defence.calculate_safe_tile_against_riichi()
        #     if we wasn't able to find a safe tile, let's discard a random one
        #     if not tile_in_hand:
        #         tile_in_hand = self.player.tiles[random.randrange(len(self.player.tiles) - 1)]
        # else:
        #     tile34 = results[0]['discard']
        #     tile_in_hand = TilesConverter.find_34_tile_in_136_array(tile34, self.player.tiles)

        tile34 = results[0]['discard']
        tile_in_hand = TilesConverter.find_34_tile_in_136_array(
            tile34, self.player.tiles)

        return tile_in_hand

    def calculate_outs(self):
        tiles = TilesConverter.to_34_array(self.player.tiles)

        shanten = self.shanten.calculate_shanten(tiles)
        # win
        if shanten == Shanten.AGARI_STATE:
            return [], shanten

        raw_data = {}
        for i in range(0, 34):
            if not tiles[i]:
                continue

            tiles[i] -= 1

            raw_data[i] = []
            for j in range(0, 34):
                if i == j or tiles[j] >= 4:
                    continue

                tiles[j] += 1
                if self.shanten.calculate_shanten(tiles) == shanten - 1:
                    raw_data[i].append(j)
                tiles[j] -= 1

            tiles[i] += 1

            if raw_data[i]:
                raw_data[i] = {
                    'tile': i,
                    'tiles_count': self.count_tiles(raw_data[i], tiles),
                    'waiting': raw_data[i]
                }

        results = []
        tiles = TilesConverter.to_34_array(self.player.tiles)
        for tile in range(0, len(tiles)):
            if tile in raw_data and raw_data[tile] and raw_data[tile][
                    'tiles_count']:
                item = raw_data[tile]

                waiting = []

                for item2 in item['waiting']:
                    waiting.append(item2)

                results.append({
                    'discard': item['tile'],
                    'waiting': waiting,
                    'tiles_count': item['tiles_count']
                })

        # if we have character and honor candidates to discard with same tiles count,
        # we need to discard honor tile first
        results = sorted(results,
                         key=lambda x: (x['tiles_count'], x['discard']),
                         reverse=True)

        return results, shanten

    def count_tiles(self, raw_data, tiles):
        n = 0
        for i in range(0, len(raw_data)):
            n += 4 - tiles[raw_data[i]]
        return n
예제 #8
0
    def __init__(self, table, player):
        super(MainAI, self).__init__(table, player)

        self.agari = Agari()
        self.shanten = Shanten()
        self.defence = Defence(table)
예제 #9
0
 def __init__(self, table, player):
     super(GreedyAII, self).__init__(table, player)
     self.shanten = Shanten()
예제 #10
0
class MainAI(BaseAI):
    """
    AI that is based on Monte Carlo simulation and opponent model.
    """

    version = 'random'

    def __init__(self, player):
        super(MainAI, self).__init__(player)

        # we don't `defense` since our algorithm will defense by evaluating "trade-off" value.
        self.defence = DefenceHandler(player)
        # strategy_type is set to None. We use this BaseStrategy to call meld.
        self.current_strategy = BaseStrategy(None, player)
        # shantan
        self.shanten = Shanten()

        # We load the classifiers and regressors here
        self.clf_is_waiting = pickle.load(
            open(abs_data_path + "/train_model/trained_models/is_waiting.sav",
                 "rb"))
        self.clf_waiting_tile = []
        for n in range(34):
            clf = pickle.load(
                open(
                    abs_data_path +
                    "/train_model/trained_models/waiting_tile_{}.sav".format(
                        n), "rb"))
            self.clf_waiting_tile.append(clf)
        self.rgrs_scores = pickle.load(
            open(abs_data_path + "/train_model/trained_models/scores.sav",
                 "rb"))
        self.rgrs_wfw_scores = pickle.load(
            open(abs_data_path + "/train_model/trained_models/wfw_scores.sav",
                 "rb"))

        # We also load the scalers for regressors
        self.scaler_scores = pickle.load(
            open(
                abs_data_path +
                "/train_model/trained_models/scaler_scores.sav", "rb"))
        self.scaler_wfw_scores = pickle.load(
            open(
                abs_data_path +
                "/train_model/trained_models/scaler_wfw_scores.sav", "rb"))

    def erase_state(self):
        self.current_strategy = None
        self.in_defence = False

    def reset_melds(self):
        """This setting is used to record the discarded tiles immediately
        after calling melds. Whenever a `NEXTREADY` command is sent, this function
        should be called.
        """
        self.meld_sets = [[], [], [], []]  # four players
        self.meld_discarded_tiles = [[], [], [], []]  # four players

    def determine_strategy(self):
        return False

    def can_call_kan(self, tile, open_kan):
        """
        Method will decide should we call a kan,
        or upgrade pon to kan
        :param tile: 136 tile format
        :param open_kan: boolean
        :return: kan type
        """
        # we don't need to add dora for other players
        if self.player.ai.in_defence:
            return None

        if open_kan:
            # we don't want to start open our hand from called kan
            if not self.player.is_open_hand:
                return None

            # there is no sense to call open kan when we are not in tempai
            if not self.player.in_tempai:
                return None

            # we have a bad wait, rinshan chance is low
            if len(self.waiting) < 2:
                return None

        tile_34 = tile // 4
        tiles_34 = TilesConverter.to_34_array(self.player.tiles)
        closed_hand_34 = TilesConverter.to_34_array(self.player.closed_hand)
        pon_melds = [x for x in self.player.open_hand_34_tiles if is_pon(x)]

        # let's check can we upgrade opened pon to the kan
        if pon_melds:
            for meld in pon_melds:
                # tile is equal to our already opened pon,
                # so let's call chankan!
                if tile_34 in meld:
                    return Meld.CHANKAN

        count_of_needed_tiles = 4
        # for open kan 3 tiles is enough to call a kan
        if open_kan:
            count_of_needed_tiles = 3

        # we have 3 tiles in our hand,
        # so we can try to call closed meld
        if closed_hand_34[tile_34] == count_of_needed_tiles:
            if not open_kan:
                # to correctly count shanten in the hand
                # we had do subtract drown tile
                tiles_34[tile_34] -= 1

            melds = self.player.open_hand_34_tiles
            previous_shanten = self.shanten.calculate_shanten(tiles_34, melds)

            melds += [[tile_34, tile_34, tile_34]]
            new_shanten = self.shanten.calculate_shanten(tiles_34, melds)

            # called kan will not ruin our hand
            if new_shanten <= previous_shanten:
                return Meld.KAN

        return None

    def try_to_call_meld(self, tile, is_kamicha_discard):
        if not self.current_strategy:
            return None, None

        return self.current_strategy.try_to_call_meld(tile, is_kamicha_discard)

#    @deprecated
#    def discard_tile_randomly(self):
#        tile_to_discard = random.randrange(len(self.player.tiles) - 1)
#        tile_to_discard = self.player.tiles[tile_to_discard]
#        print("opponnet model discards: {}\n".format(tile_to_discard))
#        return tile_to_discard

    def discard_tile(self):
        tile_to_discard = random.randrange(len(self.player.tiles) - 1)
        tile_to_discard = self.player.tiles[tile_to_discard]

        print("\n")
        scores = []
        for tile in self.player.tiles:
            score = 0
            # TODO: the value of Sim(tile) should be determined by Monte-Carlo
            # simulation.
            sim = 0

            # Not Losing Probability
            NLP = 1
            for p in range(1, 4):
                # Losing Probability: LP(p,tile)
                LP = self.prob_is_waiting(p) * self.prob_winning_tile(
                    p, tile // 4)  # tile in 34 format
                # Accumulated Not Losing Probability
                NLP *= (1 - LP)
                # Hand Score (by discarding a winning tile): HS(p,tile)
                HS = self.hand_score(p, tile // 4)
                EL = LP * HS
                score -= EL
            score += sim * NLP
            scores.append(score)
        # Find out the highest score choice
        n = scores.index(max(scores))
        tile_to_discard = self.player.tiles[n]
        print("hands: {}".format(self.player.tiles))
        print("scores: {}".format(scores))
        print("opponnet model discards: {}\n".format(tile_to_discard))
        return tile_to_discard

    def get_table_info(self):
        """ Get table information
        return: table information
        """
        table = self.player.table
        dora_tiles = [d for d in range(136) if table.is_dora(d)]
        table_turns = min(
            [len(table.players[m].discards) + 1 for m in range(4)])
        table_info = "{},{},{},{},{},{},{},{},{},{}".format(
            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, dora_tiles,
            table.revealed_tiles)
        return table_info

    def parse_table_info(self, table_info):
        """ Parse the table info
        param table_info: str, information of table contained in a string
        return: tuple, numerical data of table information
        """
        (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(table_info)

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

    def get_player_info(self, p):
        """ Get player information
        param p: int (1-3), player index of the opponent
        return: player information
        """
        table = self.player.table
        player_info = ""
        player = table.players[p]
        # Discarded tiles can be seen by everybody
        discarded_tiles = [(d.value, 1) if d.is_tsumogiri else (d.value, 0)
                           for d in player.discards]
        # winning tiles of opponents are invisible, we just keep an empty 34 element array here
        winning_tiles = [0 for _ in range(34)]
        # Meld sets (both open and concealed) are also visible to everybody
        meld_sets = [mt.tiles for mt in player.melds]
        meld_open = [mt.opened for mt in player.melds]
        if meld_sets != self.meld_sets[p]:
            # It might happen that one player will discard more than one tile
            # in a round, simply b/c he pon or kan more than once.
            n = len(meld_sets) - len(self.meld_sets[p])
            self.meld_discarded_tiles[p].extend(discarded_tiles[-n:])
            # We need to update the self.meld_sets in order to compare with new
            # information later.
            self.meld_sets[p] = meld_sets
        #print("!{}, {}, {}, {}".format(meld_sets, meld_open, self.meld_discarded_tiles[p], self.meld_sets[p]))
        melds = [(meld_sets[k], 1 if meld_open[k] else 0,
                  self.meld_discarded_tiles[p][k])
                 for k in range(len(meld_sets))]
        string_to_save = "{},{},{},{},{},{},{},{},{},{},{},{},{}".format(
            winning_tiles,
            discarded_tiles,
            player.dealer_seat,
            1 if player.in_riichi else 0,
            1 if player.is_dealer else 0,
            1 if player.is_open_hand else 0,
            melds,
            -1,  # we don't need player's name; player.name if player.name else -1,
            player.position if player.position else -1,
            -1,  # we don't need player's rank; player.rank if player.rank else -1,
            player.scores if player.scores else -1,
            player.seat if player.seat else -1,
            player.uma if player.uma else -1
            #player.closed_hand,     # this info is invisible
            #player.in_defence_mode, # this info is invisible
            #player.in_tempai,       # this info is invisible
            #player.last_draw,       # this info is invisible
            #player.tiles,           # this info is invisible
        )
        player_info += string_to_save
        return player_info

    def parse_player_info(self, p, player_info):
        """Parse the player info
        param p: int (1-3), player index of the opponent
        param player_info: str, information of player contained in a string
        return: tuple, numerical data of player information
        """
        (
            player_winning_tiles,
            player_discarded_tiles,
            player_dealer_seat,
            player_in_riichi,
            player_is_dealer,
            player_is_open_hand,
            player_melds,
            player_name,  # player_name has been replaced with -1
            player_position,
            player_rank,  # player_rank has been replaced with -1
            player_scores,
            player_seat,
            player_uma) = ast.literal_eval(player_info)

        # We need p (player index) here b/c we don't want to lose any information
        # of the player. Although for the moment we might not use it, but perhaps
        # a later model will need information such as player rank et cetera.
        table = self.player.table
        player = table.players[p]
        # replace back the player name, although we may not need it
        player_name = player.name
        # replace back the player rank, although we may not need it
        player_rank = player.rank

        return (
            player_winning_tiles,
            player_discarded_tiles,
            player_dealer_seat,
            player_in_riichi,
            player_is_dealer,
            player_is_open_hand,
            player_melds,
            player_name,  # player_name has been replaced with -1
            player_position,
            player_rank,  # player_rank has been replaced with -1
            player_scores,
            player_seat,
            player_uma)

    def prob_is_waiting(self, p):
        """The probability that an opponent p is waiting.
        param p: int (1-3), index of opponent player
        return: probability of opponent p being waiting
        """
        # Get table information
        table_info = self.get_table_info()

        # Get player information
        player_info = self.get_player_info(p)

        # Parse the table 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) = self.parse_table_info(table_info)

        # Parse the player info
        (
            player_winning_tiles,
            player_discarded_tiles,
            player_dealer_seat,
            player_in_riichi,
            player_is_dealer,
            player_is_open_hand,
            player_melds,
            player_name,  # player_name has been replaced with -1
            player_position,
            player_rank,  # player_rank has been replaced with -1
            player_scores,
            player_seat,
            player_uma) = self.parse_player_info(p, player_info)

        features = gen_is_waiting_features(
            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, player_winning_tiles,
            player_discarded_tiles, player_dealer_seat, player_in_riichi,
            player_is_dealer, player_is_open_hand, player_melds, player_name,
            player_position, player_rank, player_scores, player_seat,
            player_uma)
        f1, f2, f3, f4, f5, f6, f7, f8, f9 = features
        opponent_info = [f1] + [f2] + [f3] + [f4] + [f5] + f6 + f7 + f8 + f9
        opponent_info = np.array([opponent_info])

        # Probability of `p` is waiting
        clf = self.clf_is_waiting
        prob_of_is_waiting = clf.predict_proba(opponent_info)[0][1]
        return prob_of_is_waiting

#    def prob_is_waiting(self, p):
#        """The probability that an opponent p is waiting.
#        param p: int (1-3), index of opponent player
#        return: probability of opponent p being waiting
#        """
#        # Get table information
#        table = self.player.table
#        dora_tiles = [d for d in range(136) if table.is_dora(d) ]
#        table_turns = min([len(table.players[m].discards)+1 for m in range(4)])
#        table_info = "{},{},{},{},{},{},{},{},{},{}".format(
#                      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,
#                      dora_tiles,
#                      table.revealed_tiles
#                      )
#
#        # Get player information
#        player_info = ""
#        player = table.players[p]
#        # Discarded tiles can be seen by everybody
#        discarded_tiles = [(d.value,1) if d.is_tsumogiri else (d.value,0) for d in player.discards]
#        # winning tiles of opponents are invisible, we just keep an empty 34 element array here
#        winning_tiles = [0 for _ in range(34)]
#        # Meld sets (both open and concealed) are also visible to everybody
#        meld_sets = [mt.tiles for mt in player.melds]
#        meld_open = [mt.opened for mt in player.melds]
#        if meld_sets != self.meld_sets[p]:
#            # It might happen that one player will discard more than one tile
#            # in a round, simply b/c he pon or kan more than once.
#            n = len(meld_sets) - len(self.meld_sets[p])
#            self.meld_discarded_tiles[p].extend(discarded_tiles[-n:])
#            # We need to update the self.meld_sets in order to compare with new
#            # information later.
#            self.meld_sets[p] = meld_sets
#        #print("!{}, {}, {}, {}".format(meld_sets, meld_open, self.meld_discarded_tiles[p], self.meld_sets[p]))
#        melds = [(meld_sets[k], 1 if meld_open[k] else 0, self.meld_discarded_tiles[p][k]) for k in range(len(meld_sets))]
#        string_to_save = "{},{},{},{},{},{},{},{},{},{},{},{},{}".format(
#                winning_tiles,
#                discarded_tiles,
#                player.dealer_seat,
#                1 if player.in_riichi else 0,
#                1 if player.is_dealer else 0,
#                1 if player.is_open_hand else 0,
#                melds,
#                -1, # we don't need player's name; player.name if player.name else -1,
#                player.position if player.position else -1,
#                -1, # we don't need player's rank; player.rank if player.rank else -1,
#                player.scores if player.scores else -1,
#                player.seat if player.seat else -1,
#                player.uma if player.uma else -1
#                #player.closed_hand,     # this info is invisible
#                #player.in_defence_mode, # this info is invisible
#                #player.in_tempai,       # this info is invisible
#                #player.last_draw,       # this info is invisible
#                #player.tiles,           # this info is invisible
#        )
#        player_info += string_to_save
#
#        # Parse the table 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(table_info)
#
#        # Parse the player info
#        (player_winning_tiles,
#         player_discarded_tiles,
#         player_dealer_seat,
#         player_in_riichi,
#         player_is_dealer,
#         player_is_open_hand,
#         player_melds,
#         player_name, # player_name has been replaced with -1
#         player_position,
#         player_rank, # player_rank has been replaced with -1
#         player_scores,
#         player_seat,
#         player_uma) = ast.literal_eval(player_info)
#
#        # replace back the player name, although we may not need it
#        player_name = player.name
#        # replace back the player rank, although we may not need it
#        player_rank = player.rank
#
#        features = gen_is_waiting_features(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,
#                                            player_winning_tiles,
#                                            player_discarded_tiles,
#                                            player_dealer_seat,
#                                            player_in_riichi,
#                                            player_is_dealer,
#                                            player_is_open_hand,
#                                            player_melds,
#                                            player_name,
#                                            player_position,
#                                            player_rank,
#                                            player_scores,
#                                            player_seat,
#                                            player_uma)
#        f1, f2, f3, f4, f5, f6, f7, f8, f9 = features
#        opponent_info = [f1]+[f2]+[f3]+[f4]+[f5]+f6+f7+f8+f9
#        opponent_info = np.array([opponent_info])
#
#        # Probability of p is waiting
#        clf = self.clf_is_waiting
#        prob_of_is_waiting = clf.predict_proba(opponent_info)[0][1]
#        return prob_of_is_waiting

    def prob_winning_tile(self, p, tile):
        """The probability that an opponent `p` is waiting for `tile`.
        param p: int (1-3), index of opponent player
        param tile: int (0-33), index of the tile kind
        return: probability of tile being waiting tile for opponent p
        """
        # Get table information
        table_info = self.get_table_info()

        # Get player information
        player_info = self.get_player_info(p)

        # Parse the table 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) = self.parse_table_info(table_info)

        # Parse the player info
        (
            player_winning_tiles,
            player_discarded_tiles,
            player_dealer_seat,
            player_in_riichi,
            player_is_dealer,
            player_is_open_hand,
            player_melds,
            player_name,  # player_name has been replaced with -1
            player_position,
            player_rank,  # player_rank has been replaced with -1
            player_scores,
            player_seat,
            player_uma) = self.parse_player_info(p, player_info)

        features = gen_waiting_tiles_features(
            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, player_winning_tiles,
            player_discarded_tiles, player_dealer_seat, player_in_riichi,
            player_is_dealer, player_is_open_hand, player_melds, player_name,
            player_position, player_rank, player_scores, player_seat,
            player_uma)
        f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11 = features
        opponent_info = [f1] + [f2] + [f3] + [f4] + [
            f5
        ] + f6 + f7 + f8 + f9 + f10 + f11
        opponent_info = np.array([opponent_info])

        # Probability of `tile` is waiting tile for `p`
        clf = self.clf_waiting_tile[tile]  # load (tile-th) classifier
        prob_of_winning_tile = clf.predict_proba(opponent_info)[0][1]
        return prob_of_winning_tile

    # TODO: this function has not been finished, as I need to go back to modify
    # the HS (for HS_WFW there is no need) training model to add back
    # `discarded tile` as one of the features.
    def hand_score(self, p, tile):
        """Use our trained model to predict loss if discarding a winning tile 
        `tile` to the opponent `p`.
        param p: int (1-3), player index of the opponent
        param tile: int (0-33), tile index in 34 format
        return: hand score lost to the opponent
        """
        # Get table information
        table_info = self.get_table_info()

        # Get player information
        player_info = self.get_player_info(p)

        # Parse the table 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) = self.parse_table_info(table_info)

        # Parse the player info
        (
            player_winning_tiles,
            player_discarded_tiles,
            player_dealer_seat,
            player_in_riichi,
            player_is_dealer,
            player_is_open_hand,
            player_melds,
            player_name,  # player_name has been replaced with -1
            player_position,
            player_rank,  # player_rank has been replaced with -1
            player_scores,
            player_seat,
            player_uma) = self.parse_player_info(p, player_info)

        features = gen_scores_features(
            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, player_winning_tiles,
            player_discarded_tiles, player_dealer_seat, player_in_riichi,
            player_is_dealer, player_is_open_hand, player_melds, player_name,
            player_position, player_rank, player_scores, player_seat,
            player_uma)
        f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13 = features
        f14 = tile
        opponent_info = [f1] + [f2] + [f3] + [f4] + [f5] + f6 + f7 + [f8] + [
            f9
        ] + [f10] + [f11] + [f12] + [f13] + [f14]
        opponent_info = np.array([opponent_info])

        # Predicted hand score
        rgrs = self.rgrs_scores
        scaler = self.scaler_scores
        scaled_opponent_info = scaler.transform(opponent_info)
        log_HS = rgrs.predict(scaled_opponent_info)[0]
        # HS = np.exp(log_HS)
        return log_HS
예제 #11
0
    def test_shanten_number(self):
        shanten = Shanten()

        tiles = self._string_to_136_array(sou='111234567', pin='11', man='567')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         Shanten.AGARI_STATE)

        tiles = self._string_to_136_array(sou='111345677', pin='11', man='567')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         0)

        tiles = self._string_to_136_array(sou='111345677', pin='15', man='567')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         1)

        tiles = self._string_to_136_array(sou='11134567', pin='15', man='1578')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         2)

        tiles = self._string_to_136_array(sou='113456', pin='1358', man='1358')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         3)

        tiles = self._string_to_136_array(sou='1589',
                                          pin='13588',
                                          man='1358',
                                          honors='1')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         4)

        tiles = self._string_to_136_array(sou='159',
                                          pin='13588',
                                          man='1358',
                                          honors='12')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         5)

        tiles = self._string_to_136_array(sou='1589',
                                          pin='258',
                                          man='1358',
                                          honors='123')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         6)

        tiles = self._string_to_136_array(sou='11123456788999')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         Shanten.AGARI_STATE)

        tiles = self._string_to_136_array(sou='11122245679999')
        self.assertEqual(shanten.calculate_shanten(self._to_34_array(tiles)),
                         0)
예제 #12
0
class MainAI(BaseAI):
    version = '0.2.7'

    agari = None
    shanten = None
    defence = None
    hand_divider = None
    finished_hand = None

    previous_shanten = 7
    in_defence = False
    waiting = None

    current_strategy = None

    def __init__(self, player):
        super(MainAI, self).__init__(player)

        self.agari = Agari()
        self.shanten = Shanten()
        self.defence = DefenceHandler(player)
        self.hand_divider = HandDivider()
        self.finished_hand = FinishedHand()
        self.previous_shanten = 7
        self.current_strategy = None
        self.waiting = []
        self.in_defence = False

    def erase_state(self):
        self.current_strategy = None
        self.in_defence = False

    def discard_tile(self):
        results, shanten = self.calculate_outs(self.player.tiles,
                                               self.player.closed_hand,
                                               self.player.open_hand_34_tiles)

        selected_tile = self.process_discard_options_and_select_tile_to_discard(
            results, shanten)

        # bot think that there is a threat on the table
        # and better to fold
        # if we can't find safe tiles, let's continue to build our hand
        if self.defence.should_go_to_defence_mode(selected_tile):
            if not self.in_defence:
                logger.info('We decided to fold against other players')
                self.in_defence = True

            defence_tile = self.defence.try_to_find_safe_tile_to_discard(
                results)
            if defence_tile:
                return self.process_discard_option(defence_tile,
                                                   self.player.closed_hand)
        else:
            self.in_defence = False

        return self.process_discard_option(selected_tile,
                                           self.player.closed_hand)

    def process_discard_options_and_select_tile_to_discard(
            self, results, shanten):
        tiles_34 = TilesConverter.to_34_array(self.player.tiles)

        # we had to update tiles value there
        # because it is related with shanten number
        for result in results:
            result.tiles_count = self.count_tiles(result.waiting, tiles_34)
            result.calculate_value(shanten)

        # current strategy can affect on our discard options
        # so, don't use strategy specific choices for calling riichi
        if self.current_strategy:
            results = self.current_strategy.determine_what_to_discard(
                self.player.closed_hand, results, shanten, False, None)

        return self.chose_tile_to_discard(results)

    def calculate_outs(self, tiles, closed_hand, open_sets_34=None):
        """
        :param tiles: array of tiles in 136 format
        :param closed_hand: array of tiles in 136 format
        :param open_sets_34: array of array with tiles in 34 format
        :return:
        """
        tiles_34 = TilesConverter.to_34_array(tiles)
        closed_tiles_34 = TilesConverter.to_34_array(closed_hand)
        is_agari = self.agari.is_agari(tiles_34,
                                       self.player.open_hand_34_tiles)

        results = []
        for hand_tile in range(0, 34):
            if not closed_tiles_34[hand_tile]:
                continue

            tiles_34[hand_tile] -= 1

            shanten = self.shanten.calculate_shanten(tiles_34, open_sets_34)

            waiting = []
            for j in range(0, 34):
                if hand_tile == j or tiles_34[j] == 4:
                    continue

                tiles_34[j] += 1
                if self.shanten.calculate_shanten(tiles_34,
                                                  open_sets_34) == shanten - 1:
                    waiting.append(j)
                tiles_34[j] -= 1

            tiles_34[hand_tile] += 1

            if waiting:
                results.append(
                    DiscardOption(player=self.player,
                                  shanten=shanten,
                                  tile_to_discard=hand_tile,
                                  waiting=waiting,
                                  tiles_count=self.count_tiles(
                                      waiting, tiles_34)))

        if is_agari:
            shanten = Shanten.AGARI_STATE
        else:
            shanten = self.shanten.calculate_shanten(tiles_34, open_sets_34)

        return results, shanten

    def count_tiles(self, waiting, tiles_34):
        n = 0
        for item in waiting:
            n += 4 - self.player.total_tiles(item, tiles_34)
        return n

    def try_to_call_meld(self, tile, is_kamicha_discard):
        if not self.current_strategy:
            return None, None

        return self.current_strategy.try_to_call_meld(tile, is_kamicha_discard)

    def determine_strategy(self):
        # for already opened hand we don't need to give up on selected strategy
        if self.player.is_open_hand and self.current_strategy:
            return False

        old_strategy = self.current_strategy
        self.current_strategy = None

        # order is important
        strategies = [
            YakuhaiStrategy(BaseStrategy.YAKUHAI, self.player),
            HonitsuStrategy(BaseStrategy.HONITSU, self.player),
        ]

        if settings.OPEN_TANYAO:
            strategies.append(TanyaoStrategy(BaseStrategy.TANYAO, self.player))

        for strategy in strategies:
            if strategy.should_activate_strategy():
                self.current_strategy = strategy

        if self.current_strategy:
            if not old_strategy or self.current_strategy.type != old_strategy.type:
                message = '{} switched to {} strategy'.format(
                    self.player.name, self.current_strategy)
                if old_strategy:
                    message += ' from {}'.format(old_strategy)
                logger.debug(message)
                logger.debug('With hand: {}'.format(
                    TilesConverter.to_one_line_string(self.player.tiles)))

        if not self.current_strategy and old_strategy:
            logger.debug('{} gave up on {}'.format(self.player.name,
                                                   old_strategy))

        return self.current_strategy and True or False

    def chose_tile_to_discard(self, results: [DiscardOption]) -> DiscardOption:
        """
        Try to find best tile to discard, based on different valuations
        """
        def sorting(x):
            # - is important for x.tiles_count
            # in that case we will discard tile that will give for us more tiles
            # to complete a hand
            return x.shanten, -x.tiles_count, x.valuation

        had_to_be_discarded_tiles = [
            x for x in results if x.had_to_be_discarded
        ]
        if had_to_be_discarded_tiles:
            had_to_be_discarded_tiles = sorted(had_to_be_discarded_tiles,
                                               key=sorting)
            selected_tile = had_to_be_discarded_tiles[0]
        else:
            results = sorted(results, key=sorting)
            # remove needed tiles from discard options
            results = [x for x in results if not x.had_to_be_saved]

            # let's chose most valuable tile first
            temp_tile = results[0]
            # and let's find all tiles with same shanten
            results_with_same_shanten = [
                x for x in results if x.shanten == temp_tile.shanten
            ]
            possible_options = [temp_tile]
            for discard_option in results_with_same_shanten:
                # there is no sense to check already chosen tile
                if discard_option.tile_to_discard == temp_tile.tile_to_discard:
                    continue

                # we don't need to select tiles almost dead waits
                if discard_option.tiles_count <= 2:
                    continue

                # let's check all other tiles with same shanten
                # maybe we can find tiles that have almost same tiles count number
                if temp_tile.tiles_count - 2 < discard_option.tiles_count < temp_tile.tiles_count + 2:
                    possible_options.append(discard_option)

            # let's sort got tiles by value and let's chose less valuable tile to discard
            possible_options = sorted(possible_options,
                                      key=lambda x: x.valuation)
            selected_tile = possible_options[0]

        return selected_tile

    def process_discard_option(self, selected_tile, closed_hand):
        self.waiting = selected_tile.waiting
        self.player.ai.previous_shanten = selected_tile.shanten
        self.player.in_tempai = self.player.ai.previous_shanten == 0
        return selected_tile.find_tile_in_hand(closed_hand)

    def estimate_hand_value(self, win_tile, tiles=None, call_riichi=False):
        """
        :param win_tile: 34 tile format
        :param tiles:
        :param call_riichi:
        :return:
        """
        win_tile *= 4
        # we don't need to think, that our waiting is aka dora
        if win_tile in AKA_DORA_LIST:
            win_tile += 1

        if not tiles:
            tiles = self.player.tiles

        tiles += [win_tile]
        result = self.finished_hand.estimate_hand_value(
            tiles=tiles,
            win_tile=win_tile,
            is_tsumo=False,
            is_riichi=call_riichi,
            is_dealer=self.player.is_dealer,
            open_sets=self.player.open_hand_34_tiles,
            player_wind=self.player.player_wind,
            round_wind=self.player.table.round_wind,
            dora_indicators=self.player.table.dora_indicators)
        return result

    def should_call_riichi(self):
        # empty waiting can be found in some cases
        if not self.waiting:
            return False

        # we have a good wait, let's riichi
        if len(self.waiting) > 1:
            return True

        waiting = self.waiting[0]
        tiles = self.player.closed_hand + [waiting * 4]
        closed_melds = [x for x in self.player.melds if not x.opened]
        for meld in closed_melds:
            tiles.extend(meld.tiles[:3])

        tiles_34 = TilesConverter.to_34_array(tiles)

        results = self.hand_divider.divide_hand(tiles_34, [], [])
        result = results[0]

        count_of_pairs = len([x for x in result if is_pair(x)])
        # with chitoitsu we can call a riichi with pair wait
        if count_of_pairs == 7:
            return True

        for hand_set in result:
            # better to not call a riichi for a pair wait
            # it can be easily improved
            if is_pair(hand_set) and waiting in hand_set:
                return False

        return True

    def can_call_kan(self, tile, open_kan):
        """
        Method will decide should we call a kan,
        or upgrade pon to kan
        :param tile: 136 tile format
        :param open_kan: boolean
        :return: kan type
        """
        # we don't need to add dora for other players
        if self.player.ai.in_defence:
            return None

        if open_kan:
            # we don't want to start open our hand from called kan
            if not self.player.is_open_hand:
                return None

            # there is no sense to call open kan when we are not in tempai
            if not self.player.in_tempai:
                return None

            # we have a bad wait, rinshan chance is low
            if len(self.waiting) < 2:
                return None

        tile_34 = tile // 4
        tiles_34 = TilesConverter.to_34_array(self.player.tiles)
        closed_hand_34 = TilesConverter.to_34_array(self.player.closed_hand)
        pon_melds = [x for x in self.player.open_hand_34_tiles if is_pon(x)]

        # let's check can we upgrade opened pon to the kan
        if pon_melds:
            for meld in pon_melds:
                # tile is equal to our already opened pon,
                # so let's call chankan!
                if tile_34 in meld:
                    return Meld.CHANKAN

        count_of_needed_tiles = 4
        # for open kan 3 tiles is enough to call a kan
        if open_kan:
            count_of_needed_tiles = 3

        # we have 3 tiles in our hand,
        # so we can try to call closed meld
        if closed_hand_34[tile_34] == count_of_needed_tiles:
            if not open_kan:
                # to correctly count shanten in the hand
                # we had do subtract drown tile
                tiles_34[tile_34] -= 1

            melds = self.player.open_hand_34_tiles
            previous_shanten = self.shanten.calculate_shanten(tiles_34, melds)

            melds += [[tile_34, tile_34, tile_34]]
            new_shanten = self.shanten.calculate_shanten(tiles_34, melds)

            # called kan will not ruin our hand
            if new_shanten <= previous_shanten:
                return Meld.KAN

        return None

    @property
    def valued_honors(self):
        return [
            CHUN, HAKU, HATSU, self.player.table.round_wind,
            self.player.player_wind
        ]
예제 #13
0
 def __init__(self, table, player):
     super(SLCNNPlayer, self).__init__(table, player)
     self.model = load_model("../supervised_learning/cnn_model.h5")
     self.model.load_weights("../supervised_learning/cnn_weights.h5")
     self.shanten = Shanten()
예제 #14
0
class SLCNNPlayer(BaseAI):
    version = '0.0.2'

    def __init__(self, table, player):
        super(SLCNNPlayer, self).__init__(table, player)
        self.model = load_model("../supervised_learning/cnn_model.h5")
        self.model.load_weights("../supervised_learning/cnn_weights.h5")
        self.shanten = Shanten()

    def mahjong_tile_to_discard_tile(self, t):
        return TilesConverter.find_34_tile_in_136_array(
            t.get_number() + (t.get_type() >> 4) * 9 - 1, self.player.tiles)

    def discard_tile(self):
        h = Hand(TilesConverter.to_one_line_string(self.player.tiles))

        tiles = TilesConverter.to_34_array(self.player.tiles)
        shanten = self.shanten.calculate_shanten(tiles)
        if shanten == 0:
            self.player.in_tempai = True

        if h.test_win():
            return Shanten.AGARI_STATE
        elif self.player.in_tempai:
            results, st = self.calculate_outs()
            tile34 = results[0]['discard']
            tile_in_hand = TilesConverter.find_34_tile_in_136_array(
                tile34, self.player.tiles)
            return tile_in_hand
        else:
            hand_data = h.get_data()
            it = int(
                self.model.predict_classes(transformCSVHandToCNNMatrix(
                    expandHandToCSV(hand_data)),
                                           verbose=0)[0])
            t = hand_data[it]
            tile_in_hand = self.mahjong_tile_to_discard_tile(t)
            return tile_in_hand

    '''
    Adding this bit for calculating which tile to discard when calling Richii.
    '''

    def calculate_outs(self):
        tiles = TilesConverter.to_34_array(self.player.tiles)

        shanten = self.shanten.calculate_shanten(tiles)
        # win
        if shanten == Shanten.AGARI_STATE:
            return [], shanten

        raw_data = {}
        for i in range(0, 34):
            if not tiles[i]:
                continue

            tiles[i] -= 1

            raw_data[i] = []
            for j in range(0, 34):
                if i == j or tiles[j] >= 4:
                    continue

                tiles[j] += 1
                if self.shanten.calculate_shanten(tiles) == shanten - 1:
                    raw_data[i].append(j)
                tiles[j] -= 1

            tiles[i] += 1

            if raw_data[i]:
                raw_data[i] = {
                    'tile': i,
                    'tiles_count': self.count_tiles(raw_data[i], tiles),
                    'waiting': raw_data[i]
                }

        results = []
        tiles = TilesConverter.to_34_array(self.player.tiles)
        for tile in range(0, len(tiles)):
            if tile in raw_data and raw_data[tile] and raw_data[tile][
                    'tiles_count']:
                item = raw_data[tile]

                waiting = []

                for item2 in item['waiting']:
                    waiting.append(item2)

                results.append({
                    'discard': item['tile'],
                    'waiting': waiting,
                    'tiles_count': item['tiles_count']
                })

        # if we have character and honor candidates to discard with same tiles count,
        # we need to discard honor tile first
        results = sorted(results,
                         key=lambda x: (x['tiles_count'], x['discard']),
                         reverse=True)

        return results, shanten

    def count_tiles(self, raw_data, tiles):
        n = 0
        for i in range(0, len(raw_data)):
            n += 4 - tiles[raw_data[i]]
        return n