Ejemplo n.º 1
0
    def test_shanten_number(self):
        shanten = Shanten()

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

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

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

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

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

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

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

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

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

        tiles = self._string_to_34_array(sou='11122245679999')
        self.assertEqual(shanten.calculate_shanten(tiles), 0)
Ejemplo n.º 2
0
def getTile(s: str) -> dict:
    #translate s into supported format
    if len(s) == 0:
        return {}
    tiles = TilesConverter.one_line_string_to_136_array(s, True)
    test = {}
    # check tiles length
    added = []
    if len(tiles) % 3 == 0:
        #3n, add a random tile
        while True:
            tmp = randint(0, 135)
            if tmp not in tiles:
                tiles.append(tmp)
                added.append(tmp)
                break
    if len(tiles) % 3 == 1:
        #3n+1, add a random tile
        while True:
            tmp = randint(0, 135)
            if tmp not in tiles:
                tiles.append(tmp)
                added.append(tmp)
                break
    if len(added) > 0:
        test[-1] = added
    # now is a normal form
    tiles.sort()
    avaliable = []
    for i in range(0, 136):
        if i not in tiles:
            avaliable.append(i)
    calculator = Shanten()
    baseShanten = calculator.calculate_shanten(
        TilesConverter.to_34_array(tiles))
    if baseShanten == -1:
        return {-2: '已和牌'}
    #14*122 try

    tmp = copy.deepcopy(tiles)
    for t in tiles:
        tmp.remove(t)
        one_try = []
        for i in avaliable:
            tmp.append(i)
            res = calculator.calculate_shanten(
                TilesConverter.to_34_array(sorted(tmp)))
            if res < baseShanten:
                one_try.append(i)
            tmp.remove(i)
        t = 4 * (t // 4)
        if len(one_try) > 0 and t not in test.keys():
            test[t] = copy.deepcopy(one_try)
        tmp.append(t)
    return test
Ejemplo n.º 3
0
    def test_shanten_number_and_kokushi_musou(self):
        shanten = Shanten()

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

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

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

        tiles = self._string_to_34_array(sou='129', pin='129', man='129', honors='12345')
        self.assertEqual(shanten.calculate_shanten(tiles), 2)
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    def test_shanten_number_and_open_sets(self):
        shanten = Shanten()

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

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

        tiles = self._string_to_34_array(sou='23455567', pin='222', man='345')
        melds = [
            self._string_to_open_34_set(man='345'),
            self._string_to_open_34_set(sou='555'),
        ]
        self.assertEqual(shanten.calculate_shanten(tiles, melds), 0)
Ejemplo n.º 6
0
def CountShanten(haishi):
    '''
    牌姿データからシャンテン数を数える
    '''

    man = ''
    pin = ''
    sou = ''
    honors = ''

    for x in haishi.split(','):
        if table.pai2[int(x)][1] == 'm':
            man += table.pai2[int(x)][0]
        if table.pai2[int(x)][1] == 'p':
            pin += table.pai2[int(x)][0]
        if table.pai2[int(x)][1] == 's':
            sou += table.pai2[int(x)][0]
        if table.pai2[int(x)][1] == 'z':
            honors += table.pai2[int(x)][0]

    shanten = Shanten()
    tiles = TilesConverter.string_to_34_array(
        man=man,
        pin=pin,
        sou=sou,
        honors=honors,
    )

    c = shanten.calculate_shanten(tiles)
    return (c if c > 0 else 0)
Ejemplo n.º 7
0
def shanten():
    shanten = Shanten()
    req = flask.request.get_json()

    try:
        tiles = TilesConverter.one_line_string_to_34_array(req["hands"])
    except KeyError:
        return flask.jsonify({"error": "hands required"}), 400

    result = shanten.calculate_shanten(tiles)

    if result == -2:
        return flask.jsonify({"error": "hands over 14 tiles"}), 400

    return flask.jsonify({"shanten": result, "error": None})
Ejemplo n.º 8
0
 def check_can_reach(self, player):
     if self.open_cnt[player] > 0:
         return 0
     now_reach = self.reach[player][0]
     for i in range(136):
         if now_reach[i][0]:
             return 0
     now_closehand = [
         i for i in range(136) if self.closehand[player][0][i][0] == 1
     ]
     #import mahjong
     shanten = Shanten()
     tiles = TilesConverter.to_34_array(now_closehand)
     result = shanten.calculate_shanten(tiles)
     return result <= 0
Ejemplo n.º 9
0
def tenpai(tiles, sute):
    shanten = Shanten()
    if len(tiles) == 34:
        # [3,1,1,...,3,0,...,0]
        tiles_34 = tiles
    else:
        # man='1112345678999', pin='', sou='', honors=''
        tiles_34 = TilesConverter.string_to_34_array(tiles)

    result = [0] * 34
    for i in range(34):
        if tiles_34[i] < 4 and not sute[i]:
            tiles_34[i] += 1
            if shanten.calculate_shanten(tiles_34) == -1:
                result[i] = 1
            tiles_34[i] -= 1
    return result
Ejemplo n.º 10
0
class AI(defaultAI):
    def __init__(self):
        defaultAI.__init__(self)
        self.shanten = Shanten()
        self.log = logging.getLogger('ShantenAI')

    def next_move(self, hand, tsumo, remain_number):
        min_shanten = 8
        check = [False] * 34  # 핸드 중복 확인
        check_shanten = [8] * 14  # 손에 있는 14개 중에 어떤 것이 샹텐이 작은지 확인하는 배열

        hand_34 = TilesConverter.to_34_array(hand)

        for x in range(len(hand)):
            x4 = hand[x] // 4
            if check[x4]:  # 같은 타일이면 체크 안해도 됨
                continue
            check[x4] = True

            hand_34[x4] -= 1
            max_shanten = -2
            for y in range(34):
                if y == x4 or hand_34[y] == 4:
                    continue
                hand_34[y] += 1
                result = self.shanten.calculate_shanten(hand_34)
                if result > max_shanten:
                    max_shanten = result
                hand_34[y] -= 1
            hand_34[x4] += 1
            if min_shanten > max_shanten:
                min_shanten = max_shanten
            check_shanten[x] = max_shanten

        check_discard = []  #무엇을 버려야 할지 결정해주는 배열
        for x in range(14):
            if check_shanten[x] == min_shanten:  #최소 샨텐에 해당하는 번째의 패이면
                check_discard.append(x)

        self.log.debug("#: %d", min_shanten)
        return hand[random.choice(check_discard)]

    def get_name(self):
        return "shanten_min"
Ejemplo n.º 11
0
def test_game(ai):
    pai = TEST_INIT_PAI.copy()
    random.shuffle(pai)

    calculator = HandCalculator()
    config = HandConfig(is_tsumo=True)
    config.yaku.yakuhai_place = config.yaku.east
    config.yaku.yakuhai_round = config.yaku.east

    # 1. 내 초기 패 13개 + 쯔모 1개
    hand = pai[:14].copy()
    tsumo = pai[13]
    # 2. tsumo 하자
    for x in range(1, TEST_STEP + 1):
        # DEBUG: 정상 작동하는지 중간 패 과정 출력
        logging.debug("%02d: %s", x, TilesConverter.to_one_line_string(hand))
        # 점수 확인
        result = calculator.estimate_hand_value(hand, tsumo)
        if not result.error:
            # 화료가 되었다는 뜻이다. 친의 쯔모인 3배 점수를 반환하자
            return result.cost['main'] * 3

        if x == TEST_STEP:
            break
        # 버린다
        discard = ai.next_move(hand, tsumo, TEST_STEP - x - 1)
        hand.remove(discard)
        # 쯔모한다
        tsumo = pai[14 + x]
        hand.append(tsumo)
    # 마지막으로 텐파이인지 확인한다
    # 샹텐수가 0이면 텐파이이다
    hand_34 = TilesConverter.to_34_array(hand)
    shanten = Shanten()
    result = shanten.calculate_shanten(hand_34)
    logging.debug('@: %d', result)
    if result == 0:
        return TEST_TEN_SCORE
    return TEST_NOTEN_SCORE
Ejemplo n.º 12
0
    def test_shanten_number_and_open_sets(self):
        shanten = Shanten()

        tiles = self._string_to_34_array(sou="44467778", pin="222567")
        self.assertEqual(shanten.calculate_shanten(tiles), Shanten.AGARI_STATE)

        tiles = self._string_to_34_array(sou="44468", pin="222567")
        self.assertEqual(shanten.calculate_shanten(tiles), 0)

        tiles = self._string_to_34_array(sou="68", pin="222567")
        self.assertEqual(shanten.calculate_shanten(tiles), 0)

        tiles = self._string_to_34_array(sou="68", pin="567")
        self.assertEqual(shanten.calculate_shanten(tiles), 0)

        tiles = self._string_to_34_array(sou="68")
        self.assertEqual(shanten.calculate_shanten(tiles), 0)

        tiles = self._string_to_34_array(sou="88")
        self.assertEqual(shanten.calculate_shanten(tiles), Shanten.AGARI_STATE)
Ejemplo n.º 13
0
def test_game(ai):
    pai = TEST_INIT_PAI.copy()
    random.shuffle(pai)

    calculator = HandCalculator()
    config = HandConfig(is_tsumo=True)
    config.yaku.yakuhai_place = config.yaku.east
    config.yaku.yakuhai_round = config.yaku.east

    # 1. 내 초기 패 13개 + 쯔모 1개
    hand = pai[:14].copy()
    tsumo = pai[13]

    # 1 - a.  nn_ai를 돌리는 경우에 env에 정보를 입력한다.
    err = 0
    isGameOver = False

    # 2. tsumo 하자
    for x in range(1, TEST_STEP + 1):

        # DEBUG: 정상 작동하는지 중간 패 과정 출력
        logging.debug("%02d: %s", x, TilesConverter.to_one_line_string(hand))
        # 점수 확인
        result = calculator.estimate_hand_value(hand, tsumo)
        if not result.error:
            # 화료가 되었다는 뜻이다. 친의 쯔모인 3배 점수를 반환하자
            isGameOver = True
            return result.cost['main'] * 3

        if x == TEST_STEP:
            break
        '''
        # 여기부터
        # 버린다
        discard = ai.next_move(hand, tsumo, TEST_STEP - x - 1)  # 일반적인 경우 이거 쓰기
        hand.remove(discard)
        # 쯔모한다
        tsumo = pai[14 + x]
        hand.append(tsumo)
        # 여기까지는 nn_ai 아닐때 쓰는 코드
        '''

        #여기부터
        discard_action = ai.next_move(hand, tsumo, TEST_STEP - x - 1, env, X,
                                      output_layer, nbActions, isGameOver,
                                      epsilon)  #nn_ai이면 이 코드로 변경
        discard = hand[discard_action]
        currentState = hand.copy()
        hand.remove(discard)
        tsumo = pai[14 + x]
        hand.append(tsumo)
        nextState = hand.copy()
        result = calculator.estimate_hand_value(hand, tsumo)
        if not result.error:
            isGameOver = True
            reward = result.cost['main'] * 3
        else:
            hand_34 = TilesConverter.to_34_array(hand)
            shanten = Shanten()
            result = shanten.calculate_shanten(hand_34)
            logging.debug('@: %d', result)
            if result == 0:
                reward = TEST_TEN_SCORE
            reward = TEST_NOTEN_SCORE
        memory.Reward_Handling(currentState, discard_action, reward, nextState,
                               isGameOver, env, err, epsilon,
                               epsilonMinimumValue, output_layer, batchSize,
                               nbActions, nbStates, X, Y, optimizer, cost)
        #여기까지는 nn_ai쓸때만 쓰는 코드

    # 마지막으로 텐파이인지 확인한다
    # 샹텐수가 0이면 텐파이이다
    hand_34 = TilesConverter.to_34_array(hand)
    shanten = Shanten()
    result = shanten.calculate_shanten(hand_34)
    logging.debug('@: %d', result)
    if result == 0:
        return TEST_TEN_SCORE
    return TEST_NOTEN_SCORE
Ejemplo n.º 14
0
    kawa = []
    print('東' + str(episode+1) + '局')
    resultfile.write('東' + str(episode+1) + '局\n')
    i = 0
    reward = 0
    haitei = 0
    state = []
    cpystate = []
    irerustate = np.zeros((1, 34))
    epsilon = 0.001 + 0.9 / (1.0 + episode)
    done = False
    yama = mahjong.yamatumi()
    tehai, yama = mahjong.haipai(yama)
    resultfile.write('配牌' + str(tehai) + '\n')
    tehai, yama = mahjong.tumo(tehai, yama)
    backsyanten, _, _ = shanten.calculate_shanten(mahjong.maketehailist(tehai))
    state = mahjong.maketehailist(tehai)
    episode_reward = 0

    targetQN = mainQN   # 行動決定と価値計算のQネットワークをおなじにする
    for t in range(18):  # 1試行のループ
        max = -100
        action = -100
        i = 0
        j = 0
        count = 0

        resultfile.write('手牌' + str(tehai) + '\n')

        if type(state) != list:
            cpystate = np.ndarray.tolist(state)[0]
Ejemplo n.º 15
0
class ImplementationAI(InterfaceAI):
    version = '0.3.2'

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

    previous_shanten = 7
    in_defence = False
    waiting = None

    current_strategy = None

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

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

        # Added for cowboy
        self.wanted_tiles_count = 0
        self.pushing = False

    def init_hand(self):
        """
        Let's decide what we will do with our hand (like open for tanyao and etc.)
        """
        self.determine_strategy()

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

        # Added for cowboy
        self.previous_shanten = 7
        self.pushing = False

    def draw_tile(self, tile):
        """
        :param tile: 136 tile format
        :return:
        """
        self.determine_strategy()

    def discard_tile(self, discard_tile):
        # we called meld and we had discard tile that we wanted to discard
        if discard_tile is not None:
            if not self.last_discard_option:
                return discard_tile

            return self.process_discard_option(self.last_discard_option, self.player.closed_hand, True)

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

        if shanten < self.previous_shanten:
            logger.info("Shanten: {}".format(shanten))
            self.previous_shanten = shanten

        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
                self.player.set_state("DEFENCE")
            else:
                #logger.info("Player is alreay in defence")
                pass

            defence_results, shanten = self.calculate_outs(self.player.tiles,
                                                   self.player.closed_hand,
                                                   self.player.open_hand_34_tiles)

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

        # Process the discard option before changing the state
        card2discard = self.process_discard_option(selected_tile, self.player.closed_hand)

        # After adjusting the defence, time to update the state
        if shanten == 0 and self.player.play_state == "PREPARING" and results:  # and results for debugging
            if self.wanted_tiles_count > 4:
                self.player.set_state("PROACTIVE_GOODSHAPE")
            else:
                self.player.set_state("PROACTIVE_BADSHAPE")

        return card2discard

    def process_discard_options_and_select_tile_to_discard(self, results, shanten, had_was_open=False):
        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,
                                                                      had_was_open)

        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

        meld, discard_option = self.current_strategy.try_to_call_meld(tile, is_kamicha_discard)
        tile_to_discard = None
        if discard_option:
            self.last_discard_option = discard_option
            tile_to_discard = discard_option.tile_to_discard

        return meld, tile_to_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 self.player.table.has_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.info(message)
                logger.info('With such a 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

        # util for drawing
        def get_order(t):
            # if it is honor
            if t // 9 >= 3:
                return 0
            else:
                return min((t % 9), (8 - (t % 9))) + 1

        def display_waiting(w):
            return TilesConverter.to_one_line_string([t * 4 for t in w])

        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)
            #print("Len: ", len(results))

            # init the temp_tile
            temp_tile = results[0]

            # 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
            if results:
                temp_tile = results[0]
            else:
                return temp_tile

            # and let's find all tiles with same shanten
            results_with_same_shanten = [x for x in results if x.shanten == temp_tile.shanten]

            # if there are 7 pairs
            tiles_34 = TilesConverter.to_34_array(self.player.tiles)
            paired_tiles = [x for x in range(0, 34) if tiles_34[x] == 2]
            num_pairs = len(paired_tiles)

            if num_pairs == 4 and temp_tile.shanten > 1 and not self.player.in_seven_pairs and not self.player.params.get("fool_in_pairs"):
                logger.info("There are 4 pairs!")

                if len(self.player.discards) > 6:
                    logger.info("However it's too late for seven pairs.")
                    for r in results:
                        if r.tile_to_discard in paired_tiles:
                            logger.info("With hand: {}".format(TilesConverter.to_one_line_string(self.player.tiles)))
                            logger.info("Discard {}".format(display_waiting([r.tile_to_discard])))
                            return r
                else:
                    logger.info("It's early, okay to go with seven pairs.")
                    self.player.in_seven_pairs = True


            # TODO: a smart seven pairs strategy should be carried
            if self.player.in_seven_pairs and not self.player.params.get("fool_in_pairs"):
                single_tiles = [x for x in range(0,34) if tiles_34[x] in [1,3,4]]
                single_tiles.sort(key=lambda x: (self.count_tiles([x], tiles_34) >= 2, -get_order(x)))
                for s in single_tiles: # actually only #1 would be used most of the time
                    for r in results:
                        if r.tile_to_discard == s:
                            logger.info("SevenPairsStrategy:")
                            logger.info("Hand: {}".format(TilesConverter.to_one_line_string(self.player.tiles)))
                            logger.info("Discard: {}".format(display_waiting([s])))
                            return r


            # if in drawing
            if temp_tile.shanten == 0:
                print("It's a drawing hand!")
                print("Hand: {}".format(TilesConverter.to_one_line_string(self.player.tiles)))
                # assume that temp tile got the biggest waiting
                if temp_tile.tiles_count > 4:
                    print("It's a good shape, go for it.")
                else:
                    logger.info("It's a bad shape drawing hand, need some calculation.")
                    logger.info("Possible choices: {}".format(TilesConverter.to_one_line_string([x.tile_to_discard*4 for x in results_with_same_shanten])))
                    possible_choices = [(temp_tile, 99)]
                    for r in results_with_same_shanten:
                        print("\nCut:", display_waiting([r.tile_to_discard]))
                        print("Waiting:", display_waiting(r.waiting))
                        print("Order:", [get_order(t) for t in r.waiting])
                        print("Outs:", r.tiles_count)
                        if r.tiles_count == 0:
                            print("It's an impossible drawing.")
                            continue
                        if len(r.waiting) == 1:
                            print("It's an 1 out drawing.")
                            possible_choices.append((r, get_order(r.waiting[0])))
                        else:
                            print("It's a multiple out drawing.")
                            r.waiting.sort(key=get_order)
                            possible_choices.append((r, get_order(r.waiting[0])))
                    possible_choices.sort(key=lambda x: (x[1], -x[0].tiles_count))
                    final_choice = possible_choices[0][0]
                    logger.info("Choice: {} {} {}".format(display_waiting([final_choice.tile_to_discard]), "with waiting", display_waiting(final_choice.waiting)))

                    return final_choice

            # if not in drawing or in drawing with good shape
            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
                # Cowboy: +-2 is a big difference, but +-1 is not
                diff = 1
                if self.player.params.get("big_diff"):
                    diff = 2
                if temp_tile.tiles_count - diff < discard_option.tiles_count < temp_tile.tiles_count + diff:
                    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]

            if selected_tile.shanten == 0:
                print("\nChoice:", display_waiting([selected_tile.tile_to_discard]), "with waiting",
                      display_waiting(selected_tile.waiting))

        return selected_tile

    def process_discard_option(self, discard_option, closed_hand, force_discard=False):
        self.waiting = discard_option.waiting
        self.wanted_tiles_count = discard_option.tiles_count
        self.player.ai.previous_shanten = discard_option.shanten
        self.player.in_tempai = self.player.ai.previous_shanten == 0

        # when we called meld we don't need "smart" discard
        if force_discard:
            return discard_option.find_tile_in_hand(closed_hand)

        last_draw_34 = self.player.last_draw  and self.player.last_draw // 4 or None
        if self.player.last_draw not in AKA_DORA_LIST and last_draw_34 == discard_option.tile_to_discard:
            return self.player.last_draw
        else:
            return discard_option.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]

        config = HandConfig(
            is_riichi=call_riichi,
            player_wind=self.player.player_wind,
            round_wind=self.player.table.round_wind,
            has_aka_dora=self.player.table.has_aka_dora,
            has_open_tanyao=self.player.table.has_open_tanyao
        )

        result = self.finished_hand.estimate_hand_value(tiles,
                                                        win_tile,
                                                        self.player.melds,
                                                        self.player.table.dora_indicators,
                                                        config)
        return result

    def should_call_riichi(self):
        logger.info("Can call a reach!")

        # empty waiting can be found in some cases
        if not self.waiting:
            logger.info("However it is impossible to win.")
            return False

        # In pushing state, it's better to call it
        if self.pushing:
            logger.info("Go for it! The player is in pushing state.")
            return True

        # Get the rank EV after round 3
        if self.table.round_number >= 5:  # DEBUG: set this to 0
            try:
                possible_hand_values = [self.estimate_hand_value(tile, call_riichi=True).cost["main"] for tile in self.waiting]
            except Exception as e:
                print(e)
                possible_hand_values = [2000]
            hand_value = sum(possible_hand_values) / len(possible_hand_values)
            hand_value += self.table.count_of_riichi_sticks * 1000
            if self.player.is_dealer:
                hand_value += 700  # EV for dealer combo

            lose_estimation = 6000 if self.player.is_dealer else 7000

            hand_shape = "pro_bad_shape" if self.wanted_tiles_count <= 4 else "pro_good_shape"

            rank_ev = self.defence.get_rank_ev(hand_value, lose_estimation, COUNTER_RATIO[hand_shape][len(self.player.discards)])

            logger.info('''Cowboy: Proactive reach:
            Hand value: {}    Hand shape: {}
            Is dealer: {}    Current ranking: {}
            '''.format(hand_value, hand_shape, self.player.is_dealer, self.table.get_players_sorted_by_scores()))

            logger.info("Rank EV for proactive reach: {}".format(rank_ev))

            if rank_ev < 0:
                logger.info("It's better to fold.")
                return False
            else:
                logger.info("Go for it!")
                return True

        should_attack = not self.defence.should_go_to_defence_mode()

        # For bad shape, at least 1 dora is required
        # Get count of dora
        dora_count = sum([plus_dora(x, self.player.table.dora_indicators) for x in self.player.tiles])
        # aka dora
        dora_count += sum([1 for x in self.player.tiles if is_aka_dora(x, self.player.table.has_open_tanyao)])
        if self.wanted_tiles_count <= 4 and dora_count == 0 and not self.player.is_dealer:
            should_attack = False
            logger.info("A bad shape with no dora, don't call it.")

        # # If player is on the top, no need to call reach
        # if self.player == self.player.table.get_players_sorted_by_scores()[0] and self.player.scores > 30000:
        #     should_attack = False
        #     logger.info("Player is in 1st position, no need to call reach.")

        if should_attack:
            # If we are proactive, let's set the state!
            logger.info("Go for it!")
            if self.player.play_state == "PREPARING": # If not changed in defense actions
                if self.wanted_tiles_count > 4:
                    self.player.set_state("PROACTIVE_GOODSHAPE")
                else:
                    self.player.set_state("PROACTIVE_BADSHAPE")
            return True

        else:
            logger.info("However it's better to fold.")
            return False


        # These codes are unreachable, it is fine.
        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 should_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 should_call_win(self, tile, enemy_seat):
        return True

    def enemy_called_riichi(self, enemy_seat):
        """
        After enemy riichi we had to check will we fold or not
        it is affect open hand decisions
        :return:
        """
        #if self.defence.should_go_to_defence_mode():
        #    self.in_defence = True

        # No need to check it here

        pass

    @property
    def enemy_players(self):
        """
        Return list of players except our bot
        """
        return self.player.table.players[1:]
