0
点赞
收藏
分享

微信扫一扫

贪吃蛇Python版 源码+代码分析

跳转目录

前言

运行示例

在这里插入图片描述


程序分析

捕获键盘操作

输出游戏画面


代码分析一

安装运行环境

# 测试python版本为3.9.8
pip install keyboard
pip install win32

游戏地图的实现

  • 地图的初始化
class Map():
    def __init__(self, width=10, height=10):
        if (width < 10):
            width = 10
        if (height < 10):
            height = 10
        self.size = (width, height)
        self.__map = [[0 for i in range(width)] for i in range(height)]
        # 0为空白块,1为食物,2为炸弹,3为蛇头,4为蛇尾
  • 读取某位置的元素
    def read(self, x, y):
        if (x >= 0 and x < self.size[0] and y >= 0 and y < self.size[1]):
            return self.__map[y][x]
        return -1	# 如果该位置超过地图边界, 返回-1
  • 改变某位置的元素
    def write(self, x, y, val=0):
        self.__map[y][x] = val
  • 将地图以可显示形式输出
    def list(self):
        ls = []
        ls.append('# ' * (self.size[0] + 2))	# 地图上边界
        for line in self.__map:
            li = '# '
            for k in line:
                if (k == 0):	# 0表示空白块
                    li += '  '
                elif (k == 1):	# 1表示食物
                    li += "\033[0;32m$\033[0m "
                elif (k == 2):	# 2表示炸弹
                    li += "\033[0;31m@\033[0m "
                elif (k == 3):	# 3表示蛇头
                    li += "\033[0;33m■\033[0m "
                elif (k == 4):# 4代表蛇的身体
                    li += "\033[0;36m■\033[0m "
            li += '#'
            ls.append(li)
        ls.append('# ' * (self.size[0] + 2))	# 地图下边界
        return ls

简单测试打印一下地图

ma = Map(20, 20)
ls = ma.list()
for line in ls:
    print(line)

在这里插入图片描述

炸弹的实现

class Bomb():
    def __init__(self, map : Map):
        self.x = randint(0, map.size[0] - 1)
        self.y = randint(0, map.size[1] - 1)
        while (map.read(self.x, self.y) != 0):	# 读取地图中该位置是否为空格
            self.x = randint(0, map.size[0] - 1)
            self.y = randint(0, map.size[1] - 1)
        self.life = randint(3, 6)	# 随机的存活时间
class Bombs():
    def __init__(self):
        self.list = []	# 储存每一个炸弹的信息

    def update(self, map : Map):
        tmp = self.list.copy()	# 将炸弹的信息拷贝到一个临时列表中
        self.list.clear()
        if (randint(0, 49) == 0):	# 按概率每秒生成一个新的炸弹
            tmp.append(Bomb(map))
        for bomb in tmp:	# 遍历每一个炸弹
            map.write(bomb.x, bomb.y, 0)	# 先将炸弹位置的地图重置
            bomb.life -= 1 / 50	# 计算存在时间
            if (bomb.life > 0):	# 如果存在时间大于零将其加入到炸弹列表中
                self.list.append(bomb)
        del tmp
        for bomb in self.list:	# 将炸弹显示在地图上
            map.write(bomb.x, bomb.y, 2)

食物的实现

  • 单个食物的实现 不能说和炸弹很相似, 只能说是一模一样
class Food():
    def __init__(self, map : Map):
        self.x = randint(0, map.size[0] - 1)
        self.y = randint(0, map.size[1] - 1)
        while (map.read(self.x, self.y) != 0):
            self.x = randint(0, map.size[0] - 1)
            self.y = randint(0, map.size[1] - 1)
        self.life = randint(3, 6)
  • 全部食物信息的实现, 相较于炸弹类, 仅多一个eat()方法
