从零开始,用Python打造你的第一个经典游戏:贪吃蛇

 资料合集下载链接:

​​https://pan.quark.cn/s/472bbdfcd014​

游戏玩法回顾与功能分析

在我们敲下第一行代码之前,先来回顾一下贪吃蛇的核心玩法,这正是我们项目需求的来源。

1. 基本操作:玩家通过方向键(或W/S/A/D)控制蛇头的移动方向。
2. 成长机制:蛇会自动向前移动,当蛇头碰到“食物”时,蛇的身体会变长一节。
3. 食物刷新:食物被吃掉后,会在地图的随机位置重新生成。
4. 失败条件

• 蛇头撞到游戏边界(墙壁)。
• 蛇头撞到自己的身体。

5. 游戏结束:一旦满足失败条件,游戏结束。

模块化设计:谋定而后动

一个好的程序离不开清晰的设计。根据笔记的指导,我们将项目划分为以下几个核心模块,让我们的代码结构更加清晰:

• 视图(View)模块:负责创建游戏窗口、绘制地图边界、渲染蛇和食物。这是用户能直接看到的部分。
• 蛇(Snake)模块:管理蛇的一切。包括它的初始位置、身体坐标、移动逻辑、成长逻辑以及死亡判断。
• 食物(Food)模块:负责食物的生成(随机位置)和状态。
• 控制(Control)模块:接收并处理玩家的键盘输入,改变蛇的行进方向。
• 主控(Main)模块:作为游戏的大脑,组织和调度以上所有模块,形成完整的游戏循环。

现在,让我们开始动手实现吧!

环境准备

本项目使用Python语言和​​curses​​库。​​curses​​是一个标准的Python库,用于在类Unix系统(Linux, macOS)上创建基于文本的用户界面(TUI)。

• 对于Linux/macOS用户:​​curses​​通常是Python自带的,无需额外安装。
• 对于Windows用户:​​curses​​不是标准库的一部分。你需要安装一个替代包​​windows-curses​​。

pip install windows-curses
代码实现:一步步构建贪吃蛇

我们将把所有代码写在一个Python文件中。让我们开始吧!

1. 初始化环境和视图模块

首先,我们需要导入​​curses​​和​​random​​库,并初始化游戏窗口。这部分代码实现了视图模块的基础。

import curses
import random

# 1. 初始化屏幕
# stdscr 是 standard screen 的缩写,代表整个终端窗口
stdscr = curses.initscr() 
# 关闭光标显示
curses.curs_set(0) 
# 获取屏幕的宽和高
sh, sw = stdscr.getmaxyx()
# 创建一个新的窗口,大小为 sh, sw,起始位置在 (0, 0)
win = curses.newwin(sh, sw, 0, 0)
# 开启键盘输入模式
win.keypad(1)
# 设置窗口刷新延迟,单位是毫秒。这里设置为100ms,即每秒刷新10次
win.timeout(100)

代码解析

• ​​curses.initscr()​​:初始化​​curses​​环境,返回一个代表整个屏幕的窗口对象。
• ​​curses.curs_set(0)​​:隐藏闪烁的光标,让界面更清爽。
• ​​curses.newwin(...)​​:创建一个新的游戏窗口,我们所有的绘制操作都将在这个窗口上进行。
• ​​win.keypad(1)​​:允许我们使用方向键等特殊按键。
• ​​win.timeout(100)​​:这是一个关键函数。它让​​win.getch()​​(获取键盘输入)变成非阻塞的。程序会等待100毫秒,如果期间没有按键,就继续执行下去,实现了蛇的自动移动。

2. 蛇与食物模块的初始化

接下来,我们要创建蛇和食物。这对应了蛇模块食物模块的初始化部分。

# 2. 蛇模块 和 食物模块 初始化

# 蛇头的初始位置(屏幕中央)
snk_x = sw // 4
snk_y = sh // 2

# 蛇的身体,用一个列表来存储坐标。列表的第一个元素是蛇头。
# 这就是笔记中提到的“二维数组表示蛇的位置”
snake = [
    [snk_y, snk_x],      # 蛇头
    [snk_y, snk_x - 1],  # 蛇身
    [snk_y, snk_x - 2]   # 蛇尾
]

