香雨站

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 108|回复: 0

Python完成简易版贪吃蛇小游戏制作(教程)

[复制链接]

1

主题

5

帖子

6

积分

新手上路

Rank: 1

积分
6
发表于 2022-12-17 20:19:48 | 显示全部楼层 |阅读模式
背景:(不想看可以直接看正文)
进入大学学计算机已经有一年了,也已经跟着网上的教程做过C语言版本的贪吃蛇、C++版本的五子棋和扫雷,到最近用Python做的俄罗斯方块,每次虽然都是照着网上的教程敲代码,但是还是有了很多的收获,了解了制作游戏的大致思路,就是在合适的时间把合适的图像和声音展示给玩家(个人观点,可能不正确,轻喷)。总之,照着教程学了那么久,就想着不借助现有的教程来尝试按我自己的思路敲一款游戏出来(当然,查阅相关的知识还是可以的),于是就有了本文。之前用C语言版本的贪吃蛇是跟着网上敲的最简单的版本,就二三十行代码,我发现现在依然看不懂,所以选择重新制作一款贪吃蛇游戏。
注:本文是笔者边实践边敲出来的,所以会尽可能详细地介绍,请放心食用。
一:创建窗口

先导包,tkinter这个包是Python本来就有的,不需要pip install那个命令,它可以实现简单的图形界面。as就是简写,后面写tk就是tkinter的意思。
import tkinter as tk创建窗口(注意:后面是大写的T,小写的k)
win=tk.Tk()#创建窗口先给这个窗口起个标题吧。我这里就叫AC Snake,各位可以随意取名。
win.title("AC Snake")#标题但是如果这样程序运行完窗口就会关闭(反正光写这几行代码看不到窗口),所以就需要把窗口保留下来。
win.mainloop()mainloop函数负责接收操作系统发来的事件,只有程序运行结束,才能退出该函数。
我们运行一下,发现可以得到一个窗口:


但是很快就发现标题显示不全,而且窗口太小,贪吃蛇也不可能在这么小的窗口里玩。
二:基本参数设置

我这里贪吃蛇的蛇身和墙都用正方形的格子来表示,所以先设置一下正方形的边长,还有窗体的高度和宽度。这里的30是像素。
side=30#正方形边长
height=20*side#20个正方形的边长
width=20*side#20个正方形的边长但是此时运行显然是显示不出来的,因为还没有设置进去。
这里可以用geometry函数来设置。
注意两点:一个里面要传的是字符串,一个是字符串是“宽度x高度”
这里可以先设个字符串,然后存入(字符串命名s,额,好随意,请勿模仿)
s=str(width)+"x"+str(height)
win.geometry(s)这样就设置好了窗口。


三:墙和蛇的初始化

    那么,先用灰色的正方形把整个窗口填充一下吧。
     定义初始化函数:
def init():    首先,新建一个画布工具。面积和窗口一样.
   canvas=tk.Canvas(win,height=height,width=width)#新建画布工具然后放置在指定地方
    canvas.pack()#放置在指定地方所有格子贴在一起不好看,设个间距
    margin=4#间距然后填充
    for i in range(20):
        for j in range(20):
            canvas.create_rectangle(i*side,j*side,(i+1)*side-margin,(j+1)*side-margin,fill="grey")注:canvas.create_rectangle这个函数的参数是左上角的横纵坐标(多少像素),右下角的横纵坐标,还有填充的颜色。
在主函数调用init函数的效果展示:


然后把墙设置出来,用黑色表示。
if(i==0 or i==19 or j==0 or j==19):#墙体显示
     canvas.create_rectangle(i*side,j*side,(i+1)*side-margin,(j+1)*side-margin,fill="black")效果展示:


那么,接下来再放蛇进去。我这里把初始位置设为(6,6),读者可以凭喜好设置。
     start_x=6#蛇的初始坐标
     start_y=6蛇最初占两个格子,向右移动,这里初始为黄色,这样明显一点。
if((i==start_x and j==start_y)or (i==start_x+1 and j==start_y)):#蛇的初始移动向右
          canvas.create_rectangle(i*side,j*side,(i+1)*side-margin,(j+1)*side-margin,fill="yellow")效果展示:


这里先将蛇身的坐标保存一下。
在函数外面定义一个空列表snake=[],然后在刚刚初始化蛇身的代码后面,把蛇身保存进列表。
snake.append([i,j])四:随机生成cookie