class Foods():
    def __init__(self):
        self.list = []

    def update(self, map : Map):
        tmp = self.list.copy()
        self.list.clear()
        if (randint(0, 49) == 0):
            tmp.append(Food(map))
        for food in tmp:
            map.write(food.x, food.y, 0)
            food.life -= 1 / 50
            if (food.life > 0):
                self.list.append(food)
        del tmp
        for food in self.list:
            map.write(food.x, food.y, 1)

    def eat(self, x, y):	# 将坐标处被吃掉的食物的存在时间变为0, 下一次更新时食物会被删除
        for index, food in enumerate(self.list):
            if (food.x == x and food.y == y):
                self.list[index].life = 0

蛇的实现

  • 蛇的初始化
class Snake():
    def __init__(self, map : Map):
        # [x, y], 创建蛇时需要随机蛇头的位置和方向
        self.__head = [randint(3, map.size[0] - 5), randint(3, map.size[1] - 5)]	# 随机时需要防止太靠近边界导致开局碰墙
        self.__direction = randint(1, 4)
        # [[x, y], [x, y], ....]
        self.__body = []	# 开始游戏时蛇的身体长度为0
  • 蛇身体的移动
    def move(self, map : Map, direction=0):
        self.__body.insert(0, [self.__head[0], self.__head[1]])
        map.write(self.__body[0][0], self.__body[0][1], 4)    # 第一节身体位置移动到原蛇头位置
        map.write(self.__body[-1][0], self.__body[-1][1], 0)    # 删除最后一节蛇尾位置
  • 蛇头根据给定方向移动
        if (direction != 0):	# 为0时表示无方向输入, 按照原来的轨迹移动
            self.__direction = direction
        if (self.__direction == 1):     # 向上
            self.__head[1] -= 1
        elif (self.__direction == 2):   # 向下
            self.__head[1] += 1
        elif (self.__direction == 3):   # 向左
            self.__head[0] -= 1
        elif (self.__direction == 4):   # 向右
            self.__head[0] += 1
  • 读取蛇头移动后位置处地图的情况
        result = map.read(self.__head[0], self.__head[1])   # 移动结果
  • 根据移动情况判断游戏下一步操作
        longer = False	# 是否变长
        move = True	# 是否能够移动
        tip = "just move"	# 提示信息
        if (result == -1):  # 碰墙
            move = False
            tip = "hit the wall"
        elif (result == 1): # 碰到食物
            longer = True
            tip = "eat food"
        elif (result == 2): # 碰到炸弹
            move = False
            tip = "hit the bomb"
        elif (result == 4): # 碰到蛇尾
            move = False
            tip = "eat your body"
        else:
            pass
  • 根据移动情况判断蛇尾是否变化, 以及返回移动信息(提示词, (移动后蛇头的坐标x, y))
        if (move):	# 是否能够移动
            if (not longer):	# 是否变长
                self.__body.pop()
            else:
                map.write(self.__body[-1][0], self.__body[-1][1], 4)
            map.write(self.__head[0], self.__head[1], 3)
        return (tip, (self.__head[0], self.__head[1]))

初步测试

game_map = Map(20, 20)	# 初始化地图
foods = Foods()	# 初始化食物
bombs = Bombs()	# 初始化炸弹
snake = Snake(game_map)	# 初始化蛇
tick = 0	# 游戏刻, 用于控制蛇的移动速度
while True:
    move = ("just move", (0, 0))	# 用来记录蛇move之后的信息
    if (tick == 0):	# 0刻时蛇移动一次
        move = snake.move(game_map, randint(1, 4))
    if (move[0] == "eat food"):	# 吃到食物执行eat()操作
        foods.eat(move[1][0], move[1][1])
    elif (move[0] != "just move"):	# 触发游戏结束条件
        break
    foods.update(game_map)	# 更新食物
    bombs.update(game_map)	# 更新炸弹
    ls = game_map.list()	# 地图可视化
    for line in ls:
        print(line)
    tick = (tick + 1) % 5	# 游戏刻加一
    time.sleep(0.02)	# 控制游戏帧率
    os.system("cls")	# 清屏
  • 运行效果
    运行效果

键盘控制的实现

  • key_event()函数
