예제 #1
0
 def is_wait_ryanmen(self):
     """
     Returns
     -------
     is_wait_ryanmen : bool
         両面待ちかどうか
     """
     tiles = []
     if self.latest_tile.number is None: return(False)
     if self.latest_tile.number > 2:
         tiles.append(mahjongpy.MahjongTile(self.latest_tile.tile_type, self.latest_tile.number-2))
     if self.latest_tile.number < 8:
         tiles.append(mahjongpy.MahjongTile(self.latest_tile.tile_type, self.latest_tile.number+2))
     return(any([(i in self.hands) for i in tiles]))
예제 #2
0
 def is_chitoitu(self):
     """
     Returns
     -------
     is_chitoitu : bool
         プレイヤーの手牌が七対子かどうか
     """
     if not self.is_menzen(): return(False)
     mentus = []
     tiles = self.hands[:]
     for i in range(1,10):
         for j in self.TILE_TYPES:
             if tiles.count(mahjongpy.MahjongTile(j,i)) == 2:
                 tmp = []
                 for _ in range(2):
                     tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i))))
                 mentus.append(tmp)
     return(len(tiles) == 0)
예제 #3
0
 def is_ryanpeikou(self, tiles, mentus):
     """
     Returns
     -------
     is_kokushimusou : bool
         プレイヤーの手牌が二盃口かどうか
     """
     if not self.is_menzen(): return(False)
     count = []
     for _ in range(2):
         for i in range(1,8):
             for j in self.TILE_TYPES[:3]:
                 if mahjongpy.MahjongTile(j,i) in tiles and mahjongpy.MahjongTile(j,i+1) in tiles and mahjongpy.MahjongTile(j,i+2) in tiles:
                     tmp = []
                     tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i))))
                     tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i+1))))
                     tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i+2))))
                     mentus.append(tmp)
         count.append(len(tiles))
     return(count == [8,2])
예제 #4
0
 def can_ankan(self):
     """
     Returns
     -------
     can_ankan : bool
         暗槓できるかどうか
     """
     count = []
     for i in self.TILE_TYPES:
         for j in range(1,10):
             count.append(self.hands.count(mahjongpy.MahjongTile(i,j)))
     return(count.count(4) > 0)
예제 #5
0
 def test_next_tile(self):
     t1 = mahjongpy.MahjongTile('pinzu', 5)
     t2 = mahjongpy.MahjongTile('tyun')
     t3 = mahjongpy.MahjongTile('nan')
     self.assertTrue(t1.next() == mahjongpy.MahjongTile('pinzu', 6))
     self.assertTrue(t2.next() == mahjongpy.MahjongTile('haku'))
     self.assertTrue(t3.next() == mahjongpy.MahjongTile('sha'))
예제 #6
0
    def make_kotus(self, tiles, mentus):
        """
        刻子を作る

        Parameters
        ----------
        tiles : list
            未処理のMahjongTileのリスト(手牌)
        mentus: list
            処理済みのMahjongTileのリスト。このリストに刻子が、MahjongTile3枚のリストとして追加される

        Notes
        -----
        引数のtiles、およびmentusを 破壊的に 変更するので注意
        """
        for i in range(1,10):
            for j in self.TILE_TYPES:
                if tiles.count(mahjongpy.MahjongTile(j,i)) == 3:
                    tmp = []
                    for _ in range(3):
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i))))
                    mentus.append(tmp)
예제 #7
0
    def make_zyantou(self, tiles, mentus):
        """
        雀頭を作る

        Parameters
        ----------
        tiles : list
            未処理のMahjongTileのリスト(手牌)
        mentus: list
            処理済みのMahjongTileのリスト。このリストに雀頭が、MahjongTile2枚のリストとして追加される

        Notes
        -----
        引数のtiles、およびmentusを 破壊的に 変更するので注意
        """
        for i in range(1,10):
            for j in self.TILE_TYPES:
                if tiles.count(mahjongpy.MahjongTile(j,i)) == 2:
                    tmp = []
                    for _ in range(2):
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i))))
                    mentus.append(tmp)
                    return(None)
예제 #8
0
    def make_shuntus(self, tiles, mentus):
        """
        順子を作る

        Parameters
        ----------
        tiles : list
            未処理のMahjongTileのリスト(手牌)
        mentus: list
            処理済みのMahjongTileのリスト。このリストに順子が、MahjongTile3枚のリストとして追加される

        Notes
        -----
        引数のtiles、およびmentusを 破壊的に 変更するので注意
        """
        for _ in range(2):
            for i in range(1,8):
                for j in self.TILE_TYPES[:3]:
                    if mahjongpy.MahjongTile(j,i) in tiles and mahjongpy.MahjongTile(j,i+1) in tiles and mahjongpy.MahjongTile(j,i+2) in tiles:
                        tmp = []
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i))))
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i+1))))
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i+2))))
                        mentus.append(tmp)
예제 #9
0
    def can_chi(self, tile):
        """
        Parameters
        ----------
        tile : MahjongTile
            上家が捨てた牌

        Returns
        -------
        can_pon : bool
            チーできるかどうか
        """
        if tile.tile_type in self.TILE_TYPES[3:]: return(False)
        count = []
        if tile.number > 2:
            count.append(self.hands.count(mahjongpy.MahjongTile(tile.tile_type,tile.number-2)))
        else:
            count.append(0)
        if tile.number > 1:
            count.append(self.hands.count(mahjongpy.MahjongTile(tile.tile_type,tile.number-1)))
        else:
            count.append(0)
        if tile.number < 9:
            count.append(self.hands.count(mahjongpy.MahjongTile(tile.tile_type,tile.number+1)))
        else:
            count.append(0)
        if tile.number < 8:
            count.append(self.hands.count(mahjongpy.MahjongTile(tile.tile_type,tile.number+2)))
        else:
            count.append(0)

        judge = False
        for i in range(3):
            if count[i:i+2].count(1) == 2:
                judge = True
        return(judge)
예제 #10
0
    def discard(self, tile):
        """
        牌を捨てる

        Parameters
        ----------
        tile : MahjongTile
            捨てる牌

        """
        if tile not in self.hands:
            raise RuntimeError('does NOT have such tile')
        else:
            self.turn += 1
            self.discards.append(self.hands.pop(self.hands.index(mahjongpy.MahjongTile(tile.tile_type,tile.number,akadora=tile.akadora))))
예제 #11
0
 def test_tiles_less_than(self):
     t1 = mahjongpy.MahjongTile('pinzu', 5)
     t2 = mahjongpy.MahjongTile('pinzu', 8)
     t3 = mahjongpy.MahjongTile('manzu', 2)
     t4 = mahjongpy.MahjongTile('hatu')
     t5 = mahjongpy.MahjongTile('haku')
     t6 = mahjongpy.MahjongTile('nan')
     t7 = mahjongpy.MahjongTile('pei')
     self.assertTrue(t1 < t2)
     self.assertTrue(t6 < t7)
     self.assertTrue(t3 < t1)
     self.assertTrue(t3 < t2)
     self.assertTrue(t5 < t4)
     self.assertTrue(t6 < t4)
예제 #12
0
 def test_make_tiles(self):
     t = mahjongpy.MahjongTile('pinzu', 1)
     self.assertEqual(t.tile_type, 'pinzu')
     self.assertEqual(t.number, 1)
     t = mahjongpy.MahjongTile('manzu', 8)
     self.assertEqual(t.tile_type, 'manzu')
     self.assertEqual(t.number, 8)
     t = mahjongpy.MahjongTile('haku')
     self.assertEqual(t.tile_type, 'haku')
     self.assertEqual(t.number, None)
     t = mahjongpy.MahjongTile('tyun')
     self.assertEqual(t.tile_type, 'tyun')
     self.assertEqual(t.number, None)
     t = mahjongpy.MahjongTile('ton')
     self.assertEqual(t.tile_type, 'ton')
     self.assertEqual(t.number, None)
     t = mahjongpy.MahjongTile('sha')
     self.assertEqual(t.tile_type, 'sha')
     self.assertEqual(t.number, None)