Ejemplo n.º 16
0
class ImplementationAI(InterfaceAI):
    version = '0.0.1'

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

    previous_shanten = 7
    in_defence = False
    waiting = None

    current_strategy = None

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

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

    def init_hand(self):
        """
        Let's decide what we will do with our hand (like open for tanyao and etc.)
        """
        self.determine_strategy()

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

    def draw_tile(self, tile):
        """
        :param tile: 136 tile format
        :return:
        """
        self.determine_strategy()

    def discard_tile(self, discard_tile):
        # we called meld and we had discard tile that we wanted to discard
        if discard_tile is not None:
            if not self.last_discard_option:
                return discard_tile

            return self.process_discard_option(self.last_discard_option,
                                               self.player.closed_hand, True)

        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, had_was_open=False):
        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,
                had_was_open)

        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
        if len(self.player.discards) <= 5 and tile // 4 <= 27:
            return None, None
        meld, discard_option = self.current_strategy.try_to_call_meld(
            tile, is_kamicha_discard)
        tile_to_discard = None
        if discard_option:
            self.last_discard_option = discard_option
            tile_to_discard = discard_option.tile_to_discard

        return meld, tile_to_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
        return False
        old_strategy = self.current_strategy
        self.current_strategy = None

        # order is important, the first appropriate strtegy will be used
        strategies = []

        if self.player.table.has_open_tanyao:
            strategies.append(TanyaoStrategy(BaseStrategy.TANYAO, self.player))

        strategies.append(YakuhaiStrategy(BaseStrategy.YAKUHAI, self.player))
        strategies.append(HonitsuStrategy(BaseStrategy.HONITSU, 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,
                               discard_option,
                               closed_hand,
                               force_discard=False):
        self.waiting = discard_option.waiting
        self.player.ai.previous_shanten = discard_option.shanten
        self.player.in_tempai = self.player.ai.previous_shanten == 0

        # when we called meld we don't need "smart" discard
        if force_discard:
            return discard_option.find_tile_in_hand(closed_hand)

        last_draw_34 = self.player.last_draw and self.player.last_draw // 4 or None
        if self.player.last_draw not in AKA_DORA_LIST and last_draw_34 == discard_option.tile_to_discard:
            return self.player.last_draw
        else:
            return discard_option.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]

        config = HandConfig(is_riichi=call_riichi,
                            player_wind=self.player.player_wind,
                            round_wind=self.player.table.round_wind,
                            has_aka_dora=self.player.table.has_aka_dora,
                            has_open_tanyao=self.player.table.has_open_tanyao)

        result = self.finished_hand.estimate_hand_value(
            tiles, win_tile, self.player.melds,
            self.player.table.dora_indicators, config)
        return result

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

        if self.in_defence:
            return False

        #If we tenpai fast enough
        if len(self.player.discards) <= 8:
            return True
        if len(self.player.discards) >= 14:
            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 should_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 should_call_win(self, tile, enemy_seat):
        return True

    def enemy_called_riichi(self, enemy_seat):
        """
        After enemy riichi we had to check will we fold or not
        it is affect open hand decisions
        :return:
        """
        if self.defence.should_go_to_defence_mode():
            self.in_defence = True

    @property
    def enemy_players(self):
        """
        Return list of players except our bot
        """
        return self.player.table.players[1:]
