麻雀ルールのカスタマイズ(1) ~ 終局判断とポイント計算

電脳麻将 ver.2.0 で開発中の新機能「パラメータによるルールのカスタマイズ」で終局判断ポイント計算に関わるパラメータは以下の通り。

  • 配給原点
  • 順位点
  • 場数
  • トビ終了あり
  • オーラス止めあり
  • 延長戦方式

パラメータの意味

配給原点
対局開始時の各対局者の持ち点。デフォルト値は 25,000点。終局時には30,000点を「原点」とし、原点からの差を1,000点1ポイントに換算する。配給原点との差は「オカ」として着順トップの対局者のポイントとなる。
順位点
順位に応じて付加されるポイント数。デフォルト値は着順順に [20, 10, -10, -20]
場数
一局戦: 0、東風戦: 1、東南戦: 2、一荘戦: 4。デフォルト値は 2。
トビ終了あり
true の場合、持ち点が0点未満の対局者が出た時点で終局とする。デフォルト値は true
オーラス止めあり
true の場合、規定の最終局の親番の対局者が30,000点以上で持ち点最大で途中流局を除く連荘をした際に終局となる。デフォルト値は true
延長戦方式
延長戦なし: 0、サドンデス: 1、連荘優先サドンデス: 2、4局固定: 3。デフォルト値は 1。0 以外の場合、場数 により定まる最終局を超えたときに持ち点が30,000点以上の対局者がいないと延長戦となる。1 の場合、各局終了時に持ち点30,000点以上の対局者が出た時点で終局とする。2 の場合、各局を最終局として オーラス止めあり も加味して終局判断を行う。3 の場合は延長戦4局目(東南戦なら西四局)に最終局を延長する。4局を超えた延長戦は行わない。また、場数 が 0 (一局戦)もしくは 4 (一荘戦)の場合は延長戦は行わない。

終局判断

クラス Majiang.Game のメソッド last() で以下のように終局判断している。

    last() {

        let model = this._model;

        model.lunban = -1;
        if (this._view) this._view.update();

        /* 輪荘時は次の局に進む */
        if (! this._lianzhuang) {
            model.jushu++;
            model.zhuangfeng += (model.jushu / 4)|0;
            model.jushu = model.jushu % 4;
        }

        /* 持ち点30,000点以上で持ち点最大の対局者を guanjun に設定する。
           持ち点同点の場合は起家に近い対局者を上位とする。 */
        let jieju = false;
        let guanjun = -1;
        const defen = model.defen;
        for (let i = 0; i < 4; i++) {
            let id = (model.qijia + i) % 4;
            if (defen[id] < 0 && this._rule['トビ終了あり'])    jieju = true;
                                                    // トビ終了
            if (defen[id] >= 30000
                && (guanjun < 0 || defen[id] > defen[guanjun])) guanjun = id;
        }

        let sum_jushu = model.zhuangfeng * 4 + model.jushu;

        if      (15 < sum_jushu)                                jieju = true;
                                                    //「返り東」には入らない
        else if ((this._rule['場数'] + 1) * 4 - 1 < sum_jushu)  jieju = true;
                                                    // 四局を超えた延長戦は行わない
        else if (this._max_jushu < sum_jushu) {     // 最終局を超えた場合
            if      (this._rule['延長戦方式'] == 0)             jieju = true;
                                                    // 延長戦なしなら終局
            else if (this._rule['場数'] == 0)                   jieju = true;
                                                    // 一局戦なら終局
            else if (guanjun >= 0)                              jieju = true;
                                                    // 30,000点超えがいるなら終局
            else {                                  // さらに延長戦を続ける
                this._max_jushu += this._rule['延長戦方式'] == 3 ? 4
                                                    // 4局固定延長の場合、最終局を
                                                    // 4局先に延ばす
                                 : this._rule['延長戦方式'] == 2 ? 1
                                                    // 連荘優先サドンデスの場合、
                                                    // 1局先に延ばす
                                 :                                 0;
                                                    // その他の場合、延長しない
            }
        }
        else if (this._max_jushu == sum_jushu) {    // 最終局の場合
            if (this._rule['オーラス止めあり'] && guanjun == model.player_id[0]
                && this._lianzhuang && ! this._no_game)         jieju = true;
                                                    // オーラス止めの条件を満たせば
                                                    // 終局
        }

        if (jieju)  this.delay(()=>this.jieju(), 0);
        else        this.delay(()=>this.qipai(), 0);
    }

ポイント計算

クラス Majiang.Game のメソッド jieju() で順位を決定し、ポイント計算する。

    jieju() {

        let model = this._model;

        /* 持ち点により順位を決定する。同点の場合は起家に近い方を上位とする。*/
        let paiming = [];
        const defen = model.defen;
        for (let i = 0; i < 4; i++) {
            let id = (model.qijia + i) % 4;
            for (let j = 0; j < 4; j++) {
                if (j == paiming.length || defen[id] > defen[paiming[j]]) {
                    paiming.splice(j, 0, id);
                    break;
                }
            }
        }
        defen[paiming[0]] += model.lizhibang * 1000;  // 積み残しの供託リーチ棒は
                                                      // トップの点に加算する
        this._paipu.defen = defen;

        /* 牌譜に順位を反映する。*/
        let rank = [0,0,0,0];
        for (let i = 0; i < 4; i++) {
            rank[paiming[i]] = i + 1;
        }
        this._paipu.rank = rank;

        /* 順位点を加えポイントを決定する。
           ポイントの端数(小数点以下)は四捨五入する。*/
        let point = [0,0,0,0];
        for (let i = 1; i < 4; i++) {
            let id = paiming[i];
            point[id] = Math.round((defen[id] - 30000) / 1000)
                      + this._rule['順位点'][i];
            point[paiming[0]] -= point[id];
        }
        this._paipu.point = point.map(p=>''+p);

        let paipu = { jieju: this._paipu };

        let msg = [];
        for (let l = 0; l < 4; l++) {
            msg[l] = JSON.parse(JSON.stringify(paipu));
        }
        this.call_players('jieju', msg);

        if (this._view) this._view.summary(this._paipu);
    }

現在はポイントの小数点以下を四捨五入しているが、将来的にはこれもカスタマイズ可能とするかも。*1

*1:v0.2.0 でカスタマイズ可能にしました