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

跳转目录

  • 前言
  • 运行示例
  • 程序分析
    • 捕获键盘操作
    • 输出游戏画面
  • 代码分析一
    • 安装运行环境
    • 游戏地图的实现
    • 炸弹的实现
    • 食物的实现
    • 蛇的实现
  • 初步测试
  • 键盘控制的实现
  • 主程序
    • game函数
    • 图形界面显示分数, 得分
    • 结束函数
    • 游戏主函数
  • 源码下载

前言

该程序未使用pygame库, 而是采用keyboard库的on_press()函数捕获键盘操作, 使用windows控制台缓冲区作为图形界面。
第一次写文章,如有错误请谅解

运行示例

在这里插入图片描述


程序分析

捕获键盘操作

Python从控制台读取可以使用input()函数, 但很明显的是, 贪吃蛇游戏需要在游戏运行的同时不断读取键盘操作, 使用input()输入需要每次都使用回车键, 可行性较差
本程序中使用keyboard库中的on_press()函数捕获键盘操作, 并将捕获的键盘操作传递给一个key_envent()函数进一步处理, 进而控制游戏

输出游戏画面

贪吃蛇游戏需要不断的更新和显示游戏画面, 游戏地图可以使用二维矩阵的形式储存, 显示画面时, 将矩阵中的信息转换成字符串并输出即可


代码分析一

安装运行环境

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

游戏地图的实现

创建地图需要的基本信息包括宽度width, 以及高度height
地图的每个格子代表一种元素, 0代表空格, 1代表食物, 2代表炸弹, 3代表蛇的头部, 4代表蛇的身体
对地图的操作包括读取(x, y)位置的元素, 改变(x, y)位置的元素, 以及将列表输出为可以显示的形式

  • 地图的初始化
class Map():def __init__(self, width=10, height=10):if (width < 10):width = 10if (height < 10):height = 10self.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)

在这里插入图片描述

炸弹的实现

对于单个炸弹, 其包含的信息有在地图中的位置x y剩余存在时间life
炸弹生成时位置应该保持随机, 并且只能在空白块处生成

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)	# 随机的存活时间

游戏地图中显然炸弹有多个, 因此创建一个Bombs类用于处理地图中的全部炸弹
每一帧游戏需要对全部炸弹进行更新, 更新操作包含生成新的炸弹, 重新计算炸弹存在时间, 将炸弹显示在地图上

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 tmpfor bomb in self.list:	# 将炸弹显示在地图上map.write(bomb.x, bomb.y, 2)

食物的实现

食物的实现思路与炸弹的实现基本相同, 但是食物可以被蛇吃掉, 所以食物需要增添一个eat()方法

  • 单个食物的实现 不能说和炸弹很相似, 只能说是一模一样
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 / 50if (food.life > 0):self.list.append(food)del tmpfor 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

蛇的实现

既然是贪吃蛇, 最重要的自然是蛇
蛇有两个部分组成, 分别是蛇头head和蛇的身体body, 蛇头需要储存的信息为位置[x, y,]方向, 蛇的身体由多节组成, 每一节身体都需要储存其位置[x, y]

  • 蛇的初始化
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

蛇的主要操作为移动move(), 在移动时会触发各种场景

  1. 蛇头移向空白处, 即移动后蛇头位置处的地图为空白块, 蛇整体移动一格
  2. 蛇头移向食物处, 即移动后蛇头位置处的地图为食物, 蛇长度增长一格并整体前进一格, 同时触发食物的eat()操作, 吃掉该位置处的食物
  3. 蛇头移向炸弹处, 即移动后蛇头位置处的地图为炸弹, 游戏结束
  4. 蛇头移向墙, 即移动后蛇头位置的位置超过地图边界, 游戏结束
  5. 蛇头移向蛇身体, 即移动后蛇头位置处的地图为蛇身体, 游戏结束

