iT邦幫忙

2025 iThome 鐵人賽

DAY 2
2

本日重點

解釋 2048 的遊戲目標、滑動與合併規則、終止條件等

1. 遊戲目標

2048 是一款單人益智遊戲,玩家透過滑動方格中的數字進行合併,
最終目標是在盤面上生成一個 數字 2048 的方塊。
遊戲盤面為 4x4,每格可以放置一個數字(通常是 2 的冪次)。

2. 遊戲流程

遊戲基本迴圈如下:

  1. 初始化:隨機在兩個位置生成 2 或 4。
  2. 玩家滑動(上下左右四個方向)。
  3. 壓縮數字:將所有數字往滑動方向靠攏。
  4. 合併數字:相鄰且數值相同的數字會合併,數值加倍。
  5. 再壓縮:合併後的空格再次填補。
  6. 新增數字:在隨機空格生成 2 或 4。
  7. 檢查終止條件(勝利 / 失敗)。
  8. 重複步驟 2~7。

3. 滑動與合併規則

這是 2048 最重要的邏輯,先用向左滑為例:

操作前 壓縮非零數字 合併相同數字 再壓縮
2 0 2 4 2 2 4 0 4 0 4 0 4 4 0 0
4 4 4 4 4 4 4 4 8 0 8 0 8 8 0 0
  • 壓縮(Compress):將所有非零數字往滑動方向移動。
  • 合併(Merge):相鄰且相同的數字合併成一個,數值翻倍。
  • 再次壓縮:把合併後出現的空格補齊。

4. 終止條件

遊戲會在以下情況結束:

勝利條件

盤面上出現數字 2048(或玩家自訂目標,例如 4096)。

失敗條件

盤面已無空格,且 四個方向都無法再合併。

5. 視覺化範例

以下是一個簡化的滑動過程示意:

[2][0][2][4]   ←滑動左
 ↓
[2][2][4][0]   ←壓縮
 ↓
[4][0][4][0]   ←合併
 ↓
[4][4][0][0]   ←再壓縮

6. 與 Gemini CLI 的結合

雖然今天我們還沒寫程式,但可以先用 Gemini CLI 當學習輔助,
讓它用多種情境幫我們列舉滑動與合併的範例,
方便實作滑動邏輯時直接用來做測試資料。

gemini -p "請幫我產生 2048 遊戲向左滑動的 10 組測試案例,包含壓縮、合併、再壓縮的過程"

gemini 產生出來的測試案例

這是在 2048 遊戲中向左滑動(moveLeft)的 10 組測試案例。

每個案例都會展示一個 4x4 的遊戲板,並分解說明「向左滑動」操作的三個核心步驟:

  1. 壓縮:將所有數字方塊向左移動,填補所有空格。
  2. 合併:從左至右檢查,如果相鄰的方塊數字相同,則將它們合併(數值相加)。
  3. 再次壓縮:合併後可能產生新的空格,因此需要再次將所有方塊向左移動。

測試案例 1: 基本合併

初始狀態 (Before):

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

過程 (Process):

  • 步驟 1: 壓縮 (Squeeze)
    • 第一行: [2, 2, 0, 0] -> [2, 2, 0, 0] (無變化)
    • 第二行: [4, 4, 2, 0] -> [4, 4, 2, 0] (無變化)
    • 第三行: [8, 0, 8, 0] -> [8, 8, 0, 0]
  • 步驟 2: 合併 (Merge)
    • 第一行: [2, 2, 0, 0] -> [4, 0, 0, 0]
    • 第二行: [4, 4, 2, 0] -> [8, 0, 2, 0]
    • 第三行: [8, 8, 0, 0] -> [16, 0, 0, 0]
  • 步驟 3: 再次壓縮 (Re-squeeze)
    • 第二行: [8, 0, 2, 0] -> [8, 2, 0, 0]
    • 其他行無變化

最終狀態 (After):

[[4, 0, 0, 0],
 [8, 2, 0, 0],
 [16, 0, 0, 0],
 [0, 0, 0, 0]]

測試案例 2: 無任何合併,僅壓縮

初始狀態 (Before):

[[2, 4, 8, 16],
 [0, 2, 0, 4],
 [0, 0, 8, 0],
 [16, 0, 0, 0]]

過程 (Process):

  • 步驟 1: 壓縮 (Squeeze)
    • 第一行: [2, 4, 8, 16] (無變化)
    • 第二行: [0, 2, 0, 4] -> [2, 4, 0, 0]
    • 第三行: [0, 0, 8, 0] -> [8, 0, 0, 0]
    • 第四行: [16, 0, 0, 0] (無變化)
  • 步驟 2: 合併 (Merge)
    • 所有行都沒有可合併的方塊。
  • 步驟 3: 再次壓縮 (Re-squeeze)
    • 無變化。

