麻雀の打牌選択アルゴリズム(9) 以来、6年ぶりに打牌選択アルゴリズムを改善。
[ 東場 東家 ]
ウザク式牌効率講座「3分何切る」(2023/04/10) からの出題。
電脳麻将: 何切る解答機 の選択は 、ウザク式牌効率講座の回答も
。
打 と
は評価値が同点なので、麻雀の打牌選択アルゴリズム(3) で定義した 牌の評価値*1にしたがい
を選択しているのだが、ウザク式効率講座の解説では
を引いたときの 手変わり を根拠としている。
具体的には、
となり、打 で評価値が 3075.69 → 5106.60 に上昇する。
このような「シャンテン数は進まないが評価値が向上する牌」のことを 二次有効牌 と呼ぶことにする。
打 の場合、二次有効牌は
摸 | 枚数 | 打 | 牌姿 | 評価値 |
---|---|---|---|---|
![]() | 2 | ![]() | ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() | 4301.39 |
![]() | 2 | ![]() | ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() | 3943.75 |
の2種4枚となり、打 については
摸 | 枚数 | 打 | 牌姿 | 評価値 |
---|---|---|---|---|
![]() | 2 | ![]() | ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() | 4992.71 |
![]() | 2 | ![]() | ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() | 3943.75 |
![]() | 3 | ![]() | ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() | 3639.24 |
![]() | 1 | ![]() | ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() | 5106.60 |
の3種*28枚となる。
これを見れば打 が有利と分かる。
何切る検討(3) ~ 手変わりの考慮 でも手変わりに触れているが、その際には評価値計算の際に再帰的に二次有効牌を考慮すると計算量が爆発的に増えると考え、打牌選択のアルゴリズムには組み入れなかった。 今回は発想を転換して評価値が同点の場合のみ限定的に二次有効牌による評価値の向上を計算することとする。
まず、二次有効牌により向上した評価値の総和を求めるメソッド eval_replace()
を追加する。
eval_replace(shoupai, paishu, back, min) {
let n_xiangting = Majiang.Util.xiangting(shoupai);
function gulipai(bingpai, n) {
if (3 <= n && bingpai[n-2] > 0) return false;
if (2 <= n && bingpai[n-1] > 0) return false;
if (n != 0 && bingpai[n] > 0) return false;
if (n <= 8 && bingpai[n+1] > 0) return false;
if (n <= 9 && bingpai[n+2] > 0) return false;
return true;
}
function replace() {
let pai = [];
for (let s of ['m','p','s']) {
let bingpai = shoupai._bingpai[s];
for (let n = 1; n < bingpai.length; n++) {
if (s+n == back) continue;
if (gulipai(bingpai, n)) continue;
bingpai[n]++;
if (Majiang.Util.xiangting(shoupai) == n_xiangting)
pai.push(s+n);
bingpai[n]--;
}
}
return pai;
}
let rv = 0;
for (let p of add_hongpai(replace())) {
if (paishu[p] == 0) continue;
paishu[p]--;
let ev = this.eval_shoupai(shoupai.clone().zimo(p), paishu, back);
paishu[p]++;
if (ev - min > 0.0000001) rv += ev * paishu[p];
}
return rv / width[n_xiangting];
}