def key_envent(key):
    global direction	# 全局变量direction, Snake.move()的方向参数
    global gaming	# 全局变量gaming, 记录游戏是否正在运行, 以及结束游戏
    global pause	# 全局变量pause, 用于游戏的暂停操作
    if (key.name == "up"):	# 按上方向键
        direction = 1
    elif (key.name == "down"):	# 按下方向键
        direction = 2
    elif (key.name == "left"):	# 按左方向键
        direction = 3
    elif (key.name == "right"):	# 按右方向键
        direction = 4
    elif (key.name == "space"):	# 按空格键, 暂停/继续
        pause = not pause
    elif (key.name == "esc" and gaming):	# 按ESC键退出游戏
        gaming = False
  • keyboard.on_press() 绑定
keyboard.on_press(key_envent)

主程序

game函数

def game():
    global direction	# 方向
    global gaming		# 游戏是否在进行
    global pause		# 是否暂停
    buffers = Buffers()	# 创建一个双缓冲区用于显示游戏画面
    game_map = Map(20, 20)	# 指定大小创建游戏地图
    bombs = Bombs()
    foods = Foods()
    snake = Snake(game_map)
    tick = 0
    direction = 0
    score = 0	# 记录游戏得分
    tip = ""	# 记录游戏退出时的提示次
    gaming = True
    pause = False
    start_time = time.time()
    while gaming:	# 如果游戏结束退出循环
        if (pause):	# 游戏暂停, 休眠一秒后再判断pause的状态, 降低计算消耗
            start_time += 1	# 休眠时时间不流动
            time.sleep(1)
            continue
        loop_time = time.perf_counter()    # 记录循环开始时间

        move = ("just move", (0, 0))
        if (tick == 0):
            move = snake.move(game_map, direction)
        if (move[0] == "eat food"):
            foods.eat(move[1][0], move[1][1])
            score += 1
        elif (move[0] != "just move"):
            tip = move[0]
            gaming = False
            break
        foods.update(game_map)
        bombs.update(game_map)

        buffers.switch()	# 切换画面缓冲区
        map_ls = show_info(game_map.list(), score, int(time.time() - start_time)) # 在游戏地图后添加游戏时间, 游戏得分, 排版游戏画面
        for line in map_ls:	# 将游戏画面输出到下一个缓冲区
            buffers.print(line+'\n')
        buffers.print("ESC键退出游戏  空格键暂停\\继续")
        buffers.flash()	# 刷新游戏画面

        tick = (tick + 1) % 5
        time.sleep(0.02 - (loop_time - time.perf_counter()))	# 按照固定时间(0.02s)运行游戏程序, 即指定游戏帧数
    end(tip, score, map_ls)	# 执行结束函数显示提示信息

图形界面显示分数, 得分

  • 向该函数输入转换后的地图列表, 游戏时间, 分数信息, 返回一个新的地图列表, 列表中包含游戏的时间 T 和游戏分数 S
def show_info(map_ls, score, game_time):
	pass
	return map_ls

结束函数

def end(tip, score, map_ls):
    os.system("cls")
    for line in map_ls:
        print(line)
    if (tip == "hit the wall"):
        print("\033[0;31m您撞墙后不治身亡!\033[0m")
    elif (tip == "hit the bomb"):
        print("\033[0;31m炸弹真美味, 可惜会爆炸\033[0m")
    elif (tip == "eat your body"):
        print("\033[0;31m您真狠, 饿了连自己都不放过\033[0m")
    elif (tip == ""):
        print("\033[0;31m请问你为什么要退出游戏呢?\033[0m")
    print("\033[0;33m游戏结束\033[0m")
    print("\033[0;34m您的得分为: \033[0;32m{}\033[0m".format(score))
    print("\033[0;33m输入任意内容退出游戏  \033[0;32m输入\033[0;34m空格\033[0;32m重新开始游戏\033[0m")

游戏主函数

def main():
    keyboard.on_press(key_envent)	# 绑定键盘操作
    while True:	# 实现游戏的多次
        game()	# 执行游戏函数
        if (input("\n") != " "):	# 根据输入内容判断是否进行下一次游戏
            break
main()	# 运行主函数

源码下载

希望本文对您有所帮助, 感谢您花时间浏览本文

举报

相关推荐

0 条评论