麻雀の符を求めるプログラム

和了形を求めるプログラム(一般形) - koba::blog の続き。

和了形を求めることができたので、次は符計算である。符計算の際には面子の構成も調べることになるので、それを保存しておいて和了役の判定で使用することにする。

電脳麻将 の符計算を行う関数は get_hudi(mianzi, zhuangfeng, menfeng)

function get_hudi(mianzi, zhuangfeng, menfeng) {

    /* 面子構成のチェックに使う正規表現 */
    var zhuangfengpai = new RegExp('^z' + (zhuangfeng + 1) + '.*$');  // 場風
    var menfengpai    = new RegExp('^z' + (menfeng + 1) + '.*$');     // 自風
    var sanyuanpai    = /^z[567].*$/;                                 // 三元牌
    
    var yaojiu        = /^.*[z19].*$/;                 // 幺九牌
    var zipai         = /^z.*$/;                       // 字牌
    
    var kezi          = /^[mpsz](\d)\1\1.*$/;          // 刻子(槓子を含む)
    var ankezi        = /^[mpsz](\d)\1\1(?:\1|_\!)?$/; // 暗刻子(暗槓子を含む)
    var gangzi        = /^[mpsz](\d)\1\1.*\1.*$/;      // 槓子
    
    var danqi         = /^[mpsz](\d)\1[\-\+\=\_]\!$/;               // 単騎待ち
    var kanzhang      = /^[mps]\d\d[\-\+\=\_]\!\d$/;                // 嵌張待ち
    var bianzhang     = /^[mps](123[\-\+\=\_]\!|7[\-\+\=\_]\!89)$/; // 辺張待ち

    /* 返り値の初期値を設定する */
    var hudi = {
        fu:         20,          // 符計算の結果
        menqian:    true,        // 門前の場合 true
        zimo:       true,        // ツモ和了の場合 true
        shunzi:     { m: {}, p: {}, s: {} },         // 順子の面子構成
        kezi:       { m: [0,0,0,0,0,0,0,0,0,0],
                      p: [0,0,0,0,0,0,0,0,0,0],
                      s: [0,0,0,0,0,0,0,0,0,0],
                      z: [0,0,0,0,0,0,0,0]      },   // 刻子の面子構成
        n_shunzi:   0,           // 順子の数
        n_kezi:     0,           // 刻子の数(槓子を含む)
        n_ankezi:   0,           // 暗刻子の数(暗槓子を含む)
        n_gangzi:   0,           // 槓子の数
        n_zipai:    0,           // 字牌面子の数(雀頭を含む)
        n_yaojiu:   0,           // 幺九牌入り面子の数(雀頭を含む)
        danqi:      false,       // 単騎待ちの場合 true
        pinghu:     false,       // 平和の場合 true
        zhuangfeng: zhuangfeng,  // 場風(0: 東、1: 南、2: 西、3: 北)
        menfeng:    menfeng      // 自風(0: 東、1: 南、2: 西、3: 北)
    };
    
    for (var m of mianzi) {    // 和了形の各面子について以下の処理を行う
    
        if (m.match(/[\-\+\=]\!/))      hudi.zimo    = false;
                                               // ロン和了の場合 false を設定
        if (m.match(/[\-\+\=](?!\!)/))  hudi.menqian = false;
                                               // 副露している場合 false を設定
        
        if (m.match(yaojiu))            hudi.n_yaojiu++;
                                               // 幺九牌を含む場合その数を1加算
        if (m.match(zipai))             hudi.n_zipai++;
                                               // 字牌を含む場合その数を1加算

        if (m.match(danqi))             hudi.danqi = true;
                                               // 単騎待ちの場合 true を設定
        
        if (mianzi.length != 5) continue;
                                // 4面子1雀頭形でない場合は以下の処理はスキップ
        
        if (m == mianzi[0]) {              // 雀頭の処理
            var fu = 0;                           // 雀頭の符を 0 で初期化
            if (m.match(zhuangfengpai)) fu += 2;  // 場風の場合2符加符
            if (m.match(menfengpai))    fu += 2;  // 自風の場合2符加符
            if (m.match(sanyuanpai))    fu += 2;  // 三元牌の場合2符加符
            hudi.fu += fu;                        // 雀頭の符を加える
            if (hudi.danqi)             hudi.fu += 2; // 単騎待ちの場合2符加符
        }
        else if (m.match(kezi)) {          // 刻子の処理 
            hudi.n_kezi++;                    // 刻子の数を1加算
            var fu = 2;                       // 刻子の符を 2 で初期化
            if (m.match(yaojiu)) { fu *= 2;                  }
                                              // 幺九牌の場合2倍する
            if (m.match(ankezi)) { fu *= 2; hudi.n_ankezi++; }
                                              // 暗刻子の場合2倍しその数を1加算
            if (m.match(gangzi)) { fu *= 4; hudi.n_gangzi++; }
                                              // 槓子の場合4倍しその数を1加算
            hudi.fu += fu;                    // 刻子の符を加える

            /* 刻子の構成を記録 */
            hudi.kezi[m[0]][m[1]] = 1;
        }
        else {                             // 順子の処理
            hudi.n_shunzi++;                        // 順子の数を1加算
            if (m.match(kanzhang))  hudi.fu += 2;   // 嵌張待ちの場合2符加符
            if (m.match(bianzhang)) hudi.fu += 2;   // 辺張待ちの場合2符加符

            /* 順子の構成を記録 */
            var nnn = m.replace(/[^\d]/g, '');
            if (! hudi.shunzi[m[0]][nnn])   hudi.shunzi[m[0]][nnn] = 1;
            else                            hudi.shunzi[m[0]][nnn]++;
        }
    }
    
    if (mianzi.length == 7) {        // 七対子形の場合
        hudi.fu = 25;                    // 符は25符固定
    }
    else if (mianzi.length == 5) {   // 4面子1雀頭形の場合
        hudi.pinghu = (hudi.menqian && hudi.fu == 20); // 門前で20符なら平和
        if (hudi.zimo) {                           // ツモ和了の場合
            if (! hudi.pinghu)      hudi.fu +=  2;     // 平和でなければ2符加符
        }
        else {                                     // ロン和了の場合
            if (hudi.menqian)       hudi.fu += 10;     // 門前なら10符加符
            else if (hudi.fu == 20) hudi.fu  = 30;     // 喰い平和は30符固定
        }
        hudi.fu = Math.ceil(hudi.fu / 10) * 10;  // 10点未満は切り上げ
    }
    
    return hudi;
}

