麻雀の和了点を計算するプログラム - koba::blog の続き。
電脳麻将 では 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='
となる。
baopai
、fubaopai
はそれぞれドラ表示牌、裏ドラ表示牌。槓ドラがある場合があるので、牌の配列の形式。ドラ表示牌が 、 の場合は、
[ 'm0', 'z7' ]
となる。立直をかけていない場合は fubaopai
は空配列とする。
注意点は以下の2つ。
- 入力パラメータはドラ「表示牌」なので、
Majiang.Shan.zhenbaopai()
で実際のドラに変換する必要がある*3。 - 手牌に赤牌がある場合も正しく数え上げる必要がある。上記では
'0'
を'5'
に置換してから処理している。
次回は和了形を求めるプログラムをもう一度説明し直します。