一、概述

实现的功能有:
苹果随机出现,且当苹果被吃掉时消失,再随机生成一个苹果;
蛇的初始长度为6个block,初始方向向东;
蛇通过方向键控制方向;
当蛇吃掉一个苹果后,身体会增加一个block;
当蛇撞墙或者撞到身体后游戏结束;
游戏中按空格暂停,结束后按回车再玩一次;
每吃5个苹果游戏速度会提速,并且蛇会变颜色。

主要逻辑图大致如下:

python贪吃蛇课程设计 基于python的贪吃蛇游戏_贪吃蛇


程序效果图:

python贪吃蛇课程设计 基于python的贪吃蛇游戏_事件循环_02

二、完整代码

# 使用turtle做一个贪吃蛇游戏

import turtle as t
import random 


WIN_WIDTH = 500  # 窗口宽度
WIN_HEIGHT = 500 # 窗口高度
SPOT_WIDTH = 10  # 贪吃蛇的宽度(方格的宽度)
isPause = False  # 是否暂停


# 定义贪吃蛇类
class HungrySnake(object):
    """docstring for HungrySnake"""
    x_add = SPOT_WIDTH # 贪吃蛇x坐标增量,改变移动方向
    y_add = 0  # 贪吃蛇y坐标增量
    #贪吃蛇坐标初始化,长度为六格
    snakePos = [(0, 0), (SPOT_WIDTH, 0), (SPOT_WIDTH*2, 0), \
            (SPOT_WIDTH*3, 0), (SPOT_WIDTH*4, 0), (SPOT_WIDTH*5, 0)]
    speed = 1  # 速度
    eatNum = 0 # 吃了几个苹果
   
    # 苹果x坐标(-25,23)*10
    x_apple = random.randint(int(-1*WIN_WIDTH/20), int(WIN_WIDTH/20-2)) * 10
    # 苹果y坐标(-23,25)*10
    y_apple = random.randint(int(-1*WIN_WIDTH/20+2), int(WIN_WIDTH/20)) * 10 

    # turtle颜色库
    COLOR = ('black', 'grey', 'brown', 'orange', 'gold', 'olive', 'tomato', 'yellowgreen', 'lightgreen',\
     'green', 'aquamarine', 'teal', 'deepskyblue', 'blue', 'violet', 'purple', 'pink', 'fuchsia')
    # 贪吃蛇的颜色
    snakeColor = "black"
   
    # 构造函数,不用
    def __init__(self):
        super(HungrySnake, self).__init__()

    # 画方格,(x,y)为坐标,width为宽度,color为颜色
    def drawSpot(self, x, y, width, color):
        t.penup()
        t.goto(x, y)
        t.pendown()
        t.color(color)  # 设定填充颜色
        t.begin_fill()  # 申明开始填充
        for i in range(4):
            t.forward(SPOT_WIDTH)
            t.right(90)
        t.end_fill()   # 申明结束填充
        t.penup()

    # 画贪吃蛇
    def drawSnake(self, color="black"):
        for spot in self.snakePos:
            self.drawSpot(spot[0], spot[1], SPOT_WIDTH, color)

    # 贪吃蛇移动一步
    def moveStep(self):
        self.snakePos.pop(0)  # 去掉尾
        self.snakePos.append((self.snakePos[-1][0] + self.x_add, self.snakePos[-1][1] + self.y_add)) # 增加头

    # 改变贪吃蛇运行方向的矢量
    def changeDirection(self, x_add, y_add):
        # 注意不能使用self.x_add = x_add, 这个是改变实例变量,不能改变类的变量
        HungrySnake.x_add = x_add
        HungrySnake.y_add = y_add

    # 判断是否碰撞
    def isCrash(self):
        # 判断是否撞墙:左上角(-250, 250), 右下角(230, -230)
        if self.snakePos[-1][0] < int(-1*WIN_WIDTH/20)*10 or self.snakePos[-1][0] > int(WIN_WIDTH/20-2)*10 or \
        self.snakePos[-1][1] < int(-1*WIN_WIDTH/20+2)*10 or self.snakePos[-1][1] > int(WIN_WIDTH/20)*10:
            return True
        # 判断是否撞到蛇自己的身体
        elif self.snakePos[-1] in self.snakePos[:-1]:
            return True
        else: # 啥也没撞到
            return False

    # 随机产生一个苹果
    def generateApple(self):
        # 苹果x坐标(-25,23)*10
        x_apple = random.randint(int(-1*WIN_WIDTH/20), int(WIN_WIDTH/20-2)) * 10
        # 苹果y坐标(-23,25)*10
        y_apple = random.randint(int(-1*WIN_WIDTH/20+2), int(WIN_WIDTH/20)) * 10
        HungrySnake.x_apple = x_apple
        HungrySnake.y_apple = y_apple
        return (x_apple, y_apple)

     # 吃到苹果, 蛇身变长,再append放到蛇头
    def eatApple(self):
        self.snakePos.append(self.snakePos[-1])
        HungrySnake.eatNum += 1  # 苹果数量加1
        HungrySnake.speed = 1 + 0.5 * (HungrySnake.eatNum//5) # 每吃5个提速一次
        if HungrySnake.eatNum % 5 == 0: # 每吃5个改变一次颜色
            HungrySnake.snakeColor = random.choice(HungrySnake.COLOR)

    # 显示苹果
    def showApple(self):
        # 如果吃到了苹果,就重新生产一个苹果
        if self.snakePos[-1] == (HungrySnake.x_apple, HungrySnake.y_apple):
            self.eatApple()
            self.generateApple()
        else: # 如果没有吃到苹果,就显示苹果
            self.drawSpot(HungrySnake.x_apple, HungrySnake.y_apple, SPOT_WIDTH, "red")
        print("apple:{}".format((HungrySnake.x_apple, HungrySnake.y_apple)))

# 显示信息
def showMsg():
    t.pencolor("gray")
    t.goto(-115, 120)
    t.write("空格键:暂停/开始", font=("Arial", 16, 'normal'))
    t.goto(-115, 90)
    t.write("回车键:再玩一次!", font=("Arial", 16, 'normal'))
    t.goto(-60, 60)
    t.write("得分:{}".format(HungrySnake.eatNum), font=("Arial", 16, 'normal'))

# 游戏主循环
def game_loop():
    t.clear()  # 清除屏幕
    snake = HungrySnake()  # 实例化
    snake.showApple()  # 显示苹果
    snake.drawSnake(snake.snakeColor) # 画贪吃蛇
    snake.moveStep()   # 贪吃蛇移动一步
    
    # print("snake2:{}".format(snake))
    print("snake:{}".format(snake.snakePos))  # 打印贪吃蛇坐标
    print("direction:{}".format((snake.x_add, snake.y_add)))  # 前进方向
    print("crash:{}".format(snake.isCrash()))  # 打印是否撞到
    print("eatnum:{}".format(snake.eatNum))
    print("speed:{}".format(snake.speed))

    if not snake.isCrash() and not isPause: # 如果没有撞到且没有暂停,就不断循环
        # 安装一个计时器,在 t 毫秒后调用 fun 函数 ,只执行一次
        t.ontimer(game_loop, int(200/snake.speed))
    elif snake.isCrash(): # 如果撞到,蛇头画红色,游戏结束
        # 移动前蛇头画红色
        snake.drawSpot(snake.snakePos[-2][0], snake.snakePos[-2][1], SPOT_WIDTH, "red")
        showMsg()  # 显示信息
        t.done()  # 开始事件循环 - 调用 Tkinter 的 mainloop 函数
    else:
        pass

# 设置游戏暂停
def setPause():
    # 没撞到了才能暂停
    if not HungrySnake().isCrash():
        global isPause
        isPause = not isPause
        print("isPause:{}".format(isPause))
        game_loop()

# 游戏重新开始
def restart():
    # 撞到了才能重新游戏
    if HungrySnake().isCrash():
        # 恢复初始参数
        HungrySnake.x_add = SPOT_WIDTH
        HungrySnake.y_add = 0
        HungrySnake.snakePos = [(0, 0), (SPOT_WIDTH, 0), (SPOT_WIDTH*2, 0), \
                (SPOT_WIDTH*3, 0), (SPOT_WIDTH*4, 0), (SPOT_WIDTH*5, 0)] 
        HungrySnake.speed = 1
        HungrySnake.eatNum = 0
        HungrySnake.snakeColor = "black"
        HungrySnake().generateApple()
        game_loop() # 重新开始游戏

# 主函数
def main():
    t.setup(WIN_WIDTH, WIN_HEIGHT) # 设置窗口大小
    t.title("贪吃蛇") # 窗口标题
    t.hideturtle()  # 隐藏海龟
    t.tracer(False) # 启用/禁用海龟动画并设置刷新图形的延迟时间
    t.listen()  # 开始监听按键事件

    # 绑定 fun 指定的函数到指定键的按下事件,反应较快。使用lambda函数
    t.onkeypress(lambda: HungrySnake().changeDirection((-1)*SPOT_WIDTH, 0), 'Left') # 左
    t.onkeypress(lambda: HungrySnake().changeDirection(SPOT_WIDTH, 0), 'Right')     # 右
    t.onkeypress(lambda: HungrySnake().changeDirection(0, SPOT_WIDTH), 'Up')        # 上
    t.onkeypress(lambda: HungrySnake().changeDirection(0, (-1)*SPOT_WIDTH), 'Down') # 下
    t.onkeypress(lambda: setPause(), 'space')  # 空格,暂停游戏
    t.onkeypress(lambda: restart(), 'Return')  # 回车,重新游戏

    game_loop()  # 进入游戏主循环
    t.done()     # 开始事件循环 - 调用 Tkinter 的 mainloop 函数


if __name__ == '__main__':
    main()