牌山と河のデータ構造

麻雀の手牌を表示するプログラム - koba::blog に続いて牌山と河を表示するプログラムを説明しようかと思いましたが、そのためにはまず牌山と河がJavaScriptでどのように表現されているかを知る必要があります。ということで今回は牌山と河のデータ構造について。

牌山のデータ構造

電脳麻将 で牌山を表現するクラスは Majiang.Shan です。これを見ると以下のように表現されていることが分かります。

{
    _pai:        [],     // 牌山
    /* …… */
}

コンストラクタでこれを初期化します。

constructor(hongpai = {m:0,p:0,s:0}) {

    /* まず、変数 pai に決められた順序で牌を並べる */
    let pai = [];
    for (let s of ['m','p','s','z']) {
        for (let n = 1; n <= (s == 'z' ? 7 : 9); n++) {
            for (let i = 0; i < 4; i++) {
                if (hongpai[s] && n == 5 && i < hongpai[s]) pai.push(s+0); // 赤牌
                else                                        pai.push(s+n);
            }
        }
    }
    /* 変数 pai からランダムに1枚抜き取り、インスタンス変数 _pai に加えるという操作を
       136回繰り返す */
    this._pai = [];
    while (pai.length) {
        this._pai.push(pai.splice(Math.random()*pai.length, 1)[0]);
    }
    /* …… */
}

クラス変数 _pai に136枚の牌がランダムに並べられました*1。JavaScriptの Math.random() は牌山生成に使うには充分なランダムさです。他の乱数生成系を使ったり、何度も洗牌するのは意味のないことです*2

ランダムに並べられた136枚の牌を以下のように使います。サイコロは振りません。開門場所を決めるサイコロは不正防止が目的であり、牌山がランダムに作られていれば本来不要だからです*3。また、実際の牌山の配列と王牌の使用方が微妙にずれています*4が、これも気にしても仕方のないことと思います。毎回同じ場所を割り当てることが重要です。

王牌嶺上牌 _pai[0]_pai[3] 4枚
ドラ表示牌 _pai[4]_pai[8] 5枚
裏ドラ表示牌_pai[9]_pai[13] 5枚
ツモ牌_pai[14]_pai[83] 70枚
配牌北家 _pai[84]_pai[96] 13枚
西家 _pai[97]_pai[109] 13枚
南家 _pai[110]_pai[122]13枚
東家 _pai[123]_pai[135]13枚

ツモは _pai[83]_pai[14] の順に使用します。嶺上牌は _pai[0]_pai[3] の順です。カンにより本来はツモれるはずであった牌(_pai[14] とか)がツモれなくなるのは麻雀の競技性と思うので、ツモ牌と嶺上牌は分けるべきと思います。王牌は常に14枚なので、残りツモ数は、

    this._pai.length - 14

となります。

河のデータ構造

河を表現するクラスは Majiang.He です*5

{
    _pai:  [],  // 捨て牌
    /* …… */
}

クラス変数 _pai に捨て牌を順に並べるだけです。リーチ宣言牌は m1* (一萬切りリーチ)のように * を付与します。鳴かれた牌も河に残しますが、m1*= (一萬切りリーチの宣言牌を対面がポン)のように鳴きを示すマークを付与します。鳴きを示すマークは副露牌のときのルールと同様です。下家に一萬をチーされた場合(m1-m2m3)は、副露メンツは m1-23、河の牌は m1- となります。表示上は鳴かれたリーチ宣言牌を河から取り除き、次の捨て牌を(鳴かれたリーチ宣言牌の代わりに)横向きに置く必要がありますが、それは表示ルーチンの役割分担としています。

*1:牌山の生成に神秘性を感じる方も多いようですが、洗牌は上のソースコードでは実質たった1行です

*2:天鳳では乱数生成系にメルセンヌ・ツイスタを使っていますが、あれは乱数の精度より再現性を目的としていると理解しています

*3:Mリーグでも開門のサイコロは振らず、自動配牌しますよね

*4:ドラ表示牌は上ヅモに当たるので、本来は偶数添字の牌になる

*5:中国麻将では「河」に並べて捨てるという習慣はなく、それに相当する中国語もないので、日本語の「河」を中国語読みにした He をクラス名にしました