電脳麻将 ver.0.5 では立直に対するベタオリを実装した。ベタオリするためにはまず牌の危険度を評価する必要があるが、現代麻雀技術論 - 押し引き論13.ベタオリ を参考に以下の値とした。
牌の種類 | 無スジ | 片スジ | スジ | 生牌 | 2枚見 | 3枚見 | ラス牌 |
---|---|---|---|---|---|---|---|
字牌 | − | − | − | 3 | 2 | 1 | 0 |
一・九牌 | 6 | − | 3 | − | − | − | − |
二・八牌 | 8 | − | 4 | − | − | − | − |
三・七牌 | 8 | − | 5 | − | − | − | − |
四・五・六牌 | 12 | 8 | 4 | − | − | − | − |
牌の危険度を判定する
まず Majiang.SuanPai に上記の表にしたがい牌の危険度を判定する処理を追加する。
Majiang.SuanPai = function(hongpai) { /* ... ここまでは修正なし ... */ /* 各プレイヤーごとの打牌リストを初期化する */ this._dapai = []; for (var l = 0; l < 4; l++) { this._dapai[l] = {}; } /* 各プレイヤーの立直有無を初期化する */ this._lizhi = [false, false, false, false]; }
打牌時に打牌リストと立直有無を更新する。立直後は他者の打牌も「現物」となるので、それも打牌リストに追加する。
Majiang.SuanPai.prototype.dapai = function(data) { if (data.l != this._menfeng) this.diaopai(data.p); var p = data.p.substr(0,2).replace(/0/,'5'); // 赤牌を正規化 this._dapai[data.l][p] = true; // 打牌リストに追加 if (data.p.match(/\*$/)) this._lizhi[data.l] = true; // 立直有無を更新 for (var l = 0; l < 4; l++) { if (this._lizhi[l]) this._dapai[l][p] = true; // 立直後は他者の打牌も現物 } }
牌の危険度は Majiang.SuanPai のメソッド suan_weixian() で判定する。
Majiang.SuanPai.prototype.suan_weixian = function(p, l) { var rv = 12; var s = p[0], n = p[1]-0||5; if (this._dapai[l][s+n]) return 0; if (s == 'z') return Math.min(this.paishu(s+n), 3); if (n == 1) return this._dapai[l][s+(n+3)] ? 3 : 6; if (n == 9) return this._dapai[l][s+(n-3)] ? 3 : 6; if (n == 2) return this._dapai[l][s+(n+3)] ? 4 : 8; if (n == 8) return this._dapai[l][s+(n-3)] ? 4 : 8; if (n == 3) return this._dapai[l][s+(n+3)] ? 5 : 8; if (n == 7) return this._dapai[l][s+(n-3)] ? 5 : 8; return this._dapai[l][s+(n-3)] && this._dapai[l][s+(n+3)] ? 4 : this._dapai[l][s+(n-3)] || this._dapai[l][s+(n+3)] ? 8 : 12; }
ソースの差分は以下。
ベタオリを実装する
Majiang.Player のメソッド select_dapai() にベタオリを実装する。アルゴリズムは危険度が最も低い牌を打つというだけ。
Majiang.Player.prototype.select_dapai = function() { /* .... 省略 ... */ var anquan, min = Infinity; // 危険度の初期値を無限大とする /* 立直者がいる場合、以下を行う */ if (this._lizhi.filter(function(x){return x}).length > 0) { /* すべての可能な打牌について危険度を判定 */ for (var p of this.get_dapai()) { var weixian = 0; for (var l = 0; l < 4; l++) { // すべてのプレイヤーについて if (! this._lizhi[l]) continue; // 立直していない者は対象外 var w = this._suanpai.suan_weixian(p, l); // 危険度を判定 if (w > weixian) weixian = w; } if (weixian < min) { // 最も安全な牌を選択する min = weixian; anquan = p; } } } /* .... 省略 ... */ if (anquan) dapai = anquan; // 最も安全な牌を打牌に選択する /* .... 省略 ... */ return dapai; }
ソースの差分は以下。
ベタオリをするプレイヤーとオリないプレーヤー3人の対戦結果は以下の通り。
対戦数 | 1,000 | 総局数 | 9,460 |
---|---|---|---|
1位率 | .191 | 和了率 | .159 |
2位率 | .311 | 放銃率 | .031 |
3位率 | .363 | 立直率 | .255 |
4位率 | .135 | 副露率 | .000 |
平均順位 | 2.44 | 平均打点 | 6,803 |
対戦の様子を見てみると、ベタオリさえすれば現物が足りなくなる場面はほとんどないようである。つまりベタオリするだけなら牌の危険度の精度はこれくらいで充分*1。放銃率 3.1% はほぼ放銃しないと言えるレベルだが、オリすぎて和了率が 15.9% まで落ちてしまった。その結果、トップ率も落ちてしまい、平均順位としては大きな改善とならなかった。何よりオリ過ぎでつまらない麻雀になっている。