koba::blog

小林聡: プログラマです

和了形を求めるプログラム(特殊形)

麻雀の和了点の計算 〜 状況役と懸賞役の一覧を作る - koba::blog の続き。

状況役、懸賞役の一覧を作成したら、次は和了形の一覧を求める。以前 和了形を求めるプログラム - koba::blog で説明したように、和了形は複数に解釈できる場合があるので、そのすべてを求める必要がある。

和了形を求めるメイン関数は hule_mianzi()

function hule_mianzi(shoupai, rongpai) {

    var hulepai = rongpai || shoupai._zimo + '_';
    hulepai = hulepai.replace(/0/, '5');
    
    return [].concat(hule_mianzi_yiban(shoupai, hulepai))    // 4面子1雀頭形
             .concat(hule_mianzi_qiduizi(shoupai, hulepai))  // 七対子形
             .concat(hule_mianzi_guoshi(shoupai, hulepai))   // 国士無双形
             .concat(hule_mianzi_jiulian(shoupai, hulepai)); // 九蓮宝燈形
}

入力の shoupairongpai麻雀の和了点を計算するプログラム - koba::blog で説明した入力がそのまま渡される。

まず先頭で、ツモ和了だった場合に和了牌(hulepai)に'_' を付加している。'_' はツモ和了を示すマーク。ロン和了の場合、'-''+''=' がすでについているのは前述した通り。

一般形である4面子1雀頭七対子国士無双九蓮宝燈和了形の求め方がそれぞれ異なるので、そのすべてを試行する必要がある。二盃口の場合、一般形と七対子形で解が得られる訳だ。

返り値は和了形の一覧。

二萬二萬三萬三萬四萬四萬赤五筒五筒六筒六筒七筒七筒八索八索

八索 ツモ和了とすると返り値は、

[ [ 's88_!', 'm234', 'm234', 'p567', 'p567' ],             // 二盃口
  [ 'm22', 'm33', 'm44', 'p55', 'p66', 'p77', 's88_!' ] ]  // 七対子

となる。和了形に赤牌の情報は要らない*1ので、赤五筒五筒 に変換している。また '_' はツモを、'!'和了牌を表している*2和了形になっていない場合は空配列を返す。

さてそれぞれの和了形判定処理だが、4面子1雀頭形は処理が複雑なので後回しにして、簡単なその他の特殊形から。

七対子

function hule_mianzi_qiduizi(shoupai, hulepai) {

    var mianzi = [];
    
    for (var s in shoupai._bingpai) {
        var bingpai = shoupai._bingpai[s];
        for (var n = 1; n < bingpai.length; n++) {
            if (bingpai[n] == 0) continue;
            if (bingpai[n] == 2) {
                var p = (s+n == hulepai.substr(0,2))
                            ? s+n+n + hulepai[2] + '!'
                            : s+n+n;
                mianzi.push(p);
            }
            else return [];  // 対子でないものがあった場合、和了形でない。
        }
    }

    return (mianzi.length == 7) ? [mianzi] : [];
}

改めて説明するまでもないほど単純。

国士無双

function hule_mianzi_guoshi(shoupai, hulepai) {

    var mianzi = [];

    if (shoupai._fulou.length > 0) return mianzi;
    
    var you_duizi = false;
    for (var s in shoupai._bingpai) {
        var bingpai = shoupai._bingpai[s];
        var nn = (s == 'z') ? [1,2,3,4,5,6,7] : [1,9];
        for (var n of nn) {
            if (bingpai[n] == 2) {
                var p = (s+n == hulepai.substr(0,2))
                            ? s+n+n + hulepai[2] + '!'
                            : s+n+n;
                mianzi.unshift(p);
                you_duizi = true;
            }
            else if (bingpai[n] == 1) {
                var p = (s+n == hulepai.substr(0,2))
                            ? s+n + hulepai[2] + '!'
                            : s+n;
                mianzi.push(p);
            }
            else return [];  // 足りない幺九牌があった場合、和了形でない。
        }
    }

    return you_duizi ? [mianzi] : [];
}

幺九牌が13種そろっていること、雀頭があることをチェックしている。返り値は雀頭1つと残りの幺九牌12個の13面子として扱う。

一萬九萬一筒九筒一索九索東南西白白發中北

の場合、返り値は、

[ ['z55','m1','m9','p1','p9','s1','s9','z1','z2','z3','z4_!','z6','z7'] ]

となる。

九蓮宝燈

九蓮宝燈は4面子1雀頭形の和了なのであるが、役を判定する場合、個々の面子は意味を持たず、手牌全体を見なければならない。なのでこれを九蓮宝燈形として1面子で表すことにした。

function hule_mianzi_jiulian(shoupai, hulepai) {

    var s = hulepai[0];
    if (! s.match(/^[mps]$/)) return [];
    
    var mianzi = s;
    var bingpai = shoupai._bingpai[s];
    for (var n = 1; n <= 9; n++) {
        if ((n == 1 || n == 9) && bingpai[n] < 3) return [];
                                   // 1と9が3枚そろっていない場合、和了形でない
        if (bingpai[n] == 0) return []; // 足りない数牌がある場合、和了形でない
        var nn = (n == hulepai[1]) ? bingpai[n] - 1 : bingpai[n];
        for (var i = 0; i < nn; i++) {
            mianzi += n;
        }
    }
    mianzi += hulepai.substr(1) + '!';

    return [[mianzi]];
}

和了牌は最後に付加する。

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

の場合、返り値は、

[ [ 'm11123456789991=!' ] ]

となる。

次回は4面子1雀頭和了形判定について。

*1:赤ドラの判定が終わった段階で赤牌であるという情報は邪魔にしかならない

*2:ツモ符の判定や単騎待ち2符の判定など後で使う