一、前言

贪吃蛇是 Python 最经典的练手项目,非常适合新手理解:游戏主循环、坐标逻辑、碰撞检测、状态机、帧率控制

市面上大多数贪吃蛇代码存在很多 Bug:

  • 食物会刷新在蛇身上

  • 蛇可以180度掉头瞬间自杀

  • 游戏速度固定,可玩性差

  • 递归重启容易栈溢出

  • 蛇头蛇身颜色一致,观感差

本篇给大家带来修复全部 Bug、带动态加速、双色蛇身、安全重启、无bug食物生成的高质量升级版贪吃蛇。

代码完全可直接运行,结构规范,适合学习 & 写进项目简历。

二、完整可运行源码

import pygame
import random
import sys

# Initialize pygame
pygame.init()

# Game config
WIDTH, HEIGHT = 600, 400
BLOCK_SIZE = 20
INITIAL_SPEED = 10          # 初始速度(帧率)
MAX_SPEED = 20              # 最大速度(避免过快)
SPEED_INCREMENT = 1         # 每吃5个食物加速一次

# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
DARK_GREEN = (0, 180, 0)    # 蛇身分段颜色
BLUE = (0, 0, 255)

screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Snake Game")
clock = pygame.time.Clock()
font = pygame.font.SysFont(None, 35)

def show_text(msg, color, x, y):
    """在屏幕指定位置显示文字"""
    text = font.render(msg, True, color)
    screen.blit(text, (x, y))

