简介
2048 是一个简单而又令人上瘾的数字拼图游戏。玩家通过滑动方块使相同数字的方块合并,目标是创造出数字 2048!在这篇博客中,我们将用 Python 的 Tkinter 库 从零开始实现这款游戏,涵盖从界面设计到逻辑实现的每一个细节,帮助你全面了解背后的开发思路。
在开始之前,确保你的环境已安装 Python,并包含以下必要库:
pip install tk
Tkinter 是 Python 内置的 GUI 工具包,因此大多数环境无需额外安装。
我们将按照以下步骤构建游戏:
首先,我们定义游戏的基本参数,包括网格大小、方块的尺寸、间距和颜色等。随后,通过 Tkinter 创建一个网格界面,每个格子将用矩形和文本元素表示。
import tkinter as tk
from tkinter import messagebox
import random
# 常量定义
GRID_SIZE = 4 # 网格为 4x4
TILE_SIZE = 100 # 每个方块的像素大小
PADDING = 10 # 方块之间的间距
BACKGROUND_COLOR = "#92877d"
TILE_COLORS = {
0: "#9e948a", 2: "#eee4da", 4: "#ede0c8",
8: "#f2b179", 16: "#f59563", 32: "#f67c5f",
64: "#f65e3b", 128: "#edcf72", 256: "#edcc61",
512: "#edc850", 1024: "#edc53f", 2048: "#edc22e",
}
# 初始化游戏界面
class Game2048:
def __init__(self, master):
self.master = master
self.master.title("2048")
self.master.geometry(f"{GRID_SIZE * TILE_SIZE + PADDING * (GRID_SIZE + 1)}x{GRID_SIZE * TILE_SIZE + PADDING * (GRID_SIZE + 1)}")
self.master.resizable(False, False)
self.init_grid()
self.reset_game()
def init_grid(self):
"""创建 4x4 网格"""
self.canvas = tk.Canvas(self.master, bg=BACKGROUND_COLOR, bd=0, highlightthickness=0)
self.canvas.pack(fill=tk.BOTH, expand=True)
self.tiles = []
for i in range(GRID_SIZE):
row = []
for j in range(GRID_SIZE):
x1 = PADDING + j * (TILE_SIZE + PADDING)
y1 = PADDING + i * (TILE_SIZE + PADDING)
x2 = x1 + TILE_SIZE
y2 = y1 + TILE_SIZE
rect = self.canvas.create_rectangle(x1, y1, x2, y2, fill=TILE_COLORS[0], outline="")
text = self.canvas.create_text(x1 + TILE_SIZE // 2, y1 + TILE_SIZE // 2, text="", font=("Helvetica", 30), fill="#776e65")
row.append((rect, text))
self.tiles.append(row)
运行效果:运行后你会看到一个 4x4 的灰色网格,每个格子代表一个方块。
游戏的逻辑主要包括:
每次滑动操作可以拆分为两部分:压缩(去掉空格) 和 合并(合并相邻的相同数字)。
def compress(row):
"""将行向左压缩,去掉 0"""
new_row = [num for num in row if num != 0]
new_row += [0] * (GRID_SIZE - len(new_row))
return new_row
def merge(row):
"""合并相邻相同的数字"""
for i in range(len(row) - 1):
if row[i] == row[i + 1] and row[i] != 0:
row[i] *= 2
row[i + 1] = 0
return row
根据方向执行不同操作(上下左右)。
def move_left(grid):
"""左移操作"""
new_grid = []
for row in grid:
compressed_row = compress(row)
merged_row = merge(compressed_row)
new_grid.append(compress(merged_row))
return new_grid
每次滑动后随机选择一个空格,填入 2 或 4。
def spawn_new_tile(grid):
"""随机生成一个新的数字方块(2 或 4)"""
empty_cells = [(i, j) for i in range(GRID_SIZE) for j in range(GRID_SIZE) if grid[i][j] == 0]
if empty_cells:
i, j = random.choice(empty_cells)
grid[i][j] = 2 if random.random() < 0.9 else 4
为了让游戏动起来,我们需要捕获用户的按键(上下左右方向键),并根据按键更新网格状态。
def key_press(event):
"""处理键盘输入"""
key = event.keysym
if key in ["Up", "Down", "Left", "Right"]:
if key == "Up":
game.grid = move_left(rotate_left(game.grid))
game.grid = rotate_right(game.grid)
elif key == "Down":
game.grid = move_left(rotate_right(game.grid))
game.grid = rotate_left(game.grid)
elif key == "Left":
game.grid = move_left(game.grid)
elif key == "Right":
game.grid = move_left(reverse_rows(game.grid))
game.grid = reverse_rows(game.grid)
spawn_new_tile(game.grid)
game.update_ui()
if is_game_over(game.grid):
messagebox.showinfo("Game Over", "No more moves left!")
当所有格子都被填满且没有可合并的数字时,游戏结束。
def is_game_over(grid):
"""判断是否还有有效操作"""
for i in range(GRID_SIZE):
for j in range(GRID_SIZE):
if grid[i][j] == 0: # 空格存在
return False
if j + 1 < GRID_SIZE and grid[i][j] == grid[i][j + 1]: # 横向可合并
return False
if i + 1 < GRID_SIZE and grid[i][j] == grid[i + 1][j]: # 纵向可合并
return False
return True
将上述代码整合的完整代码如下:运行后即可得到完整的 2048 游戏!
import tkinter as tk
from tkinter import messagebox
import random
# 常量定义
GRID_SIZE = 4
TILE_SIZE = 100
PADDING = 10
BACKGROUND_COLOR = "#92877d"
TILE_COLORS = {
0: "#9e948a", 2: "#eee4da", 4: "#ede0c8",
8: "#f2b179", 16: "#f59563", 32: "#f67c5f",
64: "#f65e3b", 128: "#edcf72", 256: "#edcc61",
512: "#edc850", 1024: "#edc53f", 2048: "#edc22e",
}
class Game2048:
def __init__(self, master):
self.master = master
self.master.title("2048")
self.master.geometry(f"{GRID_SIZE * TILE_SIZE + PADDING * (GRID_SIZE + 1)}x{GRID_SIZE * TILE_SIZE + PADDING * (GRID_SIZE + 1)}")
self.master.resizable(False, False)
self.init_grid()
self.reset_game()
def init_grid(self):
"""初始化网格界面"""
self.canvas = tk.Canvas(self.master, bg=BACKGROUND_COLOR, bd=0, highlightthickness=0)
self.canvas.pack(fill=tk.BOTH, expand=True)
self.tiles = []
for i in range(GRID_SIZE):
row = []
for j in range(GRID_SIZE):
x1 = PADDING + j * (TILE_SIZE + PADDING)
y1 = PADDING + i * (TILE_SIZE + PADDING)
x2 = x1 + TILE_SIZE
y2 = y1 + TILE_SIZE
rect = self.canvas.create_rectangle(x1, y1, x2, y2, fill=TILE_COLORS[0], outline="")
text = self.canvas.create_text(x1 + TILE_SIZE // 2, y1 + TILE_SIZE // 2, text="", font=("Helvetica", 30), fill="#776e65")
row.append((rect, text))
self.tiles.append(row)
def reset_game(self):
"""重置游戏状态"""
self.grid = [[0] * GRID_SIZE for _ in range(GRID_SIZE)]
self.spawn_new_tile()
self.spawn_new_tile()
self.update_ui()
def spawn_new_tile(self):
"""随机生成一个新的数字方块(2 或 4)"""
empty_cells = [(i, j) for i in range(GRID_SIZE) for j in range(GRID_SIZE) if self.grid[i][j] == 0]
if empty_cells:
i, j = random.choice(empty_cells)
self.grid[i][j] = 2 if random.random() < 0.9 else 4
def update_ui(self):
"""更新界面显示"""
for i in range(GRID_SIZE):
for j in range(GRID_SIZE):
value = self.grid[i][j]
rect, text = self.tiles[i][j]
self.canvas.itemconfig(rect, fill=TILE_COLORS.get(value, TILE_COLORS[2048]))
self.canvas.itemconfig(text, text=str(value) if value != 0 else "")
def compress(row):
"""将行向左压缩,去掉 0"""
new_row = [num for num in row if num != 0]
new_row += [0] * (GRID_SIZE - len(new_row))
return new_row
def merge(row):
"""合并相邻相同的数字"""
for i in range(len(row) - 1):
if row[i] == row[i + 1] and row[i] != 0:
row[i] *= 2
row[i + 1] = 0
return row
def move_left(grid):
"""左移操作"""
new_grid = []
for row in grid:
compressed_row = compress(row)
merged_row = merge(compressed_row)
new_grid.append(compress(merged_row))
return new_grid
def rotate_right(grid):
"""矩阵顺时针旋转 90°"""
return [list(row) for row in zip(*grid[::-1])]
def rotate_left(grid):
"""矩阵逆时针旋转 90°"""
return [list(row) for row in zip(*grid)][::-1]
def reverse_rows(grid):
"""矩阵水平翻转"""
return [row[::-1] for row in grid]
def is_game_over(grid):
"""判断是否还有有效操作"""
for i in range(GRID_SIZE):
for j in range(GRID_SIZE):
if grid[i][j] == 0: # 空格存在
return False
if j + 1 < GRID_SIZE and grid[i][j] == grid[i][j + 1]: # 横向可合并
return False
if i + 1 < GRID_SIZE and grid[i][j] == grid[i + 1][j]: # 纵向可合并
return False
return True
def key_press(event):
"""处理键盘输入"""
key = event.keysym
if key in ["Up", "Down", "Left", "Right"]:
if key == "Up":
game.grid = move_left(rotate_left(game.grid))
game.grid = rotate_right(game.grid)
elif key == "Down":
game.grid = move_left(rotate_right(game.grid))
game.grid = rotate_left(game.grid)
elif key == "Left":
game.grid = move_left(game.grid)
elif key == "Right":
game.grid = move_left(reverse_rows(game.grid))
game.grid = reverse_rows(game.grid)
game.spawn_new_tile()
game.update_ui()
if is_game_over(game.grid):
messagebox.showinfo("Game Over", "No more moves left!")
root = tk.Tk()
game = Game2048(root)
root.bind("", key_press)
root.mainloop()
通过本篇博客,我们从头实现了经典的 2048 游戏。不仅让你掌握了 Tkinter 界面开发,还深入理解了游戏逻辑的实现过程。如果你喜欢这篇博客,请分享给更多的 Python 爱好者吧!