麻雀の和了点の計算 〜 状況役と懸賞役の一覧を作る

麻雀の和了点を計算するプログラム - koba::blog の続き。

まずは状況役*1、懸賞役*2の一覧作成から。

電脳麻将 では Majiang.Util.hule()和了点計算をしているが、その全体構造は以下の通り。

Majiang.Util.hule = function(shoupai, rongpai, param) {

    var max = {
        hupai:      null,
        fu:         0,
        fanshu:     0,
        damanguan:  0,
        defen:      0,
        fenpei:     [ 0, 0, 0, 0 ]
    };
    
    /* 状況役の一覧を作成する */
    var pre_hupai  = get_pre_hupai(param.hupai);

    /* 懸賞役の一覧を作成する */
    var post_hupai = get_post_hupai(
                        shoupai.toString(), param.baopai, param.fubaopai);
    
    /* 和了形の一覧を求め、その個々について以下を処理する */
    for (var mianzi of hule_mianzi(shoupai, rongpai)) {
    
        /* 面子構成を調べ、符を計算する */
        var hudi  = get_hudi(mianzi, param.zhuangfeng, param.menfeng);

        /* 手役の一覧を求める */
        var hupai = get_hupai(mianzi, hudi, pre_hupai);
        
        if (hupai.length == 0) continue;   // 手役がない場合は次の和了形へ
        
        /* 符、状況役、手役、懸賞役から和了点を計算する */

                /* ... 計算方法は省略 ... */
    }
    
    /* 得られた和了点の最大値を解とする */
    return max;
}

状況役の一覧を作成する

状況役は手牌からは知り得ないので、入力時に情報を指定する。具体的には入力パラメータの param.hupai を使用する。

    hupai: {
        lizhi:      0,           // 1: 立直, 2: ダブル立直
        yifa:       false,       // 一発のとき true
        qianggang:  false,       // 槍槓のとき true
        lingshang:  false,       // 嶺上開花のとき true
        haidi:      0,           // 1: 海底摸月, 2: 河底撈魚
        tianhu:     0            // 1: 天和, 2: 地和
    }

状況役の一覧を作成する関数 get_pre_hupai() は以下の通り。

function get_pre_hupai(hupai) {

    var pre_hupai = [];
    
    if (hupai.lizhi == 1)   pre_hupai.push({ name: '立直', fanshu: 1 });
    if (hupai.lizhi == 2)   pre_hupai.push({ name: 'ダブル立直', fanshu: 2 });
    if (hupai.yifa)         pre_hupai.push({ name: '一発', fanshu: 1 });
    if (hupai.haidi == 1)   pre_hupai.push({ name: '海底摸月', fanshu: 1 });
    if (hupai.haidi == 2)   pre_hupai.push({ name: '河底撈魚', fanshu: 1 });
    if (hupai.lingshang)    pre_hupai.push({ name: '嶺上開花', fanshu: 1 });
    if (hupai.qianggang)    pre_hupai.push({ name: '槍槓', fanshu: 1 });

    if (hupai.tianhu == 1)  pre_hupai = [{ name: '天和', fanshu: '*' }];
    if (hupai.tianhu == 2)  pre_hupai = [{ name: '地和', fanshu: '*' }];

    return pre_hupai;
}

基本的には役名と翻数を順に追加して行くだけだが、天和、地和の役満があった場合は、他の状況役は無効になることに注意。

役満の場合は翻数を '*' としている。四暗刻単騎などをダブル役満にする場合は、'**' とする。

懸賞役の一覧を作成する

懸賞役の一覧を作成する関数 get_post_hupai() は以下の通り。

function get_post_hupai(paistr, baopai, fubaopai) {

    var post_hupai = [];
    
    var substr = paistr.match(/[mpsz][^mpsz,]*/g) || [];
    
    var n_baopai = 0;
    for (var p of baopai) {
        p = Majiang.Shan.zhenbaopai(p);
        var regexp = new RegExp(p[1],'g');
        for (var str of substr) {
            if (str[0] != p[0]) continue;
            str = str.replace(/0/, '5');
            var nn = str.match(regexp);
            if (nn) n_baopai += nn.length;
        }
    }
    if (n_baopai) post_hupai.push({ name: 'ドラ', fanshu: n_baopai });
    
    var n_hongpai = 0;
    var nn = paistr.match(/0/g);
    if (nn) n_hongpai = nn.length;
    if (n_hongpai) post_hupai.push({ name: '赤ドラ', fanshu: n_hongpai });
    
    var n_fubaopai = 0;
    for (var p of fubaopai) {
        p = Majiang.Shan.zhenbaopai(p);
        var regexp = new RegExp(p[1],'g');
        for (var str of substr) {
            if (str[0] != p[0]) continue;
            str = str.replace(/0/, '5');
            var nn = str.match(regexp);
            if (nn) n_fubaopai += nn.length;
        }
    }
    if (n_fubaopai) post_hupai.push({ name: '裏ドラ', fanshu: n_fubaopai });
    
    return post_hupai;
}

入力パラメータの paistr麻雀の手牌の文字列表現 - koba::blog で説明した「手牌全体」の文字列表現。

赤五萬五萬五萬中中發横發發裏白白裏横八萬七萬九萬
'm055z77,m78-9,z5555,z666=' となる。

baopaifubaopai はそれぞれドラ表示牌、裏ドラ表示牌。槓ドラがある場合があるので、牌の配列の形式。ドラ表示牌が 赤五萬中 の場合は、

 [ 'm0', 'z7' ]

となる。立直をかけていない場合は fubaopai は空配列とする。

注意点は以下の2つ。

  1. 入力パラメータはドラ「表示牌」なので、Majiang.Shan.zhenbaopai() で実際のドラに変換する必要がある*3
  2. 手牌に赤牌がある場合も正しく数え上げる必要がある。上記では '0''5' に置換してから処理している。

次回は和了形を求めるプログラムをもう一度説明し直します。

*1:立直や海底摸月など手牌からは判断できない役

*2:ドラなど和了してはじめてつく役

*3:赤牌がドラ表示牌の場合も正しく処理してくれる