← 一覧へ戻る

週末で2048を作った話——ゲームループのすべて

2026-06-29

最近 2048 を ToolKoala に追加したのですが、一番うれしい驚きは、本当のゲーム部分がどれだけ小さいかでした。アニメーションや仕上げには時間がかかりますが、ルール——スライド、マージ、出現、ゲームオーバー判定——はおよそ60行に収まります。ループ全体がどう動くかを紹介します。

盤面はただのフラット配列

グリッドは4×4ですが、16個の数字からなるフラット配列として保持し、0 は空を意味します:

[2, 0, 0, 4,
 0, 2, 0, 0,
 0, 0, 0, 0,
 4, 0, 0, 2]

フラット配列はコピー、比較、localStorage への保存が簡単です。行 r、列 c はインデックス r * 4 + c にあります。

本当の仕事をするのは1つの関数

すべての移動——左、右、上、下——は、4つのタイルの並びに対する同じ操作です:すべてを一方の端へ押し込み、等しい隣り合うタイルを一度だけマージする。それが1つの関数です:

function compress(line) {
  const arr = line.filter(v => v)   // ゼロを取り除く
  const res = []
  let gained = 0
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === arr[i + 1]) {     // 等しいタイルが2つ接触 → マージ
      res.push(arr[i] * 2)
      gained += arr[i] * 2
      i++                            // マージで取り込んだ分をスキップ
    } else {
      res.push(arr[i])
    }
  }
  while (res.length < 4) res.push(0) // 長さ4に詰め直す
  return { line: res, gained }
}

マージ後の i++ が、4 4 4 4 が単一の 16 につぶれてしまうのを止めるルールです。これは 8 8 になります——各タイルは1回の移動で最大1回しかマージしません。

1方向から4方向

私は「左へスライド」しか書いていません。残りの3つはそれを再利用します:

  • — 行を反転し、左へ圧縮し、また反転して戻す。
  • 上 / 下 — 盤面を行ではなくごとに読み、あとは同じ左右のロジック。

つまり移動は各行または各列を取り出し、必要なら反転し、compress を走らせ、書き戻します。矢印ごとの個別コードはありません——どの並びを読むか、それを反転するかどうかだけです。

出現させ、それから詰みかどうかを確認

実際に盤面が変わった移動のあと、新しいタイルがランダムな空セルに現れます——たいていは 2、たまに 4。それから、まだ移動が可能かどうかを確認します:空セルはあるか、それとも等しい隣り合うタイルがあるか?なければゲームオーバーです。

function canMove(g) {
  if (g.includes(0)) return true            // 空セルがある → 可能
  // 横/縦に等しい隣接タイルがある → まだマージが可能
  // ...小さなループでチェック...
  return false
}

「盤面は実際に変わったか?」のチェックが重要です:移動が何もしないなら、ただで新しいタイルをもらえるべきではありません。壁に向かってを押すのはノーオペでなければなりません。

refを使うことを教えてくれたバグ

最初のバージョンは、キーハンドラの中で React の state から盤面を直接読んでいました。高速なキーリピートでは、React が再レンダリングする前に2回の矢印押しが発生し、2回目が古い盤面を読んで移動が取りこぼされました。修正は、最新の盤面とスコアを映す ref を保持し、移動ハンドラにその ref を読ませることでした。state は見た目を駆動し、ref はロジックを駆動します。それ以降は、矢印を連打しても正しく動きます。

再開とベストスコア、すべてローカル

localStorage には2つが入っています:ベストスコアと、現在の盤面です。盤面は移動のたびに保存し、ゲームが終わったらクリアします。読み込み時、有効な未完了のゲームがあれば、それを復元して小さな「続ける」の表示を出します——数独ワードサーチで使っているのと同じパターンです。何もアップロードされません。あなたのゲームは、あなたのデバイス上の、あなたのものです。

試してみる

ここで2048をプレイできます——キーボードかスワイプで、ページが読み込まれれば一度オフラインでも動きます。こういうのが好きなら、数独ジェネレーターはもっと面白いアルゴリズムを背後に持っています。

よくある質問

2048はプログラミングが難しいですか? 核心は驚くほど小さいです——「並びをスライドしてマージする」関数を1つ、4方向すべてに再利用し、加えてタイルの出現とゲームオーバー判定。仕上げ(アニメーション、タッチ操作、進捗の保存)にこそ大半の時間がかかります。

マージのルールはどう動きますか? 同じ数字の2つのタイルが押し合わさると、値が2倍の1つのタイルに合体します——そして各タイルは1回の移動で1回しかマージできないので、2が4つ並んだ行は8が1つではなく4が2つになります。

4つの方向はどう扱いますか? 実装が必要なのは1方向だけです。右は行を反転した左、上と下は同じロジックを行ではなく列に適用したものです。

ToolKoala の2048は私のゲームを保存しますか? はい——ベストスコアと現在の盤面はブラウザのローカルストレージに保存されるので、タブを閉じてあとで再開できます。デバイスを離れることは決してありません。

— Milo 🐨