最終狀態 (After):

[[2, 4, 8, 16],
 [2, 4, 0, 0],
 [8, 0, 0, 0],
 [16, 0, 0, 0]]

測試案例 3: 連續合併(鏈式反應)

初始狀態 (Before):

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

過程 (Process):

  • 步驟 1: 壓縮 (Squeeze)
    • 無變化。
  • 步驟 2: 合併 (Merge)
    • 第一行: [2, 2, 4, 4] -> [4, 0, 8, 0]
    • 第二行: [8, 8, 8, 8] -> [16, 0, 16, 0]
  • 步驟 3: 再次壓縮 (Re-squeeze)
    • 第一行: [4, 0, 8, 0] -> [4, 8, 0, 0]
    • 第二行: [16, 0, 16, 0] -> [16, 16, 0, 0]

最終狀態 (After):

[[4, 8, 0, 0],
 [16, 16, 0, 0],
 [0, 0, 0, 0],
 [0, 0, 0, 0]]

測試案例 4: 三個相同方塊

初始狀態 (Before):

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

過程 (Process):

  • 步驟 1: 壓縮 (Squeeze)
    • 第一行: [2, 2, 2, 0] (無變化)
    • 第二行: [4, 0, 4, 4] -> [4, 4, 4, 0]
  • 步驟 2: 合併 (Merge) (合併只發生在最左邊的一對)
    • 第一行: [2, 2, 2, 0] -> [4, 0, 2, 0]
    • 第二行: [4, 4, 4, 0] -> [8, 0, 4, 0]
  • 步驟 3: 再次壓縮 (Re-squeeze)
    • 第一行: [4, 0, 2, 0] -> [4, 2, 0, 0]
    • 第二行: [8, 0, 4, 0] -> [8, 4, 0, 0]

最終狀態 (After):

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

測試案例 5: 全滿的棋盤

初始狀態 (Before):

[[2, 2, 4, 8],
 [4, 4, 2, 2],
 [2, 4, 2, 4],
 [4, 2, 4, 2]]

過程 (Process):

  • 步驟 1: 壓縮 (Squeeze)
    • 無變化。
  • 步驟 2: 合併 (Merge)
    • 第一行: [2, 2, 4, 8] -> [4, 0, 4, 8]
    • 第二行: [4, 4, 2, 2] -> [8, 0, 4, 0]
    • 第三行: [2, 4, 2, 4] (無合併)
    • 第四行: [4, 2, 4, 2] (無合併)
  • 步驟 3: 再次壓縮 (Re-squeeze)
    • 第一行: [4, 0, 4, 8] -> [4, 4, 8, 0]
    • 第二行: [8, 0, 4, 0] -> [8, 4, 0, 0]

最終狀態 (After):

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

測試案例 6: 空棋盤

初始狀態 (Before):

[[0, 0, 0, 0],
 [0, 0, 0, 0],
 [0, 0, 0, 0],
 [0, 0, 0, 0]]

過程 (Process):

  • 所有步驟均無變化。

最終狀態 (After):

[[0, 0, 0, 0],
 [0, 0, 0, 0],
 [0, 0, 0, 0],
 [0, 0, 0, 0]]

測試案例 7: 複雜的間隔

初始狀態 (Before):

[[2, 0, 2, 0],
 [0, 4, 0, 4],
 [8, 0, 0, 8],
 [0, 16, 16, 0]]

過程 (Process):

  • 步驟 1: 壓縮 (Squeeze)
    • 第一行: [2, 0, 2, 0] -> [2, 2, 0, 0]
    • 第二行: [0, 4, 0, 4] -> [4, 4, 0, 0]
    • 第三行: [8, 0, 0, 8] -> [8, 8, 0, 0]
    • 第四行: [0, 16, 16, 0] -> [16, 16, 0, 0]
  • 步驟 2: 合併 (Merge)
    • 第一行: [2, 2, 0, 0] -> [4, 0, 0, 0]
    • 第二行: [4, 4, 0, 0] -> [8, 0, 0, 0]
    • 第三行: [8, 8, 0, 0] -> [16, 0, 0, 0]
    • 第四行: [16, 16, 0, 0] -> [32, 0, 0, 0]
  • 步驟 3: 再次壓縮 (Re-squeeze)
    • 無變化。

最終狀態 (After):

[[4, 0, 0, 0],
 [8, 0, 0, 0],
 [16, 0, 0, 0],
 [32, 0, 0, 0]]

測試案例 8: 合併後不產生連鎖合併

初始狀態 (Before):

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