예제 #13
0
    def yakus(self, cache=True):
        """
        プレイヤーの手牌でできる役のリストを返す

        Parameters
        ----------
        cache : bool
            キャッシュを使用するかどうか(Falseにするとキャッシュの有無を無視して再計算します)

        Returns
        -------
        yakus : list
            役のリスト。役の名前(適当ローマ字表記)が入っている
        """
        if not self.is_hora: raise RuntimeError('Not hora')
        yakus = []

        if self.tiles_cache != self.hands:
            cache = False
            self.tiles_cache = self.hands[:]

        if self.yakus_cache != [] and cache:
            return(self.yakus_cache)

        if self.is_riichi: yakus.append('riichi')
        if self.riichi_turn + 1 == self.turn: yakus.append('ippatu')
        if self.is_menzen() and self.is_tumo: yakus.append('menzentumo')
        tiles = self.hands[:] + sum(self.melds, [])
        table_wind = 'ton' if self.table is None else self.table.wind
        TILE_TYPES_YAKUHAI = self.TILE_TYPES[7:] + [self.wind] + [table_wind]
        for i in TILE_TYPES_YAKUHAI:
            if tiles.count(mahjongpy.MahjongTile(i)) == 3:
                yakus.append('yakuhai')
        kuitan = 'True' if self.table is None else self.table.kuitan
        tmp = tiles[:] if kuitan else self.hands[:]
        count = 0
        count += tmp.count(mahjongpy.MahjongTile('pinzu',1))
        count += tmp.count(mahjongpy.MahjongTile('pinzu',9))
        count += tmp.count(mahjongpy.MahjongTile('manzu',1))
        count += tmp.count(mahjongpy.MahjongTile('manzu',9))
        count += tmp.count(mahjongpy.MahjongTile('souzu',1))
        count += tmp.count(mahjongpy.MahjongTile('souzu',9))
        count += tmp.count(mahjongpy.MahjongTile('ton'))
        count += tmp.count(mahjongpy.MahjongTile('nan'))
        count += tmp.count(mahjongpy.MahjongTile('sha'))
        count += tmp.count(mahjongpy.MahjongTile('pei'))
        count += tmp.count(mahjongpy.MahjongTile('haku'))
        count += tmp.count(mahjongpy.MahjongTile('hatu'))
        count += tmp.count(mahjongpy.MahjongTile('tyun'))
        if count == 0: yakus.append('tanyao')
        if self.is_menzen() and len(self.shuntus()) == 4 and self.zyantou()[0].tile_type not in TILE_TYPES_YAKUHAI and self.is_wait_ryanmen(): yakus.append('pinfu')
        if self.is_ipeikou(self.hands[:], []): yakus.append('ipeikou')
        table_tiles = [] if self.table is None else self.table.tiles
        if len(table_tiles) == 14 and self.is_tumo: yakus.append('haitei')
        if len(table_tiles) == 14 and self.is_ron: yakus.append('houtei')
        if self.is_rinsyankaihou: yakus.append('rinsyankaihou')
        if False: yakus.append('tyankan')
        if self.is_doubleriichi: yakus.append('doubleriichi')
        if self.is_chitoitu(): yakus.append('chitoitu')
        tiles = self.hands[:] + sum(self.melds, [])
        judge = False
        for i in self.TILE_TYPES[:3]:
            count = []
            for j in range(1,10):
                count.append(len([k for k in tiles if k.tile_type == i and k.number == j]))
            if all([l > 0 for l in count]): judge = True
        if judge: yakus.append('ikkituukan')
        for i in range(1,8):
            count = []
            for j in self.TILE_TYPES[:3]:
                count.append(len([k for k in tiles if k.tile_type == j and k.number == i]))
                count.append(len([k for k in tiles if k.tile_type == j and k.number == i+1]))
                count.append(len([k for k in tiles if k.tile_type == j and k.number == i+1]))
            if count.count(1) == 9: yakus.append('sansyokudouzyun')
        for i in range(1,10):
            count = []
            for j in self.TILE_TYPES[:3]:
                count.append(len([k for k in tiles if k.tile_type == j and k.number == i]))
            if count.count(3) == 3: yakus.append('sansyokudoukou')
        if len(self.ankos()) == 3 or len(self.ankos())+len(self.ankans) == 3: yakus.append('sanankou')
        #if len(self.minkos) < 2  and len(self.kotus())+len(self.kantus()) == 3: yakus.append('sanankou')
        #if len(self.minkos) == 1  and len(self.kotus())+len(self.kantus()) == 4: yakus.append('sanankou')
        if len(self.kotus()) == 4 or len(self.kotus())+len(self.kantus()) == 4:
            if self.is_menzen():
                if self.is_tumo or self.hands.count(self.latest_tile) == 2:
                    yakus.append('suankou')
            else:
                yakus.append('toitoi')
        if self.is_chanta(): yakus.append('chanta')
        if len(self.kantus()) == 3: yakus.append('sankantu')
        if self.is_ryanpeikou(self.hands[:], []): yakus.append('ryanpeikou')
        if self.is_zyuntyan():yakus.append('zyuntyan')
        count = 0
        for i in self.TILE_TYPES[:3]:
            if len([j for j in tiles if j.tile_type in [i]+self.TILE_TYPES[3:]]) == 14: count += 1
        if count > 0: yakus.append('honitu')
        if self.zyantou()[0].number is None and len([i for i in tiles if i.number is None]) > 5: yakus.append('syousangen')
        if len([i for i in tiles if i.number in [1,9,None]]) == 14 and (not self.is_kokushimusou()): yakus.append('honroutou')
        for i in self.TILE_TYPES[:3]:
            if len([j for j in tiles if j.tile_type in [i]]) == 14:yakus.append('chinitu')
        count = 0
        for i in self.TILE_TYPES[7:]:
            if len([j for j in tiles if j.tile_type == i]) > 2: count += 1
        if count == 3: yakus.append('daisangen')
        if self.is_kokushimusou(): yakus.append('kokushimusou')
        if self.zyantou()[0].tile_type in self.TILE_TYPES[3:7] and len([i for i in tiles if i.tile_type in self.TILE_TYPES[3:7]]) > 8: yakus.append('syoususi')
        count = 0
        for i in self.TILE_TYPES[3:7]:
            if len([j for j in tiles if j.tile_type == i]) > 2: count += 1
        if count == 4: yakus.append('daisusi')
        if len([i for i in tiles if i.tile_type in ['souzu', 'hatu']]) == 14: yakus.append('ryuisou')
        if len([i for i in tiles if i.tile_type in self.TILE_TYPES[3:]]) == 14: yakus.append('tuisou')
        if len([i for i in tiles if i.number in [1,9]]) == 14: yakus.append('chinroutou')
        if len(self.kantus()) == 4: yakus.append('sukantu')
        if self.is_menzen:
            for i in self.TILE_TYPES[:3]:
                count = []
                for j in range(1,10):
                    count.append(self.hands.count(mahjongpy.MahjongTile(i,j)))
                if count.count(3) == 2 and count.count(2) == 1 and count.count(1) == 6: yakus.append('tyurenboutou')
            is_furoed = False if self.table is None else self.table.is_furoed
            if not is_furoed and (not self.oya) and self.turn == 0: yakus.append('chihou')
            if self.oya and self.turn == 0: yakus.append('tenhou')

        self.yakus_cache = yakus
        return(yakus)
예제 #14
0
 def is_kokushimusou(self):
     """
     Returns
     -------
     is_kokushimusou : bool
         プレイヤーの手牌が国士無双かどうか
     """
     if not self.is_menzen(): return(False)
     tmp = []
     tiles = self.hands[:]
     tmp.append(tiles.count(mahjongpy.MahjongTile('pinzu',1)))
     tmp.append(tiles.count(mahjongpy.MahjongTile('pinzu',9)))
     tmp.append(tiles.count(mahjongpy.MahjongTile('manzu',1)))
     tmp.append(tiles.count(mahjongpy.MahjongTile('manzu',9)))
     tmp.append(tiles.count(mahjongpy.MahjongTile('souzu',1)))
     tmp.append(tiles.count(mahjongpy.MahjongTile('souzu',9)))
     tmp.append(tiles.count(mahjongpy.MahjongTile('ton')))
     tmp.append(tiles.count(mahjongpy.MahjongTile('nan')))
     tmp.append(tiles.count(mahjongpy.MahjongTile('sha')))
     tmp.append(tiles.count(mahjongpy.MahjongTile('pei')))
     tmp.append(tiles.count(mahjongpy.MahjongTile('haku')))
     tmp.append(tiles.count(mahjongpy.MahjongTile('hatu')))
     tmp.append(tiles.count(mahjongpy.MahjongTile('tyun')))
     return(tmp.count(1) == 12 and tmp.count(2) == 1)
