【Python项目】贪吃蛇小游戏

零、序章

这是一个使用Python写的贪吃蛇游戏。

可以练习一些面向对象的知识。

实现的功能有:

  • 🐍的移动、生长、死亡
  • 食物的刷新、碰撞检测
  • 计分板的刷新、最高分的本地读写

可以改进的地方有:

  • 暂停功能
  • 生长部分代码也许可以继续优化
  • 即使🐍只有3块,理论上绝不可能撞到自己,但方向键按太快仍然会死,估计是碰撞检测的原因

一、游戏主程序

import time
from turtle import Screen
from snake import Snake
from food import Food
from scoreboard import ScoreBoard


def quit_game():
    """
    这是一个负责退出游戏的函数
    :return: 无
    """
    global game_is_on
    game_is_on = False


# 实例化屏幕对象
screen = Screen()
# 设置宽、高
screen.setup(width=600, height=600)
# 设置背景色
screen.bgcolor('Black')
# 设置标题
screen.title('A Snake Game')
# 关闭屏幕实时刷新
screen.tracer(0)

# 实例化计分板对象
scoreboard = ScoreBoard(screen)
# 实例化🐍对象
snake = Snake()
# 计算边界(emmm这个计算边界的函数我不是很确定放哪儿。。。。)
limits = snake.cal_limits(screen)
# 实例化食物对象
dot = Food()
dot.refresh_pos(limits)

# 监听用户键盘行为,上下左右分别控制🐍的上下左右移动
screen.listen()
screen.onkey(key='Up', fun=snake.up)
screen.onkey(key='Down', fun=snake.down)
screen.onkey(key='Left', fun=snake.left)
screen.onkey(key='Right', fun=snake.right)
screen.onkey(key='q', fun=quit_game)

# 使用game_is_on参数控制循环
game_is_on = True
while game_is_on:

    # 每次循环刷新一次屏幕,这是为了保证所有物体(Turtle)移动完了才刷新,防止🐍分裂
    screen.update()
    # 循环间隔设置成1秒
    time.sleep(0.1)
    # 🐍必须保持移动
    snake.move()

    # 碰撞检测,使用turtle自带的distance方法
    if snake.segments[0].distance(dot) <= 10:
        # 撞到了就刷新分数
        scoreboard.refresh_score()
        # 🐍生长
        snake.grow()
        # 食物刷新位置
        dot.refresh_pos(limits)

    # 检测🐍是否死亡
    if snake.is_dead(limits):
        # 刷新🐍
        snake.refresh()
        # 刷新分数板
        scoreboard.score = -1
        scoreboard.refresh_score()
        # 把最高分写入本地存储
        scoreboard.write_highest_score('highest_score.txt')

print('Gameover!')

二、🐍类

import time
from turtle import Turtle, Screen

# 蛇的初始位置
STARTING_POSITION = [(0, 0), (-20, 0), (-40, 0)]
# 记录蛇尾后一块位置,这是为了方便写蛇生长的函数
LAST_POS = (-60, 0)
# 每次移动距离
MOVE_DISTANCE = 20


class Snake:
    def __init__(self):
        # 🐍的身体,用列表存储Turtle对象
        self.segments = []
        # 把尾巴后一格位置单独作为一个属性,为了方便写生长函数
        self.last_pos = LAST_POS
        # 初始先生长三格
        self.create_snake()

    def create_snake(self):
        # 按照初始位置常量实例化三个Turtle对象组成🐍
        for position in STARTING_POSITION:
            new_segment = Turtle(shape='square')
            new_segment.penup()
            new_segment.color('white')
            new_segment.goto(position)
            # 加入身体列表中
            self.segments.append(new_segment)

    def move(self):
        """
        这是一个控制蛇移动的函数,核心思想是每一块要移动到它前面一块的位置,这样只需要头动,后面的就跟着动
        :return:
        """
        # 每次移动要刷新🐍尾后一格的坐标
        self.last_pos = self.segments[-1].pos()
        # 每一块移动到它前面一块的位置
        for seg_num in range(len(self.segments) - 1, 0, -1):
            self.segments[seg_num].goto(self.segments[seg_num - 1].pos())
        # 头往前移动一个MOVE_DISTANCE的长度
        self.segments[0].forward(MOVE_DISTANCE)

    def grow(self):
        """
        这是一个控制🐍生长的函数
        :return: 无
        """
        new_seg = Turtle(shape='square')
        new_seg.penup()
        new_seg.color('white')
        # 这里让新的Turtle对象去🐍尾后一格跟着
        new_seg.goto(self.last_pos)
        self.segments.append(new_seg)

    def refresh(self):
        """
        这是一个刷新🐍的函数
        :return: 无
        """
        # 根据初始位置参数获取🐍初始长度
        n = len(STARTING_POSITION)
        # 其他部分都不要了
        for seg in self.segments[n:]:
            seg.ht()
        self.segments = self.segments[0:n]
        # 回到初始位置
        for i in range(n):
            self.segments[i].goto(STARTING_POSITION[i])
        # 🐍头朝前
        self.segments[0].setheading(0)

    def check_heading(self, target_heading):
        """
        这是一个检测🐍头朝向的函数
        :param target_heading: 目标方向
        :return: 是否和目标方向不一致
        """
        return self.segments[0].heading() != target_heading

    def up(self):
        # 如果🐍头不是朝下,那么允许往上移动,否则会原地掉头,违反贪吃蛇游戏规则,后面函数同理
        if self.check_heading(270):
            self.segments[0].setheading(90)

    def down(self):
        if self.check_heading(90):
            self.segments[0].setheading(270)

    def left(self):
        if self.check_heading(0):
            self.segments[0].setheading(180)

    def right(self):
        if self.check_heading(180):
            self.segments[0].setheading(0)

    def is_hit_body(self):
        # 检测是否和自己的身体相撞,坐标由于浮点数运算原因,经常跑出些奇怪的小数,所以要四舍五入
        x = round(self.segments[0].pos()[0])
        y = round(self.segments[0].pos()[1])
        # 坐标重合即判定为相撞
        if (x, y) in [(round(seg.pos()[0]), round(seg.pos()[1])) for seg in self.segments[1:]]:
            return True

    def cal_limits(self, screen):
        """
        这是一个根据屏幕计算边界的函数,是为了让整个函数更灵活,不用把一些位置写死
        :param screen: 屏幕
        :return: 上下左右四个边界
        """
        up_limit = screen.window_height() / 2
        down_limit = -screen.window_height() / 2
        left_limit = -screen.window_width() / 2
        right_limit = screen.window_width() / 2
        return up_limit, down_limit, left_limit, right_limit

    def is_hit_wall(self, limits):
        """
        这是一个检测是否撞到边界的函数
        :param limits: 边界
        :return: 是否出界
        """
        x = round(self.segments[0].pos()[0])
        y = round(self.segments[0].pos()[1])
        # 超出边界即返回True
        if x < limits[2] or x > limits[3] or y < limits[1] or y > limits[0]:
            return True

    def is_dead(self, limits):
        """
        根据之前的检测撞身体和出界两个函数检测是否死亡
        :param limits: 边界
        :return: 是否死亡
        """
        return self.is_hit_wall(limits) or self.is_hit_body()

