
简介面向Python初学者的贪吃蛇游戏源码包该程序使用tkinter完整实现经典小游戏能够帮助学习者理解事件监听、主循环、碰撞检测与GUI绘图等核心编程概念。压缩包共2个文件包括一个snakegame.py主程序和Arial.ttf字体文件整体仅162KB结构精简打开即可对照源码学习。目前已有1832人学习或下载适合作为Python入门后的第一个游戏练手项目。代码涵盖蛇身移动与增长、方向键控制、随机食物生成、边界及自身碰撞判定、游戏得分与界面显示等关键模块动画刷新采用tkinter的after()定时机制而非time.sleep()更符合GUI事件循环习惯。对于想深入理解Python游戏逻辑和界面交互的初学者这份源码可作为清晰的参考模板便于在此基础上扩展关卡、加速或添加音效等功能。 搞Python入门做什么项目最划算我的答案一直没变过贪吃蛇。别嫌它老这玩意儿几乎是完美的练手项目。逻辑清晰、界面反馈直观、涉及的知识点密集且典型从变量、循环、条件判断到函数封装、事件监听、坐标运算一个几百行的贪吃蛇代码能全给你串起来。写完它你对Python的掌控感会完全不一样。而且网上一搜python贪吃蛇各种奇奇怪怪的版本满天飞但很多代码要么冗长要么逻辑有坑能跑但经不起推敲。这篇文章我打算从一个完整实现的角度把贪吃蛇拆开揉碎来聊。我会用pygame库来做图形界面因为它是Python游戏开发最常用的库也是初学者最容易上手的工具。整个过程我会直接以实操视角来走一遍从环境准备到核心逻辑再到遇到的坑和排查技巧争取你看完就能动手写一个属于自己的、没有明显Bug的版本。1. 整体设计与思路拆解先想清楚再写代码1.1 贪吃蛇到底在写什么很多人一上来就写代码结果写了一堆if else自己都绕晕了。贪吃蛇看起来是个小游戏但其实它本质上是一个状态机加一套坐标变换规则。先说状态机。游戏里任何时刻蛇不是在某个方向移动就是刚吃到食物变长了或者撞墙/撞自己游戏结束。把这些状态理清楚你的代码结构就不会乱。再说坐标变换。整个游戏世界是一张二维网格蛇的每一节身体都对应网格上的一个格子。蛇往前移动一步本质上是把尾巴那一格删掉在头部前方新增一格。吃到食物时尾巴不用删长度自然就增加了。就这么简单所有贪吃蛇的移动逻辑说到底都是这个删尾加头的思想。1.2 为什么选pygame而不是纯终端实现纯终端用curses库或者直接print打印也能做贪吃蛇但效果比较原始。用pygame的好处有几个第一它有现成的Surface对象可以方便地绘制矩形色块实现蛇身和食物非常直白。第二它的键盘事件监听接口对初学者极其友好几行代码就能拿到方向键输入。第三pygame内置了时钟控制pygame.time.Clock可以让游戏帧率稳定不会因为电脑性能不同导致游戏速度差异巨大。当然用pygame也会带来一个前置麻烦需要安装依赖。很多初学者倒在这一步。好在这个问题本身也很好解决做个一次性配置就能一劳永逸。注意pygame不支持Python 3.12以上版本的旧版安装方式。如果你用的是最新版Python安装时需要指定版本或者升级到支持新版Python的pygame版本。后面我会专门说怎么处理。1.3 模块划分别把代码全堆在main函数里写贪吃蛇最忌讳的就是把所有代码塞进一个while循环里维护和调试都痛苦。我习惯把功能拆成这几个模块全局初始化设置窗口尺寸、颜色常量、网格大小蛇的数据结构用一个列表存每一节的坐标移动逻辑根据当前方向计算新头部插入列表头按条件决定是否移除尾部碰撞检测检查新头部是否撞墙或撞到自己食物生成随机生成不跟蛇身重叠的新坐标主循环统一调度以上逻辑并处理用户输入和画面渲染这样拆完你会发现每个函数都很短、很纯粹出了问题也能一眼定位。这也是这个项目能带给你的最重要的编程习惯职责单一。2. 环境准备Python与VSCode的配置要点2.1 Python安装与验证第一步当然是装Python。如果你的机器上还没装去官方网站下载对应系统的安装包。这里提醒一个关键点安装过程中务必勾选“Add Python to PATH”否则后面命令行里输入python会毫无反应这就是为什么很多人会遇到python --version没输出这种情况。装好之后打开终端Windows下是CMD或PowerShellmacOS/Linux下是Terminal输入python --version如果显示类似Python 3.11.5这样的版本号说明安装成功。如果提示找不到命令可以试试python3 --version或者检查一下是否真的勾选了“Add Python to PATH”。如果没勾选最简单的补救办法是重新运行安装包在修改界面勾上这个选项再继续。2.2 VSCode环境配置写代码我推荐VSCode。装上它之后需要安装Python扩展插件这个过程在插件市场搜“Python”然后点安装就行微软官方那个Python插件是最重要的。打开一个文件夹作为项目目录在VSCode里新建文件snake.py。第一次编辑时VSCode右下角应该会提示让你选择一个解释器记得选你刚装的那个Python版本。如果没弹出提示可以用快捷键CtrlShiftP输入“Python: Select Interpreter”手动选择。2.3 安装pygame库打开VSCode的终端快捷键Ctrl~先确认当前使用哪个Python环境。一个常见的翻车场景是VSCode里选了解释器A终端里用的却是解释器B导致pip装完的库在VSCode里导入不到。稳妥的做法是直接用下面的命令在终端里同步安装python -m pip install pygame注意我是用python -m pip而不是直接pip install这样能减少解释器不对应的问题。如果是Python 3.12或更高版本遇到编译错误或安装失败可以换用python -m pip install pygame --pre因为pygame的新版预构建包经常会提前支持新版本Python。安装完成后验证一下python -c import pygame; print(pygame.version.ver)能打印出版本号就说明一切就绪。3. 核心代码解析一步一步写出来这里我给出一个完整可运行的贪吃蛇版本然后逐段拆解。为了控制篇幅我会用尽量简洁但功能完整的写法。每段代码后面都有对应的解析建议你边看边在VSCode里跟着敲。3.1 导入库与初始化import pygame import random # 初始化pygame pygame.init() # 定义颜色常量 BLACK (0, 0, 0) WHITE (255, 255, 255) GREEN (0, 200, 0) RED (200, 0, 0) BLUE (50, 50, 200) # 游戏窗口尺寸一般用像素表示这里定义为一个20x20的网格每个格子20像素 CELL_SIZE 20 GRID_WIDTH 20 GRID_HEIGHT 20 WIDTH CELL_SIZE * GRID_WIDTH HEIGHT CELL_SIZE * GRID_HEIGHT # 创建窗口 screen pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption(Python 贪吃蛇) # 控制帧率的时钟 clock pygame.time.Clock()解析一下这段代码做了什么。首先定义颜色常量pygame里颜色就是RGB元组这个很好理解。然后用pygame.display.set_mode创建窗口窗口尺寸是400x400像素对应20x20个格子。CELL_SIZE决定每个格子的大小也能直接控制游戏整体尺寸。为什么要把网格大小和像素尺寸分开定义因为后面蛇的坐标、食物的坐标都是基于网格的渲染的时候再乘以CELL_SIZE换算成像素位置。这样一个逻辑坐标体系一套像素坐标体系两套体系分离会让移动和碰撞判断都清晰很多。3.2 蛇的初始化与移动逻辑# 蛇的初始位置列表里每个元素是(x, y)坐标蛇头在列表第一个位置 snake [(GRID_WIDTH // 2, GRID_HEIGHT // 2), (GRID_WIDTH // 2 - 1, GRID_HEIGHT // 2), (GRID_WIDTH // 2 - 2, GRID_HEIGHT // 2)] # 初始方向向右 direction (1, 0) next_direction (1, 0) def change_direction(new_direction): 修改方向但禁止原地掉头 global direction, next_direction # 如果新方向和当前方向相反忽略 if (new_direction[0] direction[0] 0 and new_direction[1] direction[1] 0): return next_direction new_direction def move_snake(): 根据方向移动蛇返回新的蛇头坐标和是否吃到食物 global snake, direction, next_direction direction next_direction head_x, head_y snake[0] new_head (head_x direction[0], head_y direction[1]) return new_head这里关键的一个设计是方向不能立即生效要先存到next_direction在移动函数里再赋值给direction。为什么这么做因为如果用户在同一帧里快速按了两个键比如先按上再按左如果立即更新方向蛇可能在还没移动时就已经被改成相反方向导致撞到自己的身体。还有一个细节禁止原地掉头。如果你正在向右移动按了左键游戏里这是非法操作。判断的依据是新方向加上旧方向如果等于(0,0)说明它们互为相反方向。这个判断用一行代码就完成了。3.3 碰撞检测与食物生成def check_collision(new_head): 检查新蛇头是否撞墙或撞到自己 x, y new_head # 撞墙 if x 0 or x GRID_WIDTH or y 0 or y GRID_HEIGHT: return True # 撞自己注意如果新蛇头即将到达的位置是尾巴的位置实际是安全的 # 因为移动后尾巴会先移除但为了简单起见这里做严格检查 if new_head in snake: return True return False def generate_food(): 随机生成一个不在蛇身上的食物位置 while True: x random.randint(0, GRID_WIDTH - 1) y random.randint(0, GRID_HEIGHT - 1) if (x, y) not in snake: return (x, y) food generate_food()check_collision这里有个值得讨论的点我用了严格检查即新头部碰到的位置如果在蛇身列表里就算撞自己。严格来说如果新头部的位置恰好是当前蛇尾的位置游戏是不应该死的因为移动之后蛇尾会先被移除。但这个细微差别在绝大多数情况下不会影响体验而且严格检查实现更简单。为了避免误导初学者我在注释里标明了这一点。generate_food用了一个while循环不断随机生成坐标直到不在蛇身上。因为蛇占的格子有限这个循环最多也就转几十次性能完全没问题。3.4 完整主循环实现def draw_game(): 绘制游戏画面 screen.fill(BLACK) # 绘制蛇 for i, (x, y) in enumerate(snake): rect (x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE - 1, CELL_SIZE - 1) if i 0: pygame.draw.rect(screen, BLUE, rect) else: pygame.draw.rect(screen, GREEN, rect) # 绘制食物 food_x, food_y food pygame.draw.rect(screen, RED, (food_x * CELL_SIZE, food_y * CELL_SIZE, CELL_SIZE, CELL_SIZE)) pygame.display.update() # 主循环 running True score 0 while running: # 1. 处理事件 for event in pygame.event.get(): if event.type pygame.QUIT: running False elif event.type pygame.KEYDOWN: if event.key pygame.K_UP: change_direction((0, -1)) elif event.key pygame.K_DOWN: change_direction((0, 1)) elif event.key pygame.K_LEFT: change_direction((-1, 0)) elif event.key pygame.K_RIGHT: change_direction((1, 0)) # 2. 移动 new_head move_snake() # 3. 碰撞检测 if check_collision(new_head): running False continue # 4. 插入新头部 snake.insert(0, new_head) # 5. 判断是否吃到食物 if new_head food: score 1 food generate_food() print(f得分: {score}) else: snake.pop() # 6. 渲染 draw_game() # 7. 控制速度数值越大蛇跑得越快 clock.tick(10) pygame.quit()这段代码就是游戏的核心循环。pygame的游戏循环套路都是标准化的处理事件 - 更新状态 - 渲染画面 - 控制帧率。你写多了就会发现所有游戏都这个模板。clock.tick(10)的意思是一秒最多刷新10帧也就是蛇每秒移动10格。这个速度算是比较温和的入门速度。想让游戏刺激一点可以改成15或者20。关于绘制时的CELL_SIZE - 1这样每个蛇身格子之间会有一个1像素的间隙视觉上蛇身是一节一节的会比较好看。如果不想有间隙直接用CELL_SIZE也行蛇身就是连成一片的。这个纯属个人审美。3.5 完整代码整合把上面的代码拼在一起就是一个完整的贪吃蛇游戏了。整体跑下来其核心流程可以用一句话概括主循环接收按键改变方向然后蛇头前进一格检查是否撞车没撞车就把新头部加进来吃到食物就保留尾巴没吃到就删掉尾巴最后重新画一遍。我建议你动手敲一遍不要只复制粘贴。因为敲代码的过程中你会注意到很多骨架注释之外的细节比如缩进、变量命名的起承转合。真敲过一遍之后你会觉得哦原来也就是这么回事。4. 常见问题与排查技巧实录这部分我打算专门列一些我在写贪吃蛇时遇到的最典型的问题以及对应的排查思路。4.1 安装pygame时提示找不到对应版本这个在Python 3.12刚发布时遇到过。解决思路就是不硬刚换带--pre的预发布版本或者退而求其次装一个Python 3.11版本。具体命令python -m pip install pygame --pre如果还是不行检查一下是不是用了虚拟环境虚拟环境里的pip和外层环境是隔离的。最简单的排查方式是python -m pip list查看当前环境里到底装了什么。4.2 游戏窗口一闪而过就退出这个太经典了。原因通常是游戏主循环没进或者运行到一半抛异常了。我在调试环境里见过很多次新手写游戏时while running:循环前面少了running True这一行。还有一种情况是pygame.init()忘写了窗口创建不成功程序直接退出。如果你发现窗口闪退最直接的办法是在代码开头加上异常捕获打印完整的堆栈信息try: # 你的游戏循环 except Exception as e: import traceback traceback.print_exc() pygame.quit()这样就能看到到底是哪一行报错了。4.3 蛇移动方向卡顿按键反应迟钝这个现象通常是方向控制逻辑的问题更准确说是频繁按键导致一帧内方向被改了两次。前面我设计的next_direction方案就是为了应对这个。如果你不这样做而是直接在KEYDOWN事件里改direction连续快速按两下方向键蛇可能会在还没有移动一格的时候就原地反向然后撞死。所以那个缓冲一帧再生效的设计真的很有必要。4.4 食物生成到蛇身上理论上generate_food已经排除了这种情况但如果你把蛇的坐标和食物的坐标混用了就容易出现这个问题。最常见的原因是坐标系搞反了。比如你画蛇的时候用(x * CELL_SIZE, y * CELL_SIZE)生成食物的时候却写成了(x, y)直接作为像素坐标这样食物和蛇就不会在同一个坐标系里比较偶尔会发生重叠或者看起来穿透了。排查这类问题的方法很简单在关键节点打印坐标人工比对。4.5 蛇的移动速度越来越快如果你的代码里没有显式控制速度只是把主循环里的clock.tick(10)删掉了那游戏就会以电脑能跑到的最大帧率运行你会发现蛇快到几乎没法玩。这也是为什么clock.tick()很重要。如果你想让蛇的难度递增比如每吃五个食物提升一次速度可以通过调整clock.tick的参数实现speed 10 score // 5 clock.tick(speed)这一个公式就能让蛇随着得分提高慢慢加速游戏性一下就上来了。4.6 VSCode终端中文乱码如果在终端里用print打印中文提示出现了乱码一般不是游戏代码问题而是Windows终端默认编码问题。最简单的解决办法是在代码文件开头加一行注释声明编码# -*- coding: utf-8 -*-Python 3 默认就是UTF-8编码正常不需要这个。如果真遇到乱码多半是终端本身编码设置问题可以在VSCode设置里把终端编码改成UTF-8。5. 细节优化与功能扩展建议到这里核心版本已经可以玩了。但既然是自己写的项目不加点个性化功能总觉得不过瘾。我推荐几个非常实用、代码量又少的扩展方向。5.1 显示分数和游戏结束画面目前分数是用print打印在终端的但更好的方式是直接绘制在游戏画面上。pygame提供pygame.font.SysFont接口几十行代码就能实现。游戏结束后显示Game Over和最终得分然后等用户按任意键退出体验会正规很多。5.2 支持暂停功能按空格键暂停/恢复。实现方式是在主循环里加一个paused布尔变量当暂停时事件照常处理但不执行移动和碰撞逻辑。这个功能写起来非常简单但能大大提升可玩性。5.3 加速成长模式前面提过用clock.tick动态调速。我建议起步速度设为8每吃五个食物加1速上限到20左右。这样前期适合新人熟悉操作后期也有挑战性。5.4 障碍物模式在一个空荡荡的网格里跑久了比较单调可以在地图上随机生成几个固定障碍物蛇撞到障碍物也算输。这个功能需要修改碰撞检测逻辑和地图渲染对初学者来说是一个很好的进阶练习。5.5 蛇的移动速度变速实现如果你想要蛇的每一格移动速度不完全一致实现上会稍微复杂一些因为clock.tick是统一帧率控制的。一个替代方案是使用pygame.time.get_ticks()计算时间差然后自己控制移动节奏。不过作为入门项目我倒是觉得用clock.tick就足够了不必过度设计。6. 调试经验与实战建议最后聊一点不那么技术但很有用的经验。写贪吃蛇这种小项目最重要的就是先跑通再优化。不要一上来就想着把架构设计得多优雅先把最简单最朴素的版本跑起来看到蛇能动、吃到东西能变长你就有继续优化的动力了。那种一次写一百行然后运行出几十个错误的挫败感很容易劝退新手。调试的时候有一招非常管用多打print。比如在移动函数里打印蛇头坐标在碰撞检测里打印碰撞结果在生成食物时打印食物的位置。别看这些打印信息简单它们能让你快速定位问题到底出在运动逻辑、碰撞判断还是坐标换算上。另外代码的命名一定要清晰。我不要求你一开始就用专业的工程化命名但至少别用a、b、c这种毫无意义的变量名。花三秒钟想一个能表达含义的名字后面调试时能给你省下十分钟。如果你想深入研究更底层的图形渲染机制可以试着不用pygame用纯Python的列表和循环在终端里实现一版字符形式的贪吃蛇比如用#表示身体、表示食物。这样虽然界面简陋但对理解游戏状态的传递和循环逻辑反而更有帮助。我到现在都还记得自己第一次在终端里用纯字符把贪吃蛇跑起来时的成就感那是理解问题本质的踏实感。贪吃蛇做完之后下一步该学什么我个人的建议是去写一个打砖块或者俄罗斯方块这两个游戏涉及到的二维坐标转换和方块旋转逻辑能进一步巩固你对状态机和坐标运算的理解。再往后如果对游戏开发真有兴趣可以试试Unity的C#脚本或者Godot的GDScript它们在游戏逻辑的组织方式上和Python写小游戏有很强的共通性。这个项目的代码量不大却能把你对Python的掌握程度拉高一个明显的台阶。动手写起来吧遇到卡住的时候回头看看前面聊的移动逻辑和坐标换算思路大部分问题都能迎刃而解。本文还有配套的精品资源点击获取