麻雀の役を判定するプログラム(役満編)

麻雀の役を判定するプログラム - koba::blog の続き。

役満以外の役については判定できたので、今回は役満について。

前回同様和了形からパターンマッチングで役を判定していくのだが、問題が一つあって、それは九蓮宝燈の扱いである。九蓮宝燈という役は門前・清一色の使用牌が特定のパターンの場合になるので、和了形判定の際に「九蓮宝燈形」として処理してしまった方が簡単である。

なので先に「九蓮宝燈形」の判定処理から。

和了形を求めるプログラム - koba::blog のプログラムに九蓮宝燈形の判定処理を追加する。九蓮宝燈形は手牌全体で1面子扱いにした。これで全体が1面子だったら九蓮宝燈として処理できる*1

function hule_jiulian(shoupai, rongpai) {

    var hulepai = rongpai || shoupai._zimo;

    var s = hulepai[0];
    if (s == 'z') return [];

    var hule_mianzi = s;
    var pai = shoupai._shouli[s];
    for (var n = 1; n <= 9; n++) {
        if ((n == 1 || n == 9) && pai[n-1] < 3) return [];
        if (pai[n-1] == 0) return [];
        var nn = (n == hulepai[1]) ? pai[n-1] -1 : pai[n-1];
        for (var i = 0; i < nn; i++) {
            hule_mianzi += n;
        }
    }
    hule_mianzi += hulepai.substr(1) + '_';

    return [[hule_mianzi]];
}

function hule(shoupai, rongpai) {

    if (rongpai) {
        shoupai._zimo = rongpai.substr(0,2);
        shoupai._shouli[rongpai[0]][rongpai[1]-1]++;
    }
    
    return [].concat(hule_yiban(new_shoupai, rongpai))
             .concat(hule_qiduizi(new_shoupai, rongpai))
             .concat(hule_guoshi(new_shoupai, rongpai))
             .concat(hule_jiulian(new_shoupai, rongpai));
}

これで準備はOKなので、あとはパターンマッチで処理。

function hupai(mianzi, zhuangfeng, zifeng, pre_hupai) {

    var menqian = mianzi.filter(function(m){
                            return m.match(/[\-\+\=](?!_)/)}).length == 0;
                                                        // 門前のとき true

    var zhuangfengpai   = new RegExp('^z' + ((zhuangfeng + 1) % 4) + '.*$');
                                                        // 場風
    var zifengpai       = new RegExp('^z' + ((zifeng + 1) % 4) + '.*$');
                                                        // 自風
    var fengpai         = /^z[1234].*$/;                // 風牌
    var sanyuanpai      = /^z[567].*$/;                 // 三元牌
    
    var yaojiu          = /^.*[z19].*$/;                // 幺九牌
    var zipai           = /^z.*$/;                      // 字牌
    
    var shunzi          = /^[mpsz](?!(\d)\1).*$/;       // 順子
    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)$/;
                                                        // 辺張待ち

        /**** ここに前回の一般役の判定処理が入る ****/

    function guoshiwushuang() {
        if (mianzi.length != 13)            return [];
        if (mianzi.filter(function(m){return m.match(danqi)}).length == 0)
                return [{ name: '国士無双', fanshu: '*' }];
        else    return [{ name: '国士無双十三面', fanshu: '**' }];
    }
    function sianke() {
        if (mianzi.length != 5)             return [];
        if (mianzi.filter(function(m){return m.match(ankezi)}).length != 4)
                                            return [];
        if (mianzi.filter(function(m){return m.match(danqi)}).length == 0)
                return [{ name: '四暗刻', fanshu: '*' }];
        else    return [{ name: '四暗刻単騎', fanshu: '**' }];
    }
    function dasanyuan() {
        if (mianzi.length != 5)             return [];
        if (mianzi.filter(function(m){return m.match(sanyuanpai)}).length != 3)
                                            return [];
        if (mianzi[0].match(sanyuanpai))    return [];
        return [{ name: '大三元', fanshu: '*' }];
    }
    function sixihu() {
        if (mianzi.length != 5)             return [];
        if (mianzi.filter(function(m){return m.match(fengpai)}).length != 4)
                                            return [];
        if (mianzi[0].match(fengpai))
                return [{ name: '小四喜', fanshu: '*' }];
        else    return [{ name: '大四喜', fanshu: '**' }];
    }
    function ziyise() {
        if (mianzi.filter(function(m){return ! m.match(zipai)}).length > 0)
                                            return [];
        if (mianzi.length != 7)
                return [{ name: '字一色', fanshu: '*' }];
        else    return [{ name: '字一色七対子', fanshu: '**' }];
    }
    function lvyise() {
        if (mianzi.filter(function(m){return m.match(/^[mp]/)}).length > 0)
                                            return [];
        if (mianzi.filter(function(m){return m.match(/^z[^6]/)}).length > 0)
                                            return [];
        if (mianzi.filter(function(m){return m.match(/^s.*[1579]/)}).length > 0)
                                            return [];
        return [{ name: '緑一色', fanshu: '*' }];
    }
    function qinglaotou() {
        if (mianzi.length != 5)             return [];
        if (mianzi.filter(function(m){return ! m.match(yaojiu)}).length > 0)
                                            return [];
        if (mianzi.filter(function(m){return m.match(shunzi)}).length > 0)
                                            return [];
        if (mianzi.filter(function(m){return m.match(zipai)}).length > 0)
                                            return [];
        return [{ name: '清老頭', fanshu: '*' }];
    }
    function sigangzi() {
        if (mianzi.length != 5)             return [];
        if (mianzi.filter(function(m){return m.match(gangzi)}).length != 4)
                                            return [];
        return [{ name: '四槓子', fanshu: '*' }];
    }
    function jiulianbaodeng() {
        if (mianzi.length != 1)             return [];
        if (! mianzi[0].match(/^[mps]1112345678999/))
                return [{ name: '九蓮宝燈', fanshu: '*' }];
        else    return [{ name: '純正九蓮宝燈', fanshu: '**' }];
    }
    
    var damanguan = []
                .concat(guoshiwushuang())
                .concat(sianke())
                .concat(dasanyuan())
                .concat(sixihu())
                .concat(ziyise())
                .concat(lvyise())
                .concat(qinglaotou())
                .concat(sigangzi())
                .concat(jiulianbaodeng());
    
    if (damanguan.length > 0) return damanguan
    else return []
                .concat(menqianqing())
                .concat(fanpai())
                .concat(pinghu())
                .concat(duanyaojiu())
                .concat(yibeikou())
                .concat(sansetongshun())
                .concat(yiqitongguan())
                .concat(hunquandaiyaojiu())
                .concat(qiduizi())
                .concat(duiduihu())
                .concat(sananke())
                .concat(sangangzi())
                .concat(sansetongke())
                .concat(hunlaotou())
                .concat(xiaosanyuan())
                .concat(hunyise())
                .concat(chunquandaiyaojiu())
                .concat(erbeikou())
                .concat(qingyise())
}

役満は一般役とは複合しないので、役満がある場合はそこで処理を打ち切っている。七対子形の字一色はダブル役満にしてみました。

次は符計算の予定。

*1:7面子だったら七対子、13面子だったら国士無双というわけ