東一局0本場で南家が北家から下記のロン和了をした場合、

一萬一萬二萬二萬二萬七筒九筒八筒横一筒二筒三筒横六筒四筒五筒

返り値は

    {
        fu:         30,          // 符計算の結果
        menqian:    false,       // 門前の場合 true
        zimo:       false,       // ツモ和了の場合 true
        shunzi:     { m: {}, p: { "123": 1, "456": 1, "789": 1 }, s: {} },
                                                     // 順子の面子構成
        kezi:       { m: [0,0,1,0,0,0,0,0,0,0],
                      p: [0,0,0,0,0,0,0,0,0,0],
                      s: [0,0,0,0,0,0,0,0,0,0],
                      z: [0,0,0,0,0,0,0,0]      },   // 刻子の面子構成
        n_shunzi:   3,           // 順子の数
        n_kezi:     1,           // 刻子の数(槓子を含む)
        n_ankezi:   1,           // 暗刻子の数(暗槓子を含む)
        n_gangzi:   0,           // 槓子の数
        n_zipai:    0,           // 字牌面子の数(雀頭を含む)
        n_yaojiu:   3,           // 幺九牌入り面子の数(雀頭を含む)
        danqi:      false,       // 単騎待ちの場合 true
        pinghu:     false,       // 平和の場合 true
        zhuangfeng: 0,           // 場風(0: 東、1: 南、2: 西、3: 北)
        menfeng:    1            // 自風(0: 東、1: 南、2: 西、3: 北)
    };

となる。

次回は和了役の判定。