和了形を求めるプログラム(一般形) - koba::blog の続き。
和了形を求めることができたので、次は符計算である。符計算の際には面子の構成も調べることになるので、それを保存しておいて和了役の判定で使用することにする。
電脳麻将 の符計算を行う関数は get_hudi(mianzi, zhuangfeng, menfeng)
。
function get_hudi(mianzi, zhuangfeng, menfeng) { /* 面子構成のチェックに使う正規表現 */ var zhuangfengpai = new RegExp('^z' + (zhuangfeng + 1) + '.*$'); // 場風 var menfengpai = new RegExp('^z' + (menfeng + 1) + '.*$'); // 自風 var sanyuanpai = /^z[567].*$/; // 三元牌 var yaojiu = /^.*[z19].*$/; // 幺九牌 var zipai = /^z.*$/; // 字牌 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)$/; // 辺張待ち /* 返り値の初期値を設定する */ var hudi = { fu: 20, // 符計算の結果 menqian: true, // 門前の場合 true zimo: true, // ツモ和了の場合 true shunzi: { m: {}, p: {}, s: {} }, // 順子の面子構成 kezi: { m: [0,0,0,0,0,0,0,0,0,0], p: [0,0,0,0,0,0,0,0,0,0], s: [0,0,0,0,0,0,0,0,0,0], z: [0,0,0,0,0,0,0,0] }, // 刻子の面子構成 n_shunzi: 0, // 順子の数 n_kezi: 0, // 刻子の数(槓子を含む) n_ankezi: 0, // 暗刻子の数(暗槓子を含む) n_gangzi: 0, // 槓子の数 n_zipai: 0, // 字牌面子の数(雀頭を含む) n_yaojiu: 0, // 幺九牌入り面子の数(雀頭を含む) danqi: false, // 単騎待ちの場合 true pinghu: false, // 平和の場合 true zhuangfeng: zhuangfeng, // 場風(0: 東、1: 南、2: 西、3: 北) menfeng: menfeng // 自風(0: 東、1: 南、2: 西、3: 北) }; for (var m of mianzi) { // 和了形の各面子について以下の処理を行う if (m.match(/[\-\+\=]\!/)) hudi.zimo = false; // ロン和了の場合 false を設定 if (m.match(/[\-\+\=](?!\!)/)) hudi.menqian = false; // 副露している場合 false を設定 if (m.match(yaojiu)) hudi.n_yaojiu++; // 幺九牌を含む場合その数を1加算 if (m.match(zipai)) hudi.n_zipai++; // 字牌を含む場合その数を1加算 if (m.match(danqi)) hudi.danqi = true; // 単騎待ちの場合 true を設定 if (mianzi.length != 5) continue; // 4面子1雀頭形でない場合は以下の処理はスキップ if (m == mianzi[0]) { // 雀頭の処理 var fu = 0; // 雀頭の符を 0 で初期化 if (m.match(zhuangfengpai)) fu += 2; // 場風の場合2符加符 if (m.match(menfengpai)) fu += 2; // 自風の場合2符加符 if (m.match(sanyuanpai)) fu += 2; // 三元牌の場合2符加符 hudi.fu += fu; // 雀頭の符を加える if (hudi.danqi) hudi.fu += 2; // 単騎待ちの場合2符加符 } else if (m.match(kezi)) { // 刻子の処理 hudi.n_kezi++; // 刻子の数を1加算 var fu = 2; // 刻子の符を 2 で初期化 if (m.match(yaojiu)) { fu *= 2; } // 幺九牌の場合2倍する if (m.match(ankezi)) { fu *= 2; hudi.n_ankezi++; } // 暗刻子の場合2倍しその数を1加算 if (m.match(gangzi)) { fu *= 4; hudi.n_gangzi++; } // 槓子の場合4倍しその数を1加算 hudi.fu += fu; // 刻子の符を加える /* 刻子の構成を記録 */ hudi.kezi[m[0]][m[1]] = 1; } else { // 順子の処理 hudi.n_shunzi++; // 順子の数を1加算 if (m.match(kanzhang)) hudi.fu += 2; // 嵌張待ちの場合2符加符 if (m.match(bianzhang)) hudi.fu += 2; // 辺張待ちの場合2符加符 /* 順子の構成を記録 */ var nnn = m.replace(/[^\d]/g, ''); if (! hudi.shunzi[m[0]][nnn]) hudi.shunzi[m[0]][nnn] = 1; else hudi.shunzi[m[0]][nnn]++; } } if (mianzi.length == 7) { // 七対子形の場合 hudi.fu = 25; // 符は25符固定 } else if (mianzi.length == 5) { // 4面子1雀頭形の場合 hudi.pinghu = (hudi.menqian && hudi.fu == 20); // 門前で20符なら平和 if (hudi.zimo) { // ツモ和了の場合 if (! hudi.pinghu) hudi.fu += 2; // 平和でなければ2符加符 } else { // ロン和了の場合 if (hudi.menqian) hudi.fu += 10; // 門前なら10符加符 else if (hudi.fu == 20) hudi.fu = 30; // 喰い平和は30符固定 } hudi.fu = Math.ceil(hudi.fu / 10) * 10; // 10点未満は切り上げ } return hudi; }
東一局0本場で南家が北家から下記のロン和了をした場合、
返り値は
{ fu: 30, // 符計算の結果 menqian: false, // 門前の場合 true zimo: false, // ツモ和了の場合 true shunzi: { m: {}, p: { "123": 1, "456": 1, "789": 1 }, s: {} }, // 順子の面子構成 kezi: { m: [0,0,1,0,0,0,0,0,0,0], p: [0,0,0,0,0,0,0,0,0,0], s: [0,0,0,0,0,0,0,0,0,0], z: [0,0,0,0,0,0,0,0] }, // 刻子の面子構成 n_shunzi: 3, // 順子の数 n_kezi: 1, // 刻子の数(槓子を含む) n_ankezi: 1, // 暗刻子の数(暗槓子を含む) n_gangzi: 0, // 槓子の数 n_zipai: 0, // 字牌面子の数(雀頭を含む) n_yaojiu: 3, // 幺九牌入り面子の数(雀頭を含む) danqi: false, // 単騎待ちの場合 true pinghu: false, // 平和の場合 true zhuangfeng: 0, // 場風(0: 東、1: 南、2: 西、3: 北) menfeng: 1 // 自風(0: 東、1: 南、2: 西、3: 北) };
となる。
次回は和了役の判定。