我打麻将の研究室

清一色解析の一般化

概要

清一色解析では「9種数牌が各4枚ずつ存在し、3枚組n個と2枚組1個を和牌型とする」(いわゆる「普通の」) 麻雀において、それぞれの牌の組合せが和牌型の一部となることができるかを判定してデータをまとめた。 ここでは純粋な興味により、ランクR・デュプリケーションI・面子構成牌の数S・対子構成牌の数P・和牌型に含まれるべき面子数s・同対子数pをすべてパラメータ化することを試みた。

和牌型分析

方法

以下の Python プログラムを実行した:

from integer import lcm
from listtool import prod
RANK, IDENTICAL = 9, 4 # tile condition
PAIR, SET, PAIRS, SETS = 2, 3, 1, 4 # winning condition: normal hand
TILEPERHAND = PAIRS*PAIR+SETS*SET
m = lcm(PAIR, SET)
convol = SETS*SET//m+1
SEQUENCE = 1
GANG = 4

- 定数の設定。convol は一般手と対子手とをつなぐ変数で、以下では「一般形」からm牌ずつを面子から対子に変更した和牌型として判定する。

def ptns(offlimitss, total):
    for ptn in range(prod(offlimitss)):
        ret = [ptn//prod(offlimitss[:rank])%offlimitss[rank] for rank in range(len(offlimitss))]
        if sum(ret) == total:
            yield ret

- 整数のリストofflimitssに対し、0以上その要素未満の整数からなるリストで、合計がtotalに等しいものを返す。 返り値にわたってのループとしてしか使われないため、ジェネレータとして実装することで、一度にすべてを生成して返してループさせるのに比べてメモリを大幅に節約できる (メモリ使用量が大きくなりすぎたのに気づいてジェネレータの使い方を覚えた)。

def sets(hand):
    'hand is sets'
    if SEQUENCE:
        for rank in range(RANK-SET+1):
            hand[rank] %= SET
            seqs = min(hand[rank:rank+SET])
            for srank in range(SET):
                hand[srank] -= seqs
    return not sum([hand[rank]%SET for rank in range(RANK)])

- 与えられた手が面子の集合であるかどうかを判定する。 先頭よりgreedyに面子を取り除いていき、すべての牌がなくなることを確認する。

def up(hand, conv):
    'valid as conv-th converted hand'
    pairol = [hand[rank]//PAIR + 1 for rank in range(RANK)]
    pairs = PAIRS+conv*m/PAIR
    for pptn in ptns(pairol, pairs):
        withoutpairs = [hand[rank]-pptn[rank]*PAIR for rank in range(RANK)]
        if sets(withoutpairs):
            return 1
    return 0

- 与えられた手が第conv和牌型であるかを確認する。 使用する対子パターンを生成して、それぞれを取り除いたもののうち、setsテストを通るものがあれば1を、全くなければ0を返す。

def upall(hand):
    return [up(hand, conv) for conv in range(convol)]

- すべてのconvについてupを判定する。

print 'up:'
for hand in ptns([IDENTICAL+1]*RANK, TILEPERHAND):
    print ', '.join(map(str, hand+['']+upall(hand)))

- 手牌パターンをptnsで生成し、upallを計算して出力する。

結果

「普通の」麻雀における結果。

課題