예제 #15
0
class MahjongPlayer:
    """
    プレイヤーを表すクラス。各プレイヤーの情報などを保持する

    Attributes
    ----------
    hands : list
        プレイヤーの手牌。MahjongTileのリスト
    discards : list
        プレイヤーの河。MahjongTileのリスト
    melds : list
        プレイヤーが鳴いた牌のリストのリスト。
        ポン、カン、チーをしたときに3枚または4枚のMahjongTileのリストが追加される。
    oya : bool
        プレイヤーが親かどうか
    points : int
        プレイヤーの持ち点
    wind : str
        プレイヤーの自風。東:'ton' 南:'nan' 西:'sha' 北:'pei'
    latest_tile : MahjongTile
        一番最後に引いた牌(一番最新の牌)
    table : MahjongTable
        プレイヤーがゲームを行っている卓
    turn : int
        局が始まってから経過したターン数
    riichi_turn : int
        リーチした時のターン数
    is_tumo : bool
        ツモしたかどうか
    id_ron : bool
        ロンしたかどうか
    ankans : list
        暗槓のリスト。MahjongTile4枚のリスト(暗槓)のリスト
    minkans :list
        明槓のリスト。MahjongTile4枚のリスト(明槓)のリスト
    minkos :list
        明刻のリスト。MahjongTile3枚のリスト(明刻)のリスト
    is_riichi : bool
        リーチしているかどうか
    is_doubleriichi : bool
        和了った時にダブルリーチ(役)がつく状態かどうか
    is_rinsyankaihou : bool
        和了った時に嶺上開花(役)がつく状態かどうか
    score_cache : int
        プレイヤーの点数のキャッシュ
    score_without_tsumibo_cache : int
        プレイヤーの点数(積み棒分を除く)のキャッシュ
    yakus_cache : list
        プレイヤーの手牌でできる役のリストのキャッシュ
    tilse_cache : list
        手牌のキャッシュ
    """

    TILE_TYPES = ['pinzu', 'manzu', 'souzu', 'ton', 'nan', 'sha', 'pei', 'haku', 'hatu', 'tyun']
    KYOMU_TILE = mahjongpy.MahjongTile(None)

    def __init__(self, hands=[], discards=[], melds=[], oya=False, points=25000, wind='ton', latest_tile=KYOMU_TILE,
                 table=None, turn=0, is_tumo=False, ankans=[], minkans=[], minkos=[]):
        if len(hands)+len(sum(melds,[])) not in [13, 14]: raise ValueError('amout of hands is not 13 or 14.')
        self.hands = hands[:]
        self.discards = discards[:]
        self.melds = melds[:]
        self.oya = oya
        self.points = points
        self.wind = wind
        self.latest_tile = latest_tile
        self.is_riichi = False
        self.turn = turn
        self.riichi_turn = 100
        self.is_tumo = is_tumo
        self.is_ron = False
        self.ankans = ankans[:]
        self.minkans = minkans[:]
        self.minkos = minkos[:]
        self.is_doubleriichi = False
        self.is_rinsyankaihou = False
        self.table = table
        self.score_cache = 0
        self.score_without_tsumibo_cache = 0
        self.yakus_cache = []
        self.tiles_cache = []
        self.sort()

    def sort(self):
        """
        プレイヤーの手牌を種類、番号順にソートする

        Notes
        -----
        self.hands を 破壊的に ソートするので注意

        """
        self.hands = sorted(self.hands)

    def hands_display(self):
        """
        プレイヤーの手牌すべてを実際の牌のような感じで表示
        """
        for i in self.hands:
            print(i.display)

    def hands_name_jp(self):
        """
        プレイヤーの手牌すべての名前を表示
        """
        for i in self.hands:
            print(i.name_jp)

    def discards_display(self):
        """
        プレイヤーの河すべてを実際の牌のような感じで表示
        """
        for i in self.discards:
            print(i.display)

    def discards_name_jp(self):
        """
        プレイヤーの河すべての名前を表示
        """
        for i in self.discards:
            print(i.name_jp)

    def shanten(self):
        """
        プレイヤーのシャンテン数を計算。一向聴で1、二向聴で2…… を返す

        Returns
        -------
        count : int
            プレイヤーのシャンテン数
        """
        counts = [100]

        tiles = self.hands[:]
        mentus = []
        self.make_shuntus(tiles, mentus)
        self.make_kotus(tiles, mentus)
        self.make_zyantou(tiles, mentus)
        tmp = tiles[:]
        count = 0
        for i in self.TILE_TYPES[:3]:
            for j in range(1,8):
                if mahjongpy.MahjongTile(i,j) in tmp:
                    if mahjongpy.MahjongTile(i,j+1) in tmp:
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j+1)))
                        count += 1
                    if mahjongpy.MahjongTile(i,j+2) in tmp:
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j+2)))
                        count += 1
        for i in self.TILE_TYPES:
            for j in range(1,8):
                if tmp.count(mahjongpy.MahjongTile(i,j)) == 2:
                    tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                    tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                    count += 1
                if len(tmp)-count == 2:
                    tmp.pop(0)
                    count += 1
        if len(tmp) == count: counts.append(count)

        tiles = self.hands[:]
        mentus = []
        self.make_zyantou(tiles, mentus)
        self.make_shuntus(tiles, mentus)
        self.make_kotus(tiles, mentus)
        tmp = tiles[:]
        count = 0
        for i in self.TILE_TYPES[:3]:
            for j in range(1,8):
                if mahjongpy.MahjongTile(i,j) in tmp:
                    if mahjongpy.MahjongTile(i,j+1) in tmp:
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j+1)))
                        count += 1
                    if mahjongpy.MahjongTile(i,j+2) in tmp:
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j+2)))
                        count += 1
        for i in self.TILE_TYPES:
            for j in range(1,8):
                if tmp.count(mahjongpy.MahjongTile(i,j)) == 2:
                    tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                    tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                    count += 1
                if len(tmp)-count == 2:
                    tmp.pop(0)
                    count += 1
        if len(tmp) == count: counts.append(count)

        tmp = tiles[:]
        count = 0
        for i in self.TILE_TYPES:
            for j in range(1,10):
                if tmp.count(mahjongpy.MahjongTile(i,j)) == 2: count += 1
        counts.append(7-count)  # 七対子用

        tiles = self.hands[:]
        tmp = []
        tmp.append(tiles.count(mahjongpy.MahjongTile('pinzu',1)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('pinzu',9)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('manzu',1)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('manzu',9)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('souzu',1)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('souzu',9)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('ton')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('nan')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('sha')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('pei')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('haku')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('hatu')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('tyun')))
        if tmp.count(1) == 13: counts.append(1)
        elif tmp.count(2) > 1: counts.append(13-tmp.count(1))
        else: counts.append(13-tmp.count(1)+1)

        return(min(counts)-1)

    def is_tenpai(self):
        """
        Returns
        -------
        is_tenpai : bool
            プレイヤーがテンパイかどうか
        """
        return(self.shanten() == 0)

    def is_furiten(self):
        """
        Returns
        -------
        is_furiten : bool
            プレイヤーがフリテン状態かどうか
        """
        is_furiten = False
        return(is_furiten)

    def is_kyusyukyuhai(self):
        """
        Returns
        -------
        is_kyusyukyuhai : bool
            手牌が九種九牌かどうか
        """
        count = 0
        for i in self.hands:
            if i.number in [1, 9, None]:
                count += 1
        return(count > 8)

    def is_hora(self):
        """
        Returns
        -------
        is_hora : bool
            プレイヤーの手牌が和了形かどうか
        """
        is_hora = False

        tiles = self.hands[:]
        mentus = []
        self.make_shuntus(tiles, mentus)
        self.make_kotus(tiles, mentus)
        is_hora = self.is_zyantou(tiles, mentus)

        tiles = self.hands[:]
        mentus = []
        self.make_zyantou(tiles, mentus)
        self.make_shuntus(tiles, mentus)
        self.make_kotus(tiles, mentus)
        is_hora = is_hora or (len(tiles) == 0)

        return(is_hora or self.is_chitoitu() or self.is_kokushimusou())

    def zyantou(self):
        """
        プレイヤーの手牌の内、雀頭を返す

        Returns
        -------
        tiles : list
            雀頭(2枚のMahjongTileのリスト)。雀頭がない場合ダミータイル(self.KYOMU_TILE)1枚のみのリストが返る
        """
        is_hora = False

        tiles = self.hands[:]
        mentus = []
        self.make_shuntus(tiles, mentus)
        self.make_kotus(tiles, mentus)
        is_hora = self.is_zyantou(tiles, mentus)
        if is_hora: return(mentus[-1])

        tiles = self.hands[:]
        mentus = []
        self.make_zyantou(tiles, mentus)
        self.make_shuntus(tiles, mentus)
        self.make_kotus(tiles, mentus)
        is_hora = is_hora or (len(tiles) == 0)
        if is_hora: return(mentus[0])

        return([self.KYOMU_TILE])

    def make_shuntus(self, tiles, mentus):
        """
        順子を作る

        Parameters
        ----------
        tiles : list
            未処理のMahjongTileのリスト(手牌)
        mentus: list
            処理済みのMahjongTileのリスト。このリストに順子が、MahjongTile3枚のリストとして追加される

        Notes
        -----
        引数のtiles、およびmentusを 破壊的に 変更するので注意
        """
        for _ in range(2):
            for i in range(1,8):
                for j in self.TILE_TYPES[:3]:
                    if mahjongpy.MahjongTile(j,i) in tiles and mahjongpy.MahjongTile(j,i+1) in tiles and mahjongpy.MahjongTile(j,i+2) in tiles:
                        tmp = []
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i))))
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i+1))))
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i+2))))
                        mentus.append(tmp)

    def make_kotus(self, tiles, mentus):
        """
        刻子を作る

        Parameters
        ----------
        tiles : list
            未処理のMahjongTileのリスト(手牌)
        mentus: list
            処理済みのMahjongTileのリスト。このリストに刻子が、MahjongTile3枚のリストとして追加される

        Notes
        -----
        引数のtiles、およびmentusを 破壊的に 変更するので注意
        """
        for i in range(1,10):
            for j in self.TILE_TYPES:
                if tiles.count(mahjongpy.MahjongTile(j,i)) == 3:
                    tmp = []
                    for _ in range(3):
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i))))
                    mentus.append(tmp)

    def make_zyantou(self, tiles, mentus):
        """
        雀頭を作る

        Parameters
        ----------
        tiles : list
            未処理のMahjongTileのリスト(手牌)
        mentus: list
            処理済みのMahjongTileのリスト。このリストに雀頭が、MahjongTile2枚のリストとして追加される

        Notes
        -----
        引数のtiles、およびmentusを 破壊的に 変更するので注意
        """
        for i in range(1,10):
            for j in self.TILE_TYPES:
                if tiles.count(mahjongpy.MahjongTile(j,i)) == 2:
                    tmp = []
                    for _ in range(2):
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i))))
                    mentus.append(tmp)
                    return(None)

    def is_zyantou(self, tiles, mentus):
        """
        残りの牌が雀頭かどうかを判定する

        Parameters
        ----------
        tiles : list
            未処理のMahjongTileのリスト
        mentus: list
            処理済みのMahjongTileのリスト。雀頭判定されたとき、MahjongTile2枚がリストとして追加される

        Notes
        -----
        引数のtiles、およびmentusを 破壊的に 変更するので注意
        """
        if len(tiles) == 2 and tiles[0] == tiles[1]:
            mentus.append([tiles[0], tiles[1]])
            return(True)
        else:
            return(False)

    def is_chitoitu(self):
        """
        Returns
        -------
        is_chitoitu : bool
            プレイヤーの手牌が七対子かどうか
        """
        if not self.is_menzen(): return(False)
        mentus = []
        tiles = self.hands[:]
        for i in range(1,10):
            for j in self.TILE_TYPES:
                if tiles.count(mahjongpy.MahjongTile(j,i)) == 2:
                    tmp = []
                    for _ in range(2):
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i))))
                    mentus.append(tmp)
        return(len(tiles) == 0)

    def is_kokushimusou(self):
        """
        Returns
        -------
        is_kokushimusou : bool
            プレイヤーの手牌が国士無双かどうか
        """
        if not self.is_menzen(): return(False)
        tmp = []
        tiles = self.hands[:]
        tmp.append(tiles.count(mahjongpy.MahjongTile('pinzu',1)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('pinzu',9)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('manzu',1)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('manzu',9)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('souzu',1)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('souzu',9)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('ton')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('nan')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('sha')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('pei')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('haku')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('hatu')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('tyun')))
        return(tmp.count(1) == 12 and tmp.count(2) == 1)

    def is_chanta(self):
        """
        Returns
        -------
        is_kokushimusou : bool
            プレイヤーの手牌がチャンタかどうか
        """
        is_hora = False

        tiles = self.hands[:] + sum(self.melds, [])
        mentus = []
        self.make_shuntus(tiles, mentus)
        self.make_kotus(tiles, mentus)
        is_hora = self.is_zyantou(tiles, mentus)
        count = 0
        for i in mentus:
            for j in i:
                if j.number in [1, 9, None]:
                    count += 1
                    break
        if is_hora and count == 5: return(True)

        count = 0
        tiles = self.hands[:]
        mentus = []
        self.make_zyantou(tiles, mentus)
        self.make_shuntus(tiles, mentus)
        self.make_kotus(tiles, mentus)
        is_hora = is_hora or (len(tiles) == 0)
        for i in mentus:
            for j in i:
                if j.number in [1, 9, None]:
                    count += 1
                    break
        if is_hora and count == 5: return(True)

        return(False)

    def is_zyuntyan(self):
        """
        Returns
        -------
        is_kokushimusou : bool
            プレイヤーの手牌がジュンチャンかどうか
        """
        is_hora = False

        tiles = self.hands[:] + sum(self.melds, [])
        mentus = []
        self.make_shuntus(tiles, mentus)
        self.make_kotus(tiles, mentus)
        is_hora = self.is_zyantou(tiles, mentus)
        count = 0
        for i in mentus:
            for j in i:
                if j.number in [1, 9]:
                    count += 1
                    break
        if is_hora and count == 5: return(True)

        count = 0
        tiles = self.hands[:]
        mentus = []
        self.make_zyantou(tiles, mentus)
        self.make_shuntus(tiles, mentus)
        self.make_kotus(tiles, mentus)
        is_hora = is_hora or (len(tiles) == 0)
        for i in mentus:
            for j in i:
                if j.number in [1, 9]:
                    count += 1
                    break
        if is_hora and count == 5: return(True)

        return(False)

    def is_ipeikou(self, tiles, mentus):
        """
        Returns
        -------
        is_kokushimusou : bool
            プレイヤーの手牌が一盃口かどうか
        """
        if not self.is_menzen(): return(False)
        count = []
        for _ in range(2):
            for i in range(1,8):
                for j in self.TILE_TYPES[:3]:
                    if mahjongpy.MahjongTile(j,i) in tiles and mahjongpy.MahjongTile(j,i+1) in tiles and mahjongpy.MahjongTile(j,i+2) in tiles:
                        tmp = []
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i))))
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i+1))))
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i+2))))
                        mentus.append(tmp)
            count.append(len(tiles))
        return(count == [5,2])

    def is_ryanpeikou(self, tiles, mentus):
        """
        Returns
        -------
        is_kokushimusou : bool
            プレイヤーの手牌が二盃口かどうか
        """
        if not self.is_menzen(): return(False)
        count = []
        for _ in range(2):
            for i in range(1,8):
                for j in self.TILE_TYPES[:3]:
                    if mahjongpy.MahjongTile(j,i) in tiles and mahjongpy.MahjongTile(j,i+1) in tiles and mahjongpy.MahjongTile(j,i+2) in tiles:
                        tmp = []
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i))))
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i+1))))
                        tmp.append(tiles.pop(tiles.index(mahjongpy.MahjongTile(j,i+2))))
                        mentus.append(tmp)
            count.append(len(tiles))
        return(count == [8,2])

    def displayed_doras(self):
        """
        手牌の内ドラ表示牌の次の牌の数

        Returns
        -------
        count : int
            手牌のドラ牌の数
        """
        count = 0
        tiles = self.hands[:] + sum(self.melds, [])
        doras = None if self.table is None else self.table.dora_tiles
        for i in tiles:
            for j in doras:
                if i == j: count += 1
        return(count)

    def akadoras(self):
        """
        手牌の赤ドラの数

        Returns
        -------
        count : int
            手牌の赤ドラの数
        """
        count = 0
        tiles = self.hands[:] + sum(self.melds, [])
        for i in tiles:
            if i.akadora: count += 1
        return(count)

    def doras(self):
        """
        Returns
        -------
        count : int
            手牌の内、ドラ表示牌の次の牌の数と、赤ドラの数の和
        """
        return(self.displayed_doras() + self.akadoras())

    def shuntus(self):
        """
        手牌の内、順子のリストを返す

        Returns
        -------
        tiles : list
            手牌の順子のリスト。順子1つごとにMahjongTile3枚のリストになっているためリストのリストが返る
        """
        tiles = self.hands[:]
        mentus = []
        self.make_shuntus(tiles, mentus)
        return(mentus)

    def ankos(self):
        """
        手牌の内、暗刻のリストを返す

        Returns
        -------
        tiles : list
            手牌の暗刻のリスト。暗刻1つごとにMahjongTile3枚のリストになっているためリストのリストが返る
        """
        tiles = self.hands[:]
        mentus = []
        self.make_kotus(tiles, mentus)
        if self.is_tumo or self.is_ron:
            for i in mentus:
                if self.latest_tile in i:
                    mentus.remove(i)
        return(mentus)

    def kotus(self):
        """
        Returns
        -------
        tiles : list
            手牌の刻子のリスト。刻子1つごとにMahjongTile3枚のリストになっているためリストのリストが返る
        """
        return(self.ankos() + self.minkos)

    def kantus(self):
        """
        Returns
        -------
        tiles : list
            手牌の槓子のリスト。槓子1つごとにMahjongTile3枚のリストになっているためリストのリストが返る
        """
        return(self.ankans + self.minkans)

    def yakus(self, cache=True):
        """
        プレイヤーの手牌でできる役のリストを返す

        Parameters
        ----------
        cache : bool
            キャッシュを使用するかどうか(Falseにするとキャッシュの有無を無視して再計算します)

        Returns
        -------
        yakus : list
            役のリスト。役の名前(適当ローマ字表記)が入っている
        """
        if not self.is_hora: raise RuntimeError('Not hora')
        yakus = []

        if self.tiles_cache != self.hands:
            cache = False
            self.tiles_cache = self.hands[:]

        if self.yakus_cache != [] and cache:
            return(self.yakus_cache)

        if self.is_riichi: yakus.append('riichi')
        if self.riichi_turn + 1 == self.turn: yakus.append('ippatu')
        if self.is_menzen() and self.is_tumo: yakus.append('menzentumo')
        tiles = self.hands[:] + sum(self.melds, [])
        table_wind = 'ton' if self.table is None else self.table.wind
        TILE_TYPES_YAKUHAI = self.TILE_TYPES[7:] + [self.wind] + [table_wind]
        for i in TILE_TYPES_YAKUHAI:
            if tiles.count(mahjongpy.MahjongTile(i)) == 3:
                yakus.append('yakuhai')
        kuitan = 'True' if self.table is None else self.table.kuitan
        tmp = tiles[:] if kuitan else self.hands[:]
        count = 0
        count += tmp.count(mahjongpy.MahjongTile('pinzu',1))
        count += tmp.count(mahjongpy.MahjongTile('pinzu',9))
        count += tmp.count(mahjongpy.MahjongTile('manzu',1))
        count += tmp.count(mahjongpy.MahjongTile('manzu',9))
        count += tmp.count(mahjongpy.MahjongTile('souzu',1))
        count += tmp.count(mahjongpy.MahjongTile('souzu',9))
        count += tmp.count(mahjongpy.MahjongTile('ton'))
        count += tmp.count(mahjongpy.MahjongTile('nan'))
        count += tmp.count(mahjongpy.MahjongTile('sha'))
        count += tmp.count(mahjongpy.MahjongTile('pei'))
        count += tmp.count(mahjongpy.MahjongTile('haku'))
        count += tmp.count(mahjongpy.MahjongTile('hatu'))
        count += tmp.count(mahjongpy.MahjongTile('tyun'))
        if count == 0: yakus.append('tanyao')
        if self.is_menzen() and len(self.shuntus()) == 4 and self.zyantou()[0].tile_type not in TILE_TYPES_YAKUHAI and self.is_wait_ryanmen(): yakus.append('pinfu')
        if self.is_ipeikou(self.hands[:], []): yakus.append('ipeikou')
        table_tiles = [] if self.table is None else self.table.tiles
        if len(table_tiles) == 14 and self.is_tumo: yakus.append('haitei')
        if len(table_tiles) == 14 and self.is_ron: yakus.append('houtei')
        if self.is_rinsyankaihou: yakus.append('rinsyankaihou')
        if False: yakus.append('tyankan')
        if self.is_doubleriichi: yakus.append('doubleriichi')
        if self.is_chitoitu(): yakus.append('chitoitu')
        tiles = self.hands[:] + sum(self.melds, [])
        judge = False
        for i in self.TILE_TYPES[:3]:
            count = []
            for j in range(1,10):
                count.append(len([k for k in tiles if k.tile_type == i and k.number == j]))
            if all([l > 0 for l in count]): judge = True
        if judge: yakus.append('ikkituukan')
        for i in range(1,8):
            count = []
            for j in self.TILE_TYPES[:3]:
                count.append(len([k for k in tiles if k.tile_type == j and k.number == i]))
                count.append(len([k for k in tiles if k.tile_type == j and k.number == i+1]))
                count.append(len([k for k in tiles if k.tile_type == j and k.number == i+1]))
            if count.count(1) == 9: yakus.append('sansyokudouzyun')
        for i in range(1,10):
            count = []
            for j in self.TILE_TYPES[:3]:
                count.append(len([k for k in tiles if k.tile_type == j and k.number == i]))
            if count.count(3) == 3: yakus.append('sansyokudoukou')
        if len(self.ankos()) == 3 or len(self.ankos())+len(self.ankans) == 3: yakus.append('sanankou')
        #if len(self.minkos) < 2  and len(self.kotus())+len(self.kantus()) == 3: yakus.append('sanankou')
        #if len(self.minkos) == 1  and len(self.kotus())+len(self.kantus()) == 4: yakus.append('sanankou')
        if len(self.kotus()) == 4 or len(self.kotus())+len(self.kantus()) == 4:
            if self.is_menzen():
                if self.is_tumo or self.hands.count(self.latest_tile) == 2:
                    yakus.append('suankou')
            else:
                yakus.append('toitoi')
        if self.is_chanta(): yakus.append('chanta')
        if len(self.kantus()) == 3: yakus.append('sankantu')
        if self.is_ryanpeikou(self.hands[:], []): yakus.append('ryanpeikou')
        if self.is_zyuntyan():yakus.append('zyuntyan')
        count = 0
        for i in self.TILE_TYPES[:3]:
            if len([j for j in tiles if j.tile_type in [i]+self.TILE_TYPES[3:]]) == 14: count += 1
        if count > 0: yakus.append('honitu')
        if self.zyantou()[0].number is None and len([i for i in tiles if i.number is None]) > 5: yakus.append('syousangen')
        if len([i for i in tiles if i.number in [1,9,None]]) == 14 and (not self.is_kokushimusou()): yakus.append('honroutou')
        for i in self.TILE_TYPES[:3]:
            if len([j for j in tiles if j.tile_type in [i]]) == 14:yakus.append('chinitu')
        count = 0
        for i in self.TILE_TYPES[7:]:
            if len([j for j in tiles if j.tile_type == i]) > 2: count += 1
        if count == 3: yakus.append('daisangen')
        if self.is_kokushimusou(): yakus.append('kokushimusou')
        if self.zyantou()[0].tile_type in self.TILE_TYPES[3:7] and len([i for i in tiles if i.tile_type in self.TILE_TYPES[3:7]]) > 8: yakus.append('syoususi')
        count = 0
        for i in self.TILE_TYPES[3:7]:
            if len([j for j in tiles if j.tile_type == i]) > 2: count += 1
        if count == 4: yakus.append('daisusi')
        if len([i for i in tiles if i.tile_type in ['souzu', 'hatu']]) == 14: yakus.append('ryuisou')
        if len([i for i in tiles if i.tile_type in self.TILE_TYPES[3:]]) == 14: yakus.append('tuisou')
        if len([i for i in tiles if i.number in [1,9]]) == 14: yakus.append('chinroutou')
        if len(self.kantus()) == 4: yakus.append('sukantu')
        if self.is_menzen:
            for i in self.TILE_TYPES[:3]:
                count = []
                for j in range(1,10):
                    count.append(self.hands.count(mahjongpy.MahjongTile(i,j)))
                if count.count(3) == 2 and count.count(2) == 1 and count.count(1) == 6: yakus.append('tyurenboutou')
            is_furoed = False if self.table is None else self.table.is_furoed
            if not is_furoed and (not self.oya) and self.turn == 0: yakus.append('chihou')
            if self.oya and self.turn == 0: yakus.append('tenhou')

        self.yakus_cache = yakus
        return(yakus)

    def score_fu(self, debug=False):
        """
        Parameters
        ----------
        debug : bool
            符の内訳を表示

        Returns
        -------
        score_fu : int
            手牌の符数
        """
        yakus = self.yakus()
        if self.is_tumo and 'pinfu' in yakus: return(20)
        if 'chitoitu' in yakus: return(25)

        score_fu = 20
        if debug: print('Futei:20')

        if self.is_menzen() and self.is_ron:
            score_fu += 10
            if debug: print('Menzen&Ron:10')
        elif self.is_tumo:
            score_fu += 2
            if debug: print('Tumo:2')

        for i in self.minkos:
            if i[0].number in range(2,9):
                score_fu += 2
                if debug: print('Tyuntyanpai Minko:2')
            else:
                score_fu += 4
                if debug: print('Yaotyuhai Minko:4')
        #ankos = []
        #self.make_kotus(self.hands[:], ankos)
        for i in self.ankos():
            if i[0].number in range(2,9):
                score_fu += 4
                if debug: print('Tyuntyanpai Anko:4')
            else:
                score_fu += 8
                if debug: print('Yaotyuhai Anko:8')
        for i in self.minkans:
            if i[0].number in range(2,9):
                score_fu += 8
                if debug: print('Tyuntyanpai Minkan:8')
            else:
                score_fu += 16
                if debug: print('Yaotyuhai Minkan:16')
        for i in self.ankans:
            if i[0].number in range(2,9):
                score_fu += 16
                if debug: print('Tyuntyanpai Ankan:16')
            else:
                score_fu += 32
                if debug: print('Yaotyuhai Ankan:32')
        table_wind = "" if self.table is None else self.table.wind
        if self.zyantou()[0].tile_type in ['haku', 'hatu', 'tyun', self.wind, table_wind]:
            score_fu += 2
            if debug: print('Zyantou Yakuhai:2')
        if (not self.is_wait_ryanmen()) and (not self.is_wait_syabo()):
            score_fu += 2
            if debug: print('Not ryanmenmachi:2')

        if debug: print('sum:{}'.format(score_fu))
        if score_fu % 10 == 0: score_fu -= 10
        score_fu = ((score_fu // 10)+1)*10
        score_fu = max(30, score_fu)

        if debug: print('kiriage:{}'.format(score_fu))
        return(score_fu)

    def score_han(self, debug=False):
        """
        Parameters
        ----------
        debug : bool
            翻の内訳を表示
        Returns
        -------
        score_fu : int
            手牌の翻数
        """
        if self.is_yakuman():
            if debug: print('yakuman')
            return(13)
        elif self.is_doubleyakuman():
            if debug: print('doubleYakuman')
            return(23)
        elif self.is_tripleyakuman():
            if debug: print('TripleYakuman')
            return(30)
        yaku_hans = {'riichi':1, 'ippatu':1, 'menzentumo':1, 'pinfu':1, 'tanyao':1, 'ipeikou':1, 'yakuhai':1,
                     'rinsyankaihou':1, 'haitei':1, 'houtei':1, 'tyankan':1, 'doubleriichi':2, 'chanta':2,
                     'ikkituukan':2, 'sansyokudouzyun':2, 'sansyokudoukou':2, 'sanankou':2, 'sankantu':2,
                     'toitoi':2, 'chitoitu':2, 'zyuntyan':3, 'ryanpeikou':3, 'honitu':3, 'honroutou':2,
                     'syousangen':2, 'chinitu':6}
        yaku_hans_furoed = dict(yaku_hans.items())
        yaku_hans_furoed['tyankan'] = 1
        yaku_hans_furoed['ikkituukan'] = 1
        yaku_hans_furoed['sansyokudouzyun'] = 1
        yaku_hans_furoed['zyuntyan'] = 2
        yaku_hans_furoed['honitu'] = 2
        yaku_hans_furoed['chinitu'] = 5
        score_han = 0
        yakus = self.yakus()
        if self.is_menzen():
            for i in yakus:
                score_han += yaku_hans[i]
                if debug: print('{}:{}'.format(i,yaku_hans[i]))
        else:
            for i in yakus:
                score_han += yaku_hans_furoed[i]
                if debug: print('{}(kui):{}'.format(i,yaku_hans[i]))

        score_han += self.doras()
        if debug: print('dora:{}'.format(self.displayed_doras()))
        if debug: print('akadora:{}'.format(self.akadoras()))

        if debug: print('sum:{}'.format(score_han))
        return(score_han)

    def is_mangan(self):
        """
        Returns
        -------
        is_mangan : bool
            満貫かどうか
        """
        fu = self.score_fu()
        han = self.score_han()
        return((han == 3 and fu > 69) or (han == 4 and fu > 39) or (han == 5))

    def is_haneman(self):
        """
        Returns
        -------
        is_haneman : bool
            跳満かどうか
        """
        return(self.score_han() in [6,7])

    def is_baiman(self):
        """
        Returns
        -------
        is_baiman : bool
            倍満かどうか
        """
        return(self.score_han() in [8,9,10])

    def is_sanbaiman(self):
        """
        Returns
        -------
        is_sanbaiman : bool
            三倍満かどうか
        """
        return(self.score_han() in [11,12,13])

    def is_kazoeyakuman(self):
        """
        Returns
        -------
        is_kazoeyakuman : bool
            数え役満かどうか
        """
        return(self.score_han() > 12)

    def yakuman_count(self):
        """
        Returns
        -------
        count : int
            役満の役の数
        """
        count = 0
        for i in self.yakus():
            if i in ['suankou', 'daisangen', 'kokushimusou', 'ryuisou', 'tuisou', 'chinroutou', 'sukantu',
                     'syoususi', 'daisusi', 'tyurenboutou', 'chihou', 'tenhou']:
                count += 1
        return(count)

    def is_yakuman(self):
        """
        Returns
        -------
        is_sanbaiman : bool
            役満かどうか
        """
        return(self.yakuman_count() == 1)

    def is_doubleyakuman(self):
        """
        Returns
        -------
        is_sanbaiman : bool
            ダブル役満かどうか
        """
        return(self.yakuman_count() == 2)

    def is_tripleyakuman(self):
        """
        Returns
        -------
        is_sanbaiman : bool
            トリプル役満かどうか
        """
        return(self.yakuman_count() == 3)

    def score_ron(self):
        """
        Returns
        -------
        score : int
            ロンのときの獲得点数
        """
        SCORE_OYA = [[0,0,1500,2000,2400,2900,3400,3900,4400,4800,5300],
                     [2100,2400,2900,3900,4800,5800,6800,7700,8700,9600,10600],
                     [3900,4800,5800,7700,9600,11600]+[12000]*5,
                     [7800,9600,11600]+[12000]*8,
                     [12000]]+[[18000]]*2+[[24000]]*3+[[36000]]*2+[[48000]]*9+[[96000]]*9+[[144000]]*9
        SCORE_KO = [[0,0,1000,1300,1600,2000,2300,2600,2900,3200,3600],
                    [1500,1600,2000,2600,3200,3900,4500,5200,5800,6400,7100],
                    [2700,3200,3900,5200,6400,7700]+[8000]*5,
                    [5200,6400,7700]+[8000]*8,
                    [8000]]+[[12000]]*2+[[16000]]*3+[[24000]]*2+[[32000]]*9+[[64000]]*9+[[96000]]*9
        fu_index = ([20,25] + [i*10 for i in range(3,12)]).index(self.score_fu())
        if self.score_han() > 4:fu_index = 0
        if self.oya:
            score = SCORE_OYA[self.score_han()-1][fu_index]
        else:
            score = SCORE_KO[self.score_han()-1][fu_index]
        return(score)

    def score_without_tsumibo(self):
        """
        Returns
        -------
        score : int
            手牌の点数(場の積み棒分の点数を除く)
        """
        if not self.is_hora: raise RuntimeError('Not hora')

        if self.score_without_tsumibo_cache != 0: return(self.score_without_tsumibo_cache)

        score = self.score_ron()
        if self.is_tumo:
            if self.oya:
                tmp = ((score//100)//3)*100
                if (score//100) % 3 != 0:
                    tmp += 100
                score = tmp * 3
            else:
                tmp_oya = ((score//100)//2)*100
                if (score//100) % 2 != 0:
                    tmp_oya += 100
                tmp_ko = ((score//100)//4)*100
                if (score//100) % 4 != 0:
                    tmp_ko += 100
                score = tmp_oya + tmp_ko*2

        self.score_without_tsumibo_cache = score
        return(score)

    def score(self):
        """
        Returns
        -------
        score : int
            手牌の点数(積み棒分を含む)
        """
        if self.score_cache != 0: return(self.score_cache)
        honba = 0 if self.table is None else self.table.honba

        self.score_cache = self.score_without_tsumibo() + honba*300
        return(self.score_cache)

    def payed_score(self):
        """
        Returns
        -------
        score : list
            他家に払ってもらう手牌の点数のリスト。
            [ロンした時に振り込んだ人に払ってもらう点数, 親に払ってもらう点数, 子に払ってもらう点数]
        """
        score = self.score_ron()
        honba = 0 if self.table is None else self.table.honba
        if not self.is_hora: raise RuntimeError('Not hora')
        if self.is_ron: return([self.score(), 0, 0])
        elif self.oya:
            tmp = ((score//100)//3)*100
            if (score//100) % 3 != 0:
                tmp += 100
            tmp += honba*100
            return([0, 0, tmp])
        else:
            tmp_oya = ((score//100)//2)*100
            if (score//100) % 2 != 0:
                tmp_oya += 100
            tmp_oya += honba*100
            tmp_ko = ((score//100)//4)*100
            if (score//100) % 4 != 0:
                tmp_ko += 100
            tmp_ko += honba*100
            return([0, tmp_oya, tmp_ko])

    def is_menzen(self):
        """
        Returns
        -------
        is_menzen : bool
            門前かどうか
        """
        return((len(self.melds) - len(self.ankans)) == 0)

    def is_wait_ryanmen(self):
        """
        Returns
        -------
        is_wait_ryanmen : bool
            両面待ちかどうか
        """
        tiles = []
        if self.latest_tile.number is None: return(False)
        if self.latest_tile.number > 2:
            tiles.append(mahjongpy.MahjongTile(self.latest_tile.tile_type, self.latest_tile.number-2))
        if self.latest_tile.number < 8:
            tiles.append(mahjongpy.MahjongTile(self.latest_tile.tile_type, self.latest_tile.number+2))
        return(any([(i in self.hands) for i in tiles]))

    def is_wait_syabo(self):
        """
        Returns
        -------
        is_wait_syabo : bool
            シャボ待ちかどうか
        """
        return(self.hands.count(self.latest_tile) == 3)

    def discard(self, tile):
        """
        牌を捨てる

        Parameters
        ----------
        tile : MahjongTile
            捨てる牌

        """
        if tile not in self.hands:
            raise RuntimeError('does NOT have such tile')
        else:
            self.turn += 1
            self.discards.append(self.hands.pop(self.hands.index(mahjongpy.MahjongTile(tile.tile_type,tile.number,akadora=tile.akadora))))

    def riichi(self):
        """
        リーチする

        Raises
        ------
        RuntimeError
            門前でないと鳴けない
            テンパイでないと鳴けない
        """
        if not self.is_menzen(): raise RuntimeError('Can Riichi ONLY when menzen')
        if not self.is_tenpai(): raise RuntimeError('Can Riichi ONLY when tenpai')
        if self.points < 1000: raise RuntimeError('Cannot Richii by lack of points')
        if self.table is not None and len(self.table.tiles) < 4: raise RuntimeError('Cannot Riichi by lack of table tiles')
        is_furoed = False if self.table is None else self.table.is_furoed
        if self.turn == 0 and not is_furoed: self.is_doubleriichi = True
        self.riichi_turn = self.turn
        self.is_riichi = True
        if self.table is not None: self.table.ri_bou += 1

    def can_pon(self, tile):
        """
        Parameters
        ----------
        tile : MahjongTile
            他家が捨てた牌

        Returns
        -------
        can_pon : bool
            ポンできるかどうか
        """
        return(self.hands.count(tile) > 1)

    def can_chi(self, tile):
        """
        Parameters
        ----------
        tile : MahjongTile
            上家が捨てた牌

        Returns
        -------
        can_pon : bool
            チーできるかどうか
        """
        if tile.tile_type in self.TILE_TYPES[3:]: return(False)
        count = []
        if tile.number > 2:
            count.append(self.hands.count(mahjongpy.MahjongTile(tile.tile_type,tile.number-2)))
        else:
            count.append(0)
        if tile.number > 1:
            count.append(self.hands.count(mahjongpy.MahjongTile(tile.tile_type,tile.number-1)))
        else:
            count.append(0)
        if tile.number < 9:
            count.append(self.hands.count(mahjongpy.MahjongTile(tile.tile_type,tile.number+1)))
        else:
            count.append(0)
        if tile.number < 8:
            count.append(self.hands.count(mahjongpy.MahjongTile(tile.tile_type,tile.number+2)))
        else:
            count.append(0)

        judge = False
        for i in range(3):
            if count[i:i+2].count(1) == 2:
                judge = True
        return(judge)

    def can_ankan(self):
        """
        Returns
        -------
        can_ankan : bool
            暗槓できるかどうか
        """
        count = []
        for i in self.TILE_TYPES:
            for j in range(1,10):
                count.append(self.hands.count(mahjongpy.MahjongTile(i,j)))
        return(count.count(4) > 0)

    def can_minkan(self, tile):
        """
        Parameters
        ----------
        tile : MahjongTile
            他家が捨てた牌

        Returns
        -------
        can_minkan : bool
            明槓できるかどうか
        """
        return(self.hands.count(tile) == 3)

    def can_kakan(self):
        """
        Returns
        -------
        can_kakan : bool
            加槓できるかどうか
        """
        judge = False
        for i in self.hands:
            for j in self.minkos:
                if j[0] == i:
                    judge = True
        return(judge)

    def can_ron(self, tile):
        """
        Parameters
        ----------
        tile : MahjongTile
            他家が捨てた牌

        Returns
        -------
        can_tumo : bool
            ツモできるかどうか
        """
        return(mahjongpy.MahjongPlayer(hands=self.hands[:]+[tile],melds=self.melds[:]).is_hora())

    def kan(self, tile):
        """
        カンする(暗槓または大明槓)

        Parameters
        ----------
        tile : MahjongTile
            カンする牌
        """
        p = None
        players = [None] if self.table is None else self.table.players
        count = self.hands.count(tile)
        if count != 4:
            for i in players:
                if len(i.discards) != 0 and i.discards[-1] == tile:
                    p = i
        if p is None and count != 4: raise RuntimeError('Nobody discards such tile')
        if count < 3: raise RuntimeError('Lack of amount of tiles for kan')
        tmp = []
        if p is None:  # 暗槓
            for _ in range(4):
                tmp.append(self.hands.pop(self.hands.index(tile)))
            self.ankans.append(tmp)
            self.melds.append(tmp[:3])
            self.table.draw(self)
            if self.is_hora: self.is_rinsyankaihou = True
            self.table.add_kandora()
            #self.discard(SOME_TILE)
        else:
            for _ in range(3):  # 大明槓
                tmp.append(self.hands.pop(self.hands.index(tile)))
            tmp.append(p.discards.pop(p.discards.index(tile)))
            index = 0 if self.table is None else {0:3,1:1,2:0}[self.table.players[1:].index(p)]
            tmp[index].from_tacha = True
            self.minkans.append(tmp)
            self.melds.append(tmp[:3])
            self.table.draw(self)
            if self.is_hora:self.is_rinsyankaihou = True
            if self.table.kandora_sokumekuri:
                self.table.add_kandora()
                #self.discard(SOME_TILE)
            else:
                pass
                #self.discard(SOME_TILE)
                #self.table.add_kandora()

    def kakan(self, tile):
        """
        カンする(加槓(小明槓))

        Parameters
        ----------
        tile : MahjongTile
            カンする牌
        """
        if tile not in self.hands: raise RuntimeError('You DON\'T have such tile')
        flag = False
        index = None
        for i in range(len(self.minkos)):
            if self.minkos[i][0] == tile:
                flag = True
                index = i
        if not flag: raise RuntimeError('You DON\'T have such tile of minko')
        tmp = self.hands.pop(self.hands.index(tile))
        self.minkans.append(self.minkos[i]+[tile])

    def pon(self, tile):
        """
        ポンする

        Parameters
        ----------
        tile : MahjongTile
            ポンする牌
        """
        p = None
        players = [None] if self.table is None else self.table.players
        for i in players:
            if len(i.discards) != 0:
                if i.discards[-1] == tile:
                    p = i
        count = self.hands.count(tile)
        if p is None: raise RuntimeError('Nobody discards such tile')
        if count != 2: raise RuntimeError('You DON\'T have toitu of such tile')
        tmp = []
        for _ in range(2):
            tmp.append(self.hands.pop(self.hands.index(tile)))
        tmp.append(p.discards.pop(p.discards.index(tile)))
        index = 0 if self.table is None else {0:2,1:1,2:0}[self.table.players[1:].index(p)]
        tmp[index].from_tacha = True
        self.melds.append(tmp)
        self.minkos.append(tmp)

    def chi(self, tile):
        """
        チーする

        Parameters
        ----------
        tile : MahjongTile
            チーする牌
        """
        p = None
        players = [None] if self.table is None else self.table.players
        for i in players:
            if len(i.discards) != 0:
                if i.discards[-1] == tile:
                    p = i
        if p is None: raise RuntimeError('Nobody discards such tile')
        tiles = self.hands + [tile]
        shuntus = []
        self.make_shuntus(tiles, shuntus)
        tmp = []
        for i in shuntus:
            for j in i:
                if j == tile:
                    tmp = i
        tmp2 = []
        for i in tmp:
            if i == tile:
                tmp2.append(p.discards.pop(p.discards.index(i)))
            else:
                tmp2.append(self.hands.pop(self.hands.index(i)))
        self.melds.append(tmp2)

    def ron(self, tile):
        """
        ロンする

        Parameters
        ----------
        tile : MahjongTile
            ロンする牌
        """
        p = None
        players = [None] if self.table is None else self.table.players
        for i in players:
            if len(i.discards) != 0 and i.discards[-1] == tile:
                p = i
        self.hands.append(tile)
        self.sort()
        if not self.is_hora():
            raise RuntimeError('Cannot hora')
            self.hands.pop(self.hands.index(tile))
        elif len(self.yakus()) == 0:
            raise RuntimeError('NO YAKUS!!')
        else:
            self.is_ron = True
            if self.riichi and self.table is not None:
                for i in range(len(self.table.dora_tiles)):
                    self.table.add_kandora(ura=True)
        if self.table is not None:
            self.table.furikomi_player = p
            self.table.win_player = self
            self.table.calculate_score()

    def tumo(self):
        """
        ツモする
        """
        if not self.is_hora():
            raise RuntimeError('Cannot hora')
        elif len(self.yakus()) == 0:
            raise RuntimeError('NO YAKUS!!')
        else:
            self.is_tumo = True
            if self.table is not None:
                self.table.win_player = self
                self.table.calculate_score()
            if self.riichi and self.table is not None:
                for i in range(len(self.table.dora_tiles)):
                    self.table.add_kandora(ura=True)

    def next_player(self):
        """
        Returns
        -------
        player : MahjongPlayer
            自身の次のプレイヤー
        """
        players = [None]*4 if self.table is None else self.table.players
        p1, p2, p3, p4 = players
        next_player = {p1:p2, p2:p3, p3:p4, p4:p1}
        return(next_player[self])
예제 #16
0
    def shanten(self):
        """
        プレイヤーのシャンテン数を計算。一向聴で1、二向聴で2…… を返す

        Returns
        -------
        count : int
            プレイヤーのシャンテン数
        """
        counts = [100]

        tiles = self.hands[:]
        mentus = []
        self.make_shuntus(tiles, mentus)
        self.make_kotus(tiles, mentus)
        self.make_zyantou(tiles, mentus)
        tmp = tiles[:]
        count = 0
        for i in self.TILE_TYPES[:3]:
            for j in range(1,8):
                if mahjongpy.MahjongTile(i,j) in tmp:
                    if mahjongpy.MahjongTile(i,j+1) in tmp:
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j+1)))
                        count += 1
                    if mahjongpy.MahjongTile(i,j+2) in tmp:
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j+2)))
                        count += 1
        for i in self.TILE_TYPES:
            for j in range(1,8):
                if tmp.count(mahjongpy.MahjongTile(i,j)) == 2:
                    tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                    tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                    count += 1
                if len(tmp)-count == 2:
                    tmp.pop(0)
                    count += 1
        if len(tmp) == count: counts.append(count)

        tiles = self.hands[:]
        mentus = []
        self.make_zyantou(tiles, mentus)
        self.make_shuntus(tiles, mentus)
        self.make_kotus(tiles, mentus)
        tmp = tiles[:]
        count = 0
        for i in self.TILE_TYPES[:3]:
            for j in range(1,8):
                if mahjongpy.MahjongTile(i,j) in tmp:
                    if mahjongpy.MahjongTile(i,j+1) in tmp:
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j+1)))
                        count += 1
                    if mahjongpy.MahjongTile(i,j+2) in tmp:
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                        tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j+2)))
                        count += 1
        for i in self.TILE_TYPES:
            for j in range(1,8):
                if tmp.count(mahjongpy.MahjongTile(i,j)) == 2:
                    tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                    tmp.pop(tmp.index(mahjongpy.MahjongTile(i,j)))
                    count += 1
                if len(tmp)-count == 2:
                    tmp.pop(0)
                    count += 1
        if len(tmp) == count: counts.append(count)

        tmp = tiles[:]
        count = 0
        for i in self.TILE_TYPES:
            for j in range(1,10):
                if tmp.count(mahjongpy.MahjongTile(i,j)) == 2: count += 1
        counts.append(7-count)  # 七対子用

        tiles = self.hands[:]
        tmp = []
        tmp.append(tiles.count(mahjongpy.MahjongTile('pinzu',1)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('pinzu',9)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('manzu',1)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('manzu',9)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('souzu',1)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('souzu',9)))
        tmp.append(tiles.count(mahjongpy.MahjongTile('ton')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('nan')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('sha')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('pei')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('haku')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('hatu')))
        tmp.append(tiles.count(mahjongpy.MahjongTile('tyun')))
        if tmp.count(1) == 13: counts.append(1)
        elif tmp.count(2) > 1: counts.append(13-tmp.count(1))
        else: counts.append(13-tmp.count(1)+1)

        return(min(counts)-1)