Ejemplo n.º 17
0
def index(request):
    # return HttpResponse("Hello, world. You're at the polls index.")

    if request.method == 'GET':
        return JsonResponse({})

    # JSON文字列
    datas = json.loads(request.body)

    #全37種
    syurui = {'1m':0,'2m':1,'3m':2,'4m':3,'5m':4,'6m':5,'7m':6,'8m':7,'9m':8,'1s':9,'2s':10,'3s':11,'4s':12,'5s':13,'6s':14,'7s':15,'8s':16,'9s':17,'1p':18,'2p':19,'3p':20,'4p':21,'5p':22,'6p':23,'7p':24,'8p':25,'9p':26,'a':27,'b':28,'c':29,'d':30,'e':31,'f':32,'g':33, 'a5m':34, 'a5s':35, 'a5p':36}
    #風4種
    field = {'a':0,'b':1,'c':2,'d':3}
    #萬子
    dic_man = {'1m':1, '2m':2, '3m':3, '4m':4, '5m':5, '6m':6, '7m':7, '8m':8, '9m':9, 'a5m':5}
    #索子
    dic_sou = {'1s':1, '2s':2, '3s':3, '4s':4, '5s':5, '6s':6, '7s':7, '8s':8, '9s':9, 'a5s':5}
    #筒子
    dic_pin = {'1p':1, '2p':2, '3p':3, '4p':4, '5p':5, '6p':6, '7p':7, '8p':8, '9p':9, 'a5p':5}
    #字牌
    dic_honors = {'a':1, 'b':2, 'c':3, 'd':4, 'e':5, 'f':6, 'g':7}

    dora = datas["dora"]["name"]
    ground = datas["ground"]["name"]
    own = datas["own"]["name"]

    #配牌リスト
    pai_list = [0 for i in range(37)]
    for i in datas["haipai"]:
        pai_list[syurui[i["hai"]]] += i["amount"]

    input_list = pai_list

    #シャンテン数
    man = ''
    sou = ''
    pin = ''
    honors = ''
    for i in datas["haipai"]:
        if 'm' in i["hai"]:
            for j in range(i["amount"]):
                man += str(dic_man[i["hai"]])
        elif 's' in i["hai"]:
            for j in range(i["amount"]):
                sou += str(dic_sou[i["hai"]])
        elif 'p' in i["hai"]:
            for j in range(i["amount"]):
                pin += str(dic_pin[i["hai"]])
        else:
            for j in range(i["amount"]):
                honors += str(dic_honors[i["hai"]])
    shanten = Shanten()
    tiles = TilesConverter.string_to_34_array(man=man, pin=pin, sou=sou, honors=honors)
    ss = shanten.calculate_shanten(tiles)

    ##萬子,索子,筒子,字牌,中張牌,么九牌,ドラのカウント
    dd = 0
    m = 0
    s = 0
    p = 0
    h = 0
    tyu = 0
    yao = 0
    for i in datas["haipai"]:
        #ドラ枚数
        if i["hai"] == dora or i["hai"] == 'a5m' or i["hai"] == 'a5s' or i["hai"] == 'a5p':
            dd += i["amount"]
        #萬子枚数
        if 'm' in i["hai"]:
            m += i["amount"]
        #索子枚数
        elif 's' in i["hai"]:
            s += i["amount"]
        #筒子枚数
        elif 'p' in i["hai"]:
            p += i["amount"]
        #字牌枚数
        else:
            h += i["amount"]
        #么九牌
        if '1' in i["hai"] or '9' in i["hai"] or 'a' in i["hai"] or 'b' in i["hai"] or 'c' in i["hai"] or 'd' in i["hai"] or 'e' in i["hai"] or 'f' in i["hai"] or 'g' in i["hai"]:
            yao += i["amount"]
        else:
            tyu += i["amount"]

    input_list.append(dd)
    input_list.append(ss)
    input_list.append(m)
    input_list.append(s)
    input_list.append(p)
    input_list.append(h)
    input_list.append(tyu)
    input_list.append(yao)

    #ドラリスト化
    dora_list = [0 for i in range(34)]
    dora_list[syurui[dora]] += 1

    input_list[len(input_list):len(input_list)] = dora_list

    #場風リスト化
    field1_list = [0 for i in range(4)]
    field1_list[field[ground]] += 1

    input_list[len(input_list):len(input_list)] = field1_list

    #自風リスト化
    field2_list = [0 for i in range(4)]
    field2_list[field[own]] += 1

    input_list[len(input_list):len(input_list)] = field2_list

    input_data = []
    input_data.append(input_list)
    input_data = np.array(input_data)
    input_data = input_data.astype('float32')

    ##訓練済みネットワークを用いた推論
    #保存したネットワークの読み込み
    #訓練済みのネットワークと同様のクラスのインスタンス生成
    loaded_net = Net(n_hidden=200)

    #訓練済みネットワークのパラメータを読み込ませる
    chainer.serializers.load_npz('./genapp/ml_models/m_data.net', loaded_net)

    # テストデータで予測値を計算
    with chainer.using_config('train', False), chainer.using_config('enable_backprop', False):
        y = loaded_net(input_data)

    resignation = np.argmax(y[0,:].array)

    point = random.randint(1,5)

    result = {
        'res':str(resignation),
        'point':str(point)
    }

    response = JsonResponse(
        result
    )
    return response
Ejemplo n.º 18
0
def shanten_calculator(tiles):
    shanten = Shanten()
    tiles = TilesConverter.one_line_string_to_34_array(str(tiles),
                                                       has_aka_dora=True)
    result = shanten.calculate_shanten(tiles)
    return result
Ejemplo n.º 19
0
class Tehai():
    def __init__(self, hais=[], furos=[]):
        self.calculator = Shanten()
        self.hais = hais[:]
        self.furos = furos[:]
        self.tsumo_hai = None
        self.hai_num = len(self.hais)

    # ツモ牌を手牌に格納
    def store(self):
        if self.tsumo_hai is not None:
            self.hais.append(self.tsumo_hai)
            self.tsumo_hai = None

    # ツモ
    def tsumo(self, hai):
        self.store()
        self.tsumo_hai = hai
        self.hai_num = len(self.hais) + 1

    # 追加
    def append(self, hais):
        self.store()
        for hai in hais:
            self.hais.append(hai)
        self.hai_num = len(self.hais)

    # 挿入
    def insert(self, index, hai):
        self.store()
        self.hais.insert(index, hai)
        self.hai_num = len(self.hais)

    # 番号で取り出し
    def pop(self, index=-1):
        self.store()
        pop_hai = self.hais.pop(index)
        self.hai_num = len(self.hais)
        return pop_hai

    # 牌を指定して取り出し
    def remove(self, hai):
        self.store()
        self.hais.remove(hai)
        self.hai_num = len(self.hais)
        return hai

    # 牌の種類を指定して検索
    def find(self, kind, num):
        for hai in self.hais + [self.tsumo_hai]:
            if hai is not None and hai.kind == kind and hai.num == num:
                return hai
        return None

    # 複数の牌を検索
    def find_multi(self, kinds, nums):
        if len(kinds) != len(nums):
            return None

        find_num = len(kinds)
        found_hais = []

        for hai in self.hais + [self.tsumo_hai]:
            if hai is None:
                continue

            for kind, num in zip(kinds, nums):
                if hai.kind == kind and hai.num == num:
                    found_hais.append(hai)
                    kinds.remove(kind)
                    nums.remove(num)
                    break

        if len(found_hais) == find_num:
            return found_hais
        else:
            return None

    # 並べ替え
    def sort(self):
        self.store()
        self.hais.sort()

    # 暗槓可能な牌
    def ankan_able(self):
        return_hais = []

        for hai in self.hais + [self.tsumo_hai]:
            for return_hai in return_hais:
                if hai.kind == return_hai[0].kind and hai.num == return_hai[0].num:
                    break
            else:
                found_hais = self.find_multi([hai.kind for i in range(4)], [hai.num for i in range(4)])

                if found_hais is not None:
                    return_hais.append(found_hais)

        return return_hais

    # 加槓可能な牌
    def kakan_able(self):
        return_hais = []

        for furo in self.furos:
            if furo.kind == FuroKind.PON:
                for hai in self.hais + [self.tsumo_hai]:
                    # 明刻と同じ牌だったら
                    if furo.hais[0].kind == hai.kind and furo.hais[0].num == hai.num:
                        return_hais.append(hai)
                        break

        return return_hais

    # 明槓可能な牌
    def minkan_able(self, target):
        found_hais = self.find_multi([target.kind for i in range(3)], [target.num for i in range(3)])

        if found_hais is None:
            return []
        else:
            return [found_hais]

    # ポン可能な牌
    def pon_able(self, target):
        found_hais = self.find_multi([target.kind for i in range(2)], [target.num for i in range(2)])

        if found_hais is None:
            return []
        else:
            return [found_hais]

    # チー可能な牌
    def chi_able(self, target):
        if target.kind == 3:
            return []

        return_hais = []

        for i in range(-2, 1):
            kinds = []
            nums = []

            for j in range(i, i + 3):
                if j == 0:
                    continue

                kinds.append(target.kind)
                nums.append(target.num + j)

            found_hais = self.find_multi(kinds, nums)

            if found_hais is not None:
                return_hais.append(found_hais)

        return return_hais

    # 暗槓
    def ankan(self, hais):
        append_hais = [self.remove(hai) for hai in hais]
        self.furos.append(Furo(append_hais, FuroKind.ANKAN))
        self.sort()

    # 加槓
    def kakan(self, hai):
        for furo in self.furos:
            if furo.kind == FuroKind.PON:
                # 明刻と同じ牌だったら
                if furo.hais[0].kind == hai.kind and furo.hais[0].num == hai.num:
                    furo.kind = FuroKind.KAKAN
                    furo.hais.append(self.remove(hai))

    # 明槓
    def minkan(self, hais, target, direct):
        append_hais = [self.remove(hai) for hai in hais]
        append_hais.insert(int((direct - 1) * 1.5), target)
        self.furos.append(Furo(append_hais, FuroKind.MINKAN, direct))

    # ポン
    def pon(self, hais, target, direct):
        append_hais = [self.remove(hai) for hai in hais]
        append_hais.insert(direct - 1, target)
        self.furos.append(Furo(append_hais, FuroKind.PON, direct))

    # チー
    def chi(self, hais, target, direct):
        append_hais = [self.remove(hai) for hai in hais]
        append_hais.insert(direct - 1, target)
        self.furos.append(Furo(append_hais, FuroKind.CHI, direct))

    # 表示
    def show(self):
        for hai in self.hais:
            print(format(hai.name, "<4s"), end="")

        if self.tsumo_hai is not None:
            print(" {}".format(self.tsumo_hai.name))
        else:
            print()

        for j in range(len(self.hais)):
            print(format(j, "<4d"), end="")

        if self.tsumo_hai is not None:
            print(" {}".format(j + 1))
        else:
            print()

    # シャンテン数
    def shanten(self):
        tile_strs = [""] * 4

        for hai in self.hais:
            tile_strs[hai.kind] += str(hai.num)

        if self.tsumo_hai is not None:
            tile_strs[self.tsumo_hai.kind] += str(self.tsumo_hai.num)

        tiles = TilesConverter.string_to_34_array(tile_strs[0], tile_strs[1], tile_strs[2], tile_strs[3])

        return self.calculator.calculate_shanten(tiles)
