基于pygame的贪吃蛇小游戏(附源码)

简介

利用pygame实现,可直接运行。

网上大部分都是面向过程直接设计,利用这次做作业的契机正好写一个面向对象的小demo。

逻辑清晰,更利于新手程序员的理解。

运行效果

源码

import random
import time
import pygame
from pygame.locals import *
from collections import deque


class Config:
    """全局参数集合"""
    rate = 0.6  # 界面屏幕比
    assert rate <= 1
    pygame.init()  # pygame 模块的初始化
    screen_info = pygame.display.Info()  # 获得显示器屏幕信息
    block_x = int(20)  # 最小模块的宽度
    block_y = int(20)  # 最小模块的高度
    min_x = int(0)  # 游戏场地的范围
    min_y = int(2)
    max_x = int(screen_info.current_w * rate / block_x)
    max_y = int(screen_info.current_h * rate / block_y)
    screen_width = int(max_x * block_x)  # 界面的大小
    screen_height = int(max_y * block_y)
    Pause = False  # 暂停状态标志位
    GameOver = False  # 游戏结束标志位

    @staticmethod
    def get_location(l: tuple):
        """通过坐标映射到界面实际位置"""
        return l[0] * Config.block_x, l[1] * Config.block_y

    @staticmethod
    def add(location: tuple, x: int, y: int):
        """位置坐标的改变"""
        return location[0] + x, location[1] + y


class Color:
    """各种颜色的RGB值"""
    Light = (100, 100, 100)
    Dark = (200, 200, 200)
    Black = (0, 0, 0)
    Back_Ground = (40, 40, 60)
    bYellow = (255, 215, 0)
    bGreen = (127, 255, 170)
    Red = (255, 0, 0)
    cGreen = (85, 107, 47)
    bRed = (200, 30, 30)
    cRed = (255, 240, 245)
    Maroon = (128, 0, 0)
    DarkCyan = (0, 139, 139)
    DodgerBlue = (30, 144, 255)
    BlueViolet = (138, 43, 226)
    Purple = (128, 0, 128)
    HotPink = (255, 105, 180)


class Towards:
    """朝向坐标枚举"""
    North = (0, -1)
    South = (0, 1)
    West = (-1, 0)
    East = (1, 0)


class Snake:
    """蛇的主题信息类,包括蛇体坐标队列和蛇头朝向"""
    init_place = (int(Config.max_x / 2), int(Config.max_y / 2))  # 初始化位置

    def __init__(self):
        self.towards = Towards.East  # 初始化朝向
        self.body = deque()  # 因为要频繁添加蛇头和删除蛇尾,故使用双向队列
        self.body.append(self.init_place)
        self.body.append(Config.add(self.init_place, -1, 0))
        self.body.append(Config.add(self.init_place, -2, 0))
        self.body.append(Config.add(self.init_place, -3, 0))

    def move(self, _food: tuple):
        """蛇体运动一次,会返回蛇体碰撞和获得食物信息"""
        new_head = Config.add(self.body[0], *self.towards)
        _hit = self.check_hit(new_head)
        get_food = _food[0] == new_head[0] and _food[1] == new_head[1]

        self.body.appendleft(new_head)
        if not get_food:
            self.body.pop()
        return _hit, get_food

    def turn_toward(self, t: tuple):
        """蛇头进行转向"""
        if t[0] != self.towards[0] and t[1] != self.towards[1]:
            self.towards = t

    def check_hit(self, new_head):
        """碰撞检查"""
        return new_head in self.body \
            or new_head[0] < Config.min_x or new_head[0] >= Config.max_x \
            or new_head[1] < Config.min_y or new_head[1] >= Config.max_y

    def draw_snake(self, _screen):
        """绘制蛇体与蛇头"""
        for s in self.body:
            pygame.draw.rect(_screen, Color.bRed, (*Config.get_location(s), Config.block_x, Config.block_y))
        pygame.draw.rect(_screen, Color.bYellow, (*Config.get_location(self.body[0]), Config.block_x, Config.block_y))
        for s in self.body:
            pygame.draw.rect(_screen, Color.Maroon, (*Config.get_location(s), Config.block_x, Config.block_y), 2)


class Food:
    """食物坐标信息"""
    # 食物颜色列表
    food_color = [Color.Light, Color.DarkCyan, Color.DodgerBlue, Color.HotPink, Color.Purple, Color.Dark]

    def __init__(self, snake):
        self.pos = (0, 0)
        self.create_food(snake)

    def create_food(self, snake: deque):
        """随机创建食物坐标,冲突会重新绘制"""
        x = random.randint(Config.min_x, Config.max_x - 1)
        y = random.randint(Config.min_y, Config.max_y - 1)
        while (x, y) in snake:
            x = random.randint(Config.min_x, Config.max_x - 1)
            y = random.randint(Config.min_y, Config.max_y - 1)
        self.pos = (x, y)

    def paint_food(self, _screen):
        """绘制食物,每次会随机变色"""
        color = Food.food_color[random.randint(0, len(Food.food_color) - 1)]
        pygame.draw.rect(_screen, color, (*Config.get_location(self.pos), Config.block_x, Config.block_y))