这里需要一个导包
import random定义一个生成cookie的函数:
#生成cookie
def set_cookie():random.randint可以随机生成[0,19]的整数
ci=random.randint(0,19)#随机生成cookie的横纵坐标
cj=random.randint(0,19)但是,这里有一个问题,就是生成cookie的位置不能是在墙里,也不能在蛇身上。
不在墙里,只要把(0,19)改成(1,18)即可,而不在蛇身则需要做个判断,证明(ci,cj)不在蛇身的那个列表里,如果在的话,就必须重新生成。
    #保证生成的cookie不在蛇身里
    while [ci,cj] in snake:
        ci=random.randint(1,18)#随机生成cookie
        cj=random.randint(1,18)然后返回cookie的坐标。
return [ci,cj]将生成cookie的函数加到一开始的初始化函数的顶端
ci,cj=set_cookie()#生成cookie将cookie上色,在窗口上显示出来,我这里选择的是蓝色。
if i==ci and j==cj:
         canvas.create_rectangle(i*side,j*side,(i+1)*side-margin,(j+1)*side-margin,fill="blue")效果展示:


五:蛇的移动与转向

其实这里就是把列表里的蛇的坐标更新一下,然后在窗口里更新图像即可。
定义蛇移动的函数:
def snake_move():初始化蛇是向右移动的,所以初始移动每次横坐标加1,纵坐标不变。
move=[1,0]#移动方向蛇的移动可以理解为蛇尾的那个坐标删除掉,然后添加上一个蛇头加移动方向的坐标
del(snake[0])
snake.append([snake[len(snake)-1][0]+move[0],snake[len(snake)-1][1]+move[1]])这里可以设置个输出snake来看看效果。


那么怎么显示在窗口上呢?那我们就需要实时刷新窗口。
定义更新窗口的函数:
#更新窗口
def update():刷新窗口
win.update()把蛇尾变成初始的灰色,更新蛇的坐标列表,然后再把新的蛇头上色。
canvas.create_rectangle(snake[0][0]*side,snake[0][1]*side,(snake[0][0]+1)*side-margin,(snake[0][1]+1)*side-margin,fill="grey")
snake_move()
canvas.create_rectangle((snake[len(snake)-1][0])*side,(snake[len(snake)-1][1])*side,(snake[len(snake)-1][0]+1)*side-margin,(snake[len(snake)-1][1]+1)*side-margin,fill="yellow")做到这的时候,发现init函数中的margin作为全局变量会更方便,而画布的初始化不能放在init函数里,而应该放主函数里。不然在这个函数里又得重新新建画布。
每隔一定时间重新调用这个函数,这里设为500毫秒,1秒太慢了
win.after(500,update)把这个更新函数加到初始化的函数末尾发现蛇可以正常移动了。
但是如果无法转向,肯定会撞墙。
所以这里定义一个rotage函数:这里的event是从键盘获取的事件。
#蛇的转向
def rotage(event):keysym是键的符号:
    if event.keysym=='Left':
        move[0]=-1
        move[1]=0
    elif event.keysym=='Right':
        move[0]=1
        move[1]=0
    elif event.keysym=='Up':
        move[0]=0
        move[1]=-1
    elif event.keysym=='Down':
        move[0]=0
        move[1]=1这里就改好了方向。
电脑按键是键盘右下角的方向键:


接着在主函数里完成聚焦和绑定:
canvas.focus_set()#聚焦
canvas.bind("<KeyPress-Left>",rotage)
canvas.bind("<KeyPress-Right>",rotage)
canvas.bind("<KeyPress-Up>",rotage)
canvas.bind("<KeyPress-Down>",rotage)貌似没有问题了,但是这里还少了一个条件。
举个例子:如果你向右的时候是不能向左走的,只能往下或者往上,其他同理。
观察发现,右和左的move[1]一样,上和下的move[0]一样。
所以,将转向的函数改成如下:
#蛇的转向
def rotage(event):
    if move[1] and event.keysym=='Left':
        move[0]=-1
        move[1]=0
    elif move[1] and event.keysym=='Right':
        move[0]=1
        move[1]=0
    elif move[0] and event.keysym=='Up':
        move[0]=0
        move[1]=-1
    elif  move[0] and event.keysym=='Down':
        move[0]=0
        move[1]=1六:蛇吃cookie的相关操作

我们先创建一个空列表来储存cookie
cookie=[]#cookie然后在初始化的时候保存ci,cj
   cookie.append([ci,cj])#保存cookie首先,蛇吃cookie肯定是用蛇头去吃,所以只要判断蛇头和cookie的坐标是否相等即可。
def check():
    if snake[len(snake)-1] == cookie[0]:
        return True
    return False那么,接下来会发生什么呢?
