예제 #1
0
    def test_chose_strategy_and_reset_strategy(self):
        table = Table()
        table.has_open_tanyao = True
        player = table.player

        tiles = self._string_to_136_array(man='33355788',
                                          sou='3479',
                                          honors='3')
        player.init_hand(tiles)
        self.assertEqual(player.ai.current_strategy.type, BaseStrategy.TANYAO)

        # we draw a tile that will change our selected strategy
        tile = self._string_to_136_tile(sou='8')
        player.draw_tile(tile)
        self.assertEqual(player.ai.current_strategy, None)

        tiles = self._string_to_136_array(man='33355788',
                                          sou='3479',
                                          honors='3')
        player.init_hand(tiles)
        self.assertEqual(player.ai.current_strategy.type, BaseStrategy.TANYAO)

        # for already opened hand we don't need to give up on selected strategy
        meld = Meld()
        meld.tiles = [1, 2, 3]
        player.add_called_meld(meld)
        tile = self._string_to_136_tile(sou='8')
        player.draw_tile(tile)
        self.assertEqual(player.ai.current_strategy.type, BaseStrategy.TANYAO)
예제 #2
0
    def test_call_meld(self):
        client = Client()

        client.table.init_round(0, 0, 0, 0, 0, [0, 0, 0, 0])
        self.assertEqual(client.table.count_of_remaining_tiles, 70)

        meld = Meld()
        meld.who = 3

        client.call_meld(meld)

        self.assertEqual(len(client.table.get_player(3).melds), 1)
        self.assertEqual(client.table.count_of_remaining_tiles, 71)
예제 #3
0
    def _find_best_meld_to_open(self, possible_melds, new_tiles, closed_hand,
                                discarded_tile):
        discarded_tile_34 = discarded_tile // 4

        final_results = []
        for meld_34 in possible_melds:
            meld_34_copy = meld_34.copy()
            closed_hand_copy = closed_hand.copy()

            meld_type = is_chi(meld_34_copy) and Meld.CHI or Meld.PON
            meld_34_copy.remove(discarded_tile_34)

            first_tile = TilesConverter.find_34_tile_in_136_array(
                meld_34_copy[0], closed_hand_copy)
            closed_hand_copy.remove(first_tile)

            second_tile = TilesConverter.find_34_tile_in_136_array(
                meld_34_copy[1], closed_hand_copy)
            closed_hand_copy.remove(second_tile)

            tiles = [first_tile, second_tile, discarded_tile]

            meld = Meld()
            meld.type = meld_type
            meld.tiles = sorted(tiles)

            melds = self.player.melds + [meld]

            selected_tile = self.player.ai.hand_builder.choose_tile_to_discard(
                new_tiles, closed_hand_copy, melds, print_log=False)

            final_results.append({
                'discard_tile':
                selected_tile,
                'meld_print':
                TilesConverter.to_one_line_string(
                    [meld_34[0] * 4, meld_34[1] * 4, meld_34[2] * 4]),
                'meld':
                meld
            })

        final_results = sorted(final_results,
                               key=lambda x:
                               (x['discard_tile'].shanten, -x['discard_tile'].
                                ukeire, x['discard_tile'].valuation))

        DecisionsLogger.debug(log.MELD_PREPARE,
                              'Options with meld calling',
                              context=final_results)

        return final_results[0]
예제 #4
0
def score(args):
    win_tile = to_tiles(args['winTile'])[0]
    tiles = to_tiles(args['tiles'])

    melds = []
    for i in args['melds']:
        if i['type'] == 'chi':
            melds.append(Meld(Meld.CHI, tiles=to_tiles(i), opened=i['opened']))
        elif i['type'] == 'pon':
            melds.append(Meld(Meld.PON, tiles=to_tiles(i), opened=i['opened']))
        elif i['type'] == 'kan':
            melds.append(Meld(Meld.KAN, tiles=to_tiles(i), opened=i['opened']))
        elif i['type'] == 'chankan':
            melds.append(
                Meld(Meld.CHANKAN, tiles=to_tiles(i), opened=i['opened']))
        elif i['type'] == 'nuki':
            melds.append(Meld(Meld.NUKI, tiles=to_tiles(i),
                              opened=i['opened']))

    dora_ind = []
    for i in args['doraIndicators']:
        dora_ind.append(to_tiles(i)[0])

    options = args['options']
    config = HandConfig(player_wind=options['playerWind'],
                        round_wind=options['roundWind'],
                        is_tsumo=options['isTsumo'],
                        is_riichi=options['isRiichi'],
                        is_daburu_riichi=options['isDoubleRiichi'],
                        is_ippatsu=options['isIppatsu'],
                        is_rinshan=options['isRinshan'],
                        is_chankan=options['isChankan'],
                        is_haitei=options['isHaitei'],
                        is_houtei=options['isHoutei'],
                        is_nagashi_mangan=options['isNagashiMangan'],
                        is_tenhou=options['isTenhou'],
                        is_renhou=options['isRenhou'],
                        is_chiihou=options['isChiihou'])
    result = calculator.estimate_hand_value(tiles,
                                            win_tile,
                                            melds=melds,
                                            dora_indicators=dora_ind,
                                            config=config)

    opened = False
    for i in melds:
        if i.opened:
            opened = True

    return result, opened
