失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 摸鱼宝典(一)——贪吃蛇游戏改版:贪吃龙 · 双龙戏珠小游戏(Python)

摸鱼宝典(一)——贪吃蛇游戏改版:贪吃龙 · 双龙戏珠小游戏(Python)

时间:2023-11-15 06:35:56

相关推荐

摸鱼宝典(一)——贪吃蛇游戏改版:贪吃龙 · 双龙戏珠小游戏(Python)

啊哈哈哈,最近闲来无事,随手写了个小游戏,在学习的时候摸鱼和划水那是必备啊!还等什么?快来体验这款独特的改版贪吃蛇 ——《贪吃龙 ·双龙戏珠》小游戏吧!

【Tip:源码下载链接在文章最下面】

【游戏效果】

游戏是贪吃蛇的改版,怎么样,效果还不错吧?

游戏开始时有两条“龙”,一条绿龙,一条黄龙,分别用 AWSD 和 JIKL 控制上下左右,按空格键开始游戏,每吃掉一个食物,吃掉食物的那条龙长度加一,总分加10,任意一条龙撞墙都会si 。

【教程环节】

【完整源码在后面,可以直接参考】

主要思路

两个类,一个是蛇类Snake,另一个是主类Main,实例化一个Snake类就可以添加一条蛇,我们的目标是两条蛇(毕竟咱也只有两只手),Main类的设定完全只是方便于写代码,让代码很清晰。Snake类有四个方法,move方法用于移动,update方法用于更新,eat方法用于判断是否吃掉食物,dead方法用于判断是否撞墙si亡;Main类有三个方法,start方法用于启动游戏的一些功能,fresh方法用于更新画面,move方法用于接收键盘的消息并传给Snake类的实例化的move方法。

步骤一:引入模块

from tkinter import *# 引入界面化编程模块from random import *# 引入随机数模块

引入两个模块,一个是tkinter模块,用于界面化编程,一个是random模块,用于随机产生贪吃蛇的食物。

步骤二:设置全局变量

Food,Score,Life = [None,None],0,1# 设置初始食物位置、初始分数、是否存活(1==True 0==False)

一个是食物的位置列表,还有一个是分数,最后是生命,设置为全局变量是为了方便后面的代码对其进行修改。

步骤三:Main主类初始化

class Main:'''### 主类---类似于C语言中main函数用类实现只是为了方便,在这个程序中没其他作用'''def __init__(self):'''界面的初始化'''self.game = Tk()# 初始化界面self.game.title('贪吃龙!')# 界面标题self.game.geometry('600x600+300+50') #界面大小及位置(600×600像素,偏移屏幕左上角横向300像素,纵向50像素)self.game.resizable(0,0)# 窗口横向、纵向大小是否可以拉伸(0==False 1==True)self.canvas = Canvas(self.game,bg='black',highlightthickness=0)# 初始化画布控件,背景黑色,高亮边框厚度为0self.canvas.place(width=600,height=600)# 画布控件左上角置于(0,0)[默认值] 宽600像素、高600像素self.canvas.create_text(300,200,text='贪吃龙·双龙戏珠',font=('华文行楷',30,'bold'),fill='#666666')# 字样1self.canvas.create_text(300,300,text='按 AWSD 操控绿龙\n按 JIKL 操控黄龙\n按<空格>键开始游戏',font=('微软雅黑',20,'bold'),fill='#666666',justify='center')# 字样2self.score = self.canvas.create_text(300,400,text='0',font=('微软雅黑',30,'bold'),fill='#666666')# 得分字样self.s1 = Snake(self.game,self.canvas,4,12,'springgreen')# 实例化第一条 Snake(类)self.s2 = Snake(self.game,self.canvas,25,12,'gold')# 实例化第二条 Snake(类) 【有兴趣的可以实例化第N条】self.game.bind('<Key-space>',self.start)# 空格键键盘关联 start 方法self.game.mainloop()# 消息事件循环

编写__init__方法,设置窗口的基本特征,具体每行代码的作用都写在上面的注释中了。那个bind关联的start方法在后面会写到。Snake类也在后面会添加,这里先不具体解释了。

步骤四:编写Main类的start方法、move方法和fresh方法

def start(self,event:Event):# event 参数没有用到但不能删去,为了与前面形成关联'''游戏启动'''self.game.unbind('<Key-space>')# 取消空格键的关联self.move()# 键盘关联开始有反应self.fresh()# 游戏画面开始更新

