七対子の確率

猫と麻雀 麻雀のプログラミング

七対子の聴牌確率

Twitterで見かけたネタが気になったので僕も確認してみました。

条件

有効牌が全て山にあるという条件は、\( i \) 向聴のときに有効牌の数が \( 3(2i + 1) \) 枚あるという意味です。また手牌に同じ牌が \( 3 \) 枚以上ないと仮定しています。例えば、配牌が \( 1 \) 向聴のときは有効牌の種類は \( 3 \) であり、全体で \( 9 \) 枚の有効牌が山にあります。

次に、他家の影響はなく、\( 1 \) 回目のツモの抽選は、\( 136 – 13 = 123\) 枚の牌から行われます。\( 2 \) 回目では、\( 122 \) 枚からとなります。

厳密値とは、おそらくモンテカルロ法を使わずに、純粋な確率計算で求めるということだと思います。

確率の遷移

このような問題を考える上で、全ての組み合わせと特定の条件の組み合わせの数を数えるのではなく、確率漸化式を考えることで目的の答えを得ます。プログラミング的に言えば、動的計画法です。

ここで、\( p(i, j) \) を \( i \) 巡目までに \( j \) 向聴である確率とします。配牌の向聴数を \( s \) とすると、\( p(0, s) = 1 \) であり、それ以外の値は \( 0 \) で初期化します。以降は \( 1 \leq i \leq 18 \) について考えます。

\( j = 0 \) のとき

\( i \) 巡目までに聴牌している確率は、\( i – 1 \) 巡目までに聴牌している確率と \( i – 1 \) 巡目に \( 1 \) 向聴から有効牌を引いて聴牌する確率の和なので、

\[ p(i, 0) = p(i – 1, 0) + p(i – 1, 1) \times \dfrac{9}{124 – i}\]

となります。

\( j \geq 1 \) のとき

このとき、\( p(i, j) \) は、\( i – 1 \) 巡目までに \( j \) 向聴である状態から有効牌を引かなかった確率と \( i – 1 \) 巡目までに \( j + 1\) 向聴である状態から有効牌を引いた確率の和なので、

\[ p(i, j) = p(i – 1, j) \times \dfrac{124 – i – 3(2j + 1)}{124 – i} + p(i – 1, j) \times \dfrac{3(2(j + 1) + 1)}{124 – i} \]

となります。

また自明ですが、

\[ \sum_{j= 0}^{6} p(i, j) = 1\]

です。

グラフ

コード

# n向聴のときの有効牌の数
def get_num_necessary_tiles(n):
    return  (2 * n + 1) * 3

def set_prob(s):
    # p[i][j] i巡目までにj向聴である確率
    p = [[0] * 8 for i in range(19)]
    p[0][s] = 1
    for i in range(1, 19):
        n = 136 - 13 - i + 1 # 牌山の数

        for j in range(s + 1):
            if j == 0:
                p[i][j] = p[i - 1][j] + get_num_necessary_tiles(1) / n * p[i - 1][1]
            else:
                a = get_num_necessary_tiles(j + 1) / n
                b = (n - get_num_necessary_tiles(j)) / n
                p[i][j] = p[i - 1][j] * b + p[i - 1][j + 1] * a

    for i in range(19):
        print('%.4f %.4f %.4f %.4f' % (p[i][0], p[i][1], p[i][2], sum(p[i])))


set_prob(2)

コメント

タイトルとURLをコピーしました