예제 #5
0
    def _find_best_meld_to_open(self, possible_melds, new_tiles, closed_hand, discarded_tile):
        discarded_tile_34 = discarded_tile // 4

        final_results = []
        for meld_34 in possible_melds:
            meld_34_copy = meld_34.copy()
            closed_hand_copy = closed_hand.copy()

            meld_type = is_chi(meld_34_copy) and Meld.CHI or Meld.PON
            meld_34_copy.remove(discarded_tile_34)

            first_tile = TilesConverter.find_34_tile_in_136_array(meld_34_copy[0], closed_hand_copy)
            closed_hand_copy.remove(first_tile)

            second_tile = TilesConverter.find_34_tile_in_136_array(meld_34_copy[1], closed_hand_copy)
            closed_hand_copy.remove(second_tile)

            tiles = [
                first_tile,
                second_tile,
                discarded_tile
            ]

            meld = Meld()
            meld.type = meld_type
            meld.tiles = sorted(tiles)

            melds = self.player.melds + [meld]

            selected_tile = self.player.ai.hand_builder.choose_tile_to_discard(
                new_tiles,
                closed_hand_copy,
                melds,
                print_log=False
            )

            final_results.append({
                'discard_tile': selected_tile,
                'meld_print': TilesConverter.to_one_line_string([meld_34[0] * 4, meld_34[1] * 4, meld_34[2] * 4]),
                'meld': meld
            })

        final_results = sorted(final_results, key=lambda x: (x['discard_tile'].shanten,
                                                             -x['discard_tile'].ukeire,
                                                             x['discard_tile'].valuation))

        DecisionsLogger.debug(log.MELD_PREPARE, 'Options with meld calling', context=final_results)

        return final_results[0]
    def try_to_call_meld(self, tile_136, is_kamicha_discard, meld_type):
        # 1 pon
        # 2 kan (it is a closed kan and can be send only to the self draw)
        # 4 chi
        # there is two return value, meldPrint() and discardOption(),
        # while the second would not be used by client.py
        meld_chi, meld_pon = None, None
        should_chi, should_pon = False, False

        # print(tile_136)
        # print(self.player.closed_hand)
        melds_chi, melds_pon = self.get_possible_meld(tile_136,
                                                      is_kamicha_discard)
        if melds_chi and meld_type & 4:
            should_chi, chi_score, tiles_chi = self.chi.should_call_chi(
                tile_136, melds_chi)
            # fix here: tiles_chi is now the first possible meld ---fixed!
            # tiles_chi = melds_chi[0]
            meld_chi = Meld(meld_type="chi",
                            tiles=tiles_chi) if meld_chi else None
        if melds_pon and meld_type & 1:
            should_pon, pon_score = self.pon.should_call_pon(
                tile_136, is_kamicha_discard)
            tiles_pon = melds_pon[0]
            meld_pon = Meld(meld_type="pon",
                            tiles=tiles_pon) if meld_pon else None

        if not should_chi and not should_pon:
            return None, None

        if should_chi and should_pon:
            meld = meld_chi if chi_score > pon_score else meld_pon
        elif should_chi:
            meld = meld_chi
        else:
            meld = meld_pon

        all_tiles_copy, meld_tiles_copy = self.player.tiles[:], self.player.meld_tiles[:]
        all_tiles_copy.append(tile_136)
        meld_tiles_copy.append(meld)
        closed_hand_copy = [
            item for item in all_tiles_copy if item not in meld_tiles_copy
        ]
        discard_option = self.discard.discard_tile(
            all_hands_136=all_tiles_copy, closed_hands_136=closed_hand_copy)

        return meld, discard_option
예제 #7
0
 def call_meld(self, type, who, from_who, opened, tiles, called_tile):
     meld = Meld()
     meld.type = type
     meld.who = who
     meld.from_who = from_who
     meld.opened = opened
     meld.tiles = tiles
     meld.called_tile = called_tile
     return meld
예제 #8
0
    def parse_meld(self, message):
        data = int(self.get_attribute_content(message, 'm'))

        meld = Meld()
        meld.who = int(self.get_attribute_content(message, 'who'))
        meld.from_who = data & 0x3

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

        return meld
예제 #9
0
def meld_4_tiles(boxes, opened=True):
    man, pin, sou, honors = to_136_string(boxes[:4])
    if man is None:
        return None
    # print('man', man, 'pin', pin, 'sou', sou, 'honors', honors)
    tiles = tc.string_to_136_array(man=man, pin=pin, sou=sou, honors=honors)
    # print(tc.to_one_line_string(tiles))
    return Meld(meld_type=Meld.KAN, tiles=tiles, opened=opened)