蛇向前移动时, 并不需要改变每一部分身体的位置, 只需在身体的最前方添加一节身体, 位置与原蛇头位置相同, 如果蛇没有变长, 删除最后一节蛇尾即可, 如果蛇变长, 不用删除最后一节蛇尾

  • 蛇身体的移动
    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 = directionif (self.__direction == 1):     # 向上self.__head[1] -= 1elif (self.__direction == 2):   # 向下self.__head[1] += 1elif (self.__direction == 3):   # 向左self.__head[0] -= 1elif (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 = Falsetip = "hit the wall"elif (result == 1): # 碰到食物longer = Truetip = "eat food"elif (result == 2): # 碰到炸弹move = Falsetip = "hit the bomb"elif (result == 4): # 碰到蛇尾move = Falsetip = "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"):	# 触发游戏结束条件breakfoods.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")	# 清屏
  • 运行效果
    运行效果

基本上已经正常了, 再加上键盘操作即可控制蛇的移动
但有一个明显的问题, print()+clear操作闪瞎玩家的眼睛会导致屏幕严重闪烁, 产生该问题的原因是清除控制台再重新输出不是瞬间完成, 为解决该问题需要使用双缓冲DoubleBuffer, 当前缓冲区显示, 下一个缓冲区更新完成后直接替换该缓冲区的内容, 即可解决屏幕更新不及时造成的闪烁问题
双缓冲的实现参考Python控制台双缓冲Double Buffer
本文章直接调用Buffers()类, 不再进行额外介绍

键盘控制的实现

到现在为止, 虽然蛇已经可以移动, 吃食物, 游戏判断等等, 但是蛇的移动是不受玩家控制的
控制蛇的移动需要不断读取键盘操作, 并将键盘操作处理后传递给Snake.move()
keyboard中的keyboard.on_press(call)可以绑定一个函数call(x), 每次有按键按下时将会执行call(x), 参数x为键盘事件, 读取x.name即可获得按下按键的名称

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

keyboard.on_press() 绑定key_event()函数后, 每一次按下键盘按键都会执行key_event()函数, 直到程序的主进程退出

keyboard.on_press(key_envent)

主程序

上文中已经实现了游戏的基本流程和键盘操作, 实现游戏的主程序之后即可正常游玩
部分内容本文未作详细解释, 请参考源码使用

game函数

将所有的游戏内流程, 如创建各种对象, 各种对象的更新封装在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 = 0direction = 0score = 0	# 记录游戏得分tip = ""	# 记录游戏退出时的提示次gaming = Truepause = Falsestart_time = time.time()while gaming:	# 如果游戏结束退出循环if (pause):	# 游戏暂停, 休眠一秒后再判断pause的状态, 降低计算消耗start_time += 1	# 休眠时时间不流动time.sleep(1)continueloop_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 += 1elif (move[0] != "just move"):tip = move[0]gaming = Falsebreakfoods.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) % 5time.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):passreturn 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()	# 运行主函数

源码下载

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/50598.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.02亿美元从数字资产基金撤出!BTC价格已经触底!预示下跌趋势即将逆转?

根据CoinShares的一份报告&#xff0c;上周全球数字资产基金流出总额为1.02亿美元&#xff0c;美国数字资金外流共计9800万美元&#xff0c;而欧洲仅为200万美元&#xff0c;这表明美国市场的不稳定程度越来越高。 事实上&#xff0c;数字资产基金是一种投资工具&#xff0c;旨…

c语言 搜索彩票期数,根据福利彩票的规则用c++编写一个买彩票开奖的程序,求程序代码,谢谢...

满意答案 haslis3ila 2016.03.13 采纳率&#xff1a;58% 等级&#xff1a;7 已帮助&#xff1a;611人 1、完整代码如下&#xff0c;已经过VS2012编译及实际测试 2、基本上不存在什么缺憾&#xff0c;唯一的缺憾是很难中奖。#include #include #include #include using names…

