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つの項の足し引きで成り立っています。それぞれの項が何を意味しているのかを図解します。

quality = (64 − タイル数) × 1000 + anchored × 0.1 − smooth × 2 ① 空きマス評価 (×1000) (64 - tiles.size) * 1000 1組合体するごとに+1000点。 何よりも「合体数を増やし 空き空間を空ける手」を最優先。 ② 角アンカー勾配 (×0.1) power³ × (gx + 3 - gy + gz) 右下手前の頂点 (3, 0, 3) で 距離係数が最大(9)になる。 高ランクほど角へ強く吸い寄せる。 ③ 隣接段差ペナルティ (−2) |power₁ - power₂| × 2 X, Y, Zの隣り合うタイルの 指数差が大きいほど減点。 近い数字同士を隣に並べる。
▲ 自動操作スクリプトが毎手6方向を比較する際の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` で走らせたときのマイルストーン記録が以下の表です。

CLASSIC(4×4×4・タイマーなし・UNDOなし)における8192到達ログ(Seed: 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段階です。

  1. 最優先(重み1000):合体組数が最も多く、空きマスが増える方向を選ぶ。(ただし最大タイルが角から動く手は除く)
  2. 第2優先(角アンカー):最大タイルと2番手・3番手のタイルが、自分が決めた角(例:右下手前)から離れない方向を選ぶ。
  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を狙ってみてください。

Reproducible Strategy

局面練習と本編で評価基準を試す

6方向の合体数と空きマスを比較できる局面教材と、時間制限なしのCLASSICモードをブラウザですぐに開けます。

📐 局面練習(レッスン3)を試す ➔ 📊 8192到達時の4層SVGを見る