예제 #10
0
def get_melds(openhand, open_cnt):
    if open_cnt == 0:
        return []
    melds = []
    for i in range(open_cnt):
        tile = [j for j in range(136) if openhand[i * 2][j][0] == 1]
        opens = [j for j in range(136) if openhand[i * 2 + 1][j][0] == 1]
        melds.append(Meld(tiles=tile, opened=(opens != [])))
    return melds
예제 #11
0
    def parse_meld(self, message):
        data = int(self.get_attribute_content(message, 'm'))

        meld = Meld()
        meld.who = int(self.get_attribute_content(message, 'who'))
        # 'from_who' is encoded relative the the 'who', so we want
        # to convert it to be relative to our player
        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:
            self.parse_nuki(data, meld)
        else:
            self.parse_kan(data, meld)

        return meld
예제 #12
0
    def parse_meld(self, message):
        soup = BeautifulSoup(message, 'html.parser')
        data = soup.find('n').attrs['m']
        data = int(data)

        meld = Meld()
        meld.who = int(soup.find('n').attrs['who'])
        meld.from_who = data & 0x3

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

        return meld
예제 #13
0
    def parse_meld(self, message):
        data = int(self.get_attribute_content(message, 'm'))

        meld = Meld()
        meld.who = int(self.get_attribute_content(message, 'who'))
        # 'from_who' is encoded relative the the 'who', so we want
        # to convert it to be relative to our player
        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:
            self.parse_nuki(data, meld)
        else:
            self.parse_kan(data, meld)

        return meld
예제 #14
0
    def naku(self, naki_kbn, haistrs):
        """
        なきの場合、対応するMeldオブジェクトを返却する
        :param naki_kbn:
        :param haistrs:
        :return:
        """

        # ポン
        if naki_kbn == 'N':
            meld_type = Meld.PON

        # チー
        elif naki_kbn == 'C':
            meld_type = Meld.CHI

        # カン
        elif naki_kbn == 'K':
            meld_type = Meld.KAN

        man = ""
        pin = ""
        sou = ""
        honors = ""

        for haistr in haistrs:
            buff = buff = haistr
            if buff == '東':
                honors = self.add_hainum_to_tehai(honors, '1')
            elif buff == '南':
                honors = self.add_hainum_to_tehai(honors, '2')
            elif buff == '西':
                honors = self.add_hainum_to_tehai(honors, '3')
            elif buff == '北':
                honors = self.add_hainum_to_tehai(honors, '4')
            elif buff == '白':
                honors = self.add_hainum_to_tehai(honors, '5')
            elif buff == '発':
                honors = self.add_hainum_to_tehai(honors, '6')
            elif buff == '中':
                honors = self.add_hainum_to_tehai(honors, '7')
            elif buff[1] == 'm':
                man = self.add_hainum_to_tehai(man, buff[0])
            elif buff[1] == 'p':
                pin = self.add_hainum_to_tehai(pin, buff[0])
            elif buff[1] == 's':
                sou = self.add_hainum_to_tehai(pin, buff[0])
            else:
                print('[error]不正な文字列が入力されました。')

        tiles = TilesConverter.string_to_136_array(man=man,
                                                   pin=pin,
                                                   sou=sou,
                                                   honors=honors)
        return Meld(meld_type=meld_type, tiles=tiles)
예제 #15
0
 def _make_meld(self, meld_type, tiles):
     meld = Meld()
     meld.who = 0
     meld.type = meld_type
     meld.tiles = tiles
     meld.called_tile = tiles[0]
     return meld
