koba::blog

小林聡: プログラマです

麻雀の副露判断アルゴリズム(4)

麻雀の副露判断アルゴリズム(3) - koba::blog で喰い仕掛けで手を完成させることができるようになったが、打牌を見ていると無駄がありそうである。例えば

一萬二萬六萬六萬三筒赤五筒六筒七筒八筒赤五索五索六索八索九筒

の場合、打 九筒三萬六萬四筒四索五索七索 の6種20枚待ち、打 八索 だとそれに加えて 七筒 3枚も待ちになるが、七筒八筒九筒 の面子が完成するのでタンヤオに移行できなくなってしまう。打 九筒 であれば、六萬五索 ポン、四筒七索 チーでも手を進めることができる。

待ち牌の評価値

これを考慮して、待ち牌の枚数の評価に以下を加えることにする。

  • ポンできる牌は4倍の価値
  • チーできる牌は2倍の価値

この評価にしたがえば、先の例は、

  • 八索 : 7種23枚待ち
  • 九筒 : 6種40枚待ち*1

となる。

プログラム実装

上記の考えを実現するために、まず有効牌算出ルーチン Majiang.Player.prototype.tingpai() を以下に修正する。

Majiang.Player.prototype.tingpai = function(shoupai) {

    var self = this;
    
    var n_xiangting = this.xiangting(shoupai);
    
    /* すべての有効牌についてポン・チーできるか評価する */
    var pai = [];
    for (var p of Majiang.Util.tingpai(
                        shoupai, function(s){return self.xiangting(s)}))
    {
        if (n_xiangting > 0) {

            for (var m of get_peng_mianzi(shoupai, p+'+')) {
                var new_shoupai = shoupai.clone();
                new_shoupai.fulou(m);
                if (this.xiangting(new_shoupai) < n_xiangting) {
                    pai.push(p+'+');    // ポンできる場合 + の印を付ける
                    break;
                }
            }
            if (pai[pai.length - 1] == p+'+') continue;

            for (var m of get_chi_mianzi(shoupai, p+'-')) {
                var new_shoupai = shoupai.clone();
                new_shoupai.fulou(m);
                if (this.xiangting(new_shoupai) < n_xiangting) {
                    pai.push(p+'-');    // チーできる場合 - の印を付ける
                    break;
                }
            }
            if (pai[pai.length - 1] == p+'-') continue;
        }
        pai.push(p);                    // 鳴けない場合は印なし
    }
    return pai;
}

打牌選択処理 Majiang.Player.prototype.select_dapai() には以下の修正を加えた。

Majiang.Player.prototype.select_dapai = function() {

    /*** ここまで修正なし ***/

    var dapai, max = 0;
    for (var p of this.get_dapai()) {
        var new_shoupai = this._shoupai.clone();
        new_shoupai.dapai(p);
        if (this.xiangting(new_shoupai) > n_xiangting) continue;
        var x = 1 - this._suanpai.paijia(p)/100;
        for (var tp of this.tingpai(new_shoupai)) {
            x += this._suanpai.paishu(tp.substr(0,2))
                    * (tp[2] == '+' ? 4 :    // ポンできる牌は4倍の価値
                       tp[2] == '-' ? 2 :    // チーできる牌は2倍の価値
                                      1   );
        }
        if (x >= max) {
            max = x;
            dapai = p;
        }
    }

    /*** 以降は修正なし ***/
}

対戦結果

上記の修正を加えた思考ルーチン(打牌改善)と ver.0.5 の思考ルーチンを再び対戦させてみた。

ver.0.5 鳴きあり 打牌改善 ver.0.5 鳴きあり 打牌改善
対戦数 1,000 1,000 1,000 総局数 10,427 10,444 10,553
1位率 .236 .300 .296 和了 .157 .224 .228
2位率 .283 .243 .254 放銃率 .071 .081 .081
3位率 .247 .230 .231 立直率 .356 .286 .274
4位率 .234 .227 .219 副露率 .000 .238 .262
平均順位 2.48 2.38 2.37 平均打点 7,384 5,616 5,369

副露率が若干上昇したが、残念ながら成績には大差ない模様。

続いて和了役の統計。

和了出現回数出現率 和了出現回数出現率
ドラ 1,175 48.90% 断幺九 511 21.27%
赤ドラ 1,064 44.28% 一盃口 51 2.12%
裏ドラ 557 23.18% 三色同順 27 1.12%
立直 1,266 52.68% 一気通貫 10 0.42%
ダブル立直 5 0.21% 七対子 94 3.91%
一発 239 9.95% 対々和 4 0.17%
門前清自摸 717 29.84% 混一色 16 0.67%
翻牌 920 38.29% 清一色 3 0.12%
平和 333 13.86%

たった4回ながらついに対々和が出現!断幺九混一色も若干増えたが、思ったほどの効果はなく、残念。ここまでのアルゴリズムを ver.0.6 に採用した。

*1:元の評価では6種20枚待ち