← 所有文章

我如何用一個週末做出 2048——完整的遊戲迴圈

2026-06-29

我最近把 2048 加進了 ToolKoala,最棒的意外是真正的遊戲其實有多。動畫和打磨很花時間,但規則——滑動、合併、生成、檢查遊戲結束——大概六十行就裝得下。底下是整個迴圈的運作方式。

棋盤就只是一個一維陣列

格子是 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

真正的活由一個函式完成

每一個移動——左、右、上、下——對一條四格的線來說都是同一個操作:把所有東西推向一端,並把相等的鄰居合併一次。這就是一個函式:

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]) {     // 兩個相等的方塊碰在一起 → 合併
      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——每個方塊在每次移動裡最多只合併一次。

從一個方向變出四個方向

我只寫了「往左滑」。另外三個都重複利用它:

  • — 把那一列反轉、往左 compress、再反轉回來。
  • 上 / 下 — 改用而不是列來讀棋盤,然後套同樣的左/右邏輯。

所以一次移動就是抓出每一列或每一行、視情況把它反轉、跑 compress、再寫回去。每個方向鍵不用各寫一份程式碼——差別只在我讀的是哪一條線、以及要不要把它翻過來。

先生成,再檢查你是不是卡住了

在一次真的改變了棋盤的移動之後,會在某個隨機的空格裡冒出一個新方塊——大多數時候是 2,偶爾是 4。接著我檢查還有沒有任何移動是可能的:還有空格嗎?或者有沒有任何兩個相等的鄰居?如果都沒有,就是遊戲結束。

function canMove(g) {
  if (g.includes(0)) return true            // 有空格 → 可以
  // 任何相等的水平/垂直鄰居 → 還能合併
  // ...用一個小迴圈檢查...
  return false
}

「棋盤到底有沒有真的變?」這個檢查很重要:如果一次移動什麼都沒做,你就不該白得一個新方塊。對著一面牆按必須是個沒有作用的空操作。

那個教會我用 ref 的 bug

第一個版本是在按鍵處理函式裡直接從 React state 讀取棋盤。在快速連按時,兩次方向鍵可能在 React 重新渲染之前就先觸發了,於是第二次讀到的是一個過時的棋盤,移動就被吃掉了。修法是另外保留一個 ref,鏡像最新的棋盤和分數,讓移動處理函式去讀那個 ref。state 驅動你看到的東西;ref 驅動邏輯。這樣之後,就算你狂敲方向鍵它也行為正常。

續玩和最佳分數,全都在本機

有兩樣東西住在 localStorage 裡:你的最佳分數,以及目前的棋盤。每次移動都存棋盤;遊戲結束時清掉它。載入時,如果有一局有效的、還沒打完的遊戲,我就把它還原,並顯示一個小小的「繼續」提示——跟我在數獨找字遊戲用的是同一套做法。沒有任何東西被上傳;你的這局遊戲是你的,就在你的裝置上。

玩玩看

你可以在這裡玩 2048——鍵盤或滑動都行,而且一旦頁面載入過,它就能離線運作。如果你喜歡這類東西,數獨產生器背後有個更有意思的演算法。

常見問題

2048 很難寫嗎? 核心小得出乎意料——一個「滑動並合併一條線」的函式,重複用在全部四個方向,再加上生成一個方塊以及檢查遊戲結束。打磨的部分(動畫、觸控操作、儲存進度)才是大部分時間的去處。

合併規則是怎麼運作的? 當兩個數字相同的方塊被推在一起,它們會合併成一個數值加倍的方塊——而且每個方塊在每次移動裡只能合併一次,所以一列四個 2 會變成兩個 4,而不是一個 8。

四個方向是怎麼處理的? 你只需要實作一個方向。右就是把列反轉後的左;上和下則是把同樣的邏輯套用在行而不是列上。

ToolKoala 的 2048 會儲存我的遊戲嗎? 會——你的最佳分數和目前的棋盤都存在你瀏覽器的 local storage 裡,所以你可以關掉分頁、之後再接著玩。它永遠不離開你的裝置。

— Milo 🐨