用250行代码编一个贪吃蛇游戏

202 阅读4分钟

用250行代码编一个贪吃蛇游戏

"""贪吃蛇"""
# 导入需要的库
import random  # 用来生成随机数
import time  # 用来计时
import sys  # 用来退出Python进程
from typing import NamedTuple  # 用来做蛇身子

import pygame

# 初始化pygame
pygame.init()


class Food(NamedTuple):
    """蛇的食物"""
    x: int
    y: int

    def display(self):
        """将食物绘制在屏幕上"""
        rect = pygame.Rect(0, 0, CELL_SIZE, CELL_SIZE)  # 先将食物移到左上角
        rect.left, rect.top = \
            self.x * CELL_SIZE, self.y * CELL_SIZE  # 然后再移到正确的位置
        pygame.draw.rect(screen, FOOD_COLOR, rect)  # 最后把食物绘制到窗口上去


def new_food():
    """创建食物"""
    food_ = Food(
        random.randint(0, ROWS - 1),  # 生成随机位置
        random.randint(0, COLS - 1)
    )
    while any(food_.x == body.x and food_.y == body.y for body in snake.bodys):
        food_ = Food(
            random.randint(0, ROWS - 1),  # 生成随机位置
            random.randint(0, COLS - 1)
        )
    return food_


def game_over():
    """游戏结束"""
    global game_running
    time.sleep(.5)  # 先暂停0.5秒, 让玩家看清楚游戏结束
    game_running = False  # 将game_running设为False


class SnakeBody(NamedTuple):
    """蛇身子"""
    x: int
    y: int


class Snake:
    """蛇"""
    def __init__(self):
        """初始化"""
        self.direction = 'DOWN'  # 方向
        
        # 随机位置
        start_x = random.randint(10, ROWS - 11)
        start_y = random.randint(10, COLS - 11)

        # 蛇身子列表
        self.bodys = [
            SnakeBody(start_x, start_y),
            SnakeBody(start_x, start_y - 1),
            SnakeBody(start_x, start_y - 2)
        ]

        # 上一次移动时间
        self.prev_time = time.time()

    @ property
    def head(self):
        """蛇头"""
        return self.bodys[0]

    def update(self):
        """刷新"""
        if ((now := time.time()) - self.prev_time) < .1: return  # 如果没到时间就退出

        self.prev_time = now  # 替换上次移动时间
        match self.direction:  # 确定是哪一个方向
            case 'UP':  # 向上
                tail = SnakeBody(
                    self.head.x,
                    self.head.y - 1
                )
            case 'DOWN':  # 向下
                tail = SnakeBody(
                    self.head.x,
                    self.head.y + 1
                )
            case 'LEFT':  # 向左
                tail = SnakeBody(
                    self.head.x - 1,
                    self.head.y
                )
            case _:  # 向右
                tail = SnakeBody(
                    self.head.x + 1,
                    self.head.y
                )
        self.bodys.insert(0, tail)  # 将蛇尾插入蛇头前面

        global food  # 导入food
        if self.head == food:  # 如果碰到食物
            score_board.add_points()  # 那么加分
            food = new_food()  # 变换食物的位置
        else:
            self.bodys.pop()  # 否则删除蛇尾巴

        if any(
                (
                    self.head.x == -1 and self.direction == 'LEFT',
                    self.head.y == -1 and self.direction == 'UP',
                    self.head.x == ROWS and self.direction == 'RIGHT',
                    self.head.y == COLS and self.direction == 'DOWN',
                )
        ):  # 如果蛇头碰到屏幕边缘
            game_over()  # 那么游戏结束

        for body in self.bodys[1:]:  # 检查蛇头是否碰到蛇身子
            if self.head == body:  # 如果蛇头碰到了蛇身子
                game_over()  # 那么游戏结束
                break  # 然后退出


    def display(self):
        """将蛇绘制在屏幕上"""
        for body in self.bodys:  # 一个一个的把身体绘制到屏幕上
            rect = pygame.Rect(0, 0, CELL_SIZE, CELL_SIZE)  # 先将身体移到左上角
            rect.x, rect.y = \
                body.x * CELL_SIZE, body.y * CELL_SIZE  # 然后再移到正确的位置
            pygame.draw.rect(screen, SNAKE_COLOR, rect)  # 最后把蛇绘制到窗口上去


