電脳麻将 ver.2.0 で開発中の新機能「パラメータによるルールのカスタマイズ」。今回は最後の話題。 麻雀ルールのカスタマイズ(4) ~ 和了役と点計算 - koba::blog までで説明していない パラメータ は以下の3つ。
どれも打牌の制約に関わるルール。
Majiang.Game
のクラスメソッド allow_lizhi()
で ツモ番なしリーチあり が false の場合、残り牌数4未満でのリーチを禁止している。
static allow_lizhi(rule, shoupai, p, paishu, defen) {
/* …… */
if (! rule['ツモ番なしリーチあり'] && paishu < 4) return false;
/* …… */
}
喰い替え禁止の実装はやや複雑である。以下の手牌から をチーすると残った手牌のどの牌を切っても喰い替えとなってしまう。
つまりこのような手牌の場合、チー自体を禁止する必要がある訳だ。
この処理は Majiang.Shoupai
の get_chi_mianzi()
で行なっている。
get_chi_mianzi(p, check = true) {
/* …… */
let bingpai = this._bingpai[s];
if (3 <= n && bingpai[n-2] > 0 && bingpai[n-1] > 0) {
if (! check // 喰い替えありか喰い替えとならない手牌がある場合
|| (3 < n ? bingpai[n-3] : 0) + bingpai[n]
< 14 - (this._fulou.length + 1) * 3)
{
/* チーできるメンツを追加する */
}
}
/* …… */
}
そして Majiang.Game
のクラスメソッド get_chi_mianzi()
がルールを指定する。
static get_chi_mianzi(rule, shoupai, p, paishu) {
let mianzi = shoupai.get_chi_mianzi(p, rule['喰い替え許可レベル'] == 0);
// 喰い替えなしの場合は Majiang.Shoupai に任せる
if (! mianzi) return mianzi;
if (rule['喰い替え許可レベル'] == 1 // 現物喰い替えなしの場合は
&& shoupai._fulou.length == 3 // すでに3副露していて残り2枚が
// 現物となる場合だけ鳴けない
&& shoupai._bingpai[p[0]][p[1]] == 2) mianzi = [];
return paishu == 0 ? [] : mianzi;
}
打牌時のチェックもスジ喰い替えについては処理が複雑なので Majiang.Shoupai
の get_dapai()
で実装している。
get_dapai(check = true) {
/* …… */
/* 喰い替えとなる牌を deny に設定する */
let deny = {};
if (check && this._zimo.length > 2) { // 喰い替えなしで副露後の場合
let m = this._zimo;
let s = m[0];
let n = + m.match(/\d(?=[\+\=\-])/) || 5;
deny[s+n] = true; // 現物喰いかえ
if (! m.match(/^[mpsz](\d)\1\1/)) { // スジ喰いかえ
if (n < 7 && m.match(/^[mps]\d\-\d\d$/)) deny[s+(n+3)] = true;
if (3 < n && m.match(/^[mps]\d\d\d\-$/)) deny[s+(n-3)] = true;
}
}
let dapai = []; // dapai に打牌できる牌を追加する
if (! this._lizhi) { // リーチ後はツモ切りのみ
for (let s of ['m','p','s','z']) {
let bingpai = this._bingpai[s];
for (let n = 1; n < bingpai.length; n++) {
if (deny[s+n]) continue; // 喰い替えとなる牌はスキップ
/* dapai に牌を加える */
}
}
}
/* …… */
return dapai;
}
Majiang.Game
のクラスメソッド get_dapai()
ではルールの指定と現物喰い替えのチェックを行う。
static get_dapai(rule, shoupai) {
/* 喰い替えなしの場合は Majiang.Shoupai に処理を任せる */
if (rule['喰い替え許可レベル'] == 0) return shoupai.get_dapai(true);
/* 現物喰い替えなしの場合はここで喰い替えをチェックする */
if (rule['喰い替え許可レベル'] == 1
&& shoupai._zimo && shoupai._zimo.length > 2)
{
let deny = shoupai._zimo[0]
+ (+shoupai._zimo.match(/\d(?=[\+\=\-])/)||5);
return shoupai.get_dapai(false)
.filter(p => p.replace(/0/,'5') != deny);
}
/* 喰い替えありの場合は Majiang.Shoupai でチェックしない */
return shoupai.get_dapai(false);
}
リーチ後の暗槓はルールの指定するレベルに応じて以下のチェックが必要となる。
ツモってきた牌以外で槓することを送り槓という。例えば以下の牌姿から をツモって
をカンするようなケース。
リーチ後の送り槓を許可するルールはないので Majiang.Shoupai
の get_gang_mianzi()
で送り槓の禁止を実装した。
get_gang_mianzi(p) {
/* …… */
let p = this._zimo.replace(/0/,'5'); // p は今ツモった牌
/* …… */
if (this._lizhi && s+n != p) continue;
// ツモった牌以外での槓は禁止
/* …… */
}
天鳳で採用されているルール。
例えば以下の牌姿で をカンすると
待ちがなくなるためカンできない。
Majiang.Game
のクラスメソッド get_gang_mianzi()
で禁止を実装した。
static get_gang_mianzi(rule, shoupai, p, paishu, n_gang) {
/* …… */
let new_shoupai;
new_shoupai = shoupai.clone().dapai(shoupai._zimo);
let n_tingpai1 = Majiang.Util.tingpai(new_shoupai).length;
// カンする前の和了牌を取得
new_shoupai = shoupai.clone().gang(mianzi[0]);
let n_tingpai2 = Majiang.Util.tingpai(new_shoupai).length;
// カンした後の和了牌を取得
if (n_tingpai1 > n_tingpai2) return [];
// 待ちが減るカンは禁止
/* …… */
}
Mリーグで採用されているルール。
例えば以下の牌姿で をカンすると
トイツの和了形がなくなるためカンできない。
以下の牌姿も をカンすると九蓮宝燈がなくなるためカンできないとするのが一般的*1。
これも Majiang.Game
のクラスメソッド get_gang_mianzi()
で禁止を実装した。
static get_gang_mianzi(rule, shoupai, p, paishu, n_gang) {
/* …… */
let new_shoupai, n_hule1 = 0, n_hule2 = 0;
/* カンする前の全ての和了形を取得 */
new_shoupai = shoupai.clone().dapai(shoupai._zimo);
for (let p of Majiang.Util.tingpai(new_shoupai)) {
n_hule1 += Majiang.Util.hule_mianzi(new_shoupai, p).length;
}
/* カンした後の全ての和了形を取得 */
new_shoupai = shoupai.clone().gang(mianzi[0]);
for (let p of Majiang.Util.tingpai(new_shoupai)) {
n_hule2 += Majiang.Util.hule_mianzi(new_shoupai, p).length;
}
if (n_hule1 > n_hule2) return []; // 和了形が減るカンは禁止
/* …… */
}
最高位戦Classicで採用されているルール。リーチ後は一切暗槓できない。
やはり Majiang.Game
のクラスメソッド get_gang_mianzi()
で禁止を実装した。
static get_gang_mianzi(rule, shoupai, p, paishu, n_gang) {
/* …… */
if (rule['リーチ後暗槓許可レベル'] == 0) return [];
/* …… */
}