Ejemplo n.º 20
0
class HandSolver(object):
    tiles = []
    Shanten_calculater = None
    shanten = 8
    Tiles = None
    improve_tiles_count = 0
    Hand_Calculator = None
    cnt = 0
    Hand_Config = None
    dora_indicators = []
    revealed_tiles = None
    cnt = 0

    def __init__(self, tiles, dora_indicators, revealed_tiles):
        self.tiles = tiles
        self.Shanten_calculater = Shanten()
        self.Hand_Calculator = HandCalculator()
        self.Tiles = TilesConverter()
        self.shanten = self.Shanten_calculater.calculate_shanten(
            self.Tiles.to_34_array(self.tiles))
        self.Hand_Config = HandConfig(is_riichi=True)
        self.dora_indicators = dora_indicators
        self.revealed_tiles = revealed_tiles

    def choose_tile_to_discard(self):
        if self.shanten > 1:
            return self.pure_speed_strategy()
        max_value = 0
        option_list = self.get_option_list(self.tiles)
        temp_tiles = list(self.tiles)
        option_value = [0] * 34
        for option in option_list:
            while option not in temp_tiles:
                option += 1
            temp_tiles.remove(option)
            option_value[option // 4] = self.estimate_value(
                temp_tiles, self.shanten, 1, self.shanten)
            temp_tiles.append(option)
        max_value = max(option_value)
        result = option_value.index(max_value) * 4
        while result not in self.tiles:
            result += 1
        return result

    def sort_tiles(self):
        if self.shanten == 0:
            return 'IS_TEMPAI'

    def calculate_Improve_tiles_count(self, tiles):
        cnt = 0
        for tile in range(0, 135, 4):
            now_shanten = self.Shanten_calculater.calculate_shanten(
                self.Tiles.to_34_array(tiles))
            tiles.append(tile)
            if self.Shanten_calculater.calculate_shanten(
                    self.Tiles.to_34_array(tiles)) < now_shanten:
                cnt += 4 - self.revealed_tiles[tile // 4]
            tiles.remove(tile)
        return cnt

    def get_option_list(self, tiles):  #the input are 14-tiles
        temp_tiles = list(tiles)
        option_list = []
        current_shanten = self.Shanten_calculater.calculate_shanten(
            self.Tiles.to_34_array(temp_tiles))
        for tile in tiles:
            if tile // 4 * 4 in option_list:
                continue
            temp_tiles.remove(tile)
            if self.Shanten_calculater.calculate_shanten(
                    self.Tiles.to_34_array(temp_tiles)) <= current_shanten:
                option_list.append(tile // 4 * 4)
            temp_tiles.append(tile)
        return option_list

    def pure_speed_strategy(self):
        max_improve_tiles_count = 0
        answer = 0
        option_list = self.get_option_list(self.tiles)
        temp_tiles = list(self.tiles)
        for tile in option_list:
            while tile not in temp_tiles:
                tile += 1
            temp_tiles.remove(tile)
            temp_improve_tiles_count = self.calculate_Improve_tiles_count(
                temp_tiles)
            if max_improve_tiles_count <= temp_improve_tiles_count:
                max_improve_tiles_count = temp_improve_tiles_count
                answer = tile
            temp_tiles.append(tile)
        return answer

    def solve_tempai(self):
        win_tiles = []
        temp_tiles = list(self.tiles)
        have_han = 13
        for tile in self.tiles:

            temp_tiles.remove(tile)
            if self.Shanten_calculater.calculate_shanten(
                    self.Tiles.to_34_array(temp_tiles)) <= self.shanten:
                option_list.append(tile)
            temp_tiles.append(tile)
        for options in option_list:
            temp_tiles.remove(option)
            for tile in range(0, 135):
                temp_tiles.append(tile)
                if self.Shanten_calculater.calculate_shanten(
                        self.Tiles.to_34_array(temp_tiles)) == -1:
                    have_han = min(
                        self.Hand_Calculator.estimate_hand_value(temp_tiles),
                        have_han)
                temp_tiles.remove(tile)
            temp_tiles.append(option)
        if have_han == 0:
            pass

    def get_simple_win_rate(self, win_tile, is_jinpai, remain_number):
        rate = 0
        if win_tile == 'Middle_Tile':
            if is_jinpai == 0:
                rate = 0.37
            elif is_jinpai == 1:
                rate = 0.40
            elif is_jinpai == 2:
                rate = 0.5
            rate -= (4 - remain_number) * 0.07
        elif win_tile == 'Three_Like_Tiles':
            if is_jinpai == 0:
                rate = 0.43
            else:
                rate = 0.5
            rate -= (4 - remain_number) * 0.07
        elif win_tile == 'Two_Like_Tiles':
            if is_jinpai == 0:
                rate = 0.46
            else:
                rate = 0.55
            rate -= (4 - remain_number) * 0.07
        elif win_tile == 'One_Like_Tiles':
            if is_jinpai == 0:
                rate = 0.5
            else:
                rate = 0.6
            rate -= (4 - remain_number) * 0.03
        elif win_tile == 'Honor_Tiles':
            if remain_number == 2:
                rate = 0.6
            else:
                rate = 0.55
        return rate

    def get_win_rate(self):
        pass

    def get_sort_of_tile(self, tile):
        temp_tiles = []
        temp_tiles.append(tile)
        temp_tile_string = self.Tiles.to_one_line_string(temp_tiles)
        middle_tiles = ['4m', '5m', '6m', '4s', '5s', '6s', '4p', '5p', '6p']
        three_like_tiles = ['3m', '7m', '3p', '7p', '3s', '7s']
        two_like_tiles = ['2m', '8m', '2p', '8p', '2s', '8s']
        one_like_tiles = ['1m', '9m', '1s', '9s', '1p', '9p']
        if temp_tile_string in middle_tiles:
            return 'Middle_Tile'
        elif temp_tile_string in three_like_tiles:
            return 'Three_Like_Tiles'
        elif temp_tile_string in two_like_tiles:
            return 'Two_Like_Tiles'
        elif temp_tile_string in one_like_tiles:
            return 'One_Like_Tiles'
        else:
            return 'Honor_Tiles'

    def estimate_value(self, tiles, current_shanten, depth,
                       initial_shanten):  #the input are 13 tiles
        value = 0
        temp_tiles = list(tiles)
        if current_shanten == 0:
            return self.estimate_tempai_value(tiles)
        for tile in range(0, 135, 4):
            temp_tiles.append(tile)  #search changes and turn to new tiles
            feature_shanten = self.Shanten_calculater.calculate_shanten(
                self.Tiles.to_34_array(temp_tiles))
            if feature_shanten < current_shanten:
                option_list = self.get_option_list(temp_tiles)
                flag = 0

                temp_tiles.remove(tile)
                current_improve_tiles_count = self.calculate_Improve_tiles_count(
                    temp_tiles)
                max_improve_tiles_count = current_improve_tiles_count
                temp_tiles.append(tile)
                if feature_shanten == current_shanten:  #在分析改良
                    for option in option_list:
                        while option not in temp_tiles:
                            option += 1
                        temp_tiles.remove(option)
                        max_improve_tiles_count = max(
                            max_improve_tiles_count,
                            self.calculate_Improve_tiles_count(temp_tiles))
                        temp_tiles.append(option)
                    if max_improve_tiles_count == current_improve_tiles_count:
                        temp_tiles.remove(tile)
                        continue

                max_value = 0
                for option in option_list:
                    while option not in temp_tiles:
                        option += 1
                    temp_tiles.remove(option)
                    if feature_shanten == current_shanten:
                        max_value = max(
                            self.estimate_value(temp_tiles, feature_shanten,
                                                depth + 1, initial_shanten),
                            max_value)
                    else:
                        max_value = max(
                            self.estimate_value(temp_tiles, feature_shanten,
                                                depth, initial_shanten),
                            max_value)
                    temp_tiles.append(option)
                value += max_value * (4 - self.revealed_tiles[tile // 4])
            temp_tiles.remove(tile)
        return value / 100

    def estimate_tempai_value(self, tiles):  #the input are 13 tiles
        feature_value = 0
        for tile in range(0, 135, 4):
            tiles.append(tile)
            if self.Shanten_calculater.calculate_shanten(
                    self.Tiles.to_34_array(tiles)) == -1:
                result = self.Hand_Calculator.estimate_hand_value(
                    tiles,
                    tile,
                    dora_indicators=self.dora_indicators,
                    config=self.Hand_Config)
                feature_value += result.cost['main'] * (
                    4 - self.revealed_tiles[tile // 4])
            tiles.remove(tile)
        return feature_value
Ejemplo n.º 21
0
class LogParser:
    data_to_save = None

    def __init__(self):
        self.shanten = Shanten()
        self.agari = Agari()
        self.finished_hand = HandCalculator()

        self.data_to_save = []

        self.csv_exporter = CSVExporter()

    def on_player_draw(self, player, table):
        pass

    def on_player_discard(self, player, table, discarded_tile):
        pass

    def on_player_tenpai(self, player, table):
        pass

    def get_game_rounds(self, log_content, log_id):
        """
        XML parser was really slow here,
        so I built simple parser to separate log content on tags (grouped by rounds)
        """
        tag_start = 0
        rounds = []
        tag = None

        current_tags = []

        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
            skip_tags = ["SHUFFLE", "TAIKYOKU", "mjloggm", "GO", "UN"]
            if tag and any([x in tag for x in skip_tags]):
                tag = None

            # new hand was started
            if self.is_init_tag(tag) and current_tags:
                rounds.append(current_tags)
                current_tags = []

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

            if tag:
                if self.is_init_tag(tag):
                    # we dont need seed information
                    # it appears in old logs format
                    find = re.compile(r'shuffle="[^"]*"')
                    tag = find.sub("", tag)

                    current_tags.append('<LOG_ID id="{}" />'.format(log_id))

                # add processed tag to the hand
                current_tags.append(tag)
                tag = None

        return rounds

    def parse_game_rounds(self, game):
        self.data_to_save = []
        step = 0
        for round_item in game:
            table = Table()

            log_id = None
            who_called_meld_on_this_step = None

            try:
                for tag in round_item:
                    if self.is_log_id(tag):
                        log_id = self.get_attribute_content(tag, "id")
                        table.log_id = log_id

                    if self.is_init_tag(tag):
                        seed = [
                            int(x) for x in self.get_attribute_content(
                                tag, "seed").split(",")
                        ]
                        current_hand = seed[0]
                        dora_indicator = seed[5]
                        dealer_seat = int(
                            self.get_attribute_content(tag, "oya"))
                        scores = [
                            int(x) for x in self.get_attribute_content(
                                tag, "ten").split(",")
                        ]

                        table.init(dealer_seat, current_hand, dora_indicator,
                                   step, scores)

                        table.get_player(0).init_hand(
                            self.get_attribute_content(tag, "hai0"))
                        table.get_player(1).init_hand(
                            self.get_attribute_content(tag, "hai1"))
                        table.get_player(2).init_hand(
                            self.get_attribute_content(tag, "hai2"))
                        table.get_player(3).init_hand(
                            self.get_attribute_content(tag, "hai3"))

                        step += 1

                    if self.is_draw(tag):
                        tile = self.parse_tile(tag)
                        player_seat = self.get_player_seat(tag)
                        player = table.get_player(player_seat)

                        player.draw_tile(tile)

                        self.on_player_draw(player, table)

                    if self.is_discard(tag):
                        tile = self.parse_tile(tag)
                        player_seat = self.get_player_seat(tag)
                        player = table.get_player(player_seat)

                        is_tsumogiri = tile == player.last_drawn_tile
                        after_meld = player_seat == who_called_meld_on_this_step

                        discard = Discard(tile, is_tsumogiri, after_meld,
                                          False)
                        player.discard_tile(discard)

                        tenpai_after_discard = False
                        tiles_34 = TilesConverter.to_34_array(player.tiles)
                        melds_34 = player.melds_34
                        if self.shanten.calculate_shanten(tiles_34,
                                                          melds_34) == 0:
                            tenpai_after_discard = True

                            self.on_player_tenpai(player, table)
                        else:
                            player.in_tempai = False

                        player.discards[
                            -1].tenpai_after_discard = tenpai_after_discard
                        who_called_meld_on_this_step = None
                        self.on_player_discard(player, table, tile)

                    if self.is_meld_set(tag):
                        meld = self.parse_meld(tag)
                        player = table.get_player(meld.who)

                        # when we called chankan we need to remove pon set from hand
                        if meld.type == ParserMeld.CHANKAN:
                            player.tiles.remove(meld.called_tile)
                            pon_set = [
                                x for x in player.melds
                                if x.tiles[0] == meld.tiles[0]
                            ][0]
                            player.melds.remove(pon_set)

                        player.add_meld(meld)

                        # if it was not kan/chankan let's add it to the hand
                        if meld.type != ParserMeld.CHANKAN and meld.type != ParserMeld.KAN:
                            player.tiles.append(meld.called_tile)

                        # indication that tile was taken from discard
                        if meld.opened:
                            for meld_player in table.players:
                                if meld_player.discards and meld_player.discards[
                                        -1].tile == meld.called_tile:
                                    meld_player.discards[
                                        -1].was_given_for_meld = True

                        # for closed kan we had to remove tile from hand
                        if (meld.type == ParserMeld.KAN and not meld.opened
                                and meld.called_tile in player.tiles):
                            player.tiles.remove(meld.called_tile)

                        who_called_meld_on_this_step = meld.who

                    if self.is_riichi(tag):
                        riichi_step = int(
                            self.get_attribute_content(tag, "step"))
                        who = int(self.get_attribute_content(tag, "who"))
                        player = table.get_player(who)

                        if riichi_step == 1:
                            player.in_riichi = True

                        if riichi_step == 2:
                            player.discards[-1].after_riichi = True

                    if self.is_new_dora(tag):
                        dora = int(self.get_attribute_content(tag, "hai"))
                        table.add_dora(dora)

            except Exception as e:
                logger.error("Failed to process log: {}".format(log_id))
                logger.error(e, exc_info=True)

        return self.data_to_save

    def get_player_waiting(self, player):
        tiles = player.closed_hand
        if len(tiles) == 1:
            return [tiles[0] // 4]

        tiles_34 = TilesConverter.to_34_array(tiles)

        waiting = []
        for j in range(0, 34):
            # we already have 4 tiles in hand
            # and we can't wait on 5th
            if tiles_34[j] == 4:
                continue

            tiles_34[j] += 1
            if self.agari.is_agari(tiles_34):
                waiting.append(j)
            tiles_34[j] -= 1

        return waiting

    def calculate_waiting_costs(self, player, player_waiting):
        waiting = []
        for tile in player_waiting:
            config = HandConfig(
                is_riichi=player.discards[-1].after_riichi,
                player_wind=player.player_wind,
                round_wind=player.table.round_wind,
                options=OptionalRules(has_aka_dora=True, has_open_tanyao=True),
            )

            win_tile = tile * 4
            # we don't need to think, that our waiting is aka dora
            if win_tile in AKA_DORA_LIST:
                win_tile += 1

            tiles = player.tiles + [win_tile]

            result = self.finished_hand.estimate_hand_value(
                tiles, win_tile, player.melds, player.table.dora_indicators,
                config)

            if result.error:
                waiting.append({
                    "tile": win_tile,
                    "han": None,
                    "fu": None,
                    "cost": 0,
                    "yaku": []
                })
            else:
                waiting.append({
                    "tile":
                    win_tile,
                    "han":
                    result.han,
                    "fu":
                    result.fu,
                    "cost":
                    result.cost["main"],
                    "yaku": [{
                        "id": x.yaku_id,
                        "name": x.name
                    } for x in result.yaku],
                })

        return waiting

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

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

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

        return False

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

        return False

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

    def get_player_seat(self, tag):
        player_sign = tag.lower()[1]
        if player_sign == "d" or player_sign == "t":
            player_seat = 0
        elif player_sign == "e" or player_sign == "u":
            player_seat = 1
        elif player_sign == "f" or player_sign == "v":
            player_seat = 2
        else:
            player_seat = 3

        return player_seat

    def parse_meld(self, tag):
        who = int(self.get_attribute_content(tag, "who"))
        data = int(self.get_attribute_content(tag, "m"))

        meld = ParserMeld()
        meld.who = who
        meld.from_who = ((data & 0x3) + meld.who) % 4

        if data & 0x4:
            self.parse_chi(data, meld)
        elif data & 0x18:
            self.parse_pon(data, meld)
        elif data & 0x20:
            # nuki
            pass
        else:
            self.parse_kan(data, meld)

        return meld

    def parse_chi(self, data, meld):
        meld.type = ParserMeld.CHI
        t0, t1, t2 = (data >> 3) & 0x3, (data >> 5) & 0x3, (data >> 7) & 0x3
        base_and_called = data >> 10
        base = base_and_called // 3
        called = base_and_called % 3
        base = (base // 7) * 9 + base % 7
        meld.tiles = [
            t0 + 4 * (base + 0), t1 + 4 * (base + 1), t2 + 4 * (base + 2)
        ]
        meld.called_tile = meld.tiles[called]

    def parse_pon(self, data, meld):
        t4 = (data >> 5) & 0x3
        t0, t1, t2 = ((1, 2, 3), (0, 2, 3), (0, 1, 3), (0, 1, 2))[t4]
        base_and_called = data >> 9
        base = base_and_called // 3
        called = base_and_called % 3
        if data & 0x8:
            meld.type = ParserMeld.PON
            meld.tiles = [t0 + 4 * base, t1 + 4 * base, t2 + 4 * base]
            meld.called_tile = meld.tiles[called]
        else:
            meld.type = ParserMeld.CHANKAN
            meld.tiles = [
                t0 + 4 * base, t1 + 4 * base, t2 + 4 * base, t4 + 4 * base
            ]
            meld.called_tile = meld.tiles[3]

    def parse_kan(self, data, meld):
        base_and_called = data >> 8
        base = base_and_called // 4
        meld.type = ParserMeld.KAN
        meld.tiles = [4 * base, 1 + 4 * base, 2 + 4 * base, 3 + 4 * base]
        called = base_and_called % 4
        meld.called_tile = meld.tiles[called]
        # to mark closed\opened kans
        meld.opened = meld.who != meld.from_who

    def is_init_tag(self, tag):
        return tag and "INIT" in tag

    def is_redraw_tag(self, tag):
        return tag and "RYUUKYOKU" in tag

    def is_agari_tag(self, tag):
        return tag and "AGARI" in tag

    def is_log_id(self, tag):
        return tag and "LOG_ID" in tag

    def is_meld_set(self, tag):
        return tag and "<N who=" in tag

    def is_riichi(self, tag):
        return tag and "REACH " in tag

    def is_new_dora(self, tag):
        return tag and "<DORA" in tag
Ejemplo n.º 22
0
    def getTehais(self, foldername):
        hand = np.zeros((4, 34))
        shanten = Shanten()
        discard = np.array(4)
        text = self.text
        if (re.search('n3=""')):
            return
        #p1 = re.compile(r'hai\d="\d+"')
        """for i in range(4):
            Hand = re.findall('hai' + str(i) + '="(.+?)"',text)
            Hand = [kyoku.split(",") for kyoku in Hand]
            Hand = [[self.haiConverter(int(tile)) for tile in kyoku] for kyoku in Hand]
            initialHands.append(Hand)
            """
        inits = []
        for val in (re.finditer("<INIT", text)):
            inits.append(val.span())

        for i in range(len(inits)):
            csvfile = open("csvs/%s%d.csv" % (foldername, i), "w")
            hand = np.zeros((4, 34))
            discards = []
            if (i < len(inits) - 1):
                text = self.text[inits[i][1]:inits[i + 1][0]]
            else:
                text = self.text[inits[i][1]:]
            for j in range(4):
                Hand = re.findall('hai' + str(j) + '="(.+?)"', text)

                Hand = [kyoku.split(",") for kyoku in Hand]
                Hand = [[self.haiConverter(int(tile)) for tile in kyoku]
                        for kyoku in Hand]
                for k in range(len(Hand[0])):
                    hand[j][Hand[0][k]] += 1

            p1Tsumo = [
                self.haiConverter(int(tile[2:]))
                for tile in re.findall(r'<T\d+', text)
            ]
            p2Tsumo = [
                self.haiConverter(int(tile[2:]))
                for tile in re.findall(r'<U\d+', text)
            ]
            p3Tsumo = [
                self.haiConverter(int(tile[2:]))
                for tile in re.findall(r'<V\d+', text)
            ]
            p4Tsumo = [
                self.haiConverter(int(tile[2:]))
                for tile in re.findall(r'<W\d+', text)
            ]
            p1Discards = [
                self.haiConverter(int(tile[2:]))
                for tile in re.findall(r'<D\d+', text)
            ]
            p2Discards = [
                self.haiConverter(int(tile[2:]))
                for tile in re.findall(r'<E\d+', text)
            ]
            p3Discards = [
                self.haiConverter(int(tile[2:]))
                for tile in re.findall(r'<F\d+', text)
            ]
            p4Discards = [
                self.haiConverter(int(tile[2:]))
                for tile in re.findall(r'<G\d+', text)
            ]
            Tsumos = [p1Tsumo, p2Tsumo, p3Tsumo, p4Tsumo]
            Discards = [p1Discards, p2Discards, p3Discards, p4Discards]

            for player in range(4):
                discard = []
                smaller = len(Tsumos[player])
                if (len(Discards[player]) < len(Tsumos[player])):
                    smaller = len(Discards[player])
                for k in range(smaller):
                    hand[player][Tsumos[player][k]] += 1
                    hand[player][Discards[player][k]] -= 1
                    discard.append(Discards[player][k])
                    target = shanten.calculate_shanten(hand[player])
                    csvfile.write("%d" % target)
                    for m in range(len(discard)):
                        csvfile.write(",%s" % (discard[m]))
                    csvfile.write("\n")
                discards.append(discard)
            csvfile.flush()
            csvfile.close()
Ejemplo n.º 23
0
    def test_shanten_number(self):
        shanten = Shanten()

        tiles = self._string_to_34_array(sou="111234567", pin="11", man="567")
        self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles),
                         Shanten.AGARI_STATE)

        tiles = self._string_to_34_array(sou="111345677", pin="11", man="567")
        self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles), 0)

        tiles = self._string_to_34_array(sou="111345677", pin="15", man="567")
        self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles), 1)

        tiles = self._string_to_34_array(sou="11134567", pin="15", man="1578")
        self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles), 2)

        tiles = self._string_to_34_array(sou="113456", pin="1358", man="1358")
        self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles), 3)

        tiles = self._string_to_34_array(sou="1589",
                                         pin="13588",
                                         man="1358",
                                         honors="1")
        self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles), 4)

        tiles = self._string_to_34_array(sou="159",
                                         pin="13588",
                                         man="1358",
                                         honors="12")
        self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles), 5)

        tiles = self._string_to_34_array(sou="1589",
                                         pin="258",
                                         man="1358",
                                         honors="123")
        self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles), 6)

        tiles = self._string_to_34_array(sou="11123456788999")
        self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles),
                         Shanten.AGARI_STATE)

        tiles = self._string_to_34_array(sou="11122245679999")
        self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles), 0)

        tiles = self._string_to_34_array(sou="4566677",
                                         pin="1367",
                                         man="8",
                                         honors="12")
        self.assertEqual(shanten.calculate_shanten(tiles), 2)

        tiles = self._string_to_34_array(sou="14",
                                         pin="3356",
                                         man="3678",
                                         honors="2567")
        self.assertEqual(shanten.calculate_shanten(tiles), 4)

        tiles = self._string_to_34_array(sou="159",
                                         pin="17",
                                         man="359",
                                         honors="123567")
        self.assertEqual(shanten.calculate_shanten_for_regular_hand(tiles), 7)

        tiles = self._string_to_34_array(man="1111222235555", honors="1")
        self.assertEqual(shanten.calculate_shanten(tiles), 0)