按下空格键以执行start方法,为防止后面误按空格键,故取消空格键的关联。

def move(self):'''键盘关联'''self.game.bind('<Key-a>',self.s1.move)# a 按键检测self.game.bind('<Key-w>',self.s1.move)# w 按键检测self.game.bind('<Key-s>',self.s1.move)# s 按键检测self.game.bind('<Key-d>',self.s1.move)# d 按键检测self.game.bind('<Key-j>',self.s2.move)# j 按键检测self.game.bind('<Key-i>',self.s2.move)# i 按键检测self.game.bind('<Key-k>',self.s2.move)# k 按键检测self.game.bind('<Key-l>',self.s2.move)# l 按键检测

对于 AWSD 和 JIKL 八个键盘按键,都进行关联并指向Snake类的实例化的move方法。

def fresh(self):'''画面更新'''global Food,Score,Life# 声明为全局变量,方便对它们的修改if Food == [None,None]:# 判断界面上是不是没有食物,没有才会刷新食物x,y = randint(0,29),randint(0,29)# 随机产生食物的坐标 (x*20,y*20)Food = [self.canvas.create_rectangle(20*x+1,20*y+1,20*(x+1)-1,20*(y+1)-1,fill='red',width=0),[x,y]]# 产生食物self.s1.update()# 更新 s1 的位置self.s2.update()# 更新 s2 的位置self.canvas.itemconfigure(self.score,text=str(Score))# 更新分数字样if Life:self.game.after(int(100-Score**0.5),self.fresh)# 如果 Life 不为 0(False) 就继续“更新”

界面的像素大小为600×600,以20×20像素的方格为一个单位,建立一个虚拟坐标,其中0≤x≤29,0≤y≤29,最后一行代码会在全局变量Life不为0时执行,自动循环执行fresh方法,间隔时间为int(100-Score**0.5),也就是说,间隔时间会随着分数的提高而减小。

步骤五:Snake蛇类的初始化

class Snake:'''### 蛇类 ---实例化一个就可以实现一个单独的蛇Example:`s1 = Snake(self.game,self.canvas,4,12,'springgreen')`就可以实现一个颜色为'springgreen',头部左上角位置在(4×20,12*20)的“蛇”'''def __init__(self,tk:Tk,canvas:Canvas,x:int,y:int,color:str):self.game = tkself.canvas = canvasi1 = self.canvas.create_rectangle(20*x+1,20*y+1,20*(x+1)-1,20*(y+1)-1,width=0,fill='purple')# 初始化蛇头i2 = self.canvas.create_rectangle(20*x+1,20*(y+1)+1,20*(x+1)-1,20*(y+2)-1,width=0,fill=color)# 初始化蛇身1i3 = self.canvas.create_rectangle(20*x+1,20*(y+2)+1,20*(x+1)-1,20*(y+3)-1,width=0,fill=color)# 初始化蛇身2i4 = self.canvas.create_rectangle(20*x+1,20*(y+3)+1,20*(x+1)-1,20*(y+4)-1,width=0,fill=color)# 初始化蛇身3i5 = self.canvas.create_rectangle(20*x+1,20*(y+4)+1,20*(x+1)-1,20*(y+5)-1,width=0,fill=color)# 初始化蛇身4self.snake = [[i1,'w'],[i2,'w'],[i3,'w'],[i4,'w'],[i5,'w']]# 将蛇的数据放入蛇数据列表方便分析self.head,self.tail,self.color = [x,y],[x,y+4],color# 记录当前蛇头位置、当前蛇尾位置、蛇身颜色

这里只对蛇的数据列表中单个元素作一下说明,如[i1,'w']中,第一个代表蛇的身体方格,而 ‘w’ 则表示该方格下一次更新的方向,‘w’ 代表向上,‘a’,‘s’ 和 ‘d’ 分别代表向左、向下和向右。

步骤六:Snake类的move方法和update方法

def move(self,event:Event):'''移动的区分'''if event.char in ['a','j']:self.snake[0][1] = 'a'# 按 a 或 j 键向左elif event.char in ['w','i']:self.snake[0][1] = 'w'# 按 w 或 i 键向上elif event.char in ['s','k']:self.snake[0][1] = 's'# 按 s 或 k 键向下elif event.char in ['d','l']:self.snake[0][1] = 'd'# 按 d 或 l 键向右

event.char 代表键盘输入的字符。

