Python小游戏开发源码:贪吃蛇与俄罗斯方块代码实现

拾光
拾光
发布时间:2026-10-23| 5人浏览过
原创

贪吃蛇与俄罗斯方块是2D网格游戏开发中最经典的两种状态机模型。本文使用Python官方推荐的游戏开发库Pygame,直接给出两款游戏的完整可执行源码,并深入剖析其网格运动、离散坐标变换、状态刷新与消除判定的核心实现。

一、开发准备与离散网格模型

在编写游戏逻辑之前,需要搭建基础运行环境并理解2D网格坐标系统。两款游戏均基于离散栅格构建:屏幕窗口由像素组成,但游戏内部实体均以虚拟网格坐标(行、列)进行运算,渲染时再按比例转换为屏幕物理像素坐标。

依赖安装与环境说明

本教程基于 Python 3.8 及以上版本。在终端或命令行执行以下命令安装游戏开发库:
pip install pygame
安装完成后,通过 python -m pygame.examples.aliens 可验证库是否正常工作。

Python小游戏开发源码:贪吃蛇与俄罗斯方块代码实现
图1:Pygame 标准开发主循环与操作逻辑流

二、贪吃蛇游戏完整源码与实现解析

贪吃蛇的数据结构由一个表示身体单元坐标的双端列表构成。蛇头的移动过程本质上是:向当前运动方向计算新的头部网格坐标,若新坐标与食物重合,则将该坐标插入列表头部且保留尾部;若未吃到食物,则插入头部并弹出尾部元素(pop())。

Python小游戏开发源码:贪吃蛇与俄罗斯方块代码实现
图2:贪吃蛇网格移动与碰撞判定机制

1. 贪吃蛇完整代码实现

将以下代码保存为 snake_game.py,直接使用 python snake_game.py 运行即可。代码包含平滑的按键响应与计分统计功能:

import sys
import random
import pygame

# 1. 基础配置参数
CELL_SIZE = 20
GRID_WIDTH = 30
GRID_HEIGHT = 20
SCREEN_WIDTH = CELL_SIZE * GRID_WIDTH
SCREEN_HEIGHT = CELL_SIZE * GRID_HEIGHT
FPS = 10

# 2. 颜色定义 (RGB)
COLOR_BG = (250, 247, 241)       # 奶油白背景
COLOR_SNAKE_HEAD = (57, 73, 87)   # 深灰蓝蛇头
COLOR_SNAKE_BODY = (104, 120, 135)# 次灰蓝蛇身
COLOR_FOOD = (173, 139, 88)       # 暗金食物
COLOR_GRID = (226, 219, 208)      # 浅色网格线
COLOR_TEXT = (44, 62, 80)

def spawn_food(snake):
    """在空余网格中随机生成食物坐标"""
    while True:
        pos = (random.randint(0, GRID_WIDTH - 1), random.randint(0, GRID_HEIGHT - 1))
        if pos not in snake:
            return pos

