
2048遊戲畫面
🎮 Python 2048 遊戲教學:
從零開始打造好玩的方塊消除遊戲!
哈囉,同學們!今天我們要一起用 Python 寫一個超經典的遊戲--2048!
而且這個遊戲可以直接在Command line(也就是命令提示字元、Console)玩!
在這篇教學哩,會一步步帶你理解遊戲邏輯,還有怎麼撰寫對應的程式程式碼,讓你自己也能做出來,甚至還能自己改寫成其他類似的方塊消除遊戲喔!
🎯 遊戲目標與規則
目標:把數字方塊合併到 2048 ,一但有一個數字抵達2048,即宣告遊戲獲勝!規則:
2048 是一款很有趣的益智遊戲,玩法很簡單:
- 你有一個4x4的方格 🟦
- 每次可以用W、A、S、D,把所有方塊往一個方向推 ⬅️⬆️➡️⬇️
- 相同數字的方塊碰到一起會合併,數字會變成兩倍 ✖️2
- 每移動一次,空白地方會隨機出現一個2或4 🎲
- 目標是合成一個數字是2048的方塊 🏆
- 如果沒有空格也不能合併了,遊戲就結束囉!💀
📝我們要怎麼做呢? (功能分析與拆解)
們會分成幾個小步驟:
- 建立4x4的遊戲板和分數 🧮
- 顯示每一回合的遊戲畫面 👀
- 處理方塊移動和合併 🔄
- 隨機新增數字方塊 🎲
- 判斷2048遊戲是否結束Gameover,或者宣告勝利 Victory🏁
- 讀取玩家的鍵盤輸入 ⌨️
- 主遊戲迴圈 🔁
1. 建立遊戲板和分數 🟦
我們先用一個4x4的二維陣列(在Python,就是2D list)來代表4x4的遊戲板,裡面放數字,用0代表空格,其餘的數字就對應到合成出來的方塊。
遊戲開始時,我們會隨機放兩個初始數字,初始數字有可能是2或4。
def initialize_game():
grid = [[0]*4 for _ in range(4)] # 建立4x4的遊戲板
add_new_tile(grid) # 隨機放置一個初始數字
add_new_tile(grid) # 再次隨機放置一個初始數字
score = 0 # 開局分數從0開始計分
return grid, score
2. 顯示遊戲畫面 👀
每回合移動後,我們要把當下的遊戲板和分數顯示在螢幕上,
讓玩家知道現在的遊戲狀況。
import os
def display_grid(grid, score):
os.system('clear' if os.name == 'posix' else 'cls') # 清除畫面(Linux/Mac用clear,Windows用cls)
print(f"2048 遊戲!目前分數:{score} 🏅")
print("-"*25)
for row in grid: # 印出4x4遊戲版的每一條row
print("|", end="")
for num in row:
if num == 0: # 0 代表空位
print(" ", end="|")
else: # 非0 代表隊應的方塊2, 4, 8, 16, ...
print(f"{num:5d}", end="|")
print("\n" + "-"*25) # 每一列的分隔線 -----
print("請用 W/A/S/D 移動,Q 離開遊戲 ⌨️")
3. 方塊移動和合併的邏輯 2️⃣2️⃣ ➡️ 4️⃣
這是遊戲的重點!我們需要:
- 把所有非零數字推向同一邊(壓縮)🧲
- 相鄰相同的數字合併,並加上對應的分數(例如 2, 2 合併得4,加4分)
- 再次壓縮,讓空格都在一邊
我們先寫個小函式幫忙「壓縮」:
def compress(line):
new_line = [num for num in line if num != 0] # 把非零數字挑出來
new_line += [0] * (4 - len(new_line)) # 補零,對其到4個格子的寬度
return new_line # 回傳朝牆壁壓縮後的結果
接著,寫相鄰方塊數字相同的合併function:
def merge(line):
score_add = 0
for i in range(3):
# 相鄰兩個方塊數字相同的時候,進行合併
if line[i] != 0 and line[i] == line[i+1]:
line[i] *= 2
line[i+1] = 0
score_add += line[i] # 加分!🎉
return line, score_add
最後,根據W/A/S/D的方向移動整個數字方塊:
def move(grid, direction, score):
moved = False # 判斷是否至少有一塊方塊被移動過
score_add = 0 # 這次移動,帶來的分數
original = [row[:] for row in grid] # 複製一份原本的遊戲版
if direction == 'left':
for i in range(4):
line = compress(grid[i])
line, add = merge(line)
line = compress(line)
grid[i] = line
score_add += add
elif direction == 'right':
for i in range(4):
line = grid[i][::-1]
line = compress(line)
line, add = merge(line)
line = compress(line)
grid[i] = line[::-1]
score_add += add
elif direction == 'up':
for j in range(4):
line = [grid[i][j] for i in range(4)]
line = compress(line)
line, add = merge(line)
line = compress(line)
for i in range(4):
grid[i][j] = line[i]
score_add += add
elif direction == 'down':
for j in range(4):
line = [grid[i][j] for i in range(4)][::-1]
line = compress(line)
line, add = merge(line)
line = compress(line)
line = line[::-1]
for i in range(4):
grid[i][j] = line[i]
score_add += add
# 判斷有沒有動過
moved = any(grid[i][j] != original[i][j] for i in range(4) for j in range(4))
return moved, score + score_add
4. 新增隨機方塊 2️⃣ 🎲 4️⃣
每次移動成功後,我們會在空格隨機放一個新的數字方塊,
有可能是2(90%機率,或者是4(10%機率)。
def add_new_tile(grid):
empty = [(i,j) for i in range(4) for j in range(4) if grid[i][j] == 0]
if empty:
i,j = random.choice(empty)
# 在空格隨機放一個新的數字方塊,
# 有可能是2(90%機率,或者是4(10%機率)。
grid[i][j] = 2 if random.random() < 0.9 else 4
5. 判斷是否遊戲結束(Gameover),或者宣告勝利(Victory) 🏁
- 如果有成功合成出一個方塊是2048,那就贏了 🏆
- 如果沒有剩餘空格,而且也不能合併方塊,就輸了 😭
def has_won(grid):
# 如果有成功合成出一個方塊是2048,那就贏了 🏆
return any(2048 in row for row in grid)
def has_moves_left(grid):
for i in range(4):
for j in range(4):
if grid[i][j] == 0:
return True
if j < 3 and grid[i][j] == grid[i][j+1]:
return True
if i < 3 and grid[i][j] == grid[i+1][j]:
return True
# 如果沒有剩餘空格,而且也不能合併方塊,就輸了 😭
return False
6. 讀取玩家輸入 ⌨️
因為我們在Command line玩,我們用不同OS的鍵盤讀取function方式來取得玩家的選擇方向(W/A/S/D):
def get_key():
"""獲取鍵盤輸入"""
try:
import msvcrt # Windows
return msvcrt.getch().decode('utf-8',errors='ignore').lower()
except ImportError:
try:
import termios, tty, sys # Unix/Linux/MacOS
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1).lower()
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
except:
# 如果以上方法都失敗,使用input函數
key = input("輸入移動方向 (W/A/S/D): ").lower()
return key[0] if key else 'w'
7. 主遊戲迴圈 🔁
現在,把所有已經實現的模組組合在一起,就大功告成囉!
def main():
grid, score = initialize_game()
while True:
display_grid(grid, score)
if has_won(grid):
print("太棒了!你贏了,成功合成出2048!🏆")
break
if not has_moves_left(grid):
print("遊戲結束,沒有更多移動了!😢")
break
key = get_key()
if key == 'q':
print("遊戲結束,掰掰!👋")
break
directions = {'w':'up', 'a':'left', 's':'down', 'd':'right'}
if key in directions:
moved, score = move(grid, directions[key], score)
if moved:
add_new_tile(grid)
最後,記得補上entry point程式進入點的呼叫:
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n遊戲中斷囉!🛑")
完整程式碼
import random
import os
def initialize_game():
"""初始化遊戲網格和分數"""
grid = [[0 for _ in range(4)] for _ in range(4)]
score = 0
# 隨機添加兩個初始方塊
add_new_tile(grid)
add_new_tile(grid)
return grid, score
def display_grid(grid, score):
"""顯示遊戲網格和分數"""
os.system('clear' if os.name == 'posix' else 'cls') # 清除螢幕
print(f"2048遊戲 - 分數: {score}")
print("-" * 25)
for row in grid:
print("|", end="")
for cell in row:
if cell == 0:
print(" ", end="|")
else:
print(f"{cell:5d}", end="|")
print("\n" + "-" * 25)
print("使用 W/A/S/D 控制移動,Q 退出")
def add_new_tile(grid):
"""在空白處隨機添加一個新方塊(值為2或4)"""
empty_cells = []
for i in range(4):
for j in range(4):
if grid[i][j] == 0:
empty_cells.append((i, j))
if empty_cells:
i, j = random.choice(empty_cells)
grid[i][j] = 2 if random.random() < 0.9 else 4
return True
return False
def compress(line):
"""移除所有零並將非零元素移到開頭"""
new_line = [cell for cell in line if cell != 0]
new_line = new_line + [0] * (4 - len(new_line))
return new_line
def merge(line):
"""合併相鄰的相同數字"""
score_addition = 0
for i in range(3):
if line[i] != 0 and line[i] == line[i + 1]:
line[i] *= 2
score_addition += line[i]
line[i + 1] = 0
return line, score_addition
def move(grid, direction, score):
"""根據方向移動所有方塊"""
# 保存原始網格以檢查是否有移動
original_grid = [row[:] for row in grid]
total_score_addition = 0
# 根據方向處理每行/列
if direction == 'left':
for i in range(4):
# 壓縮,移除所有零
grid[i] = compress(grid[i])
# 合併
grid[i], score_add = merge(grid[i])
# 再次壓縮,移除合併後產生的零
grid[i] = compress(grid[i])
total_score_addition += score_add
elif direction == 'right':
for i in range(4):
# 反轉
grid[i].reverse()
# 壓縮
grid[i] = compress(grid[i])
# 合併
grid[i], score_add = merge(grid[i])
# 再次壓縮
grid[i] = compress(grid[i])
# 再次反轉
grid[i].reverse()
total_score_addition += score_add
elif direction == 'up':
for j in range(4):
# 提取列
column = [grid[i][j] for i in range(4)]
# 壓縮
column = compress(column)
# 合併
column, score_add = merge(column)
# 再次壓縮
column = compress(column)
# 更新網格
for i in range(4):
grid[i][j] = column[i]
total_score_addition += score_add
elif direction == 'down':
for j in range(4):
# 提取列
column = [grid[i][j] for i in range(4)]
# 反轉
column.reverse()
# 壓縮
column = compress(column)
# 合併
column, score_add = merge(column)
# 再次壓縮
column = compress(column)
# 反轉
column.reverse()
# 更新網格
for i in range(4):
grid[i][j] = column[i]
total_score_addition += score_add
# 檢查是否有移動
moved = any(original_grid[i][j] != grid[i][j] for i in range(4) for j in range(4))
return moved, score + total_score_addition
def has_won(grid):
"""檢查是否已達到2048"""
return any(any(cell >= 2048 for cell in row) for row in grid)
def has_moves_left(grid):
"""檢查是否還有可能的移動"""
# 檢查是否有空白格子
if any(0 in row for row in grid):
return True
# 檢查是否有相鄰的相同數字
for i in range(4):
for j in range(4):
val = grid[i][j]
# 檢查右邊
if j < 3 and grid[i][j+1] == val:
return True
# 檢查下邊
if i < 3 and grid[i+1][j] == val:
return True
return False
def get_key():
"""獲取按鍵輸入"""
try:
import msvcrt # Windows
return msvcrt.getch().decode('utf-8',errors='ignore').lower()
except ImportError:
try:
import termios, tty, sys # Unix/Linux/MacOS
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1).lower()
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
except:
# 如果以上方法都失敗,使用input函數
key = input("輸入移動方向 (W/A/S/D): ").lower()
return key[0] if key else 'w'
def main():
"""遊戲主函數"""
grid, score = initialize_game()
while True:
display_grid(grid, score)
# 檢查遊戲是否結束
if has_won(grid):
print("恭喜,你贏了!")
break
if not has_moves_left(grid):
print("遊戲結束!沒有更多可能的移動。")
break
# 獲取用戶輸入
key = get_key()
if key == 'q':
print("遊戲結束!")
break
direction = {
'w': 'up',
'a': 'left',
's': 'down',
'd': 'right'
}.get(key)
if direction:
moved, score = move(grid, direction, score)
if moved:
add_new_tile(grid)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n遊戲被中斷")
遊戲畫面

點這行 線上執行 與 試玩(在新的頁面按Run開始玩)
小結 與 延伸思考 🎉
到這邊,我們就完成了一個可以在Command line玩的2048遊戲啦!
你可以試著自己動手寫看看,玩玩看,甚至改改程式碼,像是改變遊戲大小、加上顏色、或是改變數字規則,讓遊戲更有趣!✨
如果你有任何問題,歡迎在下方留言提問!寫出一個小遊戲其實很有成就感的!💪
祝福各位收穫滿滿,玩得開心!😊🎉