예제 #16
0
파일: game.py 프로젝트: jonah-chen/mahjong
 def pong(self, id):
     tiles = [id]
     for t in self.hand:
         if t - t % 4 == id - id % 4:
             tiles.append(t)
     if len(tiles) != 3:
         return False
     self.melds.append(Meld(meld_type=Meld.PON, tiles=tiles))
     self.melds34.append([3 if j == id // 4 else 0 for j in range(34)])
     self.hand.remove(tiles[1])
     self.hand.remove(tiles[2])
     self.tiles34[id // 4] = 0
     return True
예제 #17
0
파일: game.py 프로젝트: jonah-chen/mahjong
    def kong(self, id, opened=True):
        id34 = id // 4
        if (self.tiles34[id34] == 3 and opened) or self.tiles34[id34] == 4:
            self.melds.append(
                Meld(meld_type=Meld.KAN, opened=opened, tiles=list(range(4 * id34, 4 * id34 + 4))))
            self.melds34.append([4 if j == id34 else 0 for j in range(34)])
            for i in range(len(self.hand)):
                if self.hand[i] // 4 == id // 4:
                    self.hand.pop(i)

            self.tiles34[id34] = 0
            return True
        return False
예제 #18
0
    def test_can_call_riichi_and_open_hand(self):
        table = Table()
        player = table.player

        player.in_tempai = True
        player.in_riichi = False
        player.scores = 2000
        player.melds = [Meld()]
        player.table.count_of_remaining_tiles = 40

        self.assertEqual(player.formal_riichi_conditions(), False)

        player.melds = []

        self.assertEqual(player.formal_riichi_conditions(), True)
예제 #19
0
def meld_3_tiles(boxes, meld_type=None):
    man, pin, sou, honors = to_136_string(boxes[:3])
    if man is None:
        return None
    # print('man', man, 'pin', pin, 'sou', sou, 'honors', honors)
    tiles = tc.string_to_136_array(man=man, pin=pin, sou=sou, honors=honors)
    if meld_type is None:
        if boxes_is_pon(boxes):
            meld_type = Meld.PON
        elif boxes_is_chi(boxes):
            meld_type = Meld.CHI
        else:
            print("error: neither pon nor chi({})".format(boxes))
            return None
    # print(tc.to_one_line_string(tiles))
    return Meld(meld_type=meld_type, tiles=tiles)
예제 #20
0
 def _make_meld(self,
                meld_type,
                is_open=True,
                man="",
                pin="",
                sou="",
                honors=""):
     tiles = self._string_to_136_array(man=man,
                                       pin=pin,
                                       sou=sou,
                                       honors=honors)
     meld = Meld(meld_type=meld_type,
                 tiles=tiles,
                 opened=is_open,
                 called_tile=tiles[0],
                 who=0)
     return meld
예제 #21
0
 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
예제 #22
0
파일: game.py 프로젝트: jonah-chen/mahjong
    def chi(self, id, z):
        # z=-1:lower tile
        # z=0 :middle tile
        # z=1 :higher tile

        if id >= 27 * 4:  # honor tiles
            return False
        a, b = (-1, -1)
        if z == -1:
            if 9 * 4 - 8 <= id < 9 * 4 or 9 * 8 - 8 <= id < 9 * 8 or 9 * 12 - 8 <= id:
                return False
            for t in self.hand:
                if t - t % 4 == 4 + id - id % 4:
                    a = t
                if t - t % 4 == 8 + id - id % 4:
                    b = t
        elif z == 0:
            if id < 4 or 9 * 4 - 4 <= id < 9 * 4 + 4 or 9 * 8 - 4 <= id < 9 * 8 + 4 or 9 * 12 - 4 <= id:
                return False
            for t in self.hand:
                if t - t % 4 == -4 + id - id % 4:
                    a = t
                if t - t % 4 == 4 + id - id % 4:
                    b = t
        elif z == 1:
            if id < 8 or 9 * 4 <= id < 9 * 4 + 8 or 9 * 8 <= id < 9 * 8 + 8:
                return False
            for t in self.hand:
                if t - t % 4 == -8 + id - id % 4:
                    a = t
                if t - t % 4 == -4 + id - id % 4:
                    b = t
        else:
            return False
        if a + b < 0:
            return False
        self.melds.append(Meld(meld_type=Meld.CHI, tiles=[a, b, id]))
        self.melds34.append(TilesConverter.to_34_array([a, b, id]))
        self.hand.remove(a)
        self.hand.remove(b)
        self.tiles34[a // 4] -= 1
        self.tiles34[b // 4] -= 1
        return True
예제 #23
0
    def test_closed_kan_and_riichi(self):
        table = Table()
        table.count_of_remaining_tiles = 60
        player = table.player
        player.scores = 25000

        kan_tiles = self._string_to_136_array(pin='7777')
        tiles = self._string_to_136_array(pin='568',
                                          sou='1235788') + kan_tiles[:3]
        player.init_hand(tiles)

        # +3 to avoid tile duplication of 7 pin
        tile = kan_tiles[3]
        player.draw_tile(tile)

        kan_type = player.should_call_kan(tile, False)
        self.assertEqual(kan_type, Meld.KAN)

        meld = Meld()
        meld.type = Meld.KAN
        meld.tiles = kan_tiles
        meld.called_tile = tile
        meld.who = 0
        meld.from_who = 0
        meld.opened = False

        # replacement from the dead wall
        player.draw_tile(self._string_to_136_tile(pin='4'))
        table.add_called_meld(meld.who, meld)
        discard = player.discard_tile()

        self.assertEqual(self._to_string([discard]), '8p')
        self.assertEqual(player.can_call_riichi(), True)

        # with closed kan we can't call riichi
        player.melds[0].opened = True
        self.assertEqual(player.can_call_riichi(), False)
예제 #24
0
    def test_call_meld_closed_kan(self):
        client = Client()

        client.table.init_round(0, 0, 0, 0, 0, [0, 0, 0, 0])
        self.assertEqual(client.table.count_of_remaining_tiles, 70)

        meld = Meld()
        client.table.add_called_meld(0, meld)

        self.assertEqual(len(client.player.melds), 1)
        self.assertEqual(client.table.count_of_remaining_tiles, 71)

        client.player.tiles = [0]
        meld = Meld()
        meld.type = Meld.KAN
        # closed kan
        meld.tiles = [0, 1, 2, 3]
        meld.called_tile = None
        meld.opened = False
        client.table.add_called_meld(0, meld)

        self.assertEqual(len(client.player.melds), 2)
        # kan was closed, so -1
        self.assertEqual(client.table.count_of_remaining_tiles, 70)
예제 #25
0
    def test_call_meld_kan_from_player(self):
        client = Client()

        client.table.init_round(0, 0, 0, 0, 0, [0, 0, 0, 0])
        self.assertEqual(client.table.count_of_remaining_tiles, 70)

        meld = Meld()
        client.table.add_called_meld(0, meld)

        self.assertEqual(len(client.player.melds), 1)
        self.assertEqual(client.table.count_of_remaining_tiles, 71)

        client.player.tiles = [0]
        meld = Meld()
        meld.type = Meld.KAN
        # closed kan
        meld.tiles = [0, 1, 2, 3]
        meld.called_tile = 0
        meld.opened = True
        client.table.add_called_meld(0, meld)

        self.assertEqual(len(client.player.melds), 2)
        # kan was called from another player, total number of remaining tiles stays the same
        self.assertEqual(client.table.count_of_remaining_tiles, 71)
예제 #26
0
    def test_call_meld(self):
        client = Client()

        client.table.init_round(0, 0, 0, 0, 0, [0, 0, 0, 0])
        self.assertEqual(client.table.count_of_remaining_tiles, 70)

        meld = Meld()
        client.table.add_called_meld(0, meld)

        self.assertEqual(len(client.player.melds), 1)
        self.assertEqual(client.table.count_of_remaining_tiles, 71)

        client.player.tiles = [0]
        meld = Meld()
        meld.type = Meld.KAN
        meld.called_tile = 0
        client.table.add_called_meld(0, meld)

        self.assertEqual(len(client.player.melds), 2)
        # +1 for called meld
        # -1 for called kan
        self.assertEqual(client.table.count_of_remaining_tiles, 71)
예제 #27
0
    def try_to_call_meld(self, tile, is_kamicha_discard):
        """
        Determine should we call a meld or not.
        If yes, it will return Meld object and tile to discard
        :param tile: 136 format tile
        :param is_kamicha_discard: boolean
        :return: Meld and DiscardOption objects
        """
        if self.player.in_riichi:
            return None, None

        if self.player.ai.in_defence:
            return None, None

        closed_hand = self.player.closed_hand[:]

        # we can't open hand anymore
        if len(closed_hand) == 1:
            return None, None

        # we can't use this tile for our chosen strategy
        if not self.is_tile_suitable(tile):
            return None, None

        discarded_tile = tile // 4
        new_tiles = self.player.tiles[:] + [tile]
        closed_hand_34 = TilesConverter.to_34_array(closed_hand + [tile])

        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 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.player.ai.hand_divider.find_valid_combinations(closed_hand_34,
                                                                               first_limit,
                                                                               second_limit, True)

        if combinations:
            combinations = combinations[0]

        possible_melds = []
        for best_meld_34 in combinations:
            # we can call pon from everyone
            if is_pon(best_meld_34) and discarded_tile in best_meld_34:
                if best_meld_34 not in possible_melds:
                    possible_melds.append(best_meld_34)

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

        # we can call melds only with allowed tiles
        validated_melds = []
        for meld in possible_melds:
            if (self.is_tile_suitable(meld[0] * 4) and
                    self.is_tile_suitable(meld[1] * 4) and
                    self.is_tile_suitable(meld[2] * 4)):
                validated_melds.append(meld)
        possible_melds = validated_melds

        if not possible_melds:
            return None, None

        best_meld_34 = self._find_best_meld_to_open(possible_melds, new_tiles)
        if best_meld_34:
            # we need to calculate count of shanten with supposed meld
            # to prevent bad hand openings
            melds = self.player.open_hand_34_tiles + [best_meld_34]
            outs_results, shanten = self.player.ai.calculate_outs(new_tiles, closed_hand, melds)

            # each strategy can use their own value to min shanten number
            if shanten > self.min_shanten:
                return None, None

            # we can't improve hand, so we don't need to open it
            if not outs_results:
                return None, None

            # sometimes we had to call tile, even if it will not improve our hand
            # otherwise we can call only with improvements of shanten
            if not self.meld_had_to_be_called(tile) and shanten >= self.player.ai.previous_shanten:
                return None, None

            meld_type = is_chi(best_meld_34) and Meld.CHI or Meld.PON
            best_meld_34.remove(discarded_tile)

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

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

            tiles = [
                first_tile,
                second_tile,
                tile
            ]

            meld = Meld()
            meld.type = meld_type
            meld.tiles = sorted(tiles)

            # we had to be sure that all our discard results exists in the closed hand
            filtered_results = []
            for result in outs_results:
                if result.find_tile_in_hand(closed_hand):
                    filtered_results.append(result)

            # we can't discard anything, so let's not open our hand
            if not filtered_results:
                return None, None

            selected_tile = self.player.ai.process_discard_options_and_select_tile_to_discard(
                filtered_results,
                shanten,
                had_was_open=True
            )

            return meld, selected_tile

        return None, None
예제 #28
0
    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
예제 #29
0
    def yaku(self, player, is_tsumo, agari_hai=None, chankan=False):
        # 手牌
        tile_strs = [""] * 4
        win_tile_strs = [""] * 4

        for hai in player.tehai.hais:
            tile_strs[hai.kind] += "r" if hai.red else str(hai.num)

        for furo in player.tehai.furos:
            for hai in furo.hais:
                tile_strs[hai.kind] += "r" if hai.red else str(hai.num)

            if furo.kind == FuroKind.ANKAN or furo.kind == FuroKind.MINKAN or furo.kind == FuroKind.KAKAN:
                tile_strs[furo.hais[0].kind] = tile_strs[
                    furo.hais[0].kind][:-1]

        if is_tsumo:
            hai = player.tehai.tsumo_hai
            tile_strs[hai.kind] += "r" if hai.red else str(hai.num)
            win_tile_strs[hai.kind] += "r" if hai.red else str(hai.num)
        else:
            tile_strs[agari_hai.kind] += "r" if agari_hai.red else str(
                agari_hai.num)
            win_tile_strs[agari_hai.kind] += "r" if agari_hai.red else str(
                agari_hai.num)

        tiles = TilesConverter.string_to_136_array(tile_strs[0], tile_strs[1],
                                                   tile_strs[2], tile_strs[3],
                                                   True)
        win_tile = TilesConverter.string_to_136_array(win_tile_strs[0],
                                                      win_tile_strs[1],
                                                      win_tile_strs[2],
                                                      win_tile_strs[3],
                                                      True)[0]

        # 副露
        FURO_MELD = {
            FuroKind.PON: Meld.PON,
            FuroKind.CHI: Meld.CHI,
            FuroKind.ANKAN: Meld.KAN,
            FuroKind.MINKAN: Meld.KAN,
            FuroKind.KAKAN: Meld.CHANKAN
        }

        melds = []

        for furo in player.tehai.furos:
            furo_strs = [""] * 4

            for hai in furo.hais:
                furo_strs[hai.kind] += "r" if hai.red else str(hai.num)

            meld_tiles = TilesConverter.string_to_136_array(
                furo_strs[0], furo_strs[1], furo_strs[2], furo_strs[3], True)
            melds.append(
                Meld(FURO_MELD[furo.kind], meld_tiles,
                     furo.kind != FuroKind.ANKAN))

        # ドラ
        dora_indicators = []

        for hai in self.dora_hais:
            dora_strs = [""] * 4
            dora_strs[hai.kind] += "r" if hai.red else str(hai.num)

            dora_tile = TilesConverter.string_to_136_array(
                dora_strs[0], dora_strs[1], dora_strs[2], dora_strs[3],
                True)[0]
            dora_indicators.append(dora_tile)

        if player.richi:
            for hai in self.uradora_hais:
                dora_strs = [""] * 4
                dora_strs[hai.kind] += "r" if hai.red else str(hai.num)

                dora_tile = TilesConverter.string_to_136_array(
                    dora_strs[0], dora_strs[1], dora_strs[2], dora_strs[3],
                    True)[0]
                dora_indicators.append(dora_tile)

        # 設定
        KAZE_WIND = [EAST, SOUTH, WEST, NORTH]

        config = HandConfig(
            is_tsumo=is_tsumo,
            is_riichi=player.richi,
            is_ippatsu=player.ippatsu,
            is_rinshan=player.rinshan,
            is_chankan=chankan,
            is_haitei=agari_hai is None and self.yama.remain == 0,
            is_houtei=agari_hai is not None and self.yama.remain == 0,
            is_daburu_riichi=len(player.kawa.hais) > 0
            and player.kawa.hais[0].richi,
            is_tenhou=agari_hai is None and self.jikaze(player) == 0
            and len(player.kawa.hais) == 0,
            is_renhou=agari_hai is not None and len(player.kawa.hais) == 0,
            is_chiihou=agari_hai is None and self.jikaze(player) != 0
            and len(player.kawa.hais) == 0,
            player_wind=KAZE_WIND[self.jikaze(player)],
            round_wind=KAZE_WIND[self.bakaze],
            options=OptionalRules(has_aka_dora=True))

        return self.calculator.estimate_hand_value(tiles, win_tile, melds,
                                                   dora_indicators, config)
예제 #30
0
def maj_cal(data):
    """{
        "tehai":{
            "man":"111",
            "pin":"222",
            "sou":"333",
            "honors":"11166"
        },
        "agari":{
            "man":"1",
            "pin":"",
            "sou":"",
            "honors":""
        },
        "wind":{
            "round":"1",
            "player":"1",
        },
        "dora":
            {"man":"",
            "pin":"1",
            "sou":"",
            "honors":""
            },
        "kan":[
            {"man":"",
            "pin":"",
            "sou":"",
            "honors":"",
            "open":""
            }
        ],
        "pon":[
            {"man":"",
            "pin":"",
            "sou":"",
            "honors":""
            }
        ],
        "chi":[
            {"man":"",
            "pin":"",
            "sou":"",
            "honors":""
            }
        ],
        "option":[]
    }"""
    print(data)
    has_aka_dora = False
    for d_i, d in data.items():
        if d_i != "option" and has_aka_dora == False:
            if type(d) == type({}):
                for i, v in d.items():
                    if i != "open" and "r" in v:
                        has_aka_dora = True
                        break
            elif type(d) == type([]):
                for d2 in d:
                    for i, v in d2.items():
                        # print("v",v)
                        if i != "open" and "r" in v:
                            has_aka_dora = True
                            break

    handconfig = HandConfig(
        options=OptionalRules(has_open_tanyao=True, has_aka_dora=has_aka_dora))
    melds = []
    dora_indicators = []
    yaku_opened = False
    if "wind" in data.keys():
        handconfig.round_wind = WINDS[int(data["wind"]["round"]) - 1]
        handconfig.player_wind = WINDS[int(data["wind"]["player"]) - 1]
        handconfig.is_dealer = handconfig.player_wind == WINDS[0]

    if "kan" in data.keys() and len(data["kan"]) > 0:
        for kan in data["kan"]:
            opened = (kan["open"] in {"1", 1})
            melds.append(
                Meld(meld_type=Meld.KAN,
                     tiles=to_136_array(obj=kan),
                     opened=opened))
            if opened:
                yaku_opened = opened

    if "pon" in data.keys() and len(data["pon"]) > 0:
        for pon in data["pon"]:
            melds.append(Meld(meld_type=Meld.PON, tiles=to_136_array(obj=pon)))
            yaku_opened = True

    if "chi" in data.keys() and len(data["chi"]) > 0:
        for chi in data["chi"]:
            #配列内をソート
            for chi_key in chi.keys():
                if chi[chi_key] != "":
                    r_flag = False
                    if "r" in chi[chi_key]:
                        r_flag = True
                        chi[chi_key] = chi[chi_key].replace("r", "5")

                    tmp = chi[chi_key]
                    tmp = sorted([tmp[0], tmp[1], tmp[2]])
                    chi[chi_key] = "".join(tmp)
                    if r_flag:
                        chi[chi_key] = chi[chi_key].replace("5", "r")
                    print(chi[chi_key])

            melds.append(Meld(meld_type=Meld.CHI, tiles=to_136_array(obj=chi)))
            yaku_opened = True

    if "dora" in data.keys():
        dora_indicators = to_136_array(obj=data["dora"])

    if "option" in data.keys():
        for i in data["option"]:
            if i in ["tsumo", "ツモ", "つも"]:
                handconfig.is_tsumo = True
            elif i in ["riichi", "リーチ", "りーち"]:
                handconfig.is_riichi = True
            elif i in ["rinshan", "嶺上", "りんしゃん", "リンシャン"]:
                handconfig.is_rinshan = True
            elif i in ["ippatsu", "一発", "いっぱつ", "イッパツ"]:
                handconfig.is_ippatsu = True
            elif i in ["chankan", "槍槓", "ちゃんかん", "チャンカン"]:
                handconfig.is_chankan = True
            elif i in ["haitei", "海底", "はいてい", "ハイテイ"]:
                handconfig.is_haitei = True
            elif i in ["houtei", "河底", "ほうてい", "ホウテイ"]:
                handconfig.is_houtei = True
            elif i in ["daburu_riichi", "だぶりー", "ダブリー", "だぶるりーち", "ダブルリーチ"]:
                handconfig.is_daburu_riichi = True
            elif i in ["nagashi_mangan", "流しマンガン", "ナガシマンガン", "ながしまんがん"]:
                handconfig.is_nagashi_mangan = True
            elif i in ["tenhou", "テンホウ", "てんほう"]:
                handconfig.is_tenhou = True
            elif i in ["renhou", "レンホウ", "れんほう"]:
                handconfig.is_renhou = True
            elif i in ["chiihou", "ちいほう", "チイホウ"]:
                handconfig.is_chiihou = True

    import json
    #print(json.dumps(data["tehai"], indent=4))
    #print(json.dumps(data["agari"], indent=4))

    tiles = to_136_array(data["tehai"], has_aka_dora=has_aka_dora)
    win_tile = to_136_array(data["agari"], has_aka_dora=has_aka_dora)[0]

    if len(dora_indicators) == []: dora_indicators = None
    if len(melds) == 0: melds = None
    result = calculator.estimate_hand_value(tiles,
                                            win_tile,
                                            melds=melds,
                                            dora_indicators=dora_indicators,
                                            config=handconfig)
    return print_hand_result(result, opened=yaku_opened)
예제 #31
0
파일: examples.py 프로젝트: io-ma/mahjong
####################################################################
# Tanyao hand by tsumo                                             #
####################################################################

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

####################################################################
# Add open set to hand                                             #
####################################################################

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)
예제 #32
0
def win():
    req = flask.request.get_json()
    calculator = HandCalculator()

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

    try:
        win_tile = TilesConverter.one_line_string_to_136_array(
            req["win_tile"], has_aka_dora=True)[0]
    except KeyError:
        return flask.jsonify({"error": "win_tile required"}), 400
    except ValueError:
        return flask.jsonify({"error": "invalid win_tile"}), 400

    melds = []
    try:
        melds = req["melds"]
    except KeyError:
        pass

    meld_tiles = []
    for meld in melds:
        try:
            m = meld["meld"]
        except KeyError:
            return flask.jsonify({"error": "meld required"}), 400
        try:
            if m == "chi":
                meld_tiles.append(
                    Meld(
                        Meld.CHI,
                        TilesConverter.one_line_string_to_136_array(
                            meld["tiles"])))
            elif m == "pon":
                meld_tiles.append(
                    Meld(
                        Meld.PON,
                        TilesConverter.one_line_string_to_136_array(
                            meld["tiles"])))
            elif m == "minkan":
                meld_tiles.append(
                    Meld(
                        Meld.KAN,
                        TilesConverter.one_line_string_to_136_array(
                            meld["tiles"]), True))
            elif m == "ankan":
                meld_tiles.append(
                    Meld(
                        Meld.KAN,
                        TilesConverter.one_line_string_to_136_array(
                            meld["tiles"]), False))
            elif m == "kakan":
                meld_tiles.append(
                    Meld(
                        Meld.CHANKAN,
                        TilesConverter.one_line_string_to_136_array(
                            meld["tiles"])))
            else:
                return flask.jsonify({"error": "invalid meld"}), 400
        except KeyError:
            return flask.jsonify({"error": "meld tiles required"}), 400
        except ValueError:
            return flask.jsonify({"error": "invalid meld tiles"}), 400

    try:
        tsumo = req["tsumo"]
    except KeyError:
        tsumo = False

    try:
        riichi = req["riichi"]
    except KeyError:
        riichi = False

    try:
        ippatsu = req["ippatsu"]
    except KeyError:
        ippatsu = False

    try:
        rinshan = req["rinshan"]
    except KeyError:
        rinshan = False

    try:
        chankan = req["chankan"]
    except KeyError:
        chankan = False

    try:
        haitei = req["haitei"]
    except KeyError:
        haitei = False

    try:
        houtei = req["houtei"]
    except KeyError:
        houtei = False

    try:
        double_riichi = req["double_riichi"]
    except KeyError:
        double_riichi = False

    try:
        tenhou = req["tenhou"]
    except KeyError:
        tenhou = False

    try:
        chiihou = req["chiihou"]
    except KeyError:
        chiihou = False

    try:
        wind_player = req["wind_player"]
        if wind_player == "east":
            wind_player = EAST
        elif wind_player == "south":
            wind_player = SOUTH
        elif wind_player == "west":
            wind_player = WEST
        elif wind_player == "north":
            wind_player = NORTH
        else:
            return flask.jsonify({"error": "invalid wind_player"}), 400
    except KeyError:
        return flask.jsonify({"error": "wind_player required"}), 400

    try:
        wind_round = req["wind_round"]
        if wind_round == "east":
            wind_round = EAST
        elif wind_round == "south":
            wind_round = SOUTH
        elif wind_round == "west":
            wind_round = WEST
        elif wind_round == "north":
            wind_round = NORTH
        else:
            return flask.jsonify({"error": "invalid wind_round"}), 400
    except KeyError:
        return flask.jsonify({"error": "wind_round required"}), 400

    try:
        dora_indicators = TilesConverter.one_line_string_to_136_array(
            req["dora_indicators"], has_aka_dora=True)
    except KeyError:
        return flask.jsonify({"error": "dora_indicators required"}), 400
    except ValueError:
        return flask.jsonify({"error": "invalid dora_indicators"}), 400

    option = OptionalRules(has_open_tanyao=True,
                           has_aka_dora=True,
                           has_double_yakuman=False)

    config = HandConfig(is_tsumo=tsumo,
                        is_riichi=riichi,
                        is_ippatsu=ippatsu,
                        is_rinshan=rinshan,
                        is_chankan=chankan,
                        is_haitei=haitei,
                        is_houtei=houtei,
                        is_daburu_riichi=double_riichi,
                        is_tenhou=tenhou,
                        is_chiihou=chiihou,
                        player_wind=wind_player,
                        round_wind=wind_round,
                        options=option)

    result = calculator.estimate_hand_value(tiles,
                                            win_tile,
                                            dora_indicators=dora_indicators,
                                            config=config)

    if result.error:
        return flask.jsonify({"error": result.error}), 400

    yaku = map(lambda y: y.japanese, result.yaku)
    return flask.jsonify({
        "cost": result.cost,
        "han": result.han,
        "fu": result.fu,
        "yaku": list(yaku),
        "error": None
    })