def draw_window():
    """窗口界面的初始化"""
    obj = pygame.display.set_mode((Config.screen_width, Config.screen_height))
    pygame.display.set_caption("Greedy Snake")
    return obj


def draw_background(_screen):
    """背景的绘制"""
    screen.fill(Color.bGreen)
    for x in range(Config.min_x * Config.block_x, Config.screen_width, Config.block_x):
        pygame.draw.line(_screen, Color.cGreen, (x, Config.min_y * Config.block_y),
                         (x, Config.screen_height), 1)
    for y in range(Config.min_y * Config.block_y, Config.screen_height, Config.block_y):
        pygame.draw.line(_screen, Color.cGreen, (Config.min_x * Config.block_y, y),
                         (Config.screen_width, y), 1)


def draw_score_and_speed(_screen, _score, _speed):
    """分数与速度的绘制"""
    text = f"   score: {_score}     speed: {11 - _speed}        press Space to stop or restart"
    font = pygame.font.SysFont('SimHei', 30)
    _screen.blit(font.render(text, True, Color.Black), (0, 0))


def init(_screen):
    """初始化界面"""
    text = "Press Space to start"
    size = int(Config.screen_width / 20)
    font = pygame.font.SysFont('SimHei', size)
    tx, ty = font.size(text)
    screen.fill(Color.bGreen)
    _screen.blit(font.render(text, True, Color.Black),
                 ((Config.screen_width - tx) // 2, (Config.screen_height - ty) // 2))
    pygame.display.update()
    Config.GameOver = False
    Config.Pause = True
    wait_until_no_pause()


def GameOver(_screen):
    """游戏结束界面"""
    text1 = "Game Over!"
    text2 = "Press Q to quit or space restart"
    size = int(Config.screen_width / 10)
    font1 = pygame.font.SysFont(None, size)
    font2 = pygame.font.SysFont(None, size // 2)
    tx1, ty1 = font1.size(text1)
    tx2, ty2 = font2.size(text2)
    screen.fill(Color.bRed)
    _screen.blit(font1.render(text1, True, Color.Black),
                 ((Config.screen_width - tx1) // 2, (Config.screen_height - ty1) // 2))
    _screen.blit(font2.render(text2, True, Color.Black),
                 ((Config.screen_width - tx2) // 2, (Config.screen_height - ty2) // 3 * 2))
    pygame.display.update()
    while True:  # 等待按键响应
        event = pygame.event.wait()
        if event.type == pygame.QUIT:
            shutdown()
        elif event.type == KEYDOWN and event.key == K_SPACE:
            return False
        elif event.type == KEYDOWN and event.key == K_q:
            return True


def shutdown():
    """结束程序"""
    pygame.quit()
    exit(1)


def change_speed(_score: int):
    """速度改变函数"""
    ret = 10 - int(_score / 10)
    if ret < 4:
        ret = 4
    return ret


def wait_until_no_pause():
    """暂停等待函数"""
    if not Config.Pause:
        return
    while True:
        event = pygame.event.wait()
        if event.type == pygame.QUIT:
            shutdown()
        elif event.type == KEYDOWN and event.key == K_SPACE:
            Config.Pause = False
            return


def paint_speed_control(last_time, _speed):
    """判断是否到达刷新时间"""
    target_time = last_time + 0.02 * _speed
    if target_time <= time.time():
        return True
    return False


def event_loop(snake):
    """按键事件监视"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            shutdown()
        elif event.type == KEYDOWN:
            if event.key == K_SPACE:
                Config.Pause = True
            elif event.key in (K_UP, K_w):
                snake.turn_toward(Towards.North)
            elif event.key in (K_DOWN, K_s):
                snake.turn_toward(Towards.South)
            elif event.key in (K_LEFT, K_a):
                snake.turn_toward(Towards.West)
            elif event.key in (K_RIGHT, K_d):
                snake.turn_toward(Towards.East)


def run(_screen):
    """逻辑运行函数"""
    snake = Snake()
    food = Food(snake.body)
    score = 0
    speed = 10
    last_flush_time = time.time()
    draw_background(screen)
    pygame.display.update()
    while not Config.GameOver:
        wait_until_no_pause()
        while not Config.Pause:
            if paint_speed_control(last_flush_time, speed):  # 到达刷新时间后重新绘制屏幕
                last_flush_time = time.time()
                draw_background(_screen)
                snake.draw_snake(_screen)
                food.paint_food(_screen)
                draw_score_and_speed(_screen, score, speed)
                pygame.display.update()
                Config.GameOver, food_flush = snake.move(food.pos)
                if Config.GameOver:
                    return
                if food_flush:
                    food.create_food(snake.body)
                    score += 1
                    speed = change_speed(score)
            event_loop(snake)  # 为了即时的控制


if __name__ == '__main__':
    screen = draw_window()
    while True:
        init(screen)
        run(screen)
        if GameOver(screen):  # 判断是否要重新开始游戏
            break
    shutdown()

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值