Remrinのpython攻略日記

python3に入門しました。python3についてあれこれとサンプルコードとか。

麻雀(1)

何年かかるかわかりませんがプログラムの練習で麻雀を作ってみようと思います。
 

m 萬子 0-8
p 筒子 9-17
s 索子 18-26
h 字牌 東:27~中:33

 

pair 対子
triplet 暗刻
Triplet 明刻
seq 順子
kan 暗槓
Kan 明槓
prevailing_wind 場風
seat_wind 自風
dealer

 

wall_seq 136枚の牌をシャッフルしたシーケンス。
hand 手牌。1次元ndarrayでsize34

 
文字列入力をhand配列へ。

import numpy as np

ji_dic2= dict((s, i + 27) for i, s in enumerate("東南西北白發中"))

def str_to_hand(string):
    hand = np.zeros(34, dtype=int)
    suit = np.zeros(9, dtype=int)
    while string:
        if string[0] in "123456789":
            suit[int(string[0]) - 1] +=1
        elif string[0] in "mps":
            mps = (string[0]=="p")*9 + (string[0]=="s")*18
            hand[mps:mps+9] += suit
            suit.fill(0)
        else:
            hand[ji_dic2[string[0]]] += 1
        string = string[1:]
    return hand

data = ["123m123p123789s中中"]
data.append("1133557799m1133p")
data.append("19m19p19s東南西北白發中")
data.append("123m123p123789s白中")
data.append("111222333999m11s")
data.append("112233777999m11p")
data.append("白1m1s白白1m1m222m11s中中")
for i, hand in enumerate(data):
    print(i, str_to_hand(data[i]))
        
# 0 [1 1 1 ..., 0 0 2]
# 1 [2 0 2 ..., 0 0 0]
# 2 [1 0 0 ..., 1 1 1]
# 3 [1 1 1 ..., 1 0 1]
# 4 [3 3 3 ..., 0 0 0]
# 5 [2 2 2 ..., 0 0 0]
# 6 [3 3 0 ..., 3 0 2]

 
クラスで作ってみると

import numpy as np

ji_dic2= dict((s, i + 27) for i, s in enumerate("東南西北白發中"))

class Hand():
    def __init__(self, hand):
        if isinstance(hand, str):
            hand = self.str_to_hand(hand)
        self.hand = hand
        
    def str_to_hand(self, string):
        hand = np.zeros(34, dtype=int)
        suit = np.zeros(9, dtype=int)
        string = string.replace(" ", "").replace("[", "").replace("]", "")
        while string:
            if string[0] in "123456789":
                suit[int(string[0]) - 1] +=1
            elif string[0] in "mps":
                mps = (string[0]=="p")*9 + (string[0]=="s")*18
                hand[mps:mps+9] += suit
                suit.fill(0)
            elif string[0] in "東南西北白發中":
                hand[ji_dic2[string[0]]] += 1
            string = string[1:]
        return hand

    def hand_to_str(self):
        pass
               
        
if __name__ == "__main__":
    data = ["123m123p123789s中中"]
    data.append("1133557799m1133p")
    data.append("19m19p19s東南西北白發中中")
    data.append("123m123p123789s白中")
    data.append("111222333999m11s")
    data.append("112233777999m11p")
    data.append("白1m1s白白1m1m222m11s 中中")
    for i, hand in enumerate(data):
        h = Hand(hand).hand
        print(i, h)
# 0 [1 1 1 ..., 0 0 2]
# 1 [2 0 2 ..., 0 0 0]
# 2 [1 0 0 ..., 1 1 2]
# 3 [1 1 1 ..., 1 0 1]
# 4 [3 3 3 ..., 0 0 0]
# 5 [2 2 2 ..., 0 0 0]
# 6 [3 3 0 ..., 3 0 2]

 
役判定から点数計算までの流れ(仮)
・手牌の形だけで判断できる役
 ・国士無双かどうか
 ・七対子かどうか
 ・その他:1雀頭、4面子になっているか
   ・まず雀頭で場合分け
   ・刻子で場合分け
   ・残りが順子か
・手牌の形と状態(面前かどうかなど)で判断できる役
風牌
・状態で判断できる役(天和、地和、海底、嶺上、立直、一発、ツモなど)
・ドラのせ
・点数計算

# coding: utf-8

import numpy as np
from itertools import combinations

ji_dic = dict((i + 27, s) for i, s in enumerate("東南西北白發中"))
ji_dic2= dict((s, i + 27) for i, s in enumerate("東南西北白發中"))
yaku_dic = {0:"7 pairs", 1:"13 orphans", 2:"Concealed four pongs"}