def spawn_food(snake_body):
    """生成不落在蛇身上的食物坐标"""
    while True:
        x = round(random.randrange(0, WIDTH - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE
        y = round(random.randrange(0, HEIGHT - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE
        # 检查是否与蛇身重叠
        if [x, y] not in snake_body:
            return x, y

def game_loop():
    """主游戏循环(每次重启都会全新开始)"""
    # 蛇的初始状态
    x, y = WIDTH // 2, HEIGHT // 2
    dx, dy = 0, 0
    snake_body = []
    snake_len = 1

    # 生成第一个食物
    food_x, food_y = spawn_food(snake_body)

    # 游戏状态标记
    game_over = False
    game_close = False

    # 速度与加速控制
    speed = INITIAL_SPEED
    eat_count = 0           # 用于统计吃了几次食物,决定加速

    while not game_over:
        # ----- 游戏结束界面(暂停循环) -----
        while game_close:
            screen.fill(BLACK)
            show_text("Game Over! Press C to Restart, Q to Quit", RED, 40, HEIGHT//2 - 30)
            show_text(f"Final Score: {snake_len - 1}", WHITE, WIDTH//2 - 60, HEIGHT//2 + 10)
            pygame.display.update()

            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_q:
                        game_over = True
                        game_close = False
                    if event.key == pygame.K_c:
                        return   # 退出当前局,外层循环重启
                if event.type == pygame.QUIT:
                    game_over = True
                    game_close = False

        # ----- 事件处理(键盘输入) -----
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                game_over = True
            if event.type == pygame.KEYDOWN:
                # 防止蛇180度掉头自杀
                if event.key == pygame.K_LEFT and dx == 0:
                    dx = -BLOCK_SIZE
                    dy = 0
                elif event.key == pygame.K_RIGHT and dx == 0:
                    dx = BLOCK_SIZE
                    dy = 0
                elif event.key == pygame.K_UP and dy == 0:
                    dy = -BLOCK_SIZE
                    dx = 0
                elif event.key == pygame.K_DOWN and dy == 0:
                    dy = BLOCK_SIZE
                    dx = 0

        # ----- 更新蛇头位置 -----
        x += dx
        y += dy

        # ----- 墙壁碰撞检测 -----
        if x < 0 or x >= WIDTH or y < 0 or y >= HEIGHT:
            game_close = True

        # 更新蛇身体
        head = [x, y]
        snake_body.append(head)
        if len(snake_body) > snake_len:
            del snake_body[0]

        # ----- 自身碰撞检测 -----
        for segment in snake_body[:-1]:
            if segment == head:
                game_close = True

        # ----- 渲染画面 -----
        screen.fill(BLACK)

        # 绘制食物
        pygame.draw.rect(screen, RED, (food_x, food_y, BLOCK_SIZE, BLOCK_SIZE))

        # 绘制蛇(头亮绿、身深绿)
        for i, segment in enumerate(snake_body):
            color = GREEN if i == len(snake_body)-1 else DARK_GREEN
            pygame.draw.rect(screen, color, (segment[0], segment[1], BLOCK_SIZE, BLOCK_SIZE))

        # 显示信息
        show_text(f"Score: {snake_len - 1}", WHITE, 10, 10)
        show_text(f"Speed: {speed}", WHITE, 10, 40)

        pygame.display.update()

        # ----- 吃食物逻辑 + 动态加速 -----
        if x == food_x and y == food_y:
            snake_len += 1
            eat_count += 1
            # 每吃5个加速一次,不超过最大速度
            if eat_count % 5 == 0 and speed < MAX_SPEED:
                speed += SPEED_INCREMENT
            food_x, food_y = spawn_food(snake_body)

        clock.tick(speed)

    pygame.quit()
    sys.exit()

# 外层循环实现安全重启
if __name__ == "__main__":
    while True:
        game_loop()
    

三、模块技术讲解

1. 项目整体架构

本项目采用典型游戏循环架构

  • 事件监听(键盘、关闭窗口)

  • 逻辑更新(移动、碰撞、吃食物、加速)

  • 画面渲染(清空画布、绘图、刷新)

  • 帧率控制

完全符合标准游戏开发思想,非常适合新手入门游戏开发。

2. 核心技术点:蛇移动原理

贪吃蛇移动并不是整体移动,而是经典的「增头删尾」算法:

  • 每一帧在前进方向新增一个蛇头

  • 没吃到食物 → 删除尾部,保持长度不变,实现前进

  • 吃到食物 → 不删尾部,长度+1,实现变长

这是所有贪吃蛇游戏的通用底层算法。

3. 防自杀移动逻辑

普通代码会出现:向右走瞬间按左键直接反向撞死自己。

本次优化规则:

  • 当前左右移动时,禁止直接左右反向

  • 当前上下移动时,禁止直接上下反向

代码核心:dx == 0 / dy == 0 判断,保证移动方向只能垂直切换。

4. 安全食物生成

旧版本食物可能刷新在蛇身体上,导致开局自动加分。

优化方案:循环随机生成坐标,直到坐标不在蛇身体列表中才返回。

彻底解决食物重叠Bug。

5. 动态加速系统

  • 初始速度:10

  • 每吃5个食物速度+1

  • 最大速度限制20,防止游戏过快无法游玩

让游戏难度平滑递增,可玩性大幅提升。

6. 双色蛇身视觉优化

通过遍历蛇身体下标:

  • 最后一个元素 = 蛇头(亮绿色)

  • 其余 = 身体(深绿色)

玩家可以清晰分辨前进方向,手感更舒服。

7. 安全重启机制(重点工程优化)

很多新手代码用「递归调用 game_loop()」重启,多次游戏会栈溢出崩溃

本版本使用外层 while 死循环

一局结束 return → 外层重新调用,永不栈溢出,工业级稳定。

四、游戏操作说明

  • 上下左右方向键控制移动

  • 吃到红色食物加分、变长、提速

  • 撞墙 / 撞到身体游戏结束

  • C = 重新开始

  • Q = 退出游戏

五、学习总结

通过本项目可以掌握以下编程能力:

  • Pygame 窗口、渲染、事件系统

  • 游戏主循环设计思想

  • 坐标系统与2D运动逻辑

  • 列表模拟动态身体结构

  • 碰撞检测算法

  • 游戏状态机设计(运行、结束)

  • Bug修复与项目优化思维

六、拓展方向

  • 添加背景音乐、吃食物音效

  • 添加障碍物地图

  • 添加最高分本地存储

  • 实现穿墙模式

  • 增加道具、减速、无敌效果

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