def main():
    pygame.init()
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    pygame.display.set_caption("Python贪吃蛇 - 经典代码实现")
    clock = pygame.time.Clock()
    font = pygame.font.SysFont("PingFang SC", 20)

    # 初始状态: 蛇身居中,向右移动
    snake = [(15, 10), (14, 10), (13, 10)]
    direction = (1, 0)
    food = spawn_food(snake)
    score = 0
    game_over = False

    while True:
        # 事件处理
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if game_over and event.key == pygame.K_SPACE:
                    # 空格键重新开始
                    snake = [(15, 10), (14, 10), (13, 10)]
                    direction = (1, 0)
                    food = spawn_food(snake)
                    score = 0
                    game_over = False
                elif not game_over:
                    # 避免 180 度直接掉头自撞
                    if event.key == pygame.K_UP and direction != (0, 1):
                        direction = (0, -1)
                    elif event.key == pygame.K_DOWN and direction != (0, -1):
                        direction = (0, 1)
                    elif event.key == pygame.K_LEFT and direction != (1, 0):
                        direction = (-1, 0)
                    elif event.key == pygame.K_RIGHT and direction != (-1, 0):
                        direction = (1, 0)

        # 逻辑步进更新
        if not game_over:
            head_x, head_y = snake[0]
            dx, dy = direction
            new_head = (head_x + dx, head_y + dy)

            # 碰撞检测:出界或自撞
            if (new_head[0] < 0 or new_head[0] >= GRID_WIDTH or
                new_head[1] < 0 or new_head[1] >= GRID_HEIGHT or
                new_head in snake):
                game_over = True
            else:
                snake.insert(0, new_head)
                if new_head == food:
                    score += 10
                    food = spawn_food(snake)
                else:
                    snake.pop()

        # 画面渲染
        screen.fill(COLOR_BG)

        # 绘制背景参考栅格
        for x in range(0, SCREEN_WIDTH, CELL_SIZE):
            pygame.draw.line(screen, COLOR_GRID, (x, 0), (x, SCREEN_HEIGHT))
        for y in range(0, SCREEN_HEIGHT, CELL_SIZE):
            pygame.draw.line(screen, COLOR_GRID, (0, y), (SCREEN_WIDTH, y))

        # 绘制食物
        food_rect = pygame.Rect(food[0] * CELL_SIZE + 2, food[1] * CELL_SIZE + 2, CELL_SIZE - 4, CELL_SIZE - 4)
        pygame.draw.rect(screen, COLOR_FOOD, food_rect, border_radius=4)

        # 绘制蛇身
        for index, segment in enumerate(snake):
            seg_rect = pygame.Rect(segment[0] * CELL_SIZE + 1, segment[1] * CELL_SIZE + 1, CELL_SIZE - 2, CELL_SIZE - 2)
            color = COLOR_SNAKE_HEAD if index == 0 else COLOR_SNAKE_BODY
            pygame.draw.rect(screen, color, seg_rect, border_radius=3)

        # 绘制 HUD 得分面板
        score_surf = font.render(f"得分: {score}", True, COLOR_TEXT)
        screen.blit(score_surf, (15, 12))

        if game_over:
            tip_surf = font.render("游戏结束!按空格键重新开始", True, (190, 50, 50))
            tip_rect = tip_surf.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2))
            screen.blit(tip_surf, tip_rect)

        pygame.display.flip()
        clock.tick(FPS)

if __name__ == "__main__":
    main()

三、俄罗斯方块游戏完整源码与实现解析

俄罗斯方块的核心机制较贪吃蛇更为复杂,它包含 7 种经典的四联骨牌(Tetrominoes:I、J、L、O、S、T、Z)。底层存储使用一个 20 行 × 10 列 的二维列表表示已被固定的方块;当前下落的方块由其形状矩阵、旋转朝向以及在全局棋盘上的 (row, col) 偏移量确定。

Python小游戏开发源码:贪吃蛇与俄罗斯方块代码实现
图3:俄罗斯方块核心旋转与消行算法逻辑

2. 俄罗斯方块完整代码实现

将以下代码保存为 tetris_game.py,直接使用 python tetris_game.py 运行。使用左右方向键移动方块,上键顺时针旋转,下键快速下落:

import sys
import random
import pygame

# 1. 棋盘与显示配置
BLOCK_SIZE = 28
GRID_COLS = 10
GRID_ROWS = 20
BOARD_WIDTH = BLOCK_SIZE * GRID_COLS
BOARD_HEIGHT = BLOCK_SIZE * GRID_ROWS
SIDE_PANEL_WIDTH = 160
SCREEN_WIDTH = BOARD_WIDTH + SIDE_PANEL_WIDTH
SCREEN_HEIGHT = BOARD_HEIGHT
FPS = 60

# 2. 配色系统
COLOR_BG = (250, 247, 241)
COLOR_PANEL_BG = (240, 234, 225)
COLOR_GRID = (230, 223, 212)
COLOR_TEXT = (57, 73, 87)
COLOR_SHADOW = (210, 203, 192)

# 7 种方块的经典形态矩阵与对应配色 (RGB)
SHAPES = [
    [[1, 1, 1, 1]], # I
    [[1, 1], [1, 1]], # O
    [[0, 1, 0], [1, 1, 1]], # T
    [[1, 0, 0], [1, 1, 1]], # J
    [[0, 0, 1], [1, 1, 1]], # L
    [[1, 1, 0], [0, 1, 1]], # Z
    [[0, 1, 1], [1, 1, 0]]  # S
]

