四川省上海を作ったので麻雀ネタで聴牌の待ちを調べる方法を考えます。今回の記事は麻雀のルールを知らない方には完全に意味不明なものとなります。

聴牌であるかどうかを調べる

一筒から九筒まで4枚ずつ合計36枚の牌から13枚の牌を選びます。これが聴牌であればこれを問題として出題します。聴牌でなければもう一度選び直す処理を繰り返します。

では聴牌であるかどうかはどうやれば判別できるでしょうか? 13枚の牌に一筒から九筒までの牌を1枚足して14枚にしてこれが和了形かどうかを調べます。もし和了形であれば3枚の牌は聴牌であり、追加した牌が待ち牌のひとつであったことになります。

和了形であるかどうかを調べる

では14枚の牌が和了形かどうかはどうやって調べればよいでしょうか? いろいろなやり方があると思うのですが、ここでは以下の方法で判定します。

七対子については別に考えます。

2枚以上同じ種類の牌があればそれは雀頭である可能性があります。そこで雀頭の候補を14枚の牌のなかから取り除きます。和了形であれば、残された12枚のなかには同じ種類の3つの牌で構成される刻子と連続する3つの牌で構成される順子があわせて4組存在するはずです。ただしどれが刻子で順子なのかはわかりません。

そこで残された12枚のなかに3枚以上同じ種類の牌があればそれは刻子かもしれないので、それらをチェックします。刻子の候補を取り除く場合と取り除かない場合で残された牌から順子を取り除き、すべての牌を取り除けるかを考えます。刻子の候補の数は0以上4以下なのでビット全探索をすればすぐに終わりそうです。

また順子を取り除く処理は、一番小さな数の牌とその次の牌とさらにその次の牌を取り除くという操作を繰り返すだけです。

七対子であるかどうかは異なる7つの対子が存在するかどうかを調べるだけです。

HTML部分

style.css

グローバル変数と定数

グローバル変数と定数を示します。

app.js

ページが読み込まれたときの処理

ページが読み込まれたら以下の処理をおこないます。

canvasサイズと位置を調整、追加
画像ファイルを読み込んで牌の描画につかうイメージを取得する
ボタンがクリックされたときのイベントリスナの追加
問題の生成
描画処理

描画処理

描画に関する処理を示します。配列 problemに格納されている整数から聴牌の牌を描画します。showAnswerフラグがtrueであれば配列ansに格納されている解も描画します。

問題の生成

問題の生成を生成する処理を示します。9種類 × 4枚の牌のなかから13枚をランダムに選んで聴牌かどうかを調べます。後述するsolve関数を実行して聴牌なら同時に解も得られるのでこれを出題します。そうでない場合はもう一度選び直します。

聴牌かどうか調べる

聴牌かどうか調べる処理を示します。

配列 counts を定義してどの数牌が何枚あるかわかるようにしておきます。

刻子と順子をすべて取り除けるか調べる処理を示します。どれが刻子なのかはわからないので3枚以上揃っている牌は刻子の候補とみなしてそれぞれが刻子の場合、そうでない場合を全探索します(ビット全探索)。