首先会在一个新的地方生成一个新的cookie,然后蛇的长度会增加1。
这里先定义一个eat函数来执行蛇吃cookie之后的操作:
#蛇吃了cookie会发生什么
def eat():前面有随机生成cookie的函数了,所以只要调用一下,然后在画布生成的位置改个颜色即可。
cookie[0][0],cookie[0][1]=set_cookie()
canvas.create_rectangle(cookie[0][0]*side,cookie[0][1]*side,(cookie[0][0]+1)*side-margin,(cookie[0][1]+1)*side-margin,fill="blue")至于蛇吃掉的cookie颜色会被蛇身的颜色覆盖。
蛇的长度增加可以是把蛇尾的后面一格变成蛇的颜色。
一开始想的时候,认为蛇的身子很长的时候,判断蛇尾的移动方向很难,因为只有判断出蛇尾的移动方向,才知道蛇尾的后面一格这个后面到底在哪。也尝试过把蛇头的前面一格变成新的蛇头,但是因为在更新窗口的时候只会把蛇头和蛇尾改变颜色,总之,会出现蛇身的一部分变成初始的灰色的情况。
最后,发现只要调整顺序,即先更新蛇的列表,然后再移动蛇,最后显示在窗口就不会有问题,同时,新加上的蛇尾无论是哪个方向都无所谓,因为不会在窗口里显示。
snake.insert(0,[snake[0][0]-move[0],snake[0][1]-move[1]])当然,这里在调试的时候发现一个bug,如果新加上的蛇尾在墙里的话,会把墙给变成灰色。所以,如果新加上的蛇尾在墙里的话就需要将墙变回黑色。
if snake[0][0]>=19 or snake[0][0]<=0 or snake[0][1]>=19 or snake[0][1]<=0:
        canvas.create_rectangle(snake[0][0]*side,snake[0][1]*side,(snake[0][0]+1)*side-margin,(snake[0][1]+1)*side-margin,fill="black")然后,只要把代码加到更新窗口的函数里即可.
if check():
      eat()七:游戏的结束和计分功能

现在其实游戏基本上就可以玩了,但是可以发现整个游戏无法结束。所以必须设置蛇撞到墙或者撞到自己身子游戏结束。
定义一个判断游戏结束的函数:
#游戏结束
def check_lose():我们其实只需判断蛇头的情况即可。
从列表里搞出蛇头的坐标
    #蛇头的坐标
    x=snake[len(snake)-1][0]
    y=snake[len(snake)-1][1]与墙或者蛇身比较
只要把蛇头去掉就是蛇身了
    #蛇身
    check=[]
    for i in range(len(snake)):
        check.append([snake[0],snake[1]])
    del(check[len(check)-1])如果撞上了,那就输了
if [x,y] in check:
        return True如果撞墙了,那也是输了
if x<=0 or x>=19 or y<=0 or y>=19:
        return True其他就return False,表示没输
在update函数里随时查看,如果输了,就将这个窗口销毁
    if check_lose():
        win.destroy()
        return 不过,可能刚输的时候,人没有反应过来,所以需要停顿两秒
import time
        time.sleep(2000)下面是演示结果:


最后,就是如果玩家想知道自己玩了多少时间,得了多少分呢?怎么办?
安排!
初始化得分为0
score=[0]#得分在蛇吃到cookie的时候加分
score[0]+=10至于时间,先搞个列表
t=[]#时间在初始化的时候获取开始时间存入列表
t.append(time.time())在输掉的时候用同样的方法获取结束时间。
这样分数和时间就都搞到了。
然后显示在弹窗里。
导入messagebox
from tkinter import messagebox然后在游戏结束的时候显示分数和游戏时间
result="Your Score is "+str(score[0])+", time is "+str(int(t[1]-t[0]))+" second"
messagebox.showinfo("Game Over!",result)#弹窗显示分数和时间
最终运行效果展示:(仅仅展示效果,非笔者真实水平)


代码其实不多:


下面是完整代码:(算上空行刚好125行)
#20221014 yun su xiao zi made 尊重原创
import tkinter as tk
from tkinter import messagebox
import random
import time
side=30#正方形边长
height=20*side#20个正方形的边长
width=20*side#20个正方形的边长
margin=4#间距
snake=[]#蛇身的坐标
move=[1,0]#移动方向
cookie=[]#cookie
score=[0]#得分
t=[]#时间