三、食物类

from turtle import Turtle
import random

# 食物类,继承自Turtle类
class Food(Turtle):
    def __init__(self):
        super().__init__()
        # 形状改为圆形
        self.shape('circle')
        # 提笔
        self.penup()
        # 颜色改为绿色
        self.color('green')
        # 加速
        self.speed(10000)
        # 改小点
        self.shapesize(stretch_wid=0.5, stretch_len=0.5)


    def refresh_pos(self, screen_limits):
        """
        这是一个刷新食物位置的函数
        :param screen_limits: 屏幕上下左右边界
        :return: 无
        """
        l_limit = screen_limits[2] + 50
        r_limit = screen_limits[3] - 50
        d_limit = screen_limits[1] + 50
        u_limit = screen_limits[0] - 50
        # 随机生成新位置
        pos = random.randint(l_limit, r_limit) // 20 * 20, random.randint(d_limit, u_limit) // 20 * 20
        # 去新位置
        self.goto(pos)

四、计分板类

import os
from turtle import Turtle

# 对齐方式和字体
ALIGNMENT = 'center'
FONT = 'TimesNewRoman', 20, 'normal'

# 计分板类,继承自Turtle类
class ScoreBoard(Turtle):
    def __init__(self, screen):
        super().__init__()
        # 分数属性,默认-1分(因为后面马上就要+1)
        self.score = -1
        # 隐藏自身
        self.ht()
        # 加速,以免显示得太慢
        self.speed(1000)
        # 提笔,以免出现移动轨迹
        self.penup()
        # 抵达屏幕中上方
        self.goto(0, screen.window_height() / 2 - 50)
        # 从本地文件中获取最高分
        self.highest_score = self.get_highest_score()
        # 刷新分数
        self.refresh_score()

    def refresh_score(self):
        """
        负责刷新屏幕内显示分数的函数
        :return: 无
        """
        # 首先要清空之前的分数
        self.clear()
        # 分数+1
        self.score += 1
        # 落笔
        self.pendown()
        # 笔颜色改为白色
        self.pencolor('white')
        # 每次刷新分数时一并刷新最高分,这是为了保证屏幕上的最高分同步刷新
        self.refresh_highest_score()
        # 写到屏幕上
        self.write(arg=f'Score: {self.score} Highest Score: {self.highest_score}', align=ALIGNMENT, font=FONT)

    def get_highest_score(self):
        """
        这是从本地获取最高分的函数
        :return: 获取到的最高分,如果首次运行就是0
        """
        # 使用os模块检测本地文件
        if not os.path.exists('highest_score.txt'):
            # 没有就创建文件并写入0
            with open('highest_score.txt', mode='w', encoding='utf-8') as f:
                f.write('0')
                return 0
        else:
            # 有就读取分数
            with open('highest_score.txt', mode='r', encoding='utf-8') as f:
                return eval(f.read())

    def refresh_highest_score(self):
        """
        这是个刷新计分板最高分属性的函数
        :return: 无
        """
        # 如果发现当前分数超过最高分了,就把当前分数赋给最高分
        if self.score > self.highest_score:
            self.highest_score = self.score

    def write_highest_score(self, path):
        """
        这是一个把最高分写入本地的函数
        :return: 无
        """
        with open(path, mode='w', encoding='utf-8') as f:
            f.write(str(self.highest_score))

五、效果展示

在这里插入图片描述

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sprite.Nym

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值