向聴数を求めるプログラム(七対子・国士無双編)

手牌の表現方法を決めたら、次にやるべきことは向聴数(シャンテン数)を求めるプログラムの実装である。思考アルゴリズムは向聴数が減って行くように打牌を選択するのが基本になるし、ゲーム進行に関しても流局時のノー聴、聴牌判断は向聴数が0であるか否かで判断できる。

麻雀の和了形は4面子1雀頭以外に七対子形、国士無双形があるのでそれぞれ実装する必要がある。まずは簡単な七対子形、国士無双形から。

七対子

七対子形の場合は基本的には

  • 向聴数 = 6 ー 対子の数

なのだが注意すべきことが2つある。

一萬一萬四萬四萬三筒三筒三筒六筒六筒一索九索東白發

の牌姿の場合、正確には対子は3つなので上の式にあてはめると3向聴だが、孤立牌が2つ重なれば聴牌なので、実際には2向聴である。つまり三筒の暗刻も対子に含める必要があるということ。日本の麻雀では4枚使いの牌を2対子に数えることはできない*1ので、2枚以上ある牌を対子として数えればよい。

次の問題となるパターンは以下の牌姿。

一萬一萬一萬四萬四萬三筒三筒三筒六筒六筒八索八索九索九索

(暗刻も含めた)対子の数は6なので上の式にあてはめると聴牌だが、単騎待ちとなる牌がないので実際には1向聴である。つまり対子と単騎待ち候補の合計が7に満たない場合はその分向聴数を増やす必要がある。

  • 向聴数 = 6 ー 対子の数 + (7 ー 対子の数 ー 単騎待ち候補の数)

これらを考慮して 麻雀の手牌のJavascript表現 - koba::blog の手牌表現を使用したプログラムは以下の通り。

function xiangting_qiduizi(shoupai) {

    var n_duizi = 0;
    var n_danqi = 0;
    
    for (var s of ['m','p','s','z']) {
        var bingpai = shoupai._bingpai[s];
        for (var n = 1; n < bingpai.length; n++) {
            if      (bingpai[n] >= 2) n_duizi++;
            else if (bingpai[n] == 1) n_danqi++;
        }
    }
    
    return (n_duizi + n_danqi < 7)
                ? 6 - n_duizi + (7 - n_duizi - n_danqi)
                : 6 - n_duizi;
}

国士無双

国士無双形の場合は非常に簡単で

だがやはり罠があって

一萬一萬一筒九筒一索九索東南西北白發五筒六筒

のような形の場合は上の式にあてはめると2向聴牌だが、すでに雀頭があるので実際には1向聴となる。つまり雀頭がある場合は

とすればよい。

プログラムは以下の通り。

function xiangting_guoshi(shoupai) {

    var n_yaojiu  = 0;
    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] > 0) n_yaojiu++;
            if (bingpai[n] > 1) you_duizi = true;
        }
    }

    return you_duizi ? 12 - n_yaojiu : 13 - n_yaojiu;
}

次回は、4面子1雀頭形をもう一度おさらい。

*1:中国麻雀ではOKらしい