def update(self):'''更新蛇的位置'''for k in range(len(self.snake)-1,-1,-1):# 遍历蛇数据列表(必须倒遍历!)dx = 0 if self.snake[k][1] in ['w','s'] else 20 if self.snake[k][1] == 'd' else -20# 解析当前蛇身在水平方向应该的位移dy = 0 if self.snake[k][1] in ['a','d'] else 20 if self.snake[k][1] == 's' else -20# 解析当前蛇身在垂直方向应该的位移self.canvas.move(self.snake[k][0],dx,dy)# 更新当前蛇身位置if k:self.snake[k][1] = self.snake[k-1][1]# 更新当前蛇身下次前行的方向(就是更新为前一个的蛇身的方向)else:# else的情况只能是 k 为 0,也就是蛇头位置hx,hy = self.head# 读取旧蛇头位置self.head = [hx+dx//20,hy+dy//20]# 更新蛇头位置数据if k == len(self.snake)-1:# 判断是否为蛇尾tx,ty = self.tail# 读取旧蛇尾位置self.tail = [tx+dx//20,ty+dy//20]# 更新蛇尾位置数据self.dead()# 判定是否撞墙死亡self.eat()# 判定是否吃掉食物

每次更新的时候,根据Snake类的蛇数据列表 self.snake中每个小列表的第二项来确定该方格单元应移向哪个方向,更新完方格在界面上的位置之后,还要再次更新该小列表的第二项,将其改为下一次更新的方向,而这个方向就是前一个方格单元的移动方向,因此,该蛇数据列表需要倒着更新

同时,在更新蛇数据列表的同时还要判断更新循环中当前更新方格是否为蛇头或蛇尾,更新到这两个地方时,要同时更新蛇头位置存储列表self.head 或者蛇尾位置存储列表 self.tail。蛇头位置存储列表是用于 dead 方法(si亡判定)和 eat 方法(吃掉食物),蛇尾位置存储列表用于 eat 方法为蛇增加长度。

最后,每次更新完后,进行si亡判定(dead方法)和吃掉食物的检测(eat方法)。

步骤七:Snake类的dead方法和eat方法

def dead(self):'''检测是否撞墙死亡,实则是坐标位置越界检测'''global Life# 声明全局变量,方便对其的修改x,y = self.head# 蛇头的位置if not (0<=x<=29 and 0<=y<=29):# 检测蛇头位置是否越界(超出屏幕)Life = 0# Life 设为 0(False) 标识死亡self.canvas.create_text(300,250,text='You\nDead',fill='red',font=('华文新魏',100,'bold'),justify='center')# 产生死亡字样

读取蛇头位置,对其是否越出屏幕界限进行检测,若是则设置全局变量 Life 为0,方便于使其他功能终止(如Main类中的fresh方法),同时显示死亡字样。

def eat(self):'''检测是否吃掉食物,有点类似于碰撞检测(但又没有碰撞检测那么复杂)'''global Food,Score# 声明全局变量,方便对其的修改if self.head == Food[1]:# 当前蛇头位置与食物位置重合,判定为吃掉食物self.canvas.delete(Food[0])# 删去食物的图像Food = [None,None]# 设置食物坐标为空,即屏幕中没有食物了Score += 10# 得分加10x,y = self.tail# 读取当前蛇尾位置,准备给蛇增加长度dx = 0 if self.snake[-1][1] in ['w','s'] else 20 if self.snake[-1][1] == 'd' else -20# 判断水平方向上蛇的位移dy = 0 if self.snake[-1][1] in ['a','d'] else 20 if self.snake[-1][1] == 's' else -20# 判断垂直方向上蛇的位移x -= dx//20;y -= dy//20# 解析新蛇尾应该的实际坐标self.tail = [x,y]# 更新蛇尾坐标为新的蛇尾坐标self.snake.append([self.canvas.create_rectangle(20*x+1,20*y+1,20*(x+1)-1,20*(y+1)-1,width=0,fill=self.color),self.snake[-1][1]])# 蛇数据列表加上新蛇尾的数据

这里要注意的就是,要先删除 Food 在屏幕上的显示,再清空 Food 位置列表,读取蛇尾位置,解析出新蛇尾下一次更新的方向,然后再蛇数据列表上,增加新蛇尾的数据,最后,不要忘了,更新蛇尾位置列表

步骤八:实例化Main类,开始游戏

Main()

放在程序代码的最后,程序从这里开始运行!