PIECE_COLORS = [
    (74, 137, 220),  # 冰蓝 (I)
    (235, 185, 78),  # 亮金 (O)
    (155, 89, 182),  # 紫色 (T)
    (52, 152, 219),  # 蔚蓝 (J)
    (230, 126, 34),  # 橙色 (L)
    (231, 76, 60),   # 红色 (Z)
    (46, 204, 113)   # 翡翠绿 (S)
]

def rotate_matrix(matrix):
    """顺时针旋转二维方块矩阵"""
    return [list(row) for row in zip(*matrix[::-1])]

def is_valid_position(board, shape, offset_r, offset_c):
    """检测当前方块在指定偏移位置是否合法(无越界且无重合)"""
    for r, row in enumerate(shape):
        for c, val in enumerate(row):
            if val != 0:
                target_r = offset_r + r
                target_c = offset_c + c
                if target_c < 0 or target_c >= GRID_COLS or target_r >= GRID_ROWS:
                    return False
                if target_r >= 0 and board[target_r][target_c] != 0:
                    return False
    return True

def lock_shape(board, shape, color_id, offset_r, offset_c):
    """将方块固定写入底板矩阵"""
    for r, row in enumerate(shape):
        for c, val in enumerate(row):
            if val != 0 and offset_r + r >= 0:
                board[offset_r + r][offset_c + c] = color_id

def clear_lines(board):
    """扫描并清除满行,返回消除行数"""
    lines_cleared = 0
    new_board = [row for row in board if any(cell == 0 for cell in row)]
    lines_cleared = GRID_ROWS - len(new_board)
    for _ in range(lines_cleared):
        new_board.insert(0, [0] * GRID_COLS)
    return new_board, lines_cleared

