我如何用一個週末做出 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 🐨