大家在看完源码后,也可以自己修改源码,弄出三条龙,四条龙,甚至更多哦!

【完整源码】

from tkinter import *# 引入界面化编程模块from random import *# 引入随机数模块Food,Score,Life = [None,None],0,1# 设置初始食物位置、初始分数、是否存活(1==True 0==False)class Snake:'''### 蛇类 ---实例化一个就可以实现一个单独的蛇Example:`s1 = Snake(self.game,self.canvas,4,12,'springgreen')`就可以实现一个颜色为'springgreen',头部左上角位置在(4×20,12*20)的“蛇”'''def __init__(self,tk:Tk,canvas:Canvas,x:int,y:int,color:str):self.game = tkself.canvas = canvasi1 = self.canvas.create_rectangle(20*x+1,20*y+1,20*(x+1)-1,20*(y+1)-1,width=0,fill='purple')# 初始化蛇头i2 = self.canvas.create_rectangle(20*x+1,20*(y+1)+1,20*(x+1)-1,20*(y+2)-1,width=0,fill=color)# 初始化蛇身1i3 = self.canvas.create_rectangle(20*x+1,20*(y+2)+1,20*(x+1)-1,20*(y+3)-1,width=0,fill=color)# 初始化蛇身2i4 = self.canvas.create_rectangle(20*x+1,20*(y+3)+1,20*(x+1)-1,20*(y+4)-1,width=0,fill=color)# 初始化蛇身3i5 = self.canvas.create_rectangle(20*x+1,20*(y+4)+1,20*(x+1)-1,20*(y+5)-1,width=0,fill=color)# 初始化蛇身4self.snake = [[i1,'w'],[i2,'w'],[i3,'w'],[i4,'w'],[i5,'w']]# 将蛇的数据放入蛇数据列表方便分析self.head,self.tail,self.color = [x,y],[x,y+4],color# 记录当前蛇头位置、当前蛇尾位置、蛇身颜色def eat(self):'''检测是否吃掉食物,有点类似于碰撞检测(但又没有碰撞检测那么复杂)'''global Food,Score# 声明全局变量,方便对其的修改if self.head == Food[1]:# 当前蛇头位置与食物位置重合,判定为吃掉食物self.canvas.delete(Food[0])# 删去食物的图像Food = [None,None]# 设置食物坐标为空,即屏幕中没有食物了Score += 10# 得分加10x,y = self.tail# 读取当前蛇尾位置,准备给蛇增加长度dx = 0 if self.snake[-1][1] in ['w','s'] else 20 if self.snake[-1][1] == 'd' else -20# 判断水平方向上蛇的位移dy = 0 if self.snake[-1][1] in ['a','d'] else 20 if self.snake[-1][1] == 's' else -20# 判断垂直方向上蛇的位移x -= dx//20;y -= dy//20# 解析新蛇尾应该的实际坐标self.tail = [x,y]# 更新蛇尾坐标为新的蛇尾坐标self.snake.append([self.canvas.create_rectangle(20*x+1,20*y+1,20*(x+1)-1,20*(y+1)-1,width=0,fill=self.color),self.snake[-1][1]])# 蛇数据列表加上新蛇尾的数据def dead(self):'''检测是否撞墙死亡,实则是坐标位置越界检测'''global Life# 声明全局变量,方便对其的修改x,y = self.head# 蛇头的位置if not (0<=x<=29 and 0<=y<=29):# 检测蛇头位置是否越界(超出屏幕)Life = 0# Life 设为 0(False) 标识死亡self.canvas.create_text(300,250,text='You\nDead',fill='red',font=('华文新魏',100,'bold'),justify='center')# 产生死亡字样def update(self):'''更新蛇的位置'''for k in range(len(self.snake)-1,-1,-1):# 遍历蛇数据列表(必须倒遍历!)dx = 0 if self.snake[k][1] in ['w','s'] else 20 if self.snake[k][1] == 'd' else -20# 解析当前蛇身在水平方向应该的位移dy = 0 if self.snake[k][1] in ['a','d'] else 20 if self.snake[k][1] == 's' else -20# 解析当前蛇身在垂直方向应该的位移self.canvas.move(self.snake[k][0],dx,dy)# 更新当前蛇身位置if k:self.snake[k][1] = self.snake[k-1][1]# 更新当前蛇身下次前行的方向(就是更新为前一个的蛇身的方向)else:# else的情况只能是 k 为 0,也就是蛇头位置hx,hy = self.head# 读取旧蛇头位置self.head = [hx+dx//20,hy+dy//20]# 更新蛇头位置数据if k == len(self.snake)-1:# 判断是否为蛇尾tx,ty = self.tail# 读取旧蛇尾位置self.tail = [tx+dx//20,ty+dy//20]# 更新蛇尾位置数据self.dead()# 判定是否撞墙死亡self.eat()# 判定是否吃掉食物def move(self,event:Event):'''移动的区分'''if event.char in ['a','j']:self.snake[0][1] = 'a'# 按 a 或 j 键向左elif event.char in ['w','i']:self.snake[0][1] = 'w'# 按 w 或 i 键向上elif event.char in ['s','k']:self.snake[0][1] = 's'# 按 s 或 k 键向下elif event.char in ['d','l']:self.snake[0][1] = 'd'# 按 d 或 l 键向右class Main:'''### 主类---类似于C语言中main函数用类实现只是为了方便,在这个程序中没其他作用'''def __init__(self):'''界面的初始化'''self.game = Tk()# 初始化界面self.game.title('贪吃龙!')# 界面标题self.game.geometry('600x600+300+50') #界面大小及位置(600×600像素,偏移屏幕左上角横向300像素,纵向50像素)self.game.resizable(0,0)# 窗口横向、纵向大小是否可以拉伸(0==False 1==True)self.canvas = Canvas(self.game,bg='black',highlightthickness=0)# 初始化画布控件,背景黑色,高亮边框厚度为0self.canvas.place(width=600,height=600)# 画布控件左上角置于(0,0)[默认值] 宽600像素、高600像素self.canvas.create_text(300,200,text='贪吃龙·双龙戏珠',font=('华文行楷',30,'bold'),fill='#666666')# 字样1self.canvas.create_text(300,300,text='按 AWSD 操控绿龙\n按 JIKL 操控黄龙\n按<空格>键开始游戏',font=('微软雅黑',20,'bold'),fill='#666666',justify='center')# 字样2self.score = self.canvas.create_text(300,400,text='0',font=('微软雅黑',30,'bold'),fill='#666666')# 得分字样self.s1 = Snake(self.game,self.canvas,4,12,'springgreen')# 实例化第一条 Snake(类)self.s2 = Snake(self.game,self.canvas,25,12,'gold')# 实例化第二条 Snake(类) 【有兴趣的可以实例化第N条】self.game.bind('<Key-space>',self.start)# 空格键键盘关联 start 方法self.game.mainloop()# 消息事件循环def start(self,event:Event):# event 参数没有用到但不能删去,为了与前面形成关联'''游戏启动'''self.game.unbind('<Key-space>')# 取消空格键的关联self.move()# 键盘关联开始有反应self.fresh()# 游戏画面开始更新def fresh(self):'''画面更新'''global Food,Score,Life# 声明为全局变量,方便对它们的修改if Food == [None,None]:# 判断界面上是不是没有食物,没有才会刷新食物x,y = randint(0,29),randint(0,29)# 随机产生食物的坐标 (x*20,y*20)Food = [self.canvas.create_rectangle(20*x+1,20*y+1,20*(x+1)-1,20*(y+1)-1,fill='red',width=0),[x,y]]# 产生食物self.s1.update()# 更新 s1 的位置self.s2.update()# 更新 s2 的位置self.canvas.itemconfigure(self.score,text=str(Score))# 更新分数字样if Life:self.game.after(int(100-Score**0.5),self.fresh)# 如果 Life 不为 0(False) 就继续“更新”def move(self):'''键盘关联'''self.game.bind('<Key-a>',self.s1.move)# a 按键检测self.game.bind('<Key-w>',self.s1.move)# w 按键检测self.game.bind('<Key-s>',self.s1.move)# s 按键检测self.game.bind('<Key-d>',self.s1.move)# d 按键检测self.game.bind('<Key-j>',self.s2.move)# j 按键检测self.game.bind('<Key-i>',self.s2.move)# i 按键检测self.game.bind('<Key-k>',self.s2.move)# k 按键检测self.game.bind('<Key-l>',self.s2.move)# l 按键检测Main()# 类似于程序的入口

【下载链接】

蓝奏云:DoubleDragon.zip密码:8j4v

如果觉得《摸鱼宝典(一)——贪吃蛇游戏改版:贪吃龙 · 双龙戏珠小游戏(Python)》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。