電脳麻将 ver.2.0 で開発中の新機能「パラメータによるルールのカスタマイズ」。麻雀ルールのカスタマイズ(1) ~ 終局判断とポイント計算 - koba::blog に続き今回の話題は 流局処理 と 連荘判断。これらに関わるパラメータは以下の通り。
true
の場合、九種九牌、四風連打、四家立直、四開槓を途中流局とする。デフォルト値は true
。true
の場合、流局時に流し満貫の判定と精算を行う。デフォルト値は true
。true
の場合、流局時の対局者からの応答次第でテンパイしていてもノーテンとして扱う。デフォルト値は false
。true
の場合、流局時にノーテン罰符の精算を行う。デフォルト値は true
。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;
// 一局戦の流局はすべて連荘
/* …… */
}