Ejemplo n.º 24
0
class MjlogToCSV:
    def __init__(self, file_to_open):
        self.file_to_open = file_to_open
        self.mjlog = open(self.file_to_open, "r")
        self.text = self.mjlog.read()
        self.shanten = Shanten()


    """
    Gets the positions for <INIT tag to keep track of games
    """
    def getInitTagPos(self, text):
        inits = []
        for val in (re.finditer("<INIT", text)):
            inits.append(val.span())
        return inits

    """
    Cleans hand and discards
    """
    def initializeRound(self):
        hand = np.zeros((4,34))
        discards = []

        return hand, discards

    """
    Get initial hand from given text
    """
    def retrieveHand(self, text):
        hands = np.zeros((4,34))
        for j in range(4):
            Hand = re.findall('hai' + str(j) + '="(.+?)"',text)
            Hand = [kyoku.split(",") for kyoku in Hand]
            Hand = [[self.haiConverter(int(tile)) for tile in kyoku] for kyoku in Hand]
            for k in range(len(Hand[0])):
                hands[j][int(Hand[0][k])] += 1
        return hands

    """
    Get tsumos of players from given text
    """
    def retrieveTsumo(self, text):
        tsumos = []
        tsumos.append([self.haiConverter(int(tile[2:])) for tile in re.findall(r'<T\d+',text)])
        tsumos.append([self.haiConverter(int(tile[2:])) for tile in re.findall(r'<U\d+',text)])
        tsumos.append([self.haiConverter(int(tile[2:])) for tile in re.findall(r'<V\d+',text)])
        tsumos.append([self.haiConverter(int(tile[2:])) for tile in re.findall(r'<W\d+',text)])
        return tsumos
    """
    Get discards of players from given text
    """
    def retrieveDiscards(self, text):
        discards = []
        discards.append([self.haiConverter(int(tile[2:])) for tile in re.findall(r'<D\d+',text)])
        discards.append([self.haiConverter(int(tile[2:])) for tile in re.findall(r'<E\d+',text)])
        discards.append([self.haiConverter(int(tile[2:])) for tile in re.findall(r'<F\d+',text)])
        discards.append([self.haiConverter(int(tile[2:])) for tile in re.findall(r'<G\d+',text)])
        return discards

    """
    Get text for a round_num round
    """
    def getRoundText(self, text, round_num, inits):
        if(round_num < len(inits) - 1):
            return text[inits[round_num][1]:inits[round_num+1][0]]
        else:
            return text[inits[round_num][1]:]
    """
    Writes info in a specific way into csv
    Shanten | Discards
    """
    def writeToCSV(self, hands, tsumos, discards, csvfile):
        for player in range(4):
            discard = []
            smaller = len(tsumos[player])
            if(len(discards[player]) < len(tsumos[player])):
                smaller = len(discards[player])
            for k in range(smaller):
                hands[player][tsumos[player][k]] += 1
                hands[player][discards[player][k]] -= 1
                discard.append(discards[player][k])
                target = self.shanten.calculate_shanten(hands[player])
                csvfile.write("%d"%target)
                for m in range(len(discard)):
                    csvfile.write(",%s"%(discard[m]))
                csvfile.write("\n")

    """
    Checks if the game is Three people mahjong or not
    """
    def sanmaCheck(self):
        if(re.search('n3=\"\"',self.text)):
            return True
    """
    converts the mjlog into CSV file with specific format under /csvs/
   Csv file that is less than 10 bytes will be deleted here
    """
    def convertToCSV(self, foldername):
        hand, discards = self.initializeRound()
        filename = "csvs/%s.csv"%(foldername)
        csvfile = open(filename,"w")
        inits = self.getInitTagPos(self.text)

        for i in range(len(inits)):
            discards = []
            text = self.getRoundText(self.text, i, inits)
            if(re.search('<N',text)):
                #print("Naki, Not going to consider")
                continue

            hands = self.retrieveHand(text)
            tsumos = self.retrieveTsumo(text)
            discards = self.retrieveDiscards(text)
            self.writeToCSV(hands, tsumos, discards, csvfile)
        csvfile.flush()
        csvfile.close()
        if(os.stat(filename).st_size < 10):
            os.remove(filename)
            print("Insignificant size, deleted")

    """
    Converts 136 into 34 tile types
    """
    def haiConverter(self, tile):
        tile = tile / 4
        return int(tile)
