用 Python 实现经典的 2048 游戏:一步步带你打造属于你的小游戏!(结尾附完整代码)
简介
2048 是一个简单而又令人上瘾的数字拼图游戏。玩家通过滑动方块使相同数字的方块合并,目标是创造出数字 2048!在这篇博客中,我们将用 Python 的 Tkinter 库 从零开始实现这款游戏,涵盖从界面设计到逻辑实现的每一个细节,帮助你全面了解背后的开发思路。
游戏特点
经典玩法:滑动合并相同数字,尽可能达到 2048。随机生成新数字:每次滑动后随机生成 2 或 4。直观的图形界面:使用 Tkinter 绘制游戏网格与方块,带来清晰的视觉效果。开发环境准备
在开始之前,确保你的环境已安装 Python,并包含以下必要库:
pip install tk
Tkinter 是 Python 内置的 GUI 工具包,因此大多数环境无需额外安装。
项目实现步骤
我们将按照以下步骤构建游戏:
设计网格界面实现核心逻辑绑定键盘事件添加游戏结束检测优化用户体验1. 设计网格界面
首先,我们定义游戏的基本参数,包括网格大小、方块的尺寸、间距和颜色等。随后,通过 Tkinter 创建一个网格界面,每个格子将用矩形和文本元素表示。
import tkinter as tkfrom tkinter import messageboximport random# 常量定义GRID_SIZE = 4 # 网格为 4x4TILE_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 的灰色网格,每个格子代表一个方块。
2. 实现核心逻辑
游戏的逻辑主要包括:
压缩和合并数字:将相邻的数字合并。滑动方向操作:实现上下左右滑动功能。随机生成新数字:在空格中随机生成 2 或 4。压缩与合并
每次滑动操作可以拆分为两部分:压缩(去掉空格) 和 合并(合并相邻的相同数字)。
def compress(row): """将行向左压缩,去掉 0""" new_row = [num for num in row if num != 0] new_row += [0] * (GRID_SIZE - len(new_row)) return new_rowdef 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
3. 绑定键盘事件
为了让游戏动起来,我们需要捕获用户的按键(上下左右方向键),并根据按键更新网格状态。
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!")
4. 添加游戏结束检测
当所有格子都被填满且没有可合并的数字时,游戏结束。
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
5. 完整代码
将上述代码整合的完整代码如下:运行后即可得到完整的 2048 游戏!
import tkinter as tkfrom tkinter import messageboximport random# 常量定义GRID_SIZE = 4TILE_SIZE = 100PADDING = 10BACKGROUND_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_rowdef 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 rowdef 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_griddef 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 Truedef 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>", key_press)root.mainloop()
通过本篇博客,我们从头实现了经典的 2048 游戏。不仅让你掌握了 Tkinter 界面开发,还深入理解了游戏逻辑的实现过程。如果你喜欢这篇博客,请分享给更多的 Python 爱好者吧!