#窗口初始化
def init():
    ci,cj=set_cookie()#生成cookie
    start_x=6#蛇的初始坐标
    start_y=6
    cookie.append([ci,cj])#保存cookie
    for i in range(20):
        for j in range(20):
            canvas.create_rectangle(i*side,j*side,(i+1)*side-margin,(j+1)*side-margin,fill="grey")
            if(i==0 or i==19 or j==0 or j==19):#墙体显示
                 canvas.create_rectangle(i*side,j*side,(i+1)*side-margin,(j+1)*side-margin,fill="black")
            if((i==start_x and j==start_y)or (i==start_x+1 and j==start_y)):#蛇的初始移动向右
                 canvas.create_rectangle(i*side,j*side,(i+1)*side-margin,(j+1)*side-margin,fill="yellow")
                 snake.append([i,j])
            if i==ci and j==cj:
                 canvas.create_rectangle(i*side,j*side,(i+1)*side-margin,(j+1)*side-margin,fill="blue")
    t.append(time.time())#初始时间
    update()
#生成cookie
def set_cookie():
    ci=random.randint(1,18)#随机生成cookie的横纵坐标
    cj=random.randint(1,18)
    #保证生成的cookie不在蛇身里
    while [ci,cj] in snake:
        ci=random.randint(1,18)#随机生成cookie
        cj=random.randint(1,18)
    return [ci,cj]

#蛇移动
def snake_move():
    del(snake[0])
    snake.append([snake[len(snake)-1][0]+move[0],snake[len(snake)-1][1]+move[1]])

#更新窗口
def update():
    win.update()
    if check():
        eat()
    canvas.create_rectangle(snake[0][0]*side,snake[0][1]*side,(snake[0][0]+1)*side-margin,(snake[0][1]+1)*side-margin,fill="grey")
    snake_move()
    canvas.create_rectangle((snake[len(snake)-1][0])*side,(snake[len(snake)-1][1])*side,(snake[len(snake)-1][0]+1)*side-margin,(snake[len(snake)-1][1]+1)*side-margin,fill="yellow")
    if check_lose():
        t.append(time.time())
        result="Your Score is "+str(score[0])+", time is "+str(int(t[1]-t[0]))+" second"
        messagebox.showinfo("Game Over!",result)#弹窗显示分数和时间
        time.sleep(2000)
        win.destroy()
        return
    win.after(500,update)

#蛇的转向
def rotage(event):
    if move[1] and event.keysym=='Left':
        move[0]=-1
        move[1]=0
    elif move[1] and event.keysym=='Right':
        move[0]=1
        move[1]=0
    elif move[0] and event.keysym=='Up':
        move[0]=0
        move[1]=-1
    elif  move[0] and event.keysym=='Down':
        move[0]=0
        move[1]=1

#检查是否吃到cookie
def check():
    if snake[len(snake)-1] == cookie[0]:
        return True
    return False

#蛇吃了cookie会发生什么
def eat():
    score[0]+=10#得分加10分
    cookie[0][0],cookie[0][1]=set_cookie()
    canvas.create_rectangle(cookie[0][0]*side,cookie[0][1]*side,(cookie[0][0]+1)*side-margin,(cookie[0][1]+1)*side-margin,fill="blue")
    snake.insert(0,[snake[0][0]-move[0],snake[0][1]-move[1]])
    if snake[0][0]>=19 or snake[0][0]<=0 or snake[0][1]>=19 or snake[0][1]<=0:
        canvas.create_rectangle(snake[0][0]*side,snake[0][1]*side,(snake[0][0]+1)*side-margin,(snake[0][1]+1)*side-margin,fill="black")

#游戏结束
def check_lose():
    #蛇头的坐标
    x=snake[len(snake)-1][0]
    y=snake[len(snake)-1][1]
    #蛇身
    check=[]
    for i in range(len(snake)):
        check.append([snake[0],snake[1]])
    del(check[len(check)-1])
    if [x,y] in check:
        return True
    if x<=0 or x>=19 or y<=0 or y>=19:
        return True
    return False

#主函数
win=tk.Tk()#创建窗口
win.title("AC Snake")#标题
s=str(width)+"x"+str(height)
win.geometry(s)
canvas=tk.Canvas(win,height=height,width=width)#新建画布工具
canvas.pack()#放置在指定地方
canvas.focus_set()#聚焦
canvas.bind("<KeyPress-Left>",rotage)
canvas.bind("<KeyPress-Right>",rotage)
canvas.bind("<KeyPress-Up>",rotage)
canvas.bind("<KeyPress-Down>",rotage)
init()
win.mainloop()
写在最后:笔者能力有限,若读者朋友们发现bug,欢迎在评论区留言。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|香雨站

GMT+8, 2025-9-15 01:18 , Processed in 0.074743 second(s), 19 queries .

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.. 技术支持 by 巅峰设计

快速回复 返回顶部 返回列表