def main():
    pygame.init()
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    pygame.display.set_caption("Python俄罗斯方块 - 完整代码实现")
    clock = pygame.time.Clock()
    font = pygame.font.SysFont("PingFang SC", 20)
    title_font = pygame.font.SysFont("PingFang SC", 16, bold=True)

    board = [[0] * GRID_COLS for _ in range(GRID_ROWS)]
    
    # 当前方块生成
    shape_idx = random.randint(0, len(SHAPES) - 1)
    current_shape = SHAPES[shape_idx]
    current_color_id = shape_idx + 1
    current_r = 0
    current_c = GRID_COLS // 2 - len(current_shape[0]) // 2

    drop_timer = 0
    drop_interval = 500  # 自然下落间隔:500ms
    score = 0
    game_over = False

    while True:
        delta_time = clock.tick(FPS)
        drop_timer += delta_time

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if game_over and event.key == pygame.K_SPACE:
                    board = [[0] * GRID_COLS for _ in range(GRID_ROWS)]
                    score = 0
                    game_over = False
                elif not game_over:
                    if event.key == pygame.K_LEFT:
                        if is_valid_position(board, current_shape, current_r, current_c - 1):
                            current_c -= 1
                    elif event.key == pygame.K_RIGHT:
                        if is_valid_position(board, current_shape, current_r, current_c + 1):
                            current_c += 1
                    elif event.key == pygame.K_UP:
                        # 旋转方块
                        rotated = rotate_matrix(current_shape)
                        if is_valid_position(board, rotated, current_r, current_c):
                            current_shape = rotated
                    elif event.key == pygame.K_DOWN:
                        # 加速下落
                        if is_valid_position(board, current_shape, current_r + 1, current_c):
                            current_r += 1

        # 自然下落逻辑
        if not game_over and drop_timer >= drop_interval:
            drop_timer = 0
            if is_valid_position(board, current_shape, current_r + 1, current_c):
                current_r += 1
            else:
                # 触底固定
                lock_shape(board, current_shape, current_color_id, current_r, current_c)
                board, cleared = clear_lines(board)
                if cleared > 0:
                    score += [0, 100, 300, 700, 1500][cleared]

                # 生成新方块
                shape_idx = random.randint(0, len(SHAPES) - 1)
                current_shape = SHAPES[shape_idx]
                current_color_id = shape_idx + 1
                current_r = 0
                current_c = GRID_COLS // 2 - len(current_shape[0]) // 2

                # 初始位置即冲突则判定游戏结束
                if not is_valid_position(board, current_shape, current_r, current_c):
                    game_over = True

        # 渲染区域
        screen.fill(COLOR_BG)

        # 1. 绘制游戏区域底板已固定方块
        for r in range(GRID_ROWS):
            for c in range(GRID_COLS):
                cell_val = board[r][c]
                rect = pygame.Rect(c * BLOCK_SIZE, r * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
                if cell_val != 0:
                    color = PIECE_COLORS[cell_val - 1]
                    pygame.draw.rect(screen, color, rect.inflate(-2, -2), border_radius=3)
                else:
                    pygame.draw.rect(screen, COLOR_GRID, rect, 1)

        # 2. 绘制当前正在下落的方块
        if not game_over:
            for r, row in enumerate(current_shape):
                for c, val in enumerate(row):
                    if val != 0 and current_r + r >= 0:
                        rect = pygame.Rect((current_c + c) * BLOCK_SIZE, (current_r + r) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
                        color = PIECE_COLORS[current_color_id - 1]
                        pygame.draw.rect(screen, color, rect.inflate(-2, -2), border_radius=3)

        # 3. 绘制右侧状态栏面板
        panel_rect = pygame.Rect(BOARD_WIDTH, 0, SIDE_PANEL_WIDTH, SCREEN_HEIGHT)
        pygame.draw.rect(screen, COLOR_PANEL_BG, panel_rect)
        pygame.draw.line(screen, COLOR_GRID, (BOARD_WIDTH, 0), (BOARD_WIDTH, SCREEN_HEIGHT), 2)

        # 面板文字
        t_surf = title_font.render("控制指南", True, COLOR_TEXT)
        screen.blit(t_surf, (BOARD_WIDTH + 20, 30))

        tips = ["↑ : 旋转方块", "←/→ : 平移", "↓ : 加速下落", "空格 : 重启"]
        for i, tip in enumerate(tips):
            tip_surf = font.render(tip, True, (104, 120, 135))
            screen.blit(tip_surf, (BOARD_WIDTH + 20, 65 + i * 28))

        score_title = title_font.render("当前得分", True, COLOR_TEXT)
        screen.blit(score_title, (BOARD_WIDTH + 20, 220))
        score_val = font.render(str(score), True, (173, 139, 88))
        screen.blit(score_val, (BOARD_WIDTH + 20, 250))

        if game_over:
            over_surf = font.render("游戏结束", True, (190, 50, 50))
            screen.blit(over_surf, (BOARD_WIDTH + 20, 320))

        pygame.display.flip()

if __name__ == "__main__":
    main()

四、2D离散网格游戏架构对比与设计总结

贪吃蛇与俄罗斯方块看似玩法迥异,但在系统架构上遵循同一种设计模式——“离散世界状态机模型”。两者的核心差异集中在碰撞判定模式与实体生存周期上:

对比维度 贪吃蛇 (Snake) 俄罗斯方块 (Tetris)
实体结构 单条连续有序的坐标元组双端队列 固定的2D底层矩阵 + 单个局部下落方块矩阵
步进驱动 恒定或渐进的Tick节拍直接驱动整蛇位移 自由落体定时器驱动,支持用户随时介入加速
旋转机制 90度离散向量朝向转变(禁止180度反向) 局部二维矩阵转置与行列逆序翻转
游戏目标 持续吞食增长,避免任何自撞与越界 堆叠规整并触发整行清除,防止矩阵触顶
Python小游戏开发源码:贪吃蛇与俄罗斯方块代码实现
图4:离散网格小游戏通用架构总结
开发注意事项与调试建议

初学者容易将图形绘制坐标与网格数据坐标混淆,请始终保持“逻辑更新只操作整数行列,渲染时统一乘以单元格像素尺寸”的纯净设计。另外,事件捕获必须在主循环中每次完全消费,否则会导致按键卡顿或系统提示窗口无响应。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。