Ejemplo n.º 25
0
class PaifuDataset(torch.utils.data.Dataset):
    pai_list = [
        "1m",
        "2m",
        "3m",
        "4m",
        "5m",
        "r5m",
        "6m",
        "7m",
        "8m",
        "9m",  #萬子
        "1p",
        "2p",
        "3p",
        "4p",
        "5p",
        "r5p",
        "6p",
        "7p",
        "8p",
        "9p",  #筒子
        "1s",
        "2s",
        "3s",
        "4s",
        "5s",
        "r5s",
        "6s",
        "7s",
        "8s",
        "9s",  #索子
        "東",
        "南",
        "西",
        "北",
        "白",
        "發",
        "中"
    ]

    def __init__(self, paifu_path_list, n_max=100):
        self.paifu_path_list = paifu_path_list
        self.data_size = len(paifu_path_list) * n_max
        self.shanten_calculator = Shanten()
        self.n_max = n_max
        # self.device =  'cuda' if torch.cuda.is_available() else 'cpu'
        self.device = 'cpu'

    def __len__(self):
        return self.data_size

    def __getitem__(self, idx):
        paifu = None

        file_idx = idx // self.n_max
        inner_idx = idx % self.n_max

        with open(self.paifu_path_list[file_idx], 'rb') as f:
            paifu = pickle.load(f)
        paifu = paifu[inner_idx]
        x, y = self.paifu_state_to_xy(paifu)
        # x = paifu['x'].to(self.device)
        # y = paifu['y'].to(self.device)
        return x, y
        # return paifu['x'], paifu['y']

    def paifu_state_to_xy(self, state):
        # device =  'cuda' if torch.cuda.is_available() else 'cpu'
        device = 'cpu'

        # action
        # Discard: 37, Reach:2 , Chow: 2, Pong: 2, Kong: 2
        x = {}

        positions = self.get_positions(state['action']['who'])

        # hand
        hand = state['hands'][state['action']['who']]
        hand = self.pais2ids(hand)
        x['hand'] = self.normalize_pai_list(hand, device)

        # discards : direction
        x['discards'] = self.normalize_discards(state['discards'], positions,
                                                device)

        # Shanten : direction
        x['shanten'], x['shanten_diff'] = self.calc_shantens(hand, device)

        # n_hands_list
        # x['n_hands_list'] = [len(h) for h in state['hands']]
        x['n_hands_list'] = [len(state['hands'][p]) for p in positions[1:]]

        # paifu id
        x['paifu_id'] = state['paifu_id']

        if state['action']['type'] == 'discard':
            y = state['hands'][state['action']['who']].index(
                state['action']['tile'])

            y = []
            n_players = 4
            for i in range(n_players - 1):
                enemy_hand = state['hands'][(state['action']['who'] + i + 1) %
                                            n_players]
                enemy_hand = self.pais2ids(enemy_hand)
                sep_token = 39
                y += enemy_hand
                y += [sep_token]
                # y += [i] * len(enemy_hand)
                # y += [4]

            # pad_token = 1
            pad_token = -100
            y += [pad_token] * ((13 + 1) * 3 + 1 - len(y))
            y = torch.tensor(y, dtype=torch.long, device=device)

        # melds : direction
        x['melds'] = [
            self.normalize_melds([m for m in state['melds'] if m['who'] == i],
                                 device) for i in positions
        ]

        # action_meld_tiles
        if state['action']['type'] in ['chow', 'pong', 'kong']:
            x['action_meld_tiles'] = self.normalize_action_meld_tiles(
                state['action']['meld_state']['meld_tiles'], device)
        else:
            x['action_meld_tiles'] = torch.zeros(4,
                                                 dtype=torch.long,
                                                 device=device)

        # menzen : direction
        x['menzen'] = torch.tensor([state['menzen'][i] for i in positions],
                                   dtype=torch.long,
                                   device=device)

        # reach_state : direction
        x['reach_state'] = torch.tensor(
            [state['reach_state'][i] for i in positions],
            dtype=torch.long,
            device=device)

        # n_reach
        x['n_reach'] = torch.tensor([min([state['n_reach'], 2])],
                                    dtype=torch.long,
                                    device=device)

        # reach_ippatsu : direction
        x['reach_ippatsu'] = torch.tensor(
            [state['reach_ippatsu'][i] for i in positions],
            dtype=torch.long,
            device=device)

        # doras
        x['doras'] = self.normalize_doras(state['doras'], device)

        # dans : direction
        x['dans'] = torch.tensor([state['dans'][i] for i in positions],
                                 dtype=torch.long,
                                 device=device)

        # rates : direction
        x['rates'] = self.normalize_rates(state['rates'],
                                          positions,
                                          device=device)

        # oya : direction
        x['oya'] = torch.tensor(
            [(state['oya'] - state['action']['who'] + 4) % 4],
            dtype=torch.long,
            device=device)

        # scores : direction
        x['scores'] = self.normalize_scores(state['scores'], positions, device)

        # n_honba
        x['n_honba'] = torch.tensor([min([state['n_honba'], 3])],
                                    dtype=torch.long,
                                    device=device)

        # n_round
        x['n_round'] = torch.tensor([state['n_round']],
                                    dtype=torch.long,
                                    device=device)

        # sanma_or_yonma
        x['sanma_or_yonma'] = torch.tensor([state['sanma_or_yonma']],
                                           dtype=torch.long,
                                           device=device)

        # han_or_ton
        x['han_or_ton'] = torch.tensor([state['han_or_ton']],
                                       dtype=torch.long,
                                       device=device)

        # aka_ari
        x['aka_ari'] = torch.tensor([state['aka_ari']],
                                    dtype=torch.long,
                                    device=device)

        # kui_ari
        x['kui_ari'] = torch.tensor([state['kui_ari']],
                                    dtype=torch.long,
                                    device=device)

        # who
        x['who'] = torch.tensor([state['action']['who']],
                                dtype=torch.long,
                                device=device)

        x['sum_discards'] = torch.tensor(
            [self.calc_sum_discards(state['discards'])],
            dtype=torch.long,
            device=device)

        return x, y

    def calc_sum_discards(self, discards):
        #  0から11 : 0
        # 12から23 : 1
        # 24から35 : 2
        # 36から47 : 3
        # 48から59 : 4
        # 60以上   : 5
        sum_discards = sum([len(d) for d in discards])

        if sum_discards >= 60:
            return 5

        return sum_discards // (4 * 3)

    def normalize_score(self, score):
        # 0 : 4000以下(満貫は2000-4000だから)
        # 1 : 4001から8000
        # …
        # 11 : 44000から48000
        # 12 : 48001以上

        score = (score - 1) // 4000

        if score < 0:
            return 0

        if score > 12:
            return 12

        return score

    def normalize_scores(self, scores, positions, device):
        return torch.tensor(
            [self.normalize_score(scores[i]) for i in positions],
            dtype=torch.long,
            device=device)

    def normalize_rate(self, rate):
        rate = int(rate) // 100 - 14

        if rate < 0:
            return 0

        if rate > 9:
            return 9

        return rate

    def normalize_rates(self, rates, positions, device):
        # id : rate range
        # 0 : 1499以下
        # 1 : 1500から1600
        # …
        # 9 : 2300以上

        return torch.tensor([self.normalize_rate(rates[i]) for i in positions],
                            dtype=torch.long,
                            device=device)

    def normalize_doras(self, doras, device, max_len=5):
        doras_tensor = torch.zeros(max_len, dtype=torch.long, device=device)
        l = len(doras)
        doras = self.pais2ids(doras)
        doras_tensor[:l] = torch.tensor(doras, dtype=torch.long, device=device)
        return doras_tensor

    def normalize_action_meld_tiles(self, tiles, device, max_len=4):
        tiles_tensor = torch.zeros(max_len, dtype=torch.long, device=device)
        l = len(tiles)
        tiles = self.pais2ids(tiles)
        tiles_tensor[:l] = torch.tensor(tiles, dtype=torch.long, device=device)

        return tiles_tensor

    def normalize_melds(self, melds, device, max_len=20):
        meld_tensor_list = [self.meld2tensor(meld, device) for meld in melds]
        meld_tensor = torch.zeros((2, max_len),
                                  dtype=torch.long,
                                  device=device)

        if len(meld_tensor_list) < 1:
            return meld_tensor

        meld_tensor_cat = torch.cat(meld_tensor_list, dim=1)
        l = meld_tensor_cat.size()[1]
        if l > max_len:
            print('Invalid meld data.')
            print(meld_tensor_cat)
        meld_tensor[:, :l] = meld_tensor_cat
        return meld_tensor

    def meld2tensor(self, meld, device):
        if meld['meld_type'] == 2:
            # Add Kan
            tiles = [self.pais2ids(meld['meld_tiles'])[0]]
        else:
            tiles = self.pais2ids(meld['meld_tiles'])

        l = len(tiles)
        tile_ids = torch.tensor(tiles, dtype=torch.long, device=device)
        token_type = torch.full((l, ),
                                fill_value=meld['meld_type'],
                                dtype=torch.long,
                                device=device)
        return torch.tensor([*tile_ids, *token_type]).reshape([2, -1])

    def normalize_discards(self, discards, positions, device):
        max_n_discards = 25
        l = len(discards)
        res = torch.zeros((4, max_n_discards), dtype=torch.long, device=device)

        for i, pos in enumerate(positions):
            res[i, :len(discards[pos])] = torch.tensor(self.pais2ids(
                discards[pos]),
                                                       dtype=torch.long,
                                                       device=device)
        return res

    def calc_shantens(self, hand, device):
        shantens = []
        hand_34_count = self.to_34_count(hand)
        hand_34 = self.to_34_array(hand)
        base_shanten, _ = self.calc_shanten(hand_34_count)

        # for tile in hand_34:
        #     hand_34_count[tile] -= 1
        #     shanten, _ = self.calc_shanten(hand_34_count)
        #     if shanten > base_shanten:
        #         shantens.append(1)
        #     else:
        #         shantens.append(0)
        #     hand_34_count[tile] += 1

        l = len(shantens)
        x = torch.full((14, ), fill_value=-1, dtype=torch.long, device=device)
        # x[:l] = torch.tensor(shantens, dtype=torch.long, device=device)

        base_shanten = min([base_shanten, 6]) + 1
        base_shanten = torch.tensor([base_shanten],
                                    dtype=torch.long,
                                    device=device)

        return base_shanten, x

    def to_34_array(self, hand):
        return [self.to_34(t) for t in hand]

    def to_34_count(self, hand):
        res = [0] * 34
        for tile in hand:
            res[self.to_34(tile)] += 1
        return res

    def to_34(self, tile):
        if tile <= 5:
            return tile - 1
        if tile <= 15:
            return tile - 2
        if tile <= 25:
            return tile - 3
        return tile - 4

    def calc_shanten(self, tiles_34, open_sets_34=None):
        shanten_with_chiitoitsu = self.shanten_calculator.calculate_shanten(
            tiles_34, open_sets_34, chiitoitsu=True)
        shanten_without_chiitoitsu = self.shanten_calculator.calculate_shanten(
            tiles_34, open_sets_34, chiitoitsu=False)

        return min([shanten_with_chiitoitsu, shanten_without_chiitoitsu
                    ]), shanten_with_chiitoitsu <= shanten_without_chiitoitsu

    def normalize_pai_list(self, pai_list, device, n=14):
        l = len(pai_list)
        x = torch.zeros(n, dtype=torch.long, device=device)
        x[:l] = torch.tensor(pai_list, dtype=torch.long, device=device)
        return x

    def pais2ids(self, pai_list):
        return [self.pai2id(pai, pai_list) for pai in pai_list]

    def pai2id(self, pai, pai_list=[]):
        assert (pai in pai_list)
        return self.pai_list.index(pai) + 1

    def get_positions(self, who, n_players=4):
        return [(i + who) % n_players for i in range(n_players)]