def hand_to_str(hand):
    string = str(np.repeat(np.arange(1, 10), hand[:9]))
    string += "m " * any(hand[:9])    
    string += str(np.repeat(np.arange(1, 10), hand[9:18]))
    string += "p " * any(hand[9:18])    
    string += str(np.repeat(np.arange(1, 10), hand[18:27]))
    string += "s " * any(hand[18:27])
    for i in range(27, 34):
        string += ji_dic[i] * hand[i]
    return string

def is_winhand(hand):
    # seven pairs
    seven_pairs = sum(hand == 2) == 7
    yaku = "seven_pairs" if seven_pairs else ""
    # 13 orphans
    if np.all(hand[[0, 8, 9, 17, 18, 26, 27, 28, 29, 30, 31, 32, 33]] > 0):
        return "13 orphans"
    else:
        result = melding(hand)
    return yaku if yaku else result if result else "not a winning hand"

# parsing
def melding(tiles):
    # 順子さがし
    def find_seq(tiles, melds):
        while sum(tiles):
            where = np.where(tiles > 0)[0] # 牌が存在する位置の0次元目→横方向インデックス
            if len(where)>2 and where[0] + 2 == where[1] + 1 == where[2]:
                melds.append((where[0], "seq"))
                tiles[where[0]] -= 1
                tiles[where[1]] -= 1
                tiles[where[2]] -= 1
            else:
                melds = []
                break
        return melds 
    
    # 刻子さがし
    def find_triplet(tiles, pair):
        items = np.where(tiles > 2)[0]
        triplets = []
        # find all subsets of triplets(刻子の全組み合わせを列挙)
        for i in range(len(items) + 1):
            for c in combinations(items, i):
                triplets.append(c)
        
        # find seq for each triplet-subset(それぞれについて順子さがし)
        result=[]
        for triplet in triplets:
            melds = [pair]
            tile = tiles.copy()
            for t in triplet:
                melds.append((t, "triplet"))
                tile[t] -= 3
            seq = find_seq(tile, melds)
            if seq:
                result.append(seq)
        return result

    # find pair(対子さがし)
    m = []
    for i in np.where(tiles > 1)[0]:
        hand = tiles.copy()
        meld = (i, "pair")
        hand[i] -= 2
        meld = find_triplet(hand, meld)
        if meld:
            m.append(meld)
    return m if m else ""

class Hand():
    def __init__(self, hand):
        #手牌が配列ではなく文字列なら、配列に変換
        if isinstance(hand, str):
            hand = self.str_to_hand(hand)
        self.hand = hand
    
    # 文字入力をNumPy配列に変換    
    def str_to_hand(self, string):
        hand = np.zeros(34, dtype=int)
        suit = np.zeros(9, dtype=int)
        string = string.replace(" ", "").replace("[", "").replace("]", "")
        while string:
            if string[0] in "123456789":
                suit[int(string[0]) - 1] +=1
            elif string[0] in "mps":
                mps = (string[0]=="p")*9 + (string[0]=="s")*18
                hand[mps:mps+9] += suit
                suit.fill(0)
            elif string[0] in "東南西北白發中":
                hand[ji_dic2[string[0]]] += 1
            string = string[1:]
        return hand               
        
if __name__ == "__main__":   
    data = ["123m123p123789s中中"]
    data.append("1133557799m1133p")        # 7 pairs
    data.append("19m19p19s東南西北白發中中") # 13 orphans
    data.append("123m123p123789s白中")      # not a winning hand
    data.append("111222333999m11s")
    data.append("112233777999m11p")
    data.append("白1m1s白白1m1m222m11s 中中")
    for i, hand in enumerate(data):
        h = Hand(hand).hand
        print(i, is_winhand(h))

        
# 0 [[[(33, 'pair'), (0, 'seq'), (9, 'seq'), (18, 'seq'), (24, 'seq')]]]
# 1 seven_pairs
# 2 13 orphans
# 3 not a winning hand
# 4 [[[(18, 'pair'), (8, 'triplet'), (0, 'seq'), (0, 'seq'), (0, 'seq')], [(18, 'pair'), (0, 'triplet'), (1, 'triplet'), (2, 'triplet'), (8, 'triplet')]]]
# 5 [[[(9, 'pair'), (6, 'triplet'), (8, 'triplet'), (0, 'seq'), (0, 'seq')]]]
# 6 [[[(33, 'pair'), (0, 'triplet'), (1, 'triplet'), (18, 'triplet'), (31, 'triplet')]]]