电影解说怎么操作?电影解说都用什么配音软件?

我们都知道&#xff0c;短视频现在非常火爆&#xff0c;我们平时刷视频&#xff0c;可以看到很多的电影解说&#xff0c;不仅语言生动&#xff0c;而且配音很逼真&#xff0c;很多人以为电影解说的配音都是自己配音的&#xff0c;其实不然。以目前的市场现状来说&#xff0c;绝…

使用 ChatGPT 生成数据,4 个示例

如今&#xff0c;国家列表和货币列表等标准数据源很容易在互联网上获得。然而&#xff0c;丰富或重新格式化这些数据通常非常耗时。在本文中&#xff0c;我们探讨如何使用 ChatGPT 有效地生成和增强数据。我们将提供示例来演示 ChatGPT 的功能。 示例 1 — 获取 CSV 格式的世界…

java实现双色球生成器

题目要求&#xff1a; 思路&#xff1a; 生成红球&#xff1a; 使用random对象随机生成1-33的数字&#xff0c;使用长度为6的数组接收&#xff0c;比较生成树在数组里是否有重复如果重复就&#xff0c;本次随机数作废&#xff0c;重新生成不重复就放入数组使用Arrays.sort&am…

python彩票号码生成器

突然想到好久没动代码了&#xff0c;手痒痒&#xff0c;但是又不知道弄啥。想了想平时自己闲来无事都会买彩票&#xff0c;路过买两块&#xff0c;中不中全看天意。 但是经常买彩票的时候不知道选什么号码&#xff0c;犹豫的几分钟时间就浪费了&#xff0c;所以写了个彩票号码…

什么是CMP(同意管理平台)与Cookies?

有时访问网站时&#xff0c;会弹出弹窗或下面弹出横条&#xff0c; 这就是CMP。 什么是个人数据&#xff1f; 个人数据或个人信息是可用于识别活着的个人的信息。不同的数据保护法&#xff0c;如GDPR和CCPA&#xff0c;对个人数据的定义不同&#xff0c;但大体上它们都同意这样…

信息学奥赛C++语言:可口可乐

【题目描述】 便利店给出以下的优惠&#xff1a;“每3个空瓶可以换1瓶可口可乐。” 现在&#xff0c;您准备从便利店买一些可口可乐&#xff08;N瓶&#xff09;&#xff0c;您想知道您最多可以从便利店拿到多少瓶可口可乐。下图给出N8的情况。方法是&#xff1a;喝完8瓶可乐之…

喝汽水问题(使用c语言解决)

目录 问题描述 解题方法一 解题思路&#xff1a; 代码实现 解题方法二 解题思路&#xff1a; 代码实现 解题方法三 解题思路: 代码实现 总结 问题描述 喝汽水&#xff0c;1瓶汽水1元&#xff0c;2个空瓶可以换一瓶汽水&#xff0c;给20元&#xff0c;可以喝多少汽水&…

ESP8266接入小爱同学—智能LED台灯或风扇(利用继电器)

ESP8266接入小爱同学—智能LED台灯或风扇 上周用esp8266做了一个语音智能台灯&#xff0c;也参考了很多网上的教程&#xff0c;也不难&#xff0c;挺简单的&#xff0c;在这里分享一下。 首先我用的是arduino IDE对esp8266进行的编译&#xff0c;arduino对8266编译环境的具体…

【Homeassistant 的Node-red插件之小爱同学语音功能开通】

欢迎大家阅读2345VOR的博客【Homeassistant 的Node-red插件之小爱同学语音功能开通】,本页是Homeassistant 的Node-red插件之小爱同学语音功能开通,实现播报任意带变量的文本🥳🥳🥳2345VOR鹏鹏主页: 已获得CSDN《嵌入式领域优质创作者》称号🎉🎉、阿里云《arduino…

esp8266接入米家、小爱同学,附开源app控制

