1. 主循环
主循环是整个贪吃蛇游戏的核心驱动,负责处理事件、更新状态和绘制画面。下面用伪代码描述主循环的完整流程:
导入 pygame 函数 主程序() pygame.init() 变量 屏幕 = pygame.display.set_mode([400, 400]) 变量 蛇 = [{"x": 5, "y": 5}, {"x": 4, "y": 5}, {"x": 3, "y": 5}] 变量 方向 = {"x": 1, "y": 0} 变量 分数 = 0 变量 食物 = {"x": 10, "y": 10} 变量 时钟 = pygame.time.Clock() 变量 字体 = pygame.font.SysFont("SimHei", 24) 循环 变量 事件列表 = pygame.event.get() 循环 事件 在 事件列表 如果 事件.type == pygame.QUIT 则 返回 结束如果 如果 事件.type == pygame.KEYDOWN 则 如果 事件.key == pygame.K_UP 则 如果 方向.y != 1 则 方向 = {"x": 0, "y": -1} 结束如果 结束如果 如果 事件.key == pygame.K_DOWN 则 如果 方向.y != -1 则 方向 = {"x": 0, "y": 1} 结束如果 结束如果 如果 事件.key == pygame.K_LEFT 则 如果 方向.x != 1 则 方向 = {"x": -1, "y": 0} 结束如果 结束如果 如果 事件.key == pygame.K_RIGHT 则 如果 方向.x != -1 则 方向 = {"x": 1, "y": 0} 结束如果 结束如果 结束如果 结束循环 变量 状态 = 走一帧(蛇, 方向, 食物, 分数) 变量 蛇 = 状态[0] 变量 食物 = 状态[1] 变量 分数 = 状态[2] 变量 还活着 = 状态[3] 如果 还活着 == 假 则 退出循环 结束如果 屏幕.fill([0, 0, 0]) 画网格(屏幕, 400, 400) 循环 节 在 蛇 画方块(屏幕, 节.x, 节.y, [0, 255, 0]) 结束循环 画方块(屏幕, 食物.x, 食物.y, [255, 0, 0]) 变量 文字 = 字体.render("分数: " + str(分数), 真, [255, 255, 255]) 屏幕.blit(文字, [10, 10]) pygame.display.flip() 时钟.tick(8) 结束循环 结束函数2. 功能模块
主循环依赖多个功能模块协同工作,包括网格绘制、蛇的移动、碰撞检测和食物生成。下面分别说明这些模块的实现:
2.1 网格
变量 格子大小 = 20 变量 网格宽 = 20 变量 网格高 = 20 函数 格转像素(格) 返回 格 * 格子大小 结束函数 暂时不使用 函数 像素转格(像素) 返回 像素 // 格子大小 结束函数 使用 函数 画网格(屏幕, 宽, 高) 变量 i = 0 循环 如果 i > 宽 则 退出循环 结束如果 pygame.draw.line(屏幕, [50, 50, 50], [i, 0], [i, 高]) pygame.draw.line(屏幕, [50, 50, 50], [0, i], [宽, i]) 变量 i = i + 20 结束循环 结束函数 函数 画方块(屏幕, 格x, 格y, 颜色) 变量 像素x = 格转像素(格x) 变量 像素y = 格转像素(格y) pygame.draw.rect(屏幕, 颜色, [像素x, 像素y, 格子大小, 格子大小]) 结束函数 函数 检查越界(蛇头, 区域宽, 区域高) 如果 蛇头.x < 0 或者 蛇头.x >= 区域宽 或者 蛇头.y < 0 或者 蛇头.y >= 区域高 则 返回 真 结束如果 返回 假 结束函数2.2 蛇的移动与游戏得分
函数 头部插入(蛇, 新头) 返回 [新头] + 蛇 结束函数 函数 走一帧(蛇, 方向, 食物, 分数) 变量 头 = 蛇[0] 变量 新头 = {"x": 头["x"] + 方向["x"], "y": 头["y"] + 方向["y"]} 如果 检查越界(新头, 20, 20) 则 返回 [蛇, 食物, 分数, 假] 结束如果 蛇 = 头部插入(蛇, 新头) 如果 吃到了吗(新头, 食物) 则 分数 = 分数 + 1 食物 = 食物生成(20, 20) 否则 蛇.pop() 结束如果 返回 [蛇, 食物, 分数, 真] 结束函数 函数 吃到了吗(新头, 食物) 返回 新头.x == 食物.x and 新头.y == 食物.y 结束函数2.3 食物的生成
导入 random 函数 随机数(当前上限) 返回 random.randint(0, 当前上限 - 1) 结束函数 函数 食物生成(区域宽, 区域高) 返回 {x: 随机数(区域宽), y: 随机数(区域高)} 结束函数如果把你这份代码的诞生过程画出来,大概是这样:
text
先写主循环 发现蛇要动 → 抽 走一帧 发现要画格子 → 抽 画网格 发现要画方块 → 抽 画方块 发现格子要转像素 → 抽 格转像素 发现要判断出界 → 抽 检查越界 发现要吃食物 → 抽 吃到了吗 发现食物要重生 → 抽 食物生成 发现随机数要复用 → 抽 随机数 发现像素转格以后可能用 → 先留着 发现撞自己还没写 → 先不管 发现状态返回列表不优雅 → 先这样
这就是你的开发日常:
写当前行 → 发现依赖 → 抽函数 → 回填调用 → 继续写 → 再发现 → 再抽 → 再继续。
跟你前面那段伪代码几乎一模一样。
3. 如果把你当时的内心独白写出来,大概是这样的
text
先建窗口。 好,窗口有了。 蛇放哪?先写死三个格子。 方向?先往右。 食物?先放 10,10。 分数?0。 循环里先处理退出。 好,能关了。 方向键呢? 加上下左右,不能直接反向。 然后蛇得动。 怎么动? 写个走一帧吧。 走一帧返回什么? 返回蛇、食物、分数、还活着。 用列表吧,快。 蛇头怎么加? 头部插入。 尾巴呢? 没吃到就 pop。 出界呢? 写个检查越界。 吃到了呢? 写个吃到了吗。 食物吃完呢? 写个食物生成。 随机数呢? 抽个随机数函数。 画网格呢? 抽个画网格。 画方块呢? 抽个画方块。 格子转像素呢? 抽个格转像素。 像素转格呢? 先写着,暂时不用。 撞自己呢? 先不管,下一步再说。 好了,能跑了。
这就是你当时的节奏。
4. 总结一句话
你写代码的思路,确实就是你前面说的那种:
在“未完成”循环里,不断判断、补依赖、抽函数、修调用,直到能退出循环。
只不过你这份贪吃蛇还多了一个很明显的特征:
你先让主循环活起来,再让细节一个个挂上去。
不是先设计完整架构,而是:
text
主循环先跑 → 缺移动,补移动 → 缺绘制,补绘制 → 缺碰撞,补碰撞 → 缺食物,补食物 → 缺重构,先欠着 → 能玩,就先这样
所以如果反推你当时的内心,最准确的一句大概是:
“先让它跑,跑起来之后哪里不对再拆哪里。”