PR





さくら美緒（さくら・みお）

今号の問題 図1に示す(1)から(3)までの麻雀（マージャン）の手牌があります。「あがり牌」はすべて山からツモったものとし，リーチはかけていません。またドラやハイテイ＊1なども関係ないものとします。これらの役を判定して，親の場合の点数を計算するプログラムを作ってください。 図1●(1)～(3)の役を判定して得点を計算してください

「ややこしや～ややこしや～」というのは野村萬斎ですが，思わずそううなってしまうことがプログラミングをしているとよくあります。今回の麻雀の役判定は，考えれば考えていくほどややこしく，そうしたものの代表と言えるでしょう。排他処理や優先順位が複雑にからんでいて一筋縄ではいきません。

今回はややこしい組み合わせを解決する方法を考えてみます。麻雀になじみのない方も，ちょっとしたパズル気分で試してみてください。

麻雀の役を考える

麻雀を知らない方のためにルールをおおざっぱに説明しておきましょう＊2。麻雀の牌には，大きく分けて「萬子（マンズ）」「索子（ソウズ）」「筒子（ピンズ）」と，文字通り字が書いてある「字牌」があります。萬子，索子，筒子はそれぞれ1～9まであり，ここでは「一萬」「二萬」「三索」のように記述します＊3。字牌は「東」「南」「西」「北」「白」「発」「中」の7種類です。これら34種類の牌がそれぞれ4枚ずつあるので，牌の総数は136枚になります。

ゲーム開始時には，これらを4人のプレーヤに13枚ずつ配って手牌とします。ゲームは各プレーヤが山（配った残りの牌）から一つ牌を取り（「ツモる」と言います），手牌からいらない牌を一つ捨てることで進行します。このほか，自分の手牌中の2枚とほかのプレーヤが捨てた1枚を組み合わせて後述する刻子/順子ができる場合は，ツモる代わりにその牌をもらうことができます。これを「なく」といいます。

これを繰り返し，手牌＋ツモった牌または別のプレーヤの捨て牌の計14枚が特定の組み合わせ（「役」と言います）になればあがることができます。ツモった場合は残り3人のプレーヤから，捨て牌であがった場合は捨てたプレーヤから役の種類と手配に応じた点数をもらえます。これらをそれぞれ「ツモ」「ロン」と言います。

標準的なあがり役の場合，「3個ずつの組み合わせ」×4組＋「2個同じ牌（「頭」と言います）」という組み合わせ（面子と言います）になります（図2）。「3個ずつの組み合わせ」には，3個とも同じ牌の「刻子（コーツ）」，123，789のように番号が連続している「順子（シュンツ）」があります。また同じ牌が2個ある場合は「対子（トイツ）」と言います。こうした組み合わせに当てはまらない牌を「浮き牌」といいます。

図2●一般的なあがりの形。3枚組の「刻子」または「順子」を4個と2枚組の「頭」で構成される

ゲームでは，この三つの組み合わせ方を目指していくので，「あと1枚であがる」という場面では，だいたい形が決まってきます。対子の片割れを1枚待つ「単騎（タンキ）待ち」，順子のうち両側のいずれかに牌がくればあがる「両面（リャンメン）待ち」などがあります。これをまとめて「待ち」といいます。

役は表1に示したものがあります。一気通貫など牌の組み合わせで決まる役もあれば，天和など偶然性の強いものもあります。それぞれの役には，得点に関係する「飜数（ハンスウ）」が決められており，難しい役ほど飜数が高くなっています。複数の役が付く場合は，それぞれの飜数の合計がその手の飜数になります。加えて，1回のゲームごとに「ドラ」と呼ばれる牌がランダムに決まり，それが手牌にあるとそのぶん飜数が増えます。たまにドラ13という手をあがってひんしゅくを買う私です＊4。

あがり役の組み合わせで例外的なのは「七対子」「国士無双」「十三不塔」です。この三つは先ほどの4組×頭一つの組み合わせにはなりません。

