ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python贪吃蛇游戏从入门到实战:命令行与pygame双版本教程

Python贪吃蛇游戏从入门到实战:命令行与pygame双版本教程 开始很好又来了一次完整的贪吃蛇教程。这次我直接给你输出一篇结构完整、内容详实的 CSDN 风格技术博文。1. 背景介绍为什么用 Python 写贪吃蛇在很多人的印象里游戏开发需要复杂的引擎、高深的图形学知识是一个门槛很高的事情。但实际上Python 作为一种易读、易写的脚本语言非常适合用来做入门级的游戏编程练习。贪吃蛇正是其中非常有代表性的一个项目。1.1 贪吃蛇游戏到底是什么贪吃蛇的玩法大家都熟悉一条蛇在矩形的游戏区域内移动吃掉随机出现的食物后蛇身会变长得分会增加。如果蛇头撞到墙壁或者撞到自己的身体游戏结束。整个游戏的核心逻辑可以拆解为三个要素蛇的移动和控制通过键盘方向键控制蛇头的移动方向。食物的随机生成在地图空白处随机生成食物。碰撞检测判断蛇头是否撞墙、是否撞身体、是否吃到食物。这三个要素放在一个循环里不断重复就是一个最简单也最完整的游戏循环。1.2 为什么适合新手学习贪吃蛇项目几乎涵盖了 Python 编程的多个核心知识点变量与数据类型坐标、分数、状态。条件判断if-else 控制游戏流程。循环while 循环构成主游戏循环。列表List用列表存储蛇身所有坐标。函数将移动、碰撞检测等功能封装成函数。类Class在进阶版本中用类组织代码。更重要的是做完这个项目你会对“程序是怎么跑起来的”有一个直观的认知而不是停留在打印Hello World的阶段。把代码跑起来、看着自己控制的蛇在屏幕上移动这种正反馈是驱动你继续学下去的重要动力。1.3 两种实现方案怎么选网上主流有两种实现方案我在这里先做一个区分命令行不依赖第三方库版本使用 Python 内置的随机数、列表操作配合按键输入在控制台里显示字符组成的“地图”。优点是完全不需要安装任何扩展库复制代码就能运行逻辑注意力全部集中在游戏规则本身。pygame 图形界面版本使用 pygame 库提供窗口、图形渲染、键盘事件监听等能力。优点是画面接近真正的游戏视觉反馈更好交互更流畅。本文会先讲解第一种方案因为它足够轻量能让你彻底搞懂游戏逻辑然后在文末给出 pygame 版本的核心代码思路。这样从易到难循序渐进效果最好。2. 环境准备搭建 Python 开发环境开工之前先确认一下手里的“螺丝刀”好不好用。这一节帮助你快速检查环境是否就绪。2.1 Python 版本要求如果你使用的是 Python 3.6 以上的版本本文提供的代码都可以直接运行。Python 3 的语法相对 2.x 版本更规范也在持续更新中建议优先选择 3.8 及以上版本。如果你当前还没有安装 Python可以去 Python 官方网站下载对应系统的安装包。安装时注意勾选“Add Python to PATH”选项这样在命令行中直接输入python才能识别。2.2 IDE 选择与建议初学者我比较推荐使用 PyCharm 或者 VS Code两个都是非常优秀的开发工具PyCharm Community Edition社区版是免费的对 Python 工程管理非常友好代码提示、调试功能完善。VS Code 是轻量级编辑器配合 Python 插件也很好用启动速度快。当然如果你只是想快速验证一段代码直接在文本编辑器里写完然后用命令行执行python 文件名.py也是完全可以的。2.3 检查环境是否可用打开终端Windows 下是 CMD 或 PowerShellmacOS 是 Terminal输入python --version如果能看到类似Python 3.10.0的输出说明环境已经就绪了。2.4 pygame 库安装可选如果你决定后续体验 pygame 图形版本那么需要额外安装 pygame。在命令行执行pip install pygame安装完成后可以验证python -m pygame.examples.aliens如果能弹出一个小游戏窗口说明 pygame 安装成功了。如果没有窗口检查 pip 源是否通畅或者换国内镜像源pip install pygame -i https://pypi.tuna.tsinghua.edu.cn/simple这里提醒一下命令行版本不依赖 pygame可以先跳过这一步。3. 核心逻辑拆解贪吃蛇是怎么“动”起来的写代码之前先搞明白背后的原理。很多新手直接复制代码跑起来后稍微改一点就报错根本原因就是没有理解核心逻辑。这一节我们用通俗的方式把机制拆解清楚。3.1 游戏地图的数据结构贪吃蛇的游戏区域本质上是一个二维坐标系。我们以左上角为原点向下为 y 轴正方向向右为 x 轴正方向。蛇的每一节身体都是这个坐标系上的一个点。在 Python 中最常见的表示方式是使用元组(x, y)或者字典。比如# 蛇身由多个坐标点组成 snake [(5, 5), (4, 5), (3, 5)]这表示蛇头在(5, 5)身体往左延伸到(3, 5)。3.2 蛇是如何移动的贪吃蛇移动的核心规律只有一句话蛇头向新方向移动一格蛇尾减掉一格身体其他部分跟着头走。用数据结构的语言描述就是在蛇头位置加上新的方向向量得到新的蛇头坐标。把新蛇头插入到列表头部。删除列表尾部的最后一个元素。举例说明。假设蛇是[(5,5), (4,5), (3,5)]方向向右那么新蛇头是(6,5)。完成一次移动后蛇变成[(6,5), (5,5), (4,5)]。如果吃到食物呢不再删除尾部元素蛇身长度自然就增加了。这一段逻辑是整个游戏的核心中的核心。前面铺了这么多就是为了让你理解这两行看似简单但其实很精巧的操作。3.3 食物的随机生成食物不能出现在蛇身上也不能出现在墙外。所以生成食物时的逻辑是在一个随机位置生成食物。判断这个位置是否已经被蛇占据。如果被占据就重新生成。虽然理论上存在极端情况导致无限循环但在地图足够大的情况下重复概率极低。更严谨的写法是预设一个空白位置列表从里面随机选一个。3.4 碰撞检测游戏是否结束取决于两个条件蛇头坐标是否等于墙壁坐标。蛇头坐标是否等于蛇身体任意一节身体的坐标。一旦满足游戏循环终止显示得分。3.5 一个重要的坑反向移动如果蛇正在向右移动此时玩家按下左方向键蛇头会直接扎进身体里导致游戏结束。这种情况在真实游戏中很常见所以必须在按键检测时加一个判断新的移动方向不能与当前方向刚好相反。后面代码部分会具体展示怎么写这个细节非常影响游戏体验。4. 完整实战案例命令行版贪吃蛇现在正式进入写代码环节。为了让结构清晰我们把代码拆分为几个模块虽然对于一个小游戏来说可能显得有些“重”但对初学者养成良好的代码组织习惯非常有帮助。4.1 创建项目结构在磁盘上新建一个文件夹snake_game然后在里面创建文件game.py。项目结构非常简单snake_game/ └── game.py4.2 定义游戏常量与初始化我们把这些常量集中放在文件开头方便统一调整。# 文件路径snake_game/game.py import random import os # 游戏区域大小 WIDTH 20 HEIGHT 20 # 初始蛇的位置蛇头在列表第一个 INIT_SNAKE [(WIDTH // 2, HEIGHT // 2), (WIDTH // 2 - 1, HEIGHT // 2), (WIDTH // 2 - 2, HEIGHT // 2)] # 初始方向向右 INIT_DIRECTION (1, 0) # 食物标记 FOOD_SYMBOL * WALL_SYMBOL # SNAKE_SYMBOL O EMPTY_SYMBOL 这里把地图宽高都设为 20蛇初始长度 3 节初始方向向右移动。二维元组(1, 0)表示 x 方向加 1y 方向不变也就是向右。4.3 绘制游戏界面命令行版本的界面就是在控制台打印字符。我们需要一个函数把蛇的位置、食物位置、墙壁组合成一张二维地图然后打印出来。def draw(snake, food): 根据蛇身坐标、食物坐标在控制台绘制地图 # 创建一个二维列表用空格填充 board [[EMPTY_SYMBOL for _ in range(WIDTH)] for _ in range(HEIGHT)] # 绘制墙壁 for x in range(WIDTH): board[0][x] WALL_SYMBOL board[HEIGHT - 1][x] WALL_SYMBOL for y in range(HEIGHT): board[y][0] WALL_SYMBOL board[y][WIDTH - 1] WALL_SYMBOL # 放置食物 fx, fy food board[fy][fx] FOOD_SYMBOL # 放置蛇身 for segment in snake: sx, sy segment board[sy][sx] SNAKE_SYMBOL # 打印地图 os.system(cls if os.name nt else clear) for row in board: print(.join(row)) print(f当前得分: {len(snake) - 3})这里有一点需要说明二维列表中行对应 y 坐标列对应 x 坐标。因此board[y][x]这种写法容易踩坑建议写代码前先想清楚。打印前执行os.system(cls if os.name nt else clear)可以清屏让游戏画面刷新时不至于糊成一片。4.4 蛇的移动与增长移动逻辑是重点。move函数接收当前蛇的坐标列表和新的方向向量返回移动后的新蛇身列表。def move_snake(snake, direction, food): 移动蛇。如果吃到食物则增长否则删除尾部。 返回新的蛇列表和是否吃到食物的布尔值。 head_x, head_y snake[0] dir_x, dir_y direction new_head (head_x dir_x, head_y dir_y) # 新蛇头插到最前面 new_snake [new_head] snake[:-1] # 如果新蛇头位置就是食物位置说明吃到食物蛇身加长一格 if new_head food: new_snake [new_head] snake return new_snake, True return new_snake, False细心的同学会发现上面代码在吃到食物时实际上做了[new_head] snake也就是把整条蛇都保留了新蛇头新增了一格尾部没有删除所以蛇变长了。这个写法比先删除尾部再补尾巴要直观。4.5 检测游戏结束碰撞检测逻辑简单直接def is_collision(snake, direction): 判断下一步是否会撞墙或撞到自己 head_x, head_y snake[0] dir_x, dir_y direction new_head_x head_x dir_x new_head_y head_y dir_y # 撞墙判断 if new_head_x 0 or new_head_x WIDTH - 1: return True if new_head_y 0 or new_head_y HEIGHT - 1: return True # 撞自己注意如果蛇身长度小于3不可能撞到自己 if (new_head_x, new_head_y) in snake: return True return False这里需要注意边界值墙壁在 0 行、HEIGHT-1 行、0 列、WIDTH-1 列。所以蛇头的坐标范围必须是1到WIDTH - 2之间。4.6 生成食物def generate_food(snake): 在空白位置随机生成食物 while True: x random.randint(1, WIDTH - 2) y random.randint(1, HEIGHT - 2) if (x, y) not in snake: return (x, y)4.7 键盘输入与控制命令行版本的输入处理是代码里稍微 tricky 的地方。我们希望游戏能在收到按键后立即响应而不是必须按一下回车才生效。最简单可靠的方式是使用msvcrt库Windows或sys.stdin读取Linux/macOS。为了保持跨平台下面使用一个简单的方案在 Windows 下用msvcrt.kbhit()在 Linux/macOS 下用select模块。实际上为了简洁这里用一个需求更简单的控制方案每移动一步之后让玩家输入一次方向键输入w/s/a/d分别表示上/下/左/右然后跟上一步方向做反向判断。考虑到输入经常会有回车残留下面用input()简化逻辑。这个方法对入门来说是最好的理解方案。def get_direction(current_dir): 读取玩家的方向键输入并做反向判断 mapping { w: (0, -1), s: (0, 1), a: (-1, 0), d: (1, 0), } while True: key input(输入方向(w上/s下/a左/d右): ).strip().lower() if key not in mapping: print(无效输入请重新输入) continue new_dir mapping[key] # 禁止反向 if (new_dir[0] -current_dir[0] and new_dir[1] -current_dir[1]): print(不能向相反方向移动) continue return new_dir注意反向判断的方式如果当前方向是(1, 0)那么(-1, 0)就是反向。这里判断的是new_dir[0] -current_dir[0]且new_dir[1] -current_dir[1]两者同时成立才说明是反向。4.8 组装主循环把所有模块串起来就是主循环了def main(): snake INIT_SNAKE[:] # 复制一份不影响原始常量 direction INIT_DIRECTION food generate_food(snake) while True: draw(snake, food) # 判断下一步是否会碰撞 if is_collision(snake, direction): print(游戏结束) print(f最终得分: {len(snake) - 3}) break # 获取玩家输入 direction get_direction(direction) # 移动蛇 new_snake, ate move_snake(snake, direction, food) snake new_snake if ate: food generate_food(snake) if __name__ __main__: main()这里有一个流程细节需要特别注意is_collision判断的是“下一次移动是否会碰撞”所以必须先判断再移动。看完整流程绘制当前画面。判断下一个方向是否会撞墙或撞自己。读取方向。移动并判断是否吃到食物。实际运行时玩家先看到画面然后输入方向蛇移动到新位置再次绘制画面。可以看到画面是一帧一帧刷新的每帧之间由玩家输入控制这一点和 pygame 版本不同。pygame 版本中游戏循环内置了帧率控制FPS蛇每秒钟固定移动若干次不依赖玩家输入速度。4.9 完整代码汇总为了方便复制下面把完整代码一次性贴出。# 文件路径snake_game/game.py import random import os # 游戏区域大小 WIDTH 20 HEIGHT 20 # 初始蛇的位置蛇头在列表第一个 INIT_SNAKE [(WIDTH // 2, HEIGHT // 2), (WIDTH // 2 - 1, HEIGHT // 2), (WIDTH // 2 - 2, HEIGHT // 2)] # 初始方向向右 INIT_DIRECTION (1, 0) # 食物标记 FOOD_SYMBOL * WALL_SYMBOL # SNAKE_SYMBOL O EMPTY_SYMBOL def draw(snake, food): 根据蛇身坐标、食物坐标在控制台绘制地图 board [[EMPTY_SYMBOL for _ in range(WIDTH)] for _ in range(HEIGHT)] # 绘制墙壁 for x in range(WIDTH): board[0][x] WALL_SYMBOL board[HEIGHT - 1][x] WALL_SYMBOL for y in range(HEIGHT): board[y][0] WALL_SYMBOL board[y][WIDTH - 1] WALL_SYMBOL # 放置食物 fx, fy food board[fy][fx] FOOD_SYMBOL # 放置蛇身 for segment in snake: sx, sy segment board[sy][sx] SNAKE_SYMBOL # 打印地图 os.system(cls if os.name nt else clear) for row in board: print(.join(row)) print(f当前得分: {len(snake) - 3}) def move_snake(snake, direction, food): 移动蛇。如果吃到食物则增长否则删除尾部。 返回新的蛇列表。 head_x, head_y snake[0] dir_x, dir_y direction new_head (head_x dir_x, head_y dir_y) # 吃到食物蛇身加长 if new_head food: return [new_head] snake # 未吃到食物尾部删除 return [new_head] snake[:-1] def is_collision(snake, direction): 判断下一步是否会撞墙或撞到自己 head_x, head_y snake[0] dir_x, dir_y direction new_head_x head_x dir_x new_head_y head_y dir_y # 撞墙判断 if new_head_x 0 or new_head_x WIDTH - 1: return True if new_head_y 0 or new_head_y HEIGHT - 1: return True # 撞自己 if (new_head_x, new_head_y) in snake: return True return False def generate_food(snake): 在空白位置随机生成食物 while True: x random.randint(1, WIDTH - 2) y random.randint(1, HEIGHT - 2) if (x, y) not in snake: return (x, y) def get_direction(current_dir): 读取玩家的方向键输入并做反向判断 mapping { w: (0, -1), s: (0, 1), a: (-1, 0), d: (1, 0), } while True: key input(输入方向(w上/s下/a左/d右): ).strip().lower() if key not in mapping: print(无效输入请重新输入) continue new_dir mapping[key] # 禁止反向 if new_dir[0] -current_dir[0] and new_dir[1] -current_dir[1]: print(不能向相反方向移动) continue return new_dir def main(): snake INIT_SNAKE[:] direction INIT_DIRECTION food generate_food(snake) while True: draw(snake, food) if is_collision(snake, direction): print(游戏结束) print(f最终得分: {len(snake) - 3}) break direction get_direction(direction) new_snake, ate move_snake(snake, direction, food) # 注意为了保持简洁上面我们把 move_snake 返回值写成了两个 # 但实际上第二个值没有用到可以忽略。 snake new_snake if ate: food generate_food(snake) if __name__ __main__: main()看到这里你会发现move_snake返回了两个值而实际上第二个值ate在代码里只是用来决定是否重新生成食物。但前面move_snake函数定义里返回的是一个值这里存在前后不一致。让我修正这个问题让代码完全可运行修正方案move_snake应该返回蛇的新列表和是否吃到食物的布尔值。参考前面的定义现在把返回值统一为两个。完整的可运行代码如下# 文件路径snake_game/game.py import random import os WIDTH 20 HEIGHT 20 INIT_SNAKE [(WIDTH // 2, HEIGHT // 2), (WIDTH // 2 - 1, HEIGHT // 2), (WIDTH // 2 - 2, HEIGHT // 2)] INIT_DIRECTION (1, 0) FOOD_SYMBOL * WALL_SYMBOL # SNAKE_SYMBOL O EMPTY_SYMBOL def draw(snake, food): board [[EMPTY_SYMBOL for _ in range(WIDTH)] for _ in range(HEIGHT)] for x in range(WIDTH): board[0][x] WALL_SYMBOL board[HEIGHT - 1][x] WALL_SYMBOL for y in range(HEIGHT): board[y][0] WALL_SYMBOL board[y][WIDTH - 1] WALL_SYMBOL fx, fy food board[fy][fx] FOOD_SYMBOL for segment in snake: sx, sy segment board[sy][sx] SNAKE_SYMBOL os.system(cls if os.name nt else clear) for row in board: print(.join(row)) print(f当前得分: {len(snake) - 3}) def move_snake(snake, direction, food): head_x, head_y snake[0] dir_x, dir_y direction new_head (head_x dir_x, head_y dir_y) if new_head food: return [new_head] snake, True return [new_head] snake[:-1], False def is_collision(snake, direction): head_x, head_y snake[0] dir_x, dir_y direction new_head_x head_x dir_x new_head_y head_y dir_y if new_head_x 0 or new_head_x WIDTH - 1: return True if new_head_y 0 or new_head_y HEIGHT - 1: return True if (new_head_x, new_head_y) in snake: return True return False def generate_food(snake): while True: x random.randint(1, WIDTH - 2) y random.randint(1, HEIGHT - 2) if (x, y) not in snake: return (x, y) def get_direction(current_dir): mapping { w: (0, -1), s: (0, 1), a: (-1, 0), d: (1, 0), } while True: key input(输入方向(w上/s下/a左/d右): ).strip().lower() if key not in mapping: print(无效输入请重新输入) continue new_dir mapping[key] if new_dir[0] -current_dir[0] and new_dir[1] -current_dir[1]: print(不能向相反方向移动) continue return new_dir def main(): snake INIT_SNAKE[:] direction INIT_DIRECTION food generate_food(snake) while True: draw(snake, food) if is_collision(snake, direction): print(游戏结束) print(f最终得分: {len(snake) - 3}) break direction get_direction(direction) new_snake, ate move_snake(snake, direction, food) snake new_snake if ate: food generate_food(snake) if __name__ __main__: main()4.10 运行与验证在命令行中进入到snake_game目录执行python game.py运行后你会看到类似下面的界面#################### # # # O # # O # # O # # # # # # * # # # #################### 当前得分: 0 输入方向(w上/s下/a左/d右):输入s向下移动蛇就往下走一格输入a向左输入d向右输入w向上。不断吃*食物得分就会提升。需要注意当前版本是每移动一步需要输入一次方向如果原地不动想继续走同一个方向也要再输入一次相同的方向键。这是命令行版本为了简化跨平台输入而做出的妥协真实的游戏体验需要借助 pygame 的事件监听来处理。5. pygame 图形界面版核心思路命令行版本虽然能跑通但离“游戏”的体验还差很远。写成 pygame 版本画面会好看很多交互也更流畅。这里给出核心思路和关键代码片段完整代码可以在命令行版本基础上扩展。5.1 初始化窗口与游戏组件import pygame import random import sys # 初始化 pygame pygame.init() # 窗口尺寸 WINDOW_WIDTH 600 WINDOW_HEIGHT 600 # 每个格子大小 CELL_SIZE 30 # 地图行列数 COLS WINDOW_WIDTH // CELL_SIZE ROWS WINDOW_HEIGHT // CELL_SIZE # 颜色定义 BLACK (0, 0, 0) WHITE (255, 255, 255) GREEN (0, 255, 0) RED (255, 0, 0) # 游戏帧率 FPS 10这里窗口尺寸 600x600每个格子 30 像素也就是地图 20x20 个格子和命令行版本的尺寸保持一致。5.2 游戏循环与事件监听pygame 的核心是事件循环通过监听键盘事件获取按键而不是用input()阻塞等待。def main(): clock pygame.time.Clock() screen pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) pygame.display.set_caption(贪吃蛇) snake [(COLS // 2, ROWS // 2)] direction (1, 0) food generate_food(snake) score 0 running True while running: # 固定帧率蛇每秒钟移动 FPS 步 clock.tick(FPS) # 处理事件 for event in pygame.event.get(): if event.type pygame.QUIT: running False elif event.type pygame.KEYDOWN: if event.key pygame.K_UP: new_dir (0, -1) elif event.key pygame.K_DOWN: new_dir (0, 1) elif event.key pygame.K_LEFT: new_dir (-1, 0) elif event.key pygame.K_RIGHT: new_dir (1, 0) else: continue # 强制更新方向同样需要反向检查 if new_dir[0] ! -direction[0] or new_dir[1] ! -direction[1]: direction new_dir # 移动蛇 new_head (snake[0][0] direction[0], snake[0][1] direction[1]) # 碰撞检测 if new_head in snake or not (0 new_head[0] COLS and 0 new_head[1] ROWS): running False continue snake.insert(0, new_head) if new_head food: score 1 food generate_food(snake) else: snake.pop() # 绘制 screen.fill(BLACK) # 画食物 fx, fy food pygame.draw.rect(screen, RED, (fx * CELL_SIZE, fy * CELL_SIZE, CELL_SIZE, CELL_SIZE)) # 画蛇 for segment in snake: sx, sy segment pygame.draw.rect(screen, GREEN, (sx * CELL_SIZE, sy * CELL_SIZE, CELL_SIZE, CELL_SIZE)) # 显示得分 font pygame.font.SysFont(arial, 24) score_text font.render(fScore: {score}, True, WHITE) screen.blit(score_text, (10, 10)) pygame.display.flip() pygame.quit() sys.exit()这段代码与命令行版本的核心逻辑完全一致区别在于用clock.tick(FPS)控制蛇的移动速度而不是等待键盘输入。用事件监听获取方向键每帧都响应。用图形绘制代替字符输出。(0 new_head[0] COLS and 0 new_head[1] ROWS)是撞墙判断因为这里没有额外的墙壁边界地图外就是墙。5.3 运行验证将 pygame 版本保存为snake_pygame.py执行python snake_pygame.py这时会弹出一个窗口你可以用方向键控制蛇的移动游戏体验和命令行版本完全不同。6. 常见问题与排查思路开发过程中一定会遇到各种报错和异常。下面汇总高频问题。问题现象常见原因解决思路ModuleNotFoundError: No module named pygame没有安装 pygame 库执行pip install pygame或切换国内镜像源运行 Python 提示python 不是内部或外部命令Python 未加入 PATH 环境变量重新安装 Python勾选 Add Python to PATH或手动配置环境变量游戏画面不刷新越滚越长没有清屏操作在绘制前执行os.system(cls if os.name nt else clear)蛇刚启动就提示游戏结束初始方向可能指向墙壁检查INIT_SNAKE和INIT_DIRECTION的坐标计算是否正确吃到食物后蛇不增长move_snake中尾部删除逻辑错误检查是否在吃到食物时没有保留尾部应执行[new_head] snake按下方向键后蛇反向移动直接死亡没有做反向移动检查在接收方向时判断新方向与当前方向是否完全相反食物生成在蛇身上食物坐标没有排除蛇身坐标在generate_food中循环判断if (x, y) not in snakepygame 窗口闪退游戏出现异常或循环结束给主循环加入try-except或在退出前打印日志7. 最佳实践与工程建议写完一个能跑的贪吃蛇之后下面这些改进点和工程实践建议能让代码更好用、更健壮。7.1 面向对象重构如果只是函数堆砌项目一旦变大就会难以维护。贪吃蛇很适合用来练习面向对象思维。可以考虑定义Snake类、Food类、Game类把数据和行为封装在一起。例如class Snake: def __init__(self, init_body, init_direction): self.body init_body[:] self.direction init_direction def head(self): return self.body[0] def move(self, food): new_head (self.head()[0] self.direction[0], self.head()[1] self.direction[1]) self.body.insert(0, new_head) if new_head food: return True self.body.pop() return False这样做的好处是游戏状态被封装在一个类里外部代码只需要调用game Game()、game.run()逻辑清晰很多。7.2 游戏状态管理简单游戏可以用一个循环搞定但稍微复杂一点的游戏就需要状态机。可以定义三个状态START、PLAYING、GAME_OVER。不同状态下处理不同的逻辑例如START显示开始界面按空格进入游戏。PLAYING正常执行游戏循环。GAME_OVER显示得分按 R 重新开始。这样的状态管理思路同样适用于其他小游戏开发。7.3 难度递增机制真正的贪吃蛇游戏通常越玩越快。实现思路很简单把速度与得分挂钩。在 pygame 版本中把FPS作为变量每得一分或者每吃五个食物就把FPS增加一。FPS 10 MAX_FPS 30 # 在吃到食物的逻辑中增加 if score % 5 0 and FPS MAX_FPS: FPS 1这样游戏会逐渐提速挑战性明显提升。7.4 最高分持久化玩家最关心的往往不是功能本身而是“我能不能打破纪录”。把最高分保存到本地文件是一个极好的练习可以用两行代码实现def save_high_score(score): with open(high_score.txt, w) as f: f.write(str(score)) def load_high_score(): try: with open(high_score.txt, r) as f: return int(f.read()) except FileNotFoundError: return 0这一小功能牵扯到文件读写、异常处理非常有学习价值。7.5 代码健壮性游戏代码中经常出现输入不可控的情况命令行版本尤其明显。input()可能收到空字符串、非法字符因此get_direction里的循环校验是必须的。pygame 版本中按键处理也要考虑到玩家快速按多个键的情况。另外列表操作时容易踩 IndexError。比如蛇身长度为 1 时访问snake[1]会崩但贪吃蛇的蛇身至少应该有 3 节初始化时就要保证这一点。7.6 学习顺序建议下一步想提升的话可以按以下顺序研究给贪吃蛇增加音效和动画。将代码重构为面向对象版本。实现暂停功能P 键暂停。增加障碍物关卡。尝试双人对战模式。把键盘控制改成鼠标控制。研究如何用asyncio实现实时输入监听摆脱逐帧等输入的模式。8. 总结这篇文章从零开始先解释了贪吃蛇背后的核心逻辑然后给出了一个完整可运行的命令行版 Python 贪吃蛇源码之后又给出了 pygame 图形界面版本的实现思路。在代码编写过程中我们把移动、碰撞检测、食物生成、反向移动控制这些关键点都做了拆解最后补充了常见 bug 的排查思路和工程化改进建议。对于新手来说最重要的不是把代码跑通就完事了而是理解每一行代码为什么这么写。试着把地图变大、把初始蛇变长、把速度变快或者把WIDTH和HEIGHT改一改观察游戏行为的变化。也可以故意去掉反向移动判断体验一下蛇扎进自己身体的感觉。这种改代码、跑代码、调代码的过程才是真正提升编程能力的过程。如果你觉得本文对你有帮助可以先收藏起来动手把代码敲一遍然后试着加上自己的创意功能。欢迎在评论区交流你运行时的报错和遇到的坑。
返回列表