class ScoreBoard:
    """计分板"""
    def __init__(self):
        """初始化"""
        self.score = 0  # 分数
        self.font = pygame.font.Font(FONT, 50)  # 字体

    def add_points(self):
        """加分"""
        self.score += 1000

    def display(self):
        surface = self.font.render(
            format(self.score, ',') + '分',
            1,
            (255, 255, 255)
        )  # 创建surface
        rect = surface.get_rect()
        rect.topright = screen_rect.topright  # 计分板在屏幕右上角

        screen.blit(surface, rect)  # 把计分板绘制到窗口上去


def check_events():
    """检查退出和键盘事件"""
    global game_running, snake, food, score_board  # 导入全局变量

    for event in pygame.event.get():  # 遍历事件
        if event.type == pygame.QUIT:  # 检查玩家是否关闭窗口
            sys.exit()  # 退出Python进程
        elif event.type == pygame.KEYDOWN: # 检查是否按下按键
            if not game_running:  # 如果游戏还没有开始
                game_running = True  # 那么开始游戏
                snake = Snake()  # 蛇
                food = new_food()  # 食物
                score_board = ScoreBoard()  # 计分板
            else:  # 否则检查↑↓←→键
                if event.key == pygame.K_UP:  # ↑
                    snake.direction = 'UP'
                elif event.key == pygame.K_DOWN:  # ↓
                    snake.direction = 'DOWN'
                elif event.key == pygame.K_LEFT:  # ←
                    snake.direction = 'LEFT'
                elif event.key == pygame.K_RIGHT:  # →
                    snake.direction = 'RIGHT'


def display_grid():
    """绘制格子"""
    for x in range(
        CELL_SIZE,
        WINDOW_WIDTH - CELL_SIZE + 1,
        CELL_SIZE
    ):  # 先绘制竖着的线
        pygame.draw.line(screen, LINE_COLOR, (x, 0), (x, WINDOW_HEIGHT))
    for y in range(
        CELL_SIZE,
        WINDOW_HEIGHT - CELL_SIZE + 1,
        CELL_SIZE
    ):  # 再绘制竖着的线
        pygame.draw.line(screen, LINE_COLOR, (0, y), (WINDOW_WIDTH, y))


def display():
    """将东西绘制在窗口上"""
    screen.fill(BG)  # 清空屏幕
    if not game_running:
        screen.blit(title, title_rect)  # 绘制标题
    else:
        display_grid()  # 绘制格子
        snake.display()  # 绘制蛇
        food.display()  # 绘制食物
        score_board.display()  # 绘制计分板
    pygame.display.update()  # 刷新屏幕


# 游戏常量和变量
BG = 0, 0, 0  # 游戏背景颜色
LINE_COLOR = 50, 50, 50  # 线的颜色
FOOD_COLOR = 255, 0, 0  # 食物的颜色
SNAKE_COLOR = 0, 255, 0  # 蛇的颜色
CELL_SIZE = 20  # 格子的大小(像素)
ROWS = 50  # 多少行
COLS = 45  # 多少列
WINDOW_WIDTH = CELL_SIZE * ROWS  # 窗口宽度(像素)
WINDOW_HEIGHT = CELL_SIZE * COLS  # 窗口高度(像素)
FONT = r'C:\Windows\Fonts\simfang.ttf'  # 字体
game_running = False  # 游戏是否在运行中
score = 0  # 游戏分数
snake = Snake()  # 蛇
food = new_food()  # 食物
score_board = ScoreBoard()  # 计分板

# 游戏窗口
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))  # 创建游戏窗口
screen_rect = screen.get_rect()  # 窗口的rect
pygame.display.set_caption('贪吃蛇')  # 设置窗口标题

# 标题
title_font = pygame.font.Font(FONT, 300)  # 创建字体
title_content = '贪吃蛇'  # 标题内容
title = title_font.render(title_content, True, (255, 255, 255))  # 标题
title_rect = title.get_rect()  # 标题的rect
title_rect.center = screen_rect.center  # 将标题放在游戏窗口中间

if __name__ == '__main__':
    # 游戏主循环
    while True:
        check_events()  # 检查事件
        if game_running:
            snake.update()  # 刷新蛇
        display()  # 将东西绘制在窗口上