# 食物的初始位置
food = [sh // 2, sw // 2]
# 在窗口中绘制第一个食物,'π' 是我们选择的食物字符
win.addch(int(food[0]), int(food[1]), 'π')

# 蛇的初始移动方向
key = curses.KEY_RIGHT

代码解析

• 我们用一个列表​​snake​​来表示蛇。列表中的每个元素是​​[y, x]​​坐标。​​snake[0]​​永远是蛇头。这种数据结构非常直观,方便我们进行后续的移动和增长操作。
• 食物​​food​​也用一个​​[y, x]​​坐标表示。
• ​​win.addch(y, x, char)​​是​​curses​​的绘图函数,可以在指定坐标绘制一个字符。
• ​​key​​变量存储了蛇当前的前进方向,初始为向右。

3. 游戏主循环:控制与逻辑的核心

这是整个游戏的心脏,它负责接收输入、更新状态、判断逻辑并刷新屏幕。

# 3. 游戏主循环
while True:
    # 3.1 控制模块:获取下一个按键
    next_key = win.getch()
    # 如果没有按键,next_key会是-1,此时保持原有方向;否则更新方向
    key = key if next_key == -1 else next_key

    # 3.2 蛇模块:判断游戏是否结束(撞墙或撞自己)
    # 撞墙判断
    if snake[0][0] in [0, sh-1] or 
       snake[0][1] in [0, sw-1] or 
       snake[0] in snake[1:]: # 撞自己判断
        curses.endwin() # 关闭curses环境
        print("Game Over!")
        quit()

    # 3.3 蛇模块:计算蛇头的新位置
    new_head = [snake[0][0], snake[0][1]]

    if key == curses.KEY_DOWN:
        new_head[0] += 1
    if key == curses.KEY_UP:
        new_head[0] -= 1
    if key == curses.KEY_LEFT:
        new_head[1] -= 1
    if key == curses.KEY_RIGHT:
        new_head[1] += 1

    # 3.4 蛇模块:移动蛇(在列表头部插入新蛇头)
    snake.insert(0, new_head)

    # 3.5 蛇与食物模块:判断是否吃到食物
    if snake[0] == food:
        # 吃到食物,蛇变长(不移除蛇尾),生成新食物
        food = None
        while food is None:
            nf = [
                random.randint(1, sh - 2),
                random.randint(1, sw - 2)
            ]
            # 确保新食物不会生成在蛇身上
            food = nf if nf not in snake else None
        win.addch(food[0], food[1], 'π')
    else:
        # 没吃到食物,蛇正常移动(移除蛇尾)
        tail = snake.pop()
        win.addch(tail[0], tail[1], ' ') # 在原蛇尾位置画一个空格,实现擦除效果

    # 3.6 视图模块:绘制新的蛇头
    win.addch(snake[0][0], snake[0][1], curses.ACS_CKBOARD)

代码解析

• ​​while True:​​ 创建了一个无限循环,游戏会一直运行直到结束条件满足。
• 控制模块 (​​win.getch()​​): 获取键盘输入。注意我们只在有新按键时才更新​​key​​,这防止了玩家快速反向操作(比如从右立刻到左)导致蛇直接撞到自己。
• 死亡判断

• ​​snake[0][0] in [0, sh-1]​​ 检查蛇头是否碰到了上下边界。
• ​​snake[0][1] in [0, sw-1]​​ 检查蛇头是否碰到了左右边界。
• ​​snake[0] in snake[1:]​​ 检查蛇头坐标是否存在于蛇身的其余部分,这是判断是否“咬到自己”的精髓。

• 移动逻辑:我们先计算出下一个蛇头​​new_head​​的位置,然后用​​snake.insert(0, new_head)​​将它加到蛇身体列表的最前面。
• 成长逻辑
• 如果new_head的位置和food一样,说明吃到了食物。此时,我们不执行snake.pop()​,这样蛇的列表长度就增加了1,实现了“变长”。然后我们重新生成一个随机位置的食物。
• 如果没吃到,就执行snake.pop(),移除列表的最后一个元素(蛇尾),并用空格擦除它在屏幕上的痕跡,保持蛇的长度不变。
• 视图渲染win.addch(snake[0][0], snake[0][1], curses.ACS_CKBOARD) 在新蛇头的位置画一个方块字符,代表蛇的身体。

4. 整合完整代码

将以上所有部分组合起来,就是我们完整的贪吃蛇游戏代码。

# snake_game.py

import curses
import random

def main(stdscr):
    # --- 1. 初始化环境和视图模块 ---
    curses.curs_set(0)
    sh, sw = stdscr.getmaxyx()
    win = curses.newwin(sh, sw, 0, 0)
    win.keypad(1)
    win.timeout(100) # 游戏速度,数值越小越快

    # --- 2. 蛇与食物模块的初始化 ---
    snk_x = sw // 4
    snk_y = sh // 2
    snake = [
        [snk_y, snk_x],
        [snk_y, snk_x - 1],
        [snk_y, snk_x - 2]
    ]

    food = [sh // 2, sw // 2]
    win.addch(food[0], food[1], 'π')

    key = curses.KEY_RIGHT
    score = 0
    
    # --- 3. 游戏主循环 ---
    while True:
        # 绘制分数和边框
        win.border(0)
        win.addstr(0, 2, f'Score: {score} ')

        next_key = win.getch()
        
        # 防止蛇反向移动
        if next_key != -1:
            if (key == curses.KEY_RIGHT and next_key != curses.KEY_LEFT) or 
               (key == curses.KEY_LEFT and next_key != curses.KEY_RIGHT) or 
               (key == curses.KEY_UP and next_key != curses.KEY_DOWN) or 
               (key == curses.KEY_DOWN and next_key != curses.KEY_UP):
                key = next_key

        # 判断游戏是否结束
        if snake[0][0] in [0, sh - 1] or 
           snake[0][1] in [0, sw - 1] or 
           snake[0] in snake[1:]:
            curses.endwin()
            print(f"Game Over! Final Score: {score}")
            return

        new_head = [snake[0][0], snake[0][1]]

        if key == curses.KEY_DOWN:
            new_head[0] += 1
        if key == curses.KEY_UP:
            new_head[0] -= 1
        if key == curses.KEY_LEFT:
            new_head[1] -= 1
        if key == curses.KEY_RIGHT:
            new_head[1] += 1

        snake.insert(0, new_head)

        if snake[0] == food:
            score += 10
            food = None
            while food is None:
                nf = [
                    random.randint(1, sh - 2),
                    random.randint(1, sw - 2)
                ]
                food = nf if nf not in snake else None
            win.addch(food[0], food[1], 'π')
        else:
            tail = snake.pop()
            win.addch(tail[0], tail[1], ' ')

        win.addch(snake[0][0], snake[0][1], curses.ACS_CKBOARD)
        win.refresh()

if __name__ == "__main__":
    curses.wrapper(main)

新增内容解析

• 我们把代码封装在​​main​​函数中,并使用​​curses.wrapper(main)​​来启动。这是一个推荐的做法,​​wrapper​​会自动处理​​curses​​的初始化和善后工作(比如程序出错时恢复终端状态)。
• 增加了​​score​​计分功能。
• 增加了​​win.border(0)​​来绘制游戏边界。
• 优化了方向键的判断,防止蛇直接180度掉头。

运行效果

将以上代码保存为​​snake_game.py​​,然后在终端中运行:

python snake_game.py

你将看到如下的界面:

1. 游戏开始
你的终端会变成一个游戏窗口,一条短蛇和一个食物出现在屏幕上。

+------------------------------------------------------------------------------+
| Score: 0                                                                     |
|                                                                              |
|                                                                              |
|                                                                              |
|                         ■■■                                                  |
|                                                                              |
|                                           π                                  |
|                                                                              |
|                                                                              |
+------------------------------------------------------------------------------+

2. 游戏进行中
使用方向键控制蛇移动,吃到食物后,蛇会变长,分数增加,食物会刷新到新的位置。

+------------------------------------------------------------------------------+
| Score: 30                                                                    |
|                                                                              |
|                                     ■                                        |
|                                     ■                                        |
|                         ■■■■■■■■■■■                                          |
|                         ■                                                    |
|                         ■                                                    |
|                         ■                                                    |
|    π                                                                         |
+------------------------------------------------------------------------------+

3. 游戏结束
当蛇撞到墙壁或自己时,程序退出,终端恢复正常,并打印出最终得分。

$ python snake_game.py
Game Over! Final Score: 30
$

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容