Ejemplo n.º 26
0
class FeatureGenerator:
    def __init__(self):
        """
        changed the input from filename to tiles_state_and_action data
        By Jun Lin
        """
        self.shanten_calculator = Shanten()
        self.hc = HandCalculator()
        self.sc = ScoresCalculator()
        self.hand_cache_shanten = {}
        self.hand_cache_points = {}

    def build_cache_key(self, tiles_34):
        return hashlib.md5(marshal.dumps(tiles_34)).hexdigest()

    def calculate_shanten_or_get_from_cache(self, closed_hand_34):
        key = self.build_cache_key(closed_hand_34)
        if key in self.hand_cache_shanten:
            return self.hand_cache_shanten[key]
        result = self.shanten_calculator.calculate_shanten(closed_hand_34)
        self.hand_cache_shanten[key] = result
        return result

    def calculate_ponits_or_get_from_cache(self, closed_left_tiles_34,
                                           win_tile, melds, dora_indicators):
        tiles_34 = closed_left_tiles_34[:]
        for meld in melds:
            for x in meld.tiles_34:
                tiles_34[x] += 1
        key = self.build_cache_key(tiles_34 + [win_tile] + dora_indicators)
        if key in self.hand_cache_points:
            return self.hand_cache_points[key]
        # print(closed_left_tiles_34)
        # print(melds)
        hc_result = self.hc.estimate_hand_value(
            TilesConverter.to_136_array(tiles_34), win_tile, melds,
            dora_indicators)
        sc_result = self.sc.calculate_scores(hc_result.han, hc_result.fu,
                                             HandConfig(HandConstants()))
        result = sc_result["main"]
        self.hand_cache_points[key] = result
        return result

    def canwinbyreplace(self, closed_left_tiles_34, melds, dora_indicators,
                        tiles_could_draw, replacelimit):
        def _draw(closed_left_tiles_34, melds, dora_indicators,
                  tiles_could_draw, replacelimit):
            if self.calculate_shanten_or_get_from_cache(
                    closed_left_tiles_34) > replacelimit:
                return 0
            result = 0
            if replacelimit == 0:
                for idx in range(34):
                    if tiles_could_draw[idx] > 0:
                        closed_left_tiles_34[idx] += 1
                        if self.calculate_shanten_or_get_from_cache(
                                closed_left_tiles_34) == -1:
                            ponits = self.calculate_ponits_or_get_from_cache(
                                closed_left_tiles_34, idx * 4, melds,
                                dora_indicators)
                            result = max(result, ponits)
                        closed_left_tiles_34[idx] -= 1

            else:
                for idx, count in enumerate(tiles_could_draw):
                    if count > 0:
                        tiles_could_draw[idx] -= 1
                        closed_left_tiles_34[idx] += 1
                        ponits = _discard(closed_left_tiles_34, melds,
                                          dora_indicators, tiles_could_draw,
                                          replacelimit)
                        result = max(result, ponits)
                        closed_left_tiles_34[idx] -= 1
                        tiles_could_draw[idx] += 1
            return result

        def _discard(closed_left_tiles_34, melds, dora_indicators,
                     tiles_could_draw, replacelimit):
            result = 0
            for idx, count in enumerate(closed_left_tiles_34):
                if count > 0:
                    closed_left_tiles_34[idx] -= 1
                    replacelimit -= 1
                    ponits = _draw(closed_left_tiles_34, melds,
                                   dora_indicators, tiles_could_draw,
                                   replacelimit)
                    result = max(result, ponits)
                    replacelimit += 1
                    closed_left_tiles_34[idx] += 1
            return result

        return _draw(closed_left_tiles_34, melds, dora_indicators,
                     tiles_could_draw, replacelimit)

    def open_hands_detail_to_melds(self, open_hands_detail):
        melds = []
        for ohd in open_hands_detail:
            tiles = ohd["tiles"]
            if ohd["meld_type"] == "Pon":
                meld_type = "pon"
                opened = True
            elif ohd["meld_type"] == "Chi":
                meld_type = "chi"
                opened = True
            elif ohd["meld_type"] == "AnKan":
                meld_type = "kan"
                opened = False
            else:
                meld_type = "kan"
                opened = True
            meld = Meld(meld_type, tiles, opened)
            melds.append(meld)
        return melds

    def getPlayerTiles(self, player_tiles):
        closed_hand_136 = player_tiles.get('closed_hand:', [])
        open_hand_136 = player_tiles.get('open_hand', [])
        discarded_tiles_136 = player_tiles.get('discarded_tiles', [])

        closed_hand_feature = np.zeros((4, 34))
        open_hand_feature = np.zeros((4, 34))
        discarded_tiles_feature = np.zeros((4, 34))

        for val in closed_hand_136:
            idx = 0
            while (closed_hand_feature[idx][val // 4] == 1):
                idx += 1
            closed_hand_feature[idx][val // 4] = 1
        for val in open_hand_136:
            idx = 0
            while (open_hand_feature[idx][val // 4] == 1):
                idx += 1
            open_hand_feature[idx][val // 4] = 1
        for val in discarded_tiles_136:
            idx = 0
            while (discarded_tiles_feature[idx][val // 4] == 1):
                idx += 1
            discarded_tiles_feature[idx][val // 4] = 1

        return np.concatenate(
            (closed_hand_feature, open_hand_feature, discarded_tiles_feature))

    def getSelfTiles(self, tiles_state_and_action):
        player_tiles = tiles_state_and_action["player_tiles"]
        return self.getPlayerTiles(player_tiles)

    def getEnemiesTiles(self, tiles_state_and_action):
        player_seat = tiles_state_and_action["player_id"]
        enemies_tiles_list = tiles_state_and_action["enemies_tiles"]
        if (len(enemies_tiles_list) == 3):
            enemies_tiles_list.insert(player_seat,
                                      tiles_state_and_action["player_tiles"])
        enemies_tiles_feature = np.empty((0, 34))
        for i in range(3):
            player_seat = (player_seat + 1) % 4
            player_tiles = self.getPlayerTiles(enemies_tiles_list[player_seat])
            enemies_tiles_feature = np.concatenate(
                (enemies_tiles_feature, player_tiles))
        return enemies_tiles_feature

    def getDoraIndicatorList(self, tiles_state_and_action):
        dora_indicator_list = tiles_state_and_action["dora"]
        dora_indicator_feature = np.zeros((5, 34))
        for idx, val in enumerate(dora_indicator_list):
            dora_indicator_feature[idx][val // 4] = 1
        return dora_indicator_feature

    def getDoraList(self, tiles_state_and_action):
        def indicator2dora(dora_indicator):
            dora = dora_indicator // 4
            if dora < 27:  # EAST
                if dora == 8:
                    dora = -1
                elif dora == 17:
                    dora = 8
                elif dora == 26:
                    dora = 17
            else:
                dora -= 9 * 3
                if dora == 3:
                    dora = -1
                elif dora == 6:
                    dora = 3
                dora += 9 * 3
            dora += 1
            return dora

        dora_indicator_list = tiles_state_and_action["dora"]
        dora_feature = np.zeros((5, 34))
        for idx, dora_indicator in enumerate(dora_indicator_list):
            dora_feature[idx][indicator2dora(dora_indicator)] = 1
        return dora_feature

    def getScoreList1(self, tiles_state_and_action):
        def trans_score(score):
            feature = np.zeros((34))
            if score > 50000:
                score = 50000
            a = score // (50000.0 / 33)
            # alpha = a + 1 - (score*33.0/50000)
            # alpha = round(alpha, 2)
            # feature[int(a)] = alpha
            # if a<33:
            #     feature[int(a+1)] = 1-alpha
            feature[int(a)] = 1
            return feature

        player_seat = tiles_state_and_action["player_id"]
        scores_list = tiles_state_and_action["scores"]
        scores_feature = np.zeros((4, 34))
        for i in range(4):
            score = scores_list[player_seat]
            scores_feature[i] = trans_score(score)
            player_seat += 1
            player_seat %= 4
        return scores_feature

    def getBoard1(self, tiles_state_and_action):
        def wind(c):
            if c == 'E':
                return 27
            if c == 'S':
                return 28
            if c == 'W':
                return 29
            if c == 'N':
                return 30

        player_seat = tiles_state_and_action["player_id"]
        dealer_seat = tiles_state_and_action["dealer"]
        repeat_dealer = tiles_state_and_action["repeat_dealer"]
        riichi_bets = tiles_state_and_action["riichi_bets"]
        player_wind = tiles_state_and_action["player_wind"]
        prevailing_wind = tiles_state_and_action["prevailing_wind"]

        dealer_feature = np.zeros((1, 34))
        dealer_feature[0][(dealer_seat + 4 - player_seat) % 4] = 1

        repeat_dealer_feature = np.zeros((1, 34))
        repeat_dealer_feature[0][repeat_dealer] = 1

        riichi_bets_feature = np.zeros((1, 34))
        riichi_bets_feature[0][riichi_bets] = 1

        player_wind_feature = np.zeros((1, 34))
        player_wind_feature[0][wind(player_wind)] = 1

        prevailing_wind_feature = np.zeros((1, 34))
        prevailing_wind_feature[0][wind(prevailing_wind)] = 1

        return np.concatenate(
            (dealer_feature, repeat_dealer_feature, riichi_bets_feature,
             player_wind_feature, prevailing_wind_feature))

    def getLookAheadFeature(self, tiles_state_and_action):
        # 0 for whether can be discarded
        # 1 2 3 for shanten
        # 4 5 6 7 for whether can get 2k 4k 6k 8k points with replacing 3 tiles ---- need review: takes too long!
        # 8 9 10 for in Shimocha Toimen Kamicha discarded
        # lookAheadFeature = np.zeros((11, 34))
        player_tiles = tiles_state_and_action["player_tiles"]
        closed_hand_136 = player_tiles.get('closed_hand:', [])
        open_hands_detail = tiles_state_and_action["open_hands_detail"]
        melds = self.open_hands_detail_to_melds(open_hands_detail)
        discarded_tiles_136 = player_tiles.get('discarded_tiles', [])
        player_seat = tiles_state_and_action["player_id"]
        enemies_tiles_list = tiles_state_and_action["enemies_tiles"]
        dora_indicators = tiles_state_and_action["dora"]
        if (len(enemies_tiles_list) == 3):
            enemies_tiles_list.insert(player_seat, player_tiles)
        tiles_could_draw = np.ones(34) * 4
        for player in enemies_tiles_list:
            for tile_set in [
                    player.get('closed_hand:', []),
                    player.get('open_hand', []),
                    player.get('discarded_tiles', [])
            ]:
                for tile in tile_set:
                    tiles_could_draw[tile // 4] -= 1
        for dora_tile in dora_indicators:
            tiles_could_draw[tile // 4] -= 1

        def feature_process(i, closed_hand_136, melds, dora_indicators,
                            tiles_could_draw, player_seat, enemies_tiles_list):
            feature = np.zeros((11, 1))
            discard_tiles = [
                x for x in [i * 4, i * 4 + 1, i * 4 + 2, i * 4 + 3]
                if x in closed_hand_136
            ]
            if len(discard_tiles) != 0:
                discard_tile = discard_tiles[0]
                feature[0] = 1
                closed_left_tiles_34 = TilesConverter.to_34_array(
                    [t for t in closed_hand_136 if t != discard_tile])
                shanten = self.calculate_shanten_or_get_from_cache(
                    closed_left_tiles_34)
                for i in range(3):
                    if shanten <= i:
                        feature[i + 1] = 1
                maxscore = 0  #self.canwinbyreplace(closed_left_tiles_34, melds, dora_indicators,
                #tiles_could_draw, replacelimit = 2)
                scores = [2000, 4000, 6000, 8000]
                for i in range(4):
                    if maxscore >= scores[i]:
                        feature[i + 4] = 1
                seat = player_seat
                for i in range(3):
                    seat = (seat + 1) % 4
                    if discard_tile // 4 in [
                            t // 4 for t in enemies_tiles_list[seat].get(
                                'discarded_tiles', [])
                    ]:
                        feature[i + 8] = 1
            return feature

        results = Parallel(n_jobs=8)(delayed(
            feature_process)(i, closed_hand_136, melds, dora_indicators,
                             tiles_could_draw, player_seat, enemies_tiles_list)
                                     for i in range(34))
        return np.concatenate(results, axis=1)

    def getGeneralFeature(self, tiles_state_and_action):
        return np.concatenate((
            #self.getLookAheadFeature(tiles_state_and_action), #(11,34)
            self.getSelfTiles(tiles_state_and_action),  # (12,34)
            self.getDoraList(tiles_state_and_action),  # (5,34)
            self.getBoard1(tiles_state_and_action),  # (5,34)
            self.getEnemiesTiles(tiles_state_and_action),  # (36,34)
            self.getScoreList1(tiles_state_and_action)  # (4,34)
        ))

    def ChiFeatureGenerator(self, tiles_state_and_action):
        """
        changed the input from filename to tiles_state_and_action data
        By Jun Lin
        """
        def _chilist(last_player_discarded_tile, closed_hand_136):
            res = []
            last_player_discarded_tile_34 = last_player_discarded_tile // 4
            tile_set = last_player_discarded_tile_34 // 9
            if tile_set == 3:
                return res
            closed_hand_34_detail = [[] for i in range(34)]
            for tile in closed_hand_136:
                closed_hand_34_detail[tile // 4].append(tile)
            if last_player_discarded_tile_34 > tile_set * 9 + 1:
                if len(closed_hand_34_detail[last_player_discarded_tile_34 - 1]
                       ) != 0 and len(closed_hand_34_detail[
                           last_player_discarded_tile_34 - 2]) != 0:
                    res.append([
                        closed_hand_34_detail[last_player_discarded_tile_34 -
                                              1][0],
                        closed_hand_34_detail[last_player_discarded_tile_34 -
                                              2][0], last_player_discarded_tile
                    ])
            if last_player_discarded_tile_34 > tile_set * 9 and last_player_discarded_tile_34 < tile_set * 9 + 8:
                if len(closed_hand_34_detail[last_player_discarded_tile_34 - 1]
                       ) != 0 and len(closed_hand_34_detail[
                           last_player_discarded_tile_34 + 1]) != 0:
                    res.append([
                        closed_hand_34_detail[last_player_discarded_tile_34 -
                                              1][0],
                        closed_hand_34_detail[last_player_discarded_tile_34 +
                                              1][0], last_player_discarded_tile
                    ])
            if last_player_discarded_tile_34 < tile_set * 9 + 7:
                if len(closed_hand_34_detail[last_player_discarded_tile_34 + 1]
                       ) != 0 and len(closed_hand_34_detail[
                           last_player_discarded_tile_34 + 2]) != 0:
                    res.append([
                        closed_hand_34_detail[last_player_discarded_tile_34 +
                                              1][0],
                        closed_hand_34_detail[last_player_discarded_tile_34 +
                                              2][0], last_player_discarded_tile
                    ])
            return res
            # res = []
            # pairs = [(a, b) for idx, a in enumerate(closed_hand_136) for b in closed_hand_136[idx + 1:]]
            # for p in pairs:
            #     meld = [last_player_discarded_tile,p[0],p[1]]
            #     if all(tile < 36 for tile in meld) or all(36 <= tile < 72 for tile in meld) or all(36 <= tile < 72 for tile in meld):
            #         meld34 = [tile//4 for tile in meld]
            #         if any(tile+1 in meld34 and tile-1 in meld34 for tile in meld34):
            #             res.append(meld)
            # return res

        could_chi = tiles_state_and_action["could_chi"]
        last_player_discarded_tile = tiles_state_and_action[
            "last_player_discarded_tile"]
        closed_hand_136 = tiles_state_and_action["player_tiles"][
            'closed_hand:']
        action = tiles_state_and_action["action"]
        if could_chi == 1:
            generalFeature = self.getGeneralFeature(tiles_state_and_action)
            if action[0] == 'Chi':
                print(_chilist(last_player_discarded_tile, closed_hand_136))
                print(action)
            for chimeld in _chilist(last_player_discarded_tile,
                                    closed_hand_136):
                last_player_discarded_tile_feature = np.zeros((1, 34))
                for chitile in chimeld:
                    last_player_discarded_tile_feature[0][chitile // 4] = 1
                x = np.concatenate(
                    (last_player_discarded_tile_feature, generalFeature))
                if action[0] == 'Chi' and all(
                        chitile // 4 in [a // 4 for a in action[1]]
                        for chitile in chimeld):
                    y = 1
                else:
                    y = 0
                # yield {'features': x.reshape((x.shape[0], x.shape[1], 1)),
                #        "labels": to_categorical(y, num_classes=2)}
                yield x.reshape(
                    (x.shape[0], x.shape[1], 1)), to_categorical(y,
                                                                 num_classes=2)

    def PonFeatureGenerator(self, tiles_state_and_action):
        """
        changed the input from filename to tiles_state_and_action data
        By Jun Lin
        """
        last_discarded_tile = tiles_state_and_action[
            "last_player_discarded_tile"]
        closed_hand_136 = tiles_state_and_action["player_tiles"][
            'closed_hand:']
        action = tiles_state_and_action["action"]
        if tiles_state_and_action["could_pon"] == 1:
            last_discarded_tile_feature = np.zeros((1, 34))
            last_discarded_tile_feature[0][last_discarded_tile // 4] = 1
            x = np.concatenate(
                (last_discarded_tile_feature,
                 self.getGeneralFeature(tiles_state_and_action)))
            if action[0] == 'Pon':
                y = 1
            else:
                y = 0
            # yield {'features': x.reshape((x.shape[0], x.shape[1], 1)),
            #        "labels": to_categorical(y, num_classes=2)}
            yield x.reshape(
                (x.shape[0], x.shape[1], 1)), to_categorical(y, num_classes=2)

    def KanFeatureGenerator(self, tiles_state_and_action):
        """
        changed the input from filename to tiles_state_and_action data
        By Jun Lin
        """
        def could_ankan(closed_hand_136):
            count = np.zeros(34)
            for tile in closed_hand_136:
                count[tile // 4] += 1
                if count[tile // 4] == 4:
                    return True
            return False

        def could_kakan(closed_hand_136, open_hand_136):
            count = np.zeros(34)
            for tile in open_hand_136:
                count[tile // 4] += 1
            for tile in closed_hand_136:
                if count[tile // 4] == 3:
                    return True
            return False

        last_discarded_tile = tiles_state_and_action[
            "last_player_discarded_tile"]
        closed_hand_136 = tiles_state_and_action["player_tiles"][
            'closed_hand:']
        open_hand_136 = tiles_state_and_action["player_tiles"]['open_hand']
        action = tiles_state_and_action["action"]
        if tiles_state_and_action["could_minkan"] == 1:  # Minkan
            kan_type_feature = np.zeros((3, 34))
            kan_type_feature[0] = 1
            last_discarded_tile_feature = np.zeros((1, 34))
            last_discarded_tile_feature[0][last_discarded_tile // 4] = 1
            x = np.concatenate(
                (kan_type_feature, last_discarded_tile_feature,
                 self.getGeneralFeature(tiles_state_and_action)))

            if action[0] == 'MinKan' and (last_discarded_tile in action[1]):
                y = 1
            else:
                y = 0
            # yield {'features': x.reshape((x.shape[0], x.shape[1], 1)),
            #        "labels": to_categorical(y, num_classes=2)}
            yield x.reshape(
                (x.shape[0], x.shape[1], 1)), to_categorical(y, num_classes=2)
        else:
            if could_ankan(closed_hand_136):  # AnKan
                kan_type_feature = np.zeros((3, 34))
                kan_type_feature[1] = 1
                last_discarded_tile_feature = np.zeros((1, 34))
                x = np.concatenate(
                    (kan_type_feature, last_discarded_tile_feature,
                     self.getGeneralFeature(tiles_state_and_action)))

                if action[0] == 'AnKan':
                    y = 1
                else:
                    y = 0
                # yield {'features': x.reshape((x.shape[0], x.shape[1], 1)),
                #        "labels": to_categorical(y, num_classes=2)}
                yield x.reshape(
                    (x.shape[0], x.shape[1], 1)), to_categorical(y,
                                                                 num_classes=2)
            else:
                if could_kakan(closed_hand_136, open_hand_136):  # KaKan
                    kan_type_feature = np.zeros((3, 34))
                    kan_type_feature[2] = 1
                    last_discarded_tile_feature = np.zeros((1, 34))
                    x = np.concatenate(
                        (kan_type_feature, last_discarded_tile_feature,
                         self.getGeneralFeature(tiles_state_and_action)))
                    if action[0] == 'KaKan':
                        y = 1
                    else:
                        y = 0
                    # yield {'features': x.reshape((x.shape[0], x.shape[1], 1)),
                    #        "labels": to_categorical(y, num_classes=2)}
                    yield x.reshape((x.shape[0], x.shape[1],
                                     1)), to_categorical(y, num_classes=2)

    def RiichiFeatureGenerator(self, tiles_state_and_action):
        action = tiles_state_and_action["action"]
        if tiles_state_and_action["is_FCH"] == 1:
            tiles_34 = TilesConverter.to_34_array(
                tiles_state_and_action["player_tiles"]["closed_hand:"])
            # min_shanten = self.shanten_calculator.calculate_shanten(tiles_34)
            min_shanten = self.calculate_shanten_or_get_from_cache(tiles_34)
            if min_shanten == 0:
                x = self.getGeneralFeature(tiles_state_and_action)
                if action[0] == 'REACH':
                    y = 1
                else:
                    y = 0
                # yield {'features': x.reshape((x.shape[0], x.shape[1], 1)),
                #        "labels": to_categorical(y, num_classes=2)}
                yield x.reshape(
                    (x.shape[0], x.shape[1], 1)), to_categorical(y,
                                                                 num_classes=2)

    def DiscardFeatureGenerator(self, tiles_state_and_action):
        x = self.getGeneralFeature(tiles_state_and_action)
        y = tiles_state_and_action["discarded_tile"] // 4
        yield x.reshape(
            (x.shape[0], x.shape[1], 1)), to_categorical(y, num_classes=34)
Ejemplo n.º 27
0
def check_ryuiso(hand):
    return np.isin(hand, [19, 20, 21, 23, 25, 32]).all()


shanten = Shanten()

cases = []
for key in tqdm(product(range(5), repeat=9), total=5 ** 9):
    if sum(key) == 14:
        hand = [0] * 18 + list(key) + [0] * 7  # 索子14枚
    elif sum(key) == 12:
        hand = [0] * 18 + list(key) + [0, 0, 0, 0, 0, 2, 0]  # 索子12枚 + 發2枚
    elif sum(key) == 11:
        hand = [0] * 18 + list(key) + [0, 0, 0, 0, 0, 3, 0]  # 索子11枚 + 發3枚
    else:
        continue

    if shanten.calculate_shanten(hand) == -1:
        # 和了形の場合
        tiles = flatten_tile34(hand)
        win_tile = np.random.choice(tiles)
        is_established = check_ryuiso(tiles)

        cases.append((tiles, win_tile, is_established))

with open(TESTCASE_DIR / "test_score_ryuiso.txt", "w") as f:
    for hand, win_tile, is_established in cases:
        hand_str = " ".join(str(x) for x in hand)
        f.write(f"{hand_str} {win_tile} {int(is_established)}\n")
Ejemplo n.º 28
0
class ImplementationAI(InterfaceAI):
    """
    AI that will discard tiles as to minimize shanten, using perfect shanten calculation.
    Picks the first tile with resulting in the lowest shanten value when choosing what to discard.
    Calls riichi if possible and hand is closed.
    Always calls wins.
    Calls kan to upgrade pon or on equivalent or reduced shanten.
    Calls melds to reduce shanten.
    """
    version = 'shantenNaive'

    shanten = None
    agari = None

    def __init__(self, player):
        super(ImplementationAI, self).__init__(player)
        self.shanten = Shanten()
        # self.agari = Agari()
        self.hand_divider = HandDivider()

    # TODO: Merge all discard functions into one to prevent code reuse and unnecessary duplication of variables
    def discard_tile(self, discard_tile):

        if discard_tile is not None:
            return discard_tile

        tiles_34 = TilesConverter.to_34_array(self.player.tiles)
        closed_tiles_34 = TilesConverter.to_34_array(self.player.closed_hand)
        # is_agari = self.agari.is_agari(tiles_34, self.player.open_hand_34_tiles)

        results = []

        for tile in range(0,34):
            # Can the tile be discarded from the concealed hand?
            if not closed_tiles_34[tile]:
                continue

            # discard the tile from hand
            tiles_34[tile] -= 1

            # calculate shanten and store
            shanten = self.shanten.calculate_shanten(tiles_34, self.player.open_hand_34_tiles)
            results.append((shanten, tile))

            # return tile to hand
            tiles_34[tile] += 1

        (shanten, discard_34) = min(results)

        discard_136 = TilesConverter.find_34_tile_in_136_array(discard_34, self.player.closed_hand)

        if discard_136 is None:
            logger.debug('Greedy search or tile conversion failed')
            discard_136 = random.randrange(len(self.player.tiles) - 1)
            discard_136 = self.player.tiles[discard_136]
        logger.info('Shanten after discard:' + str(shanten))
        return discard_136

    def should_call_riichi(self):
        return True

    def should_call_win(self, tile, enemy_seat):
        return True

    def should_call_kan(self, tile, open_kan):
        """
        When bot can call kan or chankan this method will be called
        :param tile: 136 tile format
        :param is_open_kan: boolean
        :return: kan type (Meld.KAN, Meld.CHANKAN) or None
        """

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

            # don't call open kan if not waiting for win
            if not self.player.in_tempai:
                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)]

        # upgrade open pon to kan if possible
        if pon_melds:
            for meld in pon_melds:
                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

        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)

            # check for improvement in shanten
            if new_shanten <= previous_shanten:
                return Meld.KAN

        return None

    def try_to_call_meld(self, tile, is_kamicha_discard):
        """
        When bot can open hand with a set (chi or pon/kan) this method will be called
        :param tile: 136 format tile
        :param is_kamicha_discard: boolean
        :return: Meld and DiscardOption objects or None, None
        """

        # can't call if in riichi
        if self.player.in_riichi:
            return None, None

        closed_hand = self.player.closed_hand[:]

        # check for appropriate hand size, seems to solve a bug
        if len(closed_hand) == 1:
            return None, None

        # get old shanten value
        old_tiles_34 = TilesConverter.to_34_array(self.player.tiles)
        old_shanten = self.shanten.calculate_shanten(old_tiles_34, self.player.open_hand_34_tiles)

        # setup
        discarded_tile = tile // 4
        new_closed_hand_34 = TilesConverter.to_34_array(closed_hand + [tile])

        # We will use hand_divider to find possible melds involving the discarded tile.
        # Check its suit and number to narrow the search conditions
        # skipping this will break the default mahjong functions
        combinations = []
        first_index = 0
        second_index = 0
        if is_man(discarded_tile):
            first_index = 0
            second_index = 8
        elif is_pin(discarded_tile):
            first_index = 9
            second_index = 17
        elif is_sou(discarded_tile):
            first_index = 18
            second_index = 26

        if second_index == 0:
            # honor tiles
            if new_closed_hand_34[discarded_tile] == 3:
                combinations = [[[discarded_tile] * 3]]
        else:
            # to avoid not necessary calculations
            # we can check only tiles around +-2 discarded tile
            first_limit = discarded_tile - 2
            if first_limit < first_index:
                first_limit = first_index

            second_limit = discarded_tile + 2
            if second_limit > second_index:
                second_limit = second_index

            combinations = self.hand_divider.find_valid_combinations(new_closed_hand_34,
                                                                           first_limit,
                                                                           second_limit, True)
        # Reduce combinations to list of melds
        if combinations:
            combinations = combinations[0]

        # Verify that a meld can be called
        possible_melds = []
        for meld_34 in combinations:
            # we can call pon from everyone
            if is_pon(meld_34) and discarded_tile in meld_34:
                if meld_34 not in possible_melds:
                    possible_melds.append(meld_34)

            # we can call chi only from left player
            if is_chi(meld_34) and is_kamicha_discard and discarded_tile in meld_34:
                if meld_34 not in possible_melds:
                    possible_melds.append(meld_34)

        # For each possible meld, check if calling it and discarding can improve shanten
        new_shanten = float('inf')
        discard_136 = None
        tiles = None

        for meld_34 in possible_melds:
            shanten, disc = self.meldDiscard(meld_34, tile)
            if shanten < new_shanten:
                new_shanten, discard_136 = shanten, disc
                tiles = meld_34

        # If shanten can be improved by calling meld, call it
        if new_shanten < old_shanten:
            meld = Meld()
            meld.type = is_chi(tiles) and Meld.CHI or Meld.PON

            # convert meld tiles back to 136 format for Meld type return
            # find them in a copy of the closed hand and remove
            tiles.remove(discarded_tile)

            first_tile = TilesConverter.find_34_tile_in_136_array(tiles[0], closed_hand)
            closed_hand.remove(first_tile)

            second_tile = TilesConverter.find_34_tile_in_136_array(tiles[1], closed_hand)
            closed_hand.remove(second_tile)

            tiles_136 = [
                first_tile,
                second_tile,
                tile
            ]

            discard_136 = TilesConverter.find_34_tile_in_136_array(discard_136 // 4, closed_hand)
            meld.tiles = sorted(tiles_136)
            return meld, discard_136

        return None, None

    # TODO: Merge all discard functions into one to prevent code reuse and unnecessary duplication of variables
    def meldDiscard(self, meld_34, discardtile):

        tiles_34 = TilesConverter.to_34_array(self.player.tiles + [discardtile])
        closed_tiles_34 = TilesConverter.to_34_array(self.player.closed_hand + [discardtile])
        open_hand_34 = copy.deepcopy(self.player.open_hand_34_tiles)

        # remove meld from closed and and add to open hand
        open_hand_34.append(meld_34)
        for tile_34 in meld_34:
            closed_tiles_34[tile_34] -= 1

        results = []

        for tile in range(0, 34):

            # Can the tile be discarded from the concealed hand?
            if not closed_tiles_34[tile]:
                continue

            # discard the tile from hand
            tiles_34[tile] -= 1

            # calculate shanten and store
            shanten = self.shanten.calculate_shanten(tiles_34, open_hand_34)
            results.append((shanten, tile))

            # return tile to hand
            tiles_34[tile] += 1

        (shanten, discard_34) = min(results)

        discard_136 = TilesConverter.find_34_tile_in_136_array(discard_34, self.player.closed_hand)

        return shanten, discard_136
Ejemplo n.º 29
0
melds = [
    Meld(meld_type=Meld.PON,
         tiles=TilesConverter.string_to_136_array(man='444'))
]

result = calculator.estimate_hand_value(
    tiles, win_tile, melds=melds, config=HandConfig(has_open_tanyao=True))
print_hand_result(result)

####################################################################
# Shanten calculation                                              #
####################################################################

shanten = Shanten()
tiles = TilesConverter.string_to_34_array(man='13569', pin='123459', sou='443')
result = shanten.calculate_shanten(tiles)

print(result)

####################################################################
# Kazoe as a sanbaiman                                             #
####################################################################

tiles = TilesConverter.string_to_136_array(man='22244466677788')
win_tile = TilesConverter.string_to_136_array(man='7')[0]
melds = [Meld(Meld.KAN, TilesConverter.string_to_136_array(man='2222'), False)]

dora_indicators = [
    TilesConverter.string_to_136_array(man='1')[0],
    TilesConverter.string_to_136_array(man='1')[0],
    TilesConverter.string_to_136_array(man='1')[0],
Ejemplo n.º 30
0
def check_ryuiso(hand):
    return np.isin(hand, [19, 20, 21, 23, 25, 32]).all()


shanten = Shanten()

cases = []

# 大三元
for key in tqdm(product(range(5), repeat=9), total=5**9):
    if sum(key) == 5:
        tiles = list(key) + [0] * 22 + [3, 3, 3]
    else:
        continue

    if shanten.calculate_shanten(tiles) == -1:
        tiles = flatten_tile34(tiles)
        win_tile = np.random.choice(tiles)
        cases.append((tiles, win_tile, True))

# 小三元
for key in tqdm(product(range(5), repeat=9), total=5**9):
    if sum(key) == 6:
        tiles = list(key) + [0] * 22 + np.random.permutation([2, 3, 3
                                                              ]).tolist()
    else:
        continue

    if shanten.calculate_shanten(tiles) == -1:
        tiles = flatten_tile34(tiles)
        win_tile = np.random.choice(tiles)