過程 (Process):

  • 步驟 1: 壓縮 (Squeeze)
    • 第一行: [2, 2, 4, 0] (無變化)
    • 第二行: [0, 4, 2, 2] -> [4, 2, 2, 0]
  • 步驟 2: 合併 (Merge)
    • 第一行: [2, 2, 4, 0] -> [4, 0, 4, 0] (新產生的 4 不會與後面的 4 合併)
    • 第二行: [4, 2, 2, 0] -> [4, 4, 0, 0]
  • 步驟 3: 再次壓縮 (Re-squeeze)
    • 第一行: [4, 0, 4, 0] -> [4, 4, 0, 0]
    • 第二行: [4, 4, 0, 0] (無變化)

最終狀態 (After):

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

測試案例 9: 只有最右邊有數字

初始狀態 (Before):

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

過程 (Process):

  • 步驟 1: 壓縮 (Squeeze)
    • 第一行: [0, 0, 0, 2] -> [2, 0, 0, 0]
    • 第二行: [0, 0, 4, 4] -> [4, 4, 0, 0]
    • 第三行: [0, 8, 0, 8] -> [8, 8, 0, 0]
  • 步驟 2: 合併 (Merge)
    • 第一行: [2, 0, 0, 0] (無合併)
    • 第二行: [4, 4, 0, 0] -> [8, 0, 0, 0]
    • 第三行: [8, 8, 0, 0] -> [16, 0, 0, 0]
  • 步驟 3: 再次壓縮 (Re-squeeze)
    • 無變化。

最終狀態 (After):

[[2, 0, 0, 0],
 [8, 0, 0, 0],
 [16, 0, 0, 0],
 [0, 0, 0, 0]]

測試案例 10: 綜合複雜場景

初始狀態 (Before):

[[2, 0, 2, 4],
 [2, 2, 2, 2],
 [4, 4, 8, 8],
 [16, 0, 16, 16]]

過程 (Process):

  • 步驟 1: 壓縮 (Squeeze)
    • 第一行: [2, 0, 2, 4] -> [2, 2, 4, 0]
    • 第二行: [2, 2, 2, 2] (無變化)
    • 第三行: [4, 4, 8, 8] (無變化)
    • 第四行: [16, 0, 16, 16] -> [16, 16, 16, 0]
  • 步驟 2: 合併 (Merge)
    • 第一行: [2, 2, 4, 0] -> [4, 0, 4, 0]
    • 第二行: [2, 2, 2, 2] -> [4, 0, 4, 0]
    • 第三行: [4, 4, 8, 8] -> [8, 0, 16, 0]
    • 第四行: [16, 16, 16, 0] -> [32, 0, 16, 0]
  • 步驟 3: 再次壓縮 (Re-squeeze)
    • 第一行: [4, 0, 4, 0] -> [4, 4, 0, 0]
    • 第二行: [4, 0, 4, 0] -> [4, 4, 0, 0]
    • 第三行: [8, 0, 16, 0] -> [8, 16, 0, 0]
    • 第四行: [32, 0, 16, 0] -> [32, 16, 0, 0]

最終狀態 (After):

[[4, 4, 0, 0],
 [4, 4, 0, 0],
 [8, 16, 0, 0],
 [32, 16, 0, 0]]

🔧 本日收穫

今天我們深入了解了 2048 的遊戲目標與核心規則,包括:

  • 遊戲盤面為 4x4 格子。
  • 玩家透過上下左右四個方向滑動所有方塊。
  • 相同數字方塊會合併成數字翻倍的新方塊(一次滑動中,每個方塊只能合併一次)。
  • 每次滑動後,隨機新增一個數字(2 或 4)。
  • 遊戲在無法合併且盤面已滿時結束。
  • 目標是產生數字 2048。
    並用圖文示意了「滑動、壓縮、合併」的完整過程,幫助我們建立清晰的遊戲邏輯基礎。

🔮 明日預告

接下來的 Day 3,我們將實際用 Golang 實作遊戲的核心資料結構
設計一個 4x4 的二維整數陣列來表示遊戲盤面
並在命令列介面中印出盤面內容,方便後續邏輯開發與測試。

重點將包含:

  • 建立盤面初始化函式,填滿空格(以 0 表示)。
  • 能在 CLI 用清晰格式印出盤面(空格用 . 顯示)。
  • 驗證資料結構符合遊戲需求,為後續滑動、合併邏輯打好基礎。

上一篇
打造 AI 小精靈共學遊戲實作之路:2048、採地雷、數獨,一起學 Golang + Ebiten + Gemini CLI!
下一篇
2048遊戲: 資料結構設計
系列文
在 ai 時代 gopher 遊戲開發者的 30 天自我養成22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言