得点を求めるには，まず手牌から「符」というものを計算します。符は基本となる20点（符）に，面子の状態と待ちの形から決まる「追加点」を加えたものになります。詳しい説明は麻雀の入門書などに譲りますが，おおざっぱに言えば作りにくい面子やあがりにくい待ちの場合に追加点が付くと考えていいでしょう。例えば順子には点が付かないのに対し，字牌の刻子をないて作った場合には1組について4点，なかずに作った場合には8点が追加されます。同様に，待ち牌が2種類ある両面待ちは0点，1種類しかない単騎待ちは2点が加算されます。

こうして計算した符の1の位を切り上げてから，それに2の（役の飜数の合計＋2）乗を掛け合わせ，さらに10の位を切り上げた値がプレーヤがもらえる得点になります＊5。プレーヤが親の場合は得点がこの1.5倍になります。ただし，役の飜数の合計が5以上のときなど，子の得点が8000（親の場合は12000）を超える場合は8000が上限になり，これを満貫と呼びます。これより上は飜数だけで得点が決まり，6飜，8飜，11飜以上の場合の子の得点は12000，16000，24000になります（それぞれハネ満，倍満，3倍満と言います）。

処理を区間ごとに分けて役判定を行う

役の判断は，単純そうに見えて意外と問題になるところが数多くあります。そのなかで最もパズルっぽい要素を持つのが頭/刻子/順子の見分け方です。手軽な方法としては「2個あったら頭」「3個あったら刻子」「いまの地点から三つ数が連続していたら順子」といった判定を先頭から繰り返していくことが考えられます。でもこの方法では，図1(1)の「223334445」を正しく判断できません。「頭かどうか」「刻子かどうか」「順子かどうか」という順序で先頭から判定を行うと，2を頭，3と4を刻子と判断して5が余ってしまうからです。このように，その牌だけ見ても頭/刻子/順子のどれであるかがすぐに決まらない場合は，浮き牌がなくなるような組み合わせを探すという，一種の探索処理が必要になります。

加えて，役によって同時に成立したりしなかったりする点にも注意が必要です。例えば七対子と二盃口は同時に成立しません。どちらとも判定できるなら，二盃口と判断した方が飜数やほかの役との組み合わせで七対子よりも得点が高くなる可能性が高そうです。こんなときはトータルで高い方の役を採用するというルールがあるので，最終的な判断は得点を出してからになります。このほか，混老頭はトイトイとも判定できてしまいますが，混老頭だけとして判断させるといった処理が必要です。

今回のプログラムでは，これらの問題をどう解決するかがカギになります。得点計算は，「面子を調べる」「役を調べる」「得点を得る」という流れになるでしょう。このとき，各段階で一つだけ最適なものを得るよりも，面子や役の構成がデータとして複数あってもいいようにまとめたいところです。そうすると得点を得るところで，最終的な判断ができるようになります。また例外的なケースは早めに調べてほかの処理を行わないようにするのが普通ですが，こうした例外処理をあまり増やしてしまうと各段階をまたぐようになり，処理がややこしくなる原因になってしまいます。

こうしてみると，「処理を区間ごとに分けて考える」ことにするとよさそうです。まず全体の流れを「あがりの形を得る」「役を得る」「得点を計算する」「最も高い手を採用する」という四つの区間に分けることにします。各区間では次の区間に必要なデータを作ることに専念して，区間ごとの処理での移動やスキップをしないようにします。例えば「あがりの形を得る」ところでは，考えられる面子の組み合わせをすべて作成し，「役を得る」区間に渡します。

各区間は次の区間の処理で使えるように「面子の状態」「役の構成」「得点」などといったデータを作れるだけ作ります。ベルト・コンベア式に各区間でこれらの値を埋めた後，「最も高い手を採用する」ところでふるいにかけて最終的な結果を得ます。こうしておけば，それほどややこしくなりません。この方針に基づいてアルゴリズムを作ってみましょう。