電脳麻将 ver.2.0 で開発中の新機能「パラメータによるルールのカスタマイズ」。麻雀ルールのカスタマイズ(1) ~ 終局判断とポイント計算 - koba::blog に続き今回の話題は 流局処理 と 連荘判断。これらに関わるパラメータは以下の通り。
- 途中流局あり
- 流し満貫あり
- ノーテン宣言あり
- ノーテン罰あり
- 最大同時和了数
- 連荘方式
パラメータの意味
- 途中流局あり
true
の場合、九種九牌、四風連打、四家立直、四開槓を途中流局とする。デフォルト値はtrue
。- 流し満貫あり
true
の場合、流局時に流し満貫の判定と精算を行う。デフォルト値はtrue
。- ノーテン宣言あり
true
の場合、流局時の対局者からの応答次第でテンパイしていてもノーテンとして扱う。デフォルト値はfalse
。- ノーテン罰あり
true
の場合、流局時にノーテン罰符の精算を行う。デフォルト値はtrue
。- 最大同時和了数
- 1 の場合、和了応答が複数あったときに「頭ハネ」で和了者を決定する。2 の場合、ダブロンを認め、3者が和了応答した場合は三家和の途中流局とする。3 の場合、トリロンを認める。
- 連荘方式
- 連荘なし: 0、和了連荘: 1、テンパイ連荘: 2、ノーテン連荘: 3。デフォルト値は 2。
流局判断とノーテン罰符精算
Majiang.Game の打牌応答を処理するメソッド reply_dapai() で流局判断を行い、メソッド pingju() を呼び出して流局処理を行う。
reply_dapai() { let model = this._model; /* 応答が和了の場合の処理 */ /* 打牌がリーチだった場合の場合の処理 */ /* 四風連打判断 */ /* 四開槓判断 */ if (! model.shan.paishu) { // 牌山が尽きた場合は流局 let shoupai = ['','','','']; for (let l = 0; l < 4; l++) { let reply = this.get_reply(l); // 応答を取得する if (reply.daopai) shoupai[l] = reply.daopai; // 応答が「倒牌」なら手牌を公開する } return this.delay(()=>this.pingju('', shoupai), 0); // 流局処理を呼び出す } /* 応答が副露の場合の処理 */ this.delay(()=>this.zimo(), 0); // ツモ処理に遷移する }
pingju(name, shoupai = ['','','','']) { let model = this._model; let fenpei = [0,0,0,0]; if (! name) { // name が指定されていない場合は通常流局 /* 対局者4名についてテンパイしているか確認しテンパイ者数を把握する */ let n_tingpai = 0; // テンパイ者数を 0 で初期化 for (let l = 0; l < 4; l++) { if (this._rule['ノーテン宣言あり'] && ! shoupai[l] && ! model.shoupai[l].lizhi) continue; // ノーテン宣言あり かつ 倒牌していない // かつ リーチしていない 場合は // ノーテンとして扱う if (Majiang.Util.xiangting(model.shoupai[l]) == 0 && Majiang.Util.tingpai(model.shoupai[l]).length > 0) { // テンパイの場合 n_tingpai++; // テンパイ者数を 1 増やす shoupai[l] = model.shoupai[l].toString(); // 手牌を公開する /* …… */ } else { // ノーテンの場合 shoupai[l] = ''; // 手牌を公開しない } } /* 流し満貫の処理 */ /* ノーテン罰符の精算を行う */ if (! name) { // 流し満貫がない場合 name = '荒牌平局'; // 流局理由を「荒牌平局」とする if (this._rule['ノーテン罰あり'] && 0 < n_tingpai && n_tingpai < 4) { // ノーテン罰符の精算が必要な場合 for (let l = 0; l < 4; l++) { fenpei[l] = shoupai[l] ? 3000 / n_tingpai : -3000 / (4 - n_tingpai); // テンパイ者: 3000 / テンパイ者数 // ノーテン者: -3000 / ノーテン者数 } } } /* …… */ } else { // name が指定されている場合は途中流局 /* 途中流局の処理 */ } /* …… */ }
流し満貫判断と精算
Majiang.Game の流局を処理するメソッド pingju() 内で流し満貫の判断と精算を行う。
pingju(name, shoupai = ['','','','']) { let model = this._model; let fenpei = [0,0,0,0]; if (! name) { // name が指定されていない場合は通常流局 /* 対局者4名についてテンパイしているか確認 */ if (this._rule['流し満貫あり']) { // 流し満貫ありの場合 for (let l = 0; l < 4; l++) { /* 流し満貫を達成しているか確認する */ let all_yaojiu = true; // all_yaojiu を流し満貫達成で初期化 for (let p of model.he[l]._pai) { if (p.match(/[\+\=\-]$/)) { all_yaojiu = false; break } // 鳴かれているときは不達成 if (p.match(/^z/)) continue; // 字牌なら継続 if (p.match(/^[mps][19]/)) continue; // 一九牌も継続 all_yaojiu = false; break; // それ以外は不達成 } /* 流し満貫の精算をする */ if (all_yaojiu) { // 流し満貫達成の場合 name = '流し満貫'; // 流局理由を「流し満貫」とする for (let i = 0; i < 4; i++) { fenpei[i] += l == 0 && i == l ? 12000 // 親が達成 : l == 0 ? -4000 // 親が被達成 : l != 0 && i == l ? 8000 // 子が達成 : l != 0 && i == 0 ? -4000 // 子が親に被達成 : -2000;// 子が子に被達成 } } } } /* ノーテン罰符の精算 */ } else { // name が指定されている場合は途中流局 /* 途中流局の処理 */ } /* …… */ }
途中流局判断
九種九牌
Majiang.Game のツモ応答を処理するメソッド reply_zimo() で判断を行い、メソッド pingju() を呼び出して途中流局処理を行う。
reply_zimo() { let model = this._model; let reply = this.get_reply(model.lunban); // 現在の手番の応答を取得する if (reply.daopai) { // 応答が「倒排」の場合 if (this.allow_pingju()) { // 九種九牌で流局可能な場合 let shoupai = ['','','','']; shoupai[model.lunban] = model.shoupai[model.lunban].toString(); // 手牌を公開する return this.delay(()=>this.pingju('九種九牌', shoupai), 0); // 流局処理を呼び出す } } /* …… */ }
四風連打
Majiang.Game の打牌応答を処理するメソッド reply_dapai() で判断を行い、メソッド pingju() を呼び出して途中流局処理を行う。
reply_dapai() { /* …… */ if (this._diyizimo && model.lunban == 3) { // 一巡目の北家の打牌の場合 this._diyizimo = false; // 一巡目を終わらせる if (this._fengpai) { // 四風連打が継続中の場合 return this.delay(()=>this.pingju('四風連打'), 0); // 流局処理を呼び出す } } /* …… */ }
四家立直
同じく Majiang.Game の打牌応答を処理するメソッド reply_dapai() で判断を行い、メソッド pingju() を呼び出して途中流局処理を行う。
reply_dapai() { /* …… */ if (this._dapai.substr(-1) == '*') { // 打牌がリーチだった場合 /* リーチ成立の処理を行う */ if (this._lizhi.filter(x=>x).length == 4 // リーチ者が4名 かつ && this._rule['途中流局あり']) // 途中流局あり の場合 { let shoupai = model.shoupai.map(s=>s.toString()); // 手牌を公開する return this.delay(()=>this.pingju('四家立直', shoupai)); // 流局処理を呼び出す } } /* …… */ }
四開槓
同じく Majiang.Game の打牌応答を処理するメソッド reply_dapai() で判断を行い、メソッド pingju() を呼び出して途中流局処理を行う。
reply_dapai() { /* …… */ if (this._n_gang.reduce((x, y)=> x + y) == 4) { // 4つ槓がある場合 if (Math.max(...this._n_gang) < 4 && this._rule['途中流局あり']) { // 1人で4つのカンではない // かつ 途中流局あり の場合 return this.delay(()=>this.pingju('四開槓'), 0); // 流局処理を呼び出す } } /* …… */ }
三家和
同じく Majiang.Game の打牌応答を処理するメソッド reply_dapai() で判断を行い、メソッド pingju() を呼び出して途中流局処理を行う。
reply_dapai() { let model = this._model; /* 応答が和了の場合の処理 */ for (let i = 1; i < 4; i++) { // 打牌者の下家から順に処理を行う let l = (model.lunban + i) % 4; let reply = this.get_reply(l); // 応答を取得する if (reply.hule && this.allow_hule(l)) { // 応答が「和了」かつ // 和了可能な場合 if (this._view) this._view.say('rong', l); this._hule.push(l); // 和了者のリストに追加する } else { /* …… */ } } if (this._hule.length == 3 && this._rule['最大同時和了数'] == 2) { // 和了者が3名 かつ // 最大同時和了数が 2 の場合 let shoupai = ['','','','']; for (let l of this._hule) { shoupai[l] = model.shoupai[l].toString(); // 手牌を公開する } return this.delay(()=>this.pingju('三家和', shoupai)); // 流局処理を呼び出す } else if (this._hule.length) { /* …… */ } /* …… */ }
連荘判断
Majiang.Game の和了を処理するメソッド hule() および流局を処理するメソッド pingju() で連荘判断を行っている。
hule() { let model = this._model; /* …… */ if (this._rule['連荘方式'] > 0 && menfeng == 0) this._lianzhuang = true; // 連荘なし以外で親が和了した場合は連荘 if (this._rule['場数'] == 0) this._lianzhuang = false; // ただし一局戦の場合は親の和了でも輪連 /* …… */ }
pingju(name, shoupai = ['','','','']) { let model = this._model; let fenpei = [0,0,0,0]; if (! name) { // name が指定されていない場合は通常流局 for (let l = 0; l < 4; l++) { /* …… */ if (Majiang.Util.xiangting(model.shoupai[l]) == 0 && Majiang.Util.tingpai(model.shoupai[l]).length > 0) { // テンパイの場合 /* …… */ if (this._rule['連荘方式'] == 2 && l == 0) this._lianzhuang = true; // テンパイ連荘で親がテンパイしている // 場合は連荘 } else { // ノーテンの場合 /* …… */ } } /* …… */ if (this._rule['連荘方式'] == 3) this._lianzhuang = true; // ノーテン連荘の場合は流局はすべて連荘 } else { // name が指定されている場合は途中流局 this._no_game = true; this._lianzhuang = true; // 途中流局はすべて連荘 } if (this._rule['場数'] == 0) this._lianzhuang = true; // 一局戦の流局はすべて連荘 /* …… */ }