01. なぜ「画像1枚」ではなく全7,178手のJSONログを公開したのか
ゲームの攻略ページを見ていると、よく「8192達成!」というスクリーンショットだけが貼ってあることがあります。もちろんそれ自体も嬉しい記録なのですが、ブラウザゲームの場合、コンソールから任意の数字を配置したテスト盤面なのか、それとも本当に初期状態の2枚から1手ずつ積み上げた結果なのかが、画像だけでは判別できないわけです。
そこでうちでは、本編とまったく同じ計算処理(`js/game-core.js` と `js/practice-engine.js`)を読み込み、初期タイル2枚の状態から1手ずつ6方向を比較して移動し、最後にその全手順を最初から別の空盤面にリプレイ再生(`assert.equal(replay.score, core.score)`)して一致を確かめる検証スクリプトを組みました。これなら、誰でも 公開JSONログ を取得して、1手目から7,178手目までの合法性を検証できます。
02. 6方向を採点する盤面評価関数 quality の数式とコード
では、この自動操作スクリプトがどうやって6方向(`X+`, `X-`, `Y+`, `Y-`, `Z+`, `Z-`)の中から次の一手を選んでいるのかを見ていきましょう。何手も先を読む重い探索(Expectimaxなど)は使わず、「1手動かした直後の盤面(`clone`)の形だけを採点するシンプルな評価関数」で7,178手を走り切っています。実際のコードは以下の通りです。
// 開発部/scripts/record_8192_example.mjs より抜粋
let best = null;
for (const [axis, positive] of api.directions) {
const clone = api.create(api.snapshot(core));
const res = api.move(clone, axis, positive);
if (!res.changed) continue;
let quality = (64 - clone.tiles.size) * 1000;
let smooth = 0;
let anchored = 0;
for (const t of clone.tiles.values()) {
const power = Math.log2(t.value);
anchored += (power ** 3) * (t.gx + 3 - t.gy + t.gz);
for (const [dx, dy, dz] of [[1, 0, 0], [0, 1, 0], [0, 0, 1]]) {
const id = clone.grid[t.gx + dx]?.[t.gy + dy]?.[t.gz + dz];
const neighbor = clone.tiles.get(id);
if (neighbor) {
smooth += Math.abs(power - Math.log2(neighbor.value));
}
}
}
quality += anchored * 0.1 - smooth * 2;
if (!best || quality > best.quality) best = { axis, positive, quality };
}
この評価式は、シンプルに3つの項の足し引きで成り立っています。それぞれの項が何を意味しているのかを図解します。
面白いのが第2項の `anchored += (power ** 3) * (t.gx + 3 - t.gy + t.gz)` です。4×4×4盤面では各座標が `0〜3` を取るので、`gx = 3`(右端)、`gy = 0`(最下段)、`gz = 3`(手前端)の頂点にあるとき、`gx + 3 - gy + gz` は最大値の `3 + 3 - 0 + 3 = 9` になります。そこにタイル指数の3乗(`power ** 3`、たとえば2048なら `11³ = 1331`)を掛けることで、大きなタイルが右下手前の角 `(3, 0, 3)` から外れる手を徹底的に嫌うようになっているわけです。
03. 乱数シード 20261022 における到達手数とスコア推移
このスクリプトを線形合同法(`state = (Math.imul(state, 1664525) + 1013904223) >>> 0`)の初期シード `20261022` で走らせたときのマイルストーン記録が以下の表です。
| 到達マイルストーン | 移動成功の累計手数 | 到達時点のスコア | 区間手数(前の段階からの増分) |
|---|---|---|---|
| 初期配置(2枚) | 0手 | 0 | — |
| 2048 到達 | 1,181手 | 37,340 | +1,181手 |
| 4096 到達 | 5,172手 | 158,448 | +3,991手 |
| 8192 到達 | 7,178手 | 259,880 | +2,006手 |
ここで注目してほしいデータがあります。2048から4096を作るまでに `3,991手` かかっているのに、4096から8192を作る区間は `2,006手` で通過している点です。「倍の数字を作るのだから手数も倍かかるはずやん?」と思うかもしれませんが、途中でFEVERや連鎖が重なったり、盤面上に残っていた中位タイル(512や1024)が一気に回収されたりするため、区間ごとの手数は一定にはなりません。最終盤面の4層分のSVG図と詳細な盤面解説は 4096・8192到達戦略ページ に掲載しています。
04. この評価関数を人間のプレイにどう落とし込むか
そりゃ人間がプレイするときに頭の中で `power ** 3` を暗算するわけにはいきませんが、この評価関数の優先順位そのものは、人間が迷ったときの判断基準としてそのまま使えます。優先順位はシンプルに次の3段階です。
- 最優先(重み1000):合体組数が最も多く、空きマスが増える方向を選ぶ。(ただし最大タイルが角から動く手は除く)
- 第2優先(角アンカー):最大タイルと2番手・3番手のタイルが、自分が決めた角(例:右下手前)から離れない方向を選ぶ。
- 第3優先(隣接滑らかさ):同じくらいの数字(例:128と256)がX・Y・Zの隣同士に並ぶように寄せる。2048のすぐ隣に「2」が挟まるような段差の大きい配置を避けるだけでも、後の連鎖率が大きく変わります。
05. 検証データの限界と、自分の手で試すための教材
最後に誠実に留保しておくと、この7,178手の記録は「シード `20261022` における1回の自動操作の到達例」であり、平均手数や成功率、あるいは理論上の最善手を証明するものではありません。また、64マスある4×4×4盤面だからこそ1手読みの評価関数で8192まで届きますが、27マスしかない3×3×3盤面では空きマスの枯渇が早いため、数手先の合体経路(再合体しない制限を踏まえた2手1組の整地)を人間が補う必要があります。
「1024・1024・2048」が並ぶ局面からどうやって2手で4096へまとめるかは、局面練習のレッスン3(再合体の制限) で実際にボタンを動かして確認できます。まずは教材で6方向の比較表を眺めつつ、本編の4×4×4無制限モードでぼちぼち4096・8192を狙ってみてください。
局面練習と本編で評価基準を試す
6方向の合体数と空きマスを比較できる局面教材と、時間制限なしのCLASSICモードをブラウザですぐに開けます。