超简单&#xff0c;两步完事 第一步 下载程序到esp8266第二步 绑定到米家第三步 &#xff08;附&#xff09;开源app控制 第一步 下载程序到esp8266 下载地址: 点击下载 本demo 是利用arduino IDE开发&#xff0c;关于arduino IDE 的ESP8266环境配置可参考&#xff1a;环境配…

【IoT物联网全栈之路 ②】如何快速体验,微信公众号配网,天猫精灵和小爱同学双通讯;(附带Demo)

本系列博客学习由非官方人员 半颗心脏 潜心所力所写&#xff0c;仅仅做个人技术交流分享&#xff0c;不做任何商业用途。如有不对之处&#xff0c;请留言&#xff0c;本人及时更改。 1、 开门大篇&#xff0c;xClouds是什么&#xff1f;我什么要做&#xff1f;为什么要开源&…

ESP8266对接巴法云平台实现小爱同学控制开关灯

原理&#xff1a;esp8266连接巴法云平台mqtt服务并订阅主题&#xff0c;通过小爱语音发出指令&#xff0c;相当于mqtt的消息推送&#xff0c;从而进行控制esp8266 。 1. 巴法云平台创建MQTT设备云主题 在控制台新建mqtt设备云主题&#xff0c;字母数字自定义组合即可。 现在支…

小爱同学桌面提醒器开发0基础教程

1、视频效果 小爱同学桌面显示器 学会烧录软件到开发板&#xff0c;会改代码修改wifi信息&#xff0c;我在添加一下你的信息&#xff0c;就可以玩了。 2、实现原理 3、实现步骤&#xff1a; 购买开发板》烧录代码》连接大白服务器&#xff08;服务器对接小爱同学开放平台过程…

Arduino应用开发——通过小爱同学控制灯光

Arduino应用开发——通过小爱同学控制灯光 目录 Arduino应用开发——通过小爱同学控制灯光前言1 工作原理2 硬件准备3 软件准备3.1 Arduino IDE环境搭建3.2 Blinker APP账号注册和使用 4 编写程序5 关联米家APP和Blinker设备6 语音控制测试7 进阶用法7.1 亮度控制7.2 色温控制7…

小爱同学控制单片机或者其它硬件的思路记录

由于小米只能家具生态的协议是不开放的&#xff0c;但是有很多同学想使用小爱同学的语音功能去控制单片机、树莓派或者其他DIY硬件。因此有个下面这个构想。初步验证可行。 实验具备条件 DIY设备联网&#xff08;直接或者间接&#xff09; 小米手机&#xff08;小爱同学&…

小爱同学、Blinker 控制esp32自带灯熄灭---Micropython版本

操作官方案例修改增加小爱同学控制支持&#xff0c;具体代码上传到github可以参考&#xff0c;有帮助的话麻烦请star支持下&#xff0c;有细节问题也麻烦指出和交流 github地址: https://github.com/lonngxiang/xiaoai_blinker_mpy小爱控制eap32、blinker 1、具体实现细节 主…

小爱同学控制美的美居中的家电热水器,空调等

背景 家里大多数家电都是支持接入米家App的&#xff0c;美的家电不能接入小米&#xff0c;电脑安装Home Assistant成功实现小爱语音控制美的燃气热水器。 实现步骤&#xff1a; 1. 安装docker 我的电脑是windows的&#xff0c;那就直接安装docker desktop https://desktop.…

stm8/stm32如何通过ESP8266连接天猫精灵和小爱同学,实现(AT指令)语音助手控制硬件设备

stm8/stm32如何通过ESP8266连接天猫精灵和小爱同学&#xff0c;实现&#xff08;AT指令&#xff09;语音助手控制硬件设备 博主还是菜鸟&#xff0c;只是这段时间DIY弄了这些东西&#xff0c;让同样喜欢动手的朋友有个参考&#xff0c;我这个开发超级简单&#xff0c;用的都是三…