
这次我们来看一个非常适合 Python 基础阶段练习的小项目贪吃蛇小游戏。它的价值不在于有多复杂而在于刚好能把 Python 的变量、列表、循环、条件判断、函数、事件监听和随机数这些核心知识点全部串起来而且不需要额外安装任何第三方库用 Python 自带的 turtle 模块就能写完整个游戏。整个项目代码量控制在 130 行左右完成后的游戏支持方向键控制、空格暂停、实时计分、撞墙和撞到自己后结束。如果你正在巩固 Python 基础这个项目是比单纯看语法书更有效的阶段测试。接下来我会从环境准备、源码拆解、运行测试、扩展优化一直写到打包成 exe内容可以直接照着操作。1. 核心能力速览先给出一张速览表方便你快速判断这个项目值不值得做以及需要准备什么环境。能力项说明开发语言Python依赖库仅 turtlePython 标准库自带无需安装代码量约 130 行硬件要求无特殊要求普通电脑即可支持平台Windows / macOS / Linux启动方式命令行运行 python snake.py核心功能方向键控制移动、食物随机生成、计分、暂停与恢复、撞墙结束、自撞结束可扩展功能速度递增、障碍物、重新开始、最高分持久化、Pygame 版本适合人群Python 初学者、基础不牢想练手的学习者、准备给学生做教学案例的讲师这个项目不是 Web 应用也不涉及接口 API 和批量任务。它是典型的本地桌面小游戏用来练习 Python 基础语法和程序逻辑非常合适。如果你刚学完if、while、for、list、函数却不知道能做什么那这个项目就是为这个阶段准备的。2. 项目功能与知识点拆解2.1 贪吃蛇的核心玩法贪吃蛇的规则很直观蛇在网格上按固定步长移动玩家通过方向键控制走向吃到食物后蛇身长度增加一格同时分数增加。游戏结束条件有两个撞到窗口边界或者蛇头撞到自己的身体。这个简单规则背后覆盖的知识点并不少我列一下turtle.Screen()创建窗口设置标题、背景色、窗口尺寸。turtle.Turtle()创建蛇身、食物、文字等图形元素。while与定时器循环用screen.ontimer()实现定时刷新代替while True这是图形界面编程里很重要的“事件驱动”概念。list用列表保存蛇身的每一节移动时把尾部挪到头部前面实现“蛇在爬”的效果。random.randint()随机生成食物的坐标。if/else方向判断、碰撞检测、食物判断。函数与全局变量方向控制、暂停恢复、计分逻辑都封装成函数。事件监听screen.onkey()绑定方向键和空格键。2.2 适合什么学习阶段如果你属于下面任一情况可以重点做一遍Python 语法学过一半但没写过超过 50 行的程序。想验证自己对列表和函数是否真正掌握。准备带学生做项目需要一个代码量小、可运行、可扩展的案例。想给 Python 基础阶段增加一点“看得见”的反馈。这个项目不需要面向对象、不需要装饰器、不需要多线程只要基础语法熟悉就能写完。写完以后你会对“程序如何持续运行”“键盘输入如何影响程序状态”有更具体的理解。3. 环境准备与开发工具3.1 Python 版本和环境推荐使用 Python 3.8 以上版本。turtle 在 3.x 中一直包含在标准库里不需要执行pip install。安装完成后可以先在命令行检查版本python --version如果在终端里能正常输出版本号说明 Python 环境已经可用。如果提示找不到python命令可以尝试python3 --version在 Windows 上安装 Python 时安装向导里记得勾选 “Add Python to PATH”否则后续执行命令会不方便。3.2 开发工具选择写这个项目的代码不需要重型 IDE推荐两种VS Code装上 Python 扩展后可以直接运行和调试。如果不想折腾直接使用 IDLEPython 自带打开.py文件后按 F5 就能运行。这里额外提醒一下显示问题。turtle 打开的是图形窗口如果你的电脑使用远程桌面或者无桌面服务器环境运行后可能看不到窗口。本地普通桌面环境不存在这个问题。3.3 项目目录结构新建一个目录例如snake_game里面放一个snake.py文件即可。后面打包 exe 时会用到这位文件所以目录建议保持整洁。snake_game/ └── snake.py4. 源码实现从零写一个贪吃蛇4.1 先理解程序的结构整个程序可以分成五个部分初始化窗口和界面元素。创建蛇身和食物。绑定键盘事件。编写主游戏循环每 150 毫秒让蛇移动一格。处理碰撞、计分和结束逻辑。turtle的坐标系统以屏幕中心为原点向右为 x 正方向向上为 y 正方向。窗口宽度是SCREEN_WIDTH高度是SCREEN_HEIGHT每个格子大小是CELL_SIZE。蛇头和食物都对齐到网格上方便碰撞检测。4.2 代码分模块讲解第一步导入模块、定义常量、创建窗口。import turtle import random CELL_SIZE 20 GRID_WIDTH 25 GRID_HEIGHT 25 SCREEN_WIDTH CELL_SIZE * GRID_WIDTH SCREEN_HEIGHT CELL_SIZE * GRID_HEIGHT DELAY 0.15 screen turtle.Screen() screen.title(Python 贪吃蛇) screen.bgcolor(black) screen.setup(SCREEN_WIDTH, SCREEN_HEIGHT) screen.tracer(0)screen.tracer(0)是关闭自动刷新然后我们在每一帧移动完成后再手动screen.update()。这样做的好处是蛇的移动看起来是“一格一格跳”而不是留下残影。第二步创建蛇身和食物。蛇身用列表保存初始长度为 3 节。snake [] for i in range(3): segment turtle.Turtle(square) segment.color(#4CAF50) segment.penup() segment.goto(-i * CELL_SIZE, 0) snake.append(segment) food turtle.Turtle(circle) food.color(#FF5252) food.penup()第三步创建计分文字和底部提示。score_display turtle.Turtle() score_display.color(white) score_display.penup() score_display.hideturtle() score_display.goto(0, SCREEN_HEIGHT // 2 - 40) tip_display turtle.Turtle() tip_display.color(gray) tip_display.penup() tip_display.hideturtle() tip_display.goto(0, -SCREEN_HEIGHT // 2 40) tip_display.write(方向键控制 | 空格暂停, aligncenter, font(Arial, 14, normal))第四步定义方向控制函数。这里有一个关键点按下方向键时不能立刻修改当前方向而是先保存到next_direction等主循环执行时再统一更新。这样可以避免在一次移动周期内连续按键导致蛇直接掉头撞到自己。direction Right next_direction Right score 0 paused False game_over False BOUNDARY GRID_WIDTH // 2 * CELL_SIZE def go_up(): global next_direction if direction ! Down: next_direction Up def go_down(): global next_direction if direction ! Up: next_direction Down def go_left(): global next_direction if direction ! Right: next_direction Left def go_right(): global next_direction if direction ! Left: next_direction Right第五步绑定键盘事件。screen.listen() screen.onkey(go_up, Up) screen.onkey(go_down, Down) screen.onkey(go_left, Left) screen.onkey(go_right, Right) screen.onkey(toggle_pause, space)第六步实现暂停与恢复。暂停时停止定时器循环恢复时重新启动定时器。def toggle_pause(): global paused paused not paused if paused: score_display.clear() score_display.goto(0, 0) score_display.write(暂停中按空格继续, aligncenter, font(Arial, 18, bold)) else: update_score() screen.ontimer(main_loop, int(DELAY * 1000))第七步生成食物。生成后要判断是否和蛇身重叠避免食物出现在蛇身上面。def create_food(): while True: x random.randint(-GRID_WIDTH // 2 1, GRID_WIDTH // 2 - 1) * CELL_SIZE y random.randint(-GRID_HEIGHT // 2 1, GRID_HEIGHT // 2 - 1) * CELL_SIZE if (x, y) not in [seg.pos() for seg in snake]: food.goto(x, y) break第八步是核心主循环。每一次循环做的事读取next_direction更新方向。计算新蛇头坐标。判断是否撞墙、撞自己。判断是否吃到食物。用screen.ontimer安排下一次循环。吃食物时蛇身要增长所以新增一节Turtle到列表头部。没吃食物时把蛇尾的一节移动到新蛇头位置再插到列表头部。这种“尾部移到头部”的技巧避免频繁创建和销毁对象代码也更好理解。def main_loop(): global direction, next_direction, score, game_over if paused or game_over: return direction next_direction head_x, head_y snake[0].pos() if direction Up: new_head (head_x, head_y CELL_SIZE) elif direction Down: new_head (head_x, head_y - CELL_SIZE) elif direction Left: new_head (head_x - CELL_SIZE, head_y) else: new_head (head_x CELL_SIZE, head_y) if abs(new_head[0]) BOUNDARY or abs(new_head[1]) BOUNDARY: game_over True score_display.clear() score_display.goto(0, 0) score_display.write(游戏结束, aligncenter, font(Arial, 24, bold)) return if new_head in [seg.pos() for seg in snake]: game_over True score_display.clear() score_display.goto(0, 0) score_display.write(游戏结束, aligncenter, font(Arial, 24, bold)) return if food.distance(new_head) CELL_SIZE: score 10 update_score() new_segment turtle.Turtle(square) new_segment.color(#4CAF50) new_segment.penup() new_segment.goto(new_head) snake.insert(0, new_segment) create_food() else: tail snake.pop() tail.goto(new_head) snake.insert(0, tail) screen.update() screen.ontimer(main_loop, int(DELAY * 1000))最后启动游戏create_food() update_score() screen.ontimer(main_loop, int(DELAY * 1000)) turtle.done()4.3 完整源码把前面各部分组合在一起就是一份可运行的贪吃蛇代码。新建snake.py把下面内容完整复制进去import turtle import random CELL_SIZE 20 GRID_WIDTH 25 GRID_HEIGHT 25 SCREEN_WIDTH CELL_SIZE * GRID_WIDTH SCREEN_HEIGHT CELL_SIZE * GRID_HEIGHT DELAY 0.15 screen turtle.Screen() screen.title(Python 贪吃蛇) screen.bgcolor(black) screen.setup(SCREEN_WIDTH, SCREEN_HEIGHT) screen.tracer(0) snake [] for i in range(3): segment turtle.Turtle(square) segment.color(#4CAF50) segment.penup() segment.goto(-i * CELL_SIZE, 0) snake.append(segment) food turtle.Turtle(circle) food.color(#FF5252) food.penup() score_display turtle.Turtle() score_display.color(white) score_display.penup() score_display.hideturtle() score_display.goto(0, SCREEN_HEIGHT // 2 - 40) tip_display turtle.Turtle() tip_display.color(gray) tip_display.penup() tip_display.hideturtle() tip_display.goto(0, -SCREEN_HEIGHT // 2 40) tip_display.write(方向键控制 | 空格暂停, aligncenter, font(Arial, 14, normal)) direction Right next_direction Right score 0 paused False game_over False BOUNDARY GRID_WIDTH // 2 * CELL_SIZE def create_food(): while True: x random.randint(-GRID_WIDTH // 2 1, GRID_WIDTH // 2 - 1) * CELL_SIZE y random.randint(-GRID_HEIGHT // 2 1, GRID_HEIGHT // 2 - 1) * CELL_SIZE if (x, y) not in [seg.pos() for seg in snake]: food.goto(x, y) break def update_score(): score_display.clear() score_display.goto(0, SCREEN_HEIGHT // 2 - 40) score_display.write(f分数: {score}, aligncenter, font(Arial, 18, bold)) def go_up(): global next_direction if direction ! Down: next_direction Up def go_down(): global next_direction if direction ! Up: next_direction Down def go_left(): global next_direction if direction ! Right: next_direction Left def go_right(): global next_direction if direction ! Left: next_direction Right def toggle_pause(): global paused paused not paused if paused: score_display.clear() score_display.goto(0, 0) score_display.write(暂停中按空格继续, aligncenter, font(Arial, 18, bold)) else: update_score() screen.ontimer(main_loop, int(DELAY * 1000)) screen.listen() screen.onkey(go_up, Up) screen.onkey(go_down, Down) screen.onkey(go_left, Left) screen.onkey(go_right, Right) screen.onkey(toggle_pause, space) def main_loop(): global direction, next_direction, score, game_over if paused or game_over: return direction next_direction head_x, head_y snake[0].pos() if direction Up: new_head (head_x, head_y CELL_SIZE) elif direction Down: new_head (head_x, head_y - CELL_SIZE) elif direction Left: new_head (head_x - CELL_SIZE, head_y) else: new_head (head_x CELL_SIZE, head_y) if abs(new_head[0]) BOUNDARY or abs(new_head[1]) BOUNDARY: game_over True score_display.clear() score_display.goto(0, 0) score_display.write(游戏结束, aligncenter, font(Arial, 24, bold)) return if new_head in [seg.pos() for seg in snake]: game_over True score_display.clear() score_display.goto(0, 0) score_display.write(游戏结束, aligncenter, font(Arial, 24, bold)) return if food.distance(new_head) CELL_SIZE: score 10 update_score() new_segment turtle.Turtle(square) new_segment.color(#4CAF50) new_segment.penup() new_segment.goto(new_head) snake.insert(0, new_segment) create_food() else: tail snake.pop() tail.goto(new_head) snake.insert(0, tail) screen.update() screen.ontimer(main_loop, int(DELAY * 1000)) create_food() update_score() screen.ontimer(main_loop, int(DELAY * 1000)) turtle.done()5. 运行测试与效果验证5.1 启动项目在snake_game目录下打开终端执行python snake.py如果使用 macOS 或 Linux且python命令不可用用python3 snake.py启动后可以看到一个黑色背景窗口蛇头在中心偏左的位置向右移动食物随机出现在网格中窗口左上角显示“分数: 0”底部显示操作提示。5.2 功能测试用例测试项操作预期结果方向控制按上下左右方向键蛇头朝对应方向移动移动方向立即改变禁止反向蛇向右移动时按左键方向不变不会直接掉头撞到自己吃食物控制蛇头移动到食物上分数增加 10蛇身长度增加 1 格新食物随机出现撞墙控制蛇头持续朝边界移动游戏结束屏幕中央显示“游戏结束”撞到自己绕圈让蛇头碰到身体游戏结束屏幕中央显示“游戏结束”暂停与恢复按空格键显示“暂停中按空格继续”再按空格后游戏继续5.3 判断标准游戏能正常跑通以上用例代码没有语法错误窗口不闪退就算阶段测试通过。如果出现窗口闪退优先检查是否在 IDE 里运行turtle程序时 IDE 在结束后关闭了 Python 进程直接使用命令行运行通常能避免这个问题。如果你希望更快验证游戏循环可以把DELAY改成0.05让蛇移动得更快。练手阶段建议先保持0.15方便观察移动逻辑。6. 功能扩展与代码优化基础版本跑通之后下一步就是扩展功能。写练手项目最忌讳只复制不拆解下面的扩展点每一个都能验证你对代码的理解程度。6.1 速度随分数递增目前DELAY是固定值。可以让蛇在吃到食物后移动速度逐步加快增加可玩性。修改主循环里安排下一次定时器的位置new_delay max(0.05, DELAY - score * 0.005) screen.ontimer(main_loop, int(new_delay * 1000))这种方式随着分数提高间隔越来越短但设置一个下限0.05避免速度快到无法操作。6.2 增加重新开始功能游戏结束后可以按 R 键重新开始。需要把初始化状态封装成一个reset_game()函数清空蛇身、重置坐标、重置分数。这个改动涉及全局变量比较多写的时候要小心刚好可以练习函数参数和全局变量管理。6.3 最高分持久化把最高分保存到文本文件下次启动时读取。def read_best_score(): try: with open(best.txt, r, encodingutf-8) as f: return int(f.read().strip()) except FileNotFoundError: return 0 def write_best_score(best): with open(best.txt, w, encodingutf-8) as f: f.write(str(best))这个功能很简单但能让你练习文件读写的两个基础操作open()、with。适合在练手阶段一起巩固。6.4 碰撞检测的优化当前判断new_head in [seg.pos() for seg in snake]会把蛇尾当前所在位置也算进碰撞范围。实际游戏中有一种常见策略是如果蛇头即将移动到蛇尾现在的位置而蛇尾这一帧正好会移走这是允许的。可以优化为if new_head in [seg.pos() for seg in snake[:-1]]: game_over True return不加这个优化蛇在较长的时候想掉头时可能会“莫名奇妙”死掉。作为扩展练习建议你把这一行替换进去观察不同玩法下的差异。6.5 尝试 Pygame 版本turtle 版本适合学习基础语法但它不是真正的游戏开发框架。如果你想继续深入可以尝试用 Pygame 重写一版重点体验三个差异Pygame 有真正的游戏主循环while running配合事件队列处理输入。Pygame 的图像绘制对象是 Surface而不是 Turtle 画布。Pygame 支持帧率控制pygame.time.Clock().tick(FPS)控制方式更标准。Pygame 不是标准库需要安装pip install pygamePygame 版本代码量和工程结构会比 turtle 版更接近正式游戏项目适合已经完成 turtle 版、想继续前进的学习者。7. 打包成独立可执行文件7.1 安装 PyInstaller项目做完以后可以通过 PyInstaller 把snake.py打包成一个 exe 文件在 Windows 上直接双击运行。pip install pyinstaller7.2 打包命令在snake_game目录下执行pyinstaller -F -w snake.py参数说明-F打包成单文件。-w运行时不显示命令行终端窗口。打包完成后exe 文件在dist目录下文件名与snake.py同名即dist/snake.exe。把 exe 复制到其他 Windows 电脑上即可运行不需要安装 Python。7.3 打包注意事项PyInstaller 打包 turtle 程序一般不会出问题但有几个细节需要注意exe 文件首次启动可能稍慢因为要解压运行时资源。杀毒软件可能对 PyInstaller 打包出的单文件误报这是打包工具的常见情况不代表程序有问题。如果只是自己练习可以先在本地运行测试。打包时如果提示模块缺失考虑升级 PyInstaller 版本。8. 常见问题与排查方法问题现象可能原因排查方式解决方案运行后窗口一闪而过Python 环境有问题或代码语法错误在终端运行 python snake.py 看报错根据报错修复代码避免双击运行 py 文件键盘方向键没反应screen.listen()没有调用检查键盘绑定代码确认已在onkey之前调用补上screen.listen()蛇不能反向移动防止掉头撞到自己的限制在go_up/go_down/go_left/go_right中查看判断逻辑这是正常设计不是 bug食物生成在蛇身上create_food()没有判断重叠检查生成坐标是否在蛇身列表中使用if (x, y) not in [seg.pos() for seg in snake]暂停后无法恢复恢复时没有重新启动定时器检查toggle_pause()的 else 分支调用screen.ontimer(main_loop, int(DELAY * 1000))打包后 exe 被杀毒软件拦截PyInstaller 单文件模式常见现象查看杀毒软件隔离记录加白名单或改用--onedir模式打包窗口显示半截蛇头边界判定坐标设置不准确检查BOUNDARY计算使用GRID_WIDTH // 2 * CELL_SIZE作为判定边界分数不更新update_score()没被调用检查吃食物分支在吃到食物分支调用score 10; update_score()9. 最佳实践与学习建议9.1 先跑通再改功能第一次写这个方法时不建议一开始就加暂停、最高分、加速等逻辑。先让蛇能移动、能吃食物、能撞墙结束再逐步增加功能。先跑有一个完整可运行版本后面每一项扩展都有明确的对照。9.2 学习读代码的顺序拿到这份