平均ではパンチが勝つのに、シャワーを選ぶ理由 — 戦術ゲームの選択肢を数字で作る

白物戦記の洗濯機イリヤスは、6発を放つ高圧シャワーと、確実に当たる脱水パンチを持っています。平均の損害だけならパンチが上。それでもシャワーを選ぶ場面はあるのか。2万回撃って確かめた分布と、「弱くしたつもりが強くなるところだった」調整の落とし穴、予測の表示を正直に保つための検査を書きます。

平均だけなら、パンチ一択

白物戦記の戦闘画面。冷蔵庫・洗濯機・掃除機の機体と害虫軍が等角のマップに並ぶ
移動にも攻撃にもエネルギーを使うので、どこから撃つかまでが選択の一部です。

白物戦記の洗濯機、イリヤスには二つの攻撃があります。主兵装の高圧シャワーは、射程1〜4マスで6発を浴びせ、1発ごとの命中率は60%です。副兵装の脱水パンチは、隣のマスにしか届きませんが、必ず当たります。

装甲値6の敵に、遮蔽物や強化がない状態で攻撃したときの損害を、ゲームの計算処理そのものを使って確かめました。パンチは毎回81。シャワーは6発すべて当たれば129ですが、2万回試した平均は77.4でした。

平均で比べると、パンチのほうが上です。戦術ゲームで攻撃の違いを平均の数字だけにすると、いちばん大きな数字を選び続けるゲームになります。では、シャワーは要らない武器なのでしょうか。

分布で見ると、別の武器になる

6発がそれぞれ60%で当たるなら、当たる数の分布は二項分布で計算できます。

// n発それぞれ確率pで当たるとき、k発当たる確率(k = 0…n)
function pellets(n, p) {
  const out = [];
  let c = 1;                                   // 組み合わせの数 nCk
  for (let k = 0; k <= n; k++) {
    out.push(c * p ** k * (1 - p) ** (n - k));
    c = c * (n - k) / (k + 1);
  }
  return out;
}
pellets(6, 0.6); // → [0.004, 0.037, 0.138, 0.276, 0.311, 0.187, 0.047]

実際に撃ったダメージと並べると、こうなります。

当たった数確率損害
0発0.4%0
1発3.7%21〜22
2発13.8%42〜44
3発27.6%63〜66
4発31.1%85〜87
5発18.7%107〜108
6発4.7%129

パンチの81を超えるのは、4発以上当たったときです。その確率は計算で54.4%、2万回撃った実測では54.6%でした。シャワーは「半分強の確率でパンチより強く、約20回に1回はパンチの1.6倍近いダメージを与える一方、約25回に1回は1発以下しか当たらない」攻撃なのです。

そしてもう一つの違いが、距離です。シャワーは4マス先から撃てますが、パンチは敵の隣に立たなければ届きません。白物戦記では移動にも道のりに応じてエネルギーを使います。攻撃の消費はシャワーが50、パンチが30で、基本の最大は200です。消費だけ見ればパンチが軽いのですが、隣へ寄る移動のぶんが上乗せされ、敵の反撃が届く場所に立つことにもなります。

確実に81のダメージを与えるために前へ出るか、距離を取って高圧シャワーを撃つか。平均の数字では同じくらいでも、位置とばらつきが違えば、選ぶ理由が生まれます。そのためには、遊ぶ人がばらつきを知っている必要があります。白物戦記の攻撃予測は、平均ではなく「1発ごとの命中率」「全弾命中時の最大損害」「全弾命中(概算)」の確率を並べて出しています。

弱くしたつもりが、強くなるところだった

冷蔵庫のアオイが持つ製氷機関砲に、「攻撃力を75に」という調整の指示が出ました。狙いは弱くすることです。

ところが、コードに書かれていた攻撃力は70でした。75にすれば、むしろ強くなります。調べると、この武器には防御の計算のあとで損害を1.5倍にする補正が、別の場所で掛かっていました。表に書かれた数字と、実際に効く数字が別物になっていたのです。

直し方は、補正を外したうえで75にすることでした。装甲6の敵への最大の損害は92から66に下がり、指示の意図どおり弱くなりました。

この件から学んだのは、調整に使う攻撃力と、最終的なダメージの関係を明示することです。隠れた倍率があると、数字を見て判断する人ほど読み違えます。高圧シャワーには今も同じ1.5倍の補正が残っていますが、攻撃予測は実際の攻撃と同じ計算を通して最大の損害(129)を出しているので、画面に出る数字と結果は食い違いません。調整するときも、表の数字ではなく予測に出る数字を見て決めます。

予測を見るだけで、未来が変わってはいけない

予測の表示には、もう一つ守るべき約束があります。予測を計算するときに、乱数を一つでも引いてはいけないのです。もし引いてしまうと、予測を開いた回数によって次の攻撃の結果が変わります。予測を開くことが、遊ぶ人に見えない形で攻撃結果を変えてしまいます。

確かめ方は単純です。乱数の種を固定し、予測を0回見てから撃った場合と、100回見てから撃った場合を比べます。

function shoot(peek) {
  const s = setup(12345);                        // 乱数の種を固定した同じ盤面
  for (let i = 0; i < peek; i++) E.preview(s, ilyas, enemy, 'auto', 'right');
  const seed = s.seed;                           // 予測のあとで種が動いていないか
  E.attack(s, 'ilyas', enemy.id, 'auto', 'right');
  return { seed, hp: remainingHp(s, enemy.id) };
}
assert.deepEqual(shoot(0), shoot(100));          // 何度見ても、結果は同じ

白物戦記の計算処理で試すと、種は12345のまま動かず、撃ったあとの敵の残り体力も完全に一致しました。少なくとも、この検査で比較した乱数の種と攻撃結果は変わりませんでした。

選択肢は「どれが強いか」ではなく「いまどれが合うか」で分かれる

同じ考え方は、部位の壊し方にも入っています。機体は胴体・右腕・左腕・脚部の部位ごとに耐久があり、胴体が大破すれば停止、脚部が大破すれば移動が1マスに、腕が大破すればその腕の兵装が使えなくなります。投降の対象になる敵は、両腕を失うと投降します。倒し切るだけが決着ではないので、「止める」「移動力を下げる」「武器を奪う」が別の選択肢として並びます。ただし通常の攻撃は部位を選んで狙えず、狙う部位を決められるのは掃除機のリンの集中吸引だけです。

エネルギーも選択を作ります。長射程レーザーは一撃でエネルギー190を使い、その手番に移動も行動もしていないことが条件です。強化していなければ最大が200なので、撃てば残りは10、その手番はもう動けません。強力な一撃は、手番の自由と引き換えです。

勝利の条件も、敵の全滅だけではありません。面によって、護送車を出口へ届ける、作業地点を押さえる、指定された大型機を止める、と条件が変わります。「敵を倒したのに終わらない」「作業は終えたが敵が残っている」といった状態を分けて、説明文・画面の表示・実際の終了判定の三か所が同じことを言っているかを照らし合わせると、遊ぶ人が理由の分からない終わり方を減らせます。

武器の数字を決めるときに見るのは、平均の大小ではありません。分布、距離、消費、そして「その選択のあとに何が残るか」です。そこに違いがあれば、平均が同じくらいの二つの武器でも、どちらも選ぶ理由が生まれます。

移動できる範囲の考え方は、Red Blob Gamesの経路探索の入門が図で分かりやすく説明しています。「どこまで届くか」と「何を使って届くか」を分けて考える手がかりになります。