前言
本人最近在学习python语言,发现python是一门很有意思的语音。python有大量的库,大量的函数,可以实现非常多的功能。尤其是在可视化方面,可以画图,可以弹出窗口。于是我就想着看能不能用python编写一个扫雷游戏。我看网上很多用python实现的扫雷游戏,都用了很多类(class)的知识。但是由于我对于类的掌握不太好,所以我没有采用类的写法,而是采用在函数中调用全局变量的做法来实现一些功能。
扫雷介绍
《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。其规则简单易上手。
设计与分析
为了实现游戏的功能,首先引入tkinter库。该库可以实现建立窗口、弹窗等功能,很适合用在扫雷游戏上。游戏总体分为两个界面,目录界面和游戏界面。目录界面分为“简单”“中等”“困难”三个按钮,用于玩家选择游戏难度。在游戏界面,用一个个按钮来表示扫雷的格子。初始随机生成雷,埋在这些区域。点到不同类型的格子会有不同的结果。如果点到雷所在的格子,游戏直接结束。如果点到的是周围有雷的格子,就会显示周围的雷数。如果点到周围没有雷的格子,就利用广度优先搜索把这一片区域都显示出来。每点开一个格子,都判断一次剩下的格子数是否等于总雷数,若相等,则判定玩家获胜。然后回到目录界面,重新开始下一局游戏。
为了优化玩家游戏体验,在游戏界面新增了两个按钮“退出”和“重新开始”。退出按钮使得玩家在游戏过程中可以随时退出游戏。“重新开始”按钮可以让玩家在想调整难度或者想重置游戏的时候,回到目录界面,开始下一局游戏。
程序实现
为了帮助大家看懂代码,我在代码中加入了大量注释
from tkinter import * # 导入tkinter库,该库可以实现建立一个窗口等功能,是实现扫雷游戏的基础
from tkinter import messagebox # messagebox功能使得可以建立弹窗
import random # 用来生成初始的雷
def menu(): # 用来生成目录窗口
mulu.title("目录") # 给窗口取名为目录
mulu.geometry("400x400") # 设置目录窗口的大小
anniu1 = Button(width=10, height=3, background="grey", command=lambda: zhuchengxu(10, 10, 10), text="简单")
anniu2 = Button(width=10, height=3, background="grey", command=lambda: zhuchengxu(16, 16, 40), text="中等")
anniu3 = Button(width=10, height=3, background="grey", command=lambda: zhuchengxu(30, 16, 99), text="困难")
# 生成三个按钮,分别代表三种难度
anniu1.place(x=150, y=100)
anniu2.place(x=150, y=200)
anniu3.place(x=150, y=300) #将三个按钮显示出来,并设定三个按钮的所在位置
mulu.mainloop() #在目录窗口的各个参数设定完毕后,显示目录窗口
def chongqi(): # 重启函数,用于游戏的重新开始,结束游戏,回到目录窗口
global root,b,d,mulu # 引入全局变量
root.destroy() # 结束游戏窗口
for i in range(100):
for j in range(100):
d[i][j] = 0
b[i][j] = 0 # 将游戏中的一些参数重置
mulu=Tk() # 建立新的目录窗口
menu() # 重新建立目录窗口
def check(kuan,gao,lei): # check函数用于判断游戏是否获胜
global d,root
rest = kuan*gao #所有的格子数共有这么多个
for i in range(kuan):
for j in range(gao):
rest -= d[i][j] # 计算没被点开的方块数
if rest == lei: # 如果没被点开的方块数等于雷的数量,说明游戏获胜了
messagebox.showinfo('提示信息', '恭喜你,游戏胜利!') # 弹出提示框,告知游戏获胜
chongqi() # 游戏获胜,重启游戏
def f(i,j,kuan,gao,lei): # f函数是在玩家点击(i,j)方块后,判断应该给出什么反馈
global a,b,d
if b[i][j]==-1: # 表示该方块为雷
messagebox.showinfo('提示信息', 'Game Over') # 告知玩家游戏结束
chongqi() # 游戏输了,重启游戏
return
if b[i][j]>0: # b[i][j]>0表示该点附近有几个雷
a[i][j]=Button(width=3,height=1,background="white",text=str(b[i][j])) # 把该方块点开,显示出数字,表示该方块附近有几个雷
a[i][j].place(x=50 + 30 * i, y=100 + 30 * j) # 新按钮覆盖原按钮
d[i][j]=1 # 表示该方块已被点击
check(kuan,gao,lei) # 判断游戏是否获胜
return
if b[i][j]==0: # 表示该方块附近没有雷,则需要显示一大片区域
h=0
c=[] # 建立广搜的队列
c.append([i,j]) # 该方块自己先进队
b[i][j]=-9 # 在一大片区域中,b[i][j]的范围是0~7,于是以b[i][j]-9来识别该点是否入队,若为负数则已入队,若为正数则未入队
while h<len(c): # 广度优先搜索,把这一片区域都显示出来
i,j=c[h] # 获取队头的方块位置
if b[i][j]>-9: # 表示该方块原来的数字大于0,那么该方块为广搜边界,应该停止
a[i][j] = Button(width=3, height=1, background="white",text=str(b[i][j]+9)) # 把该方块的数字显示出来
a[i][j].place(x=50 + 30 * i, y=100 + 30 * j)
d[i][j]=1 # 标记该方块已被点击
h=h+1
check(kuan,gao,lei) # 每一步都要判断游戏是否获胜
continue
a[i][j] = Button(width=3, height=1, background="white") # 做到这里表示该方块周围没有雷,那么应该继续往8个方向扩展
a[i][j].place(x=50 + 30 * i, y=100 + 30 * j) # 先把该方块显示出来
d[i][j]=1
if i - 1 >= 0 and j - 1 >= 0 and b[i - 1][j - 1] >= 0: # 往8个方向搜索
c.append([i-1,j-1])
b[i-1][j-1]-=9
if i - 1 >= 0 and b[i - 1][j] >= 0:
c.append([i-1,j])
b[i-1][j]-=9
if i - 1 >= 0 and j + 1 < gao and b[i - 1][j + 1] >= 0:
c.append([i-1,j+1])
b[i-1][j+1]-=9
if j - 1 >= 0 and b[i][j - 1] >= 0:
c.append([i,j-1])
b[i][j-1]-=9
if j + 1 < gao and b[i][j + 1] >= 0:
c.append([i,j+1])
b[i][j+1]-=9
if i + 1 < kuan and j - 1 >= 0 and b[i + 1][j - 1] >= 0:
c.append([i+1,j-1])
b[i+1][j-1]-=9
if i + 1 < kuan and b[i + 1][j] >= 0:
c.append([i+1,j])
b[i+1][j]-=9
if i + 1 < kuan and j + 1 < gao and b[i + 1][j + 1] >= 0:
c.append([i+1,j+1])
b[i+1][j+1]-=9
h=h+1
def zhuchengxu(kuan,gao,lei): # 这个函数是该程序最主要的程序,kuan和gao表示扫雷区域的宽和高,lei表示总雷数
global root,mulu
mulu.destroy() # 先把目录窗口关闭
root=Tk() # 再建立新的游戏窗口
root.title("扫雷") # 给游戏窗口命名为扫雷
x=40*kuan
y=40*gao
root.geometry(str(x)+"x"+str(y)) # 根据扫雷区域的大小计算窗口大小
i=1
while i<=lei: # 埋雷
x=random.randrange(kuan*gao)
if b[x//gao][x%gao]==-1: # b[i][j]=-1表示这个点是雷
continue
b[x // gao][x %gao] = -1
i=i+1
for i in range(kuan): # 计算每个方块周围有几个雷,用b[i][j]来标记
for j in range(gao):
if b[i][j]==-1:
continue
x=0
if i-1>=0 and j-1>=0 and b[i-1][j-1]==-1:
x=x+1
if i-1>=0 and b[i-1][j]==-1:
x=x+1
if i-1>=0 and j+1<gao and b[i-1][j+1]==-1:
x=x+1
if j-1>=0 and b[i][j-1]==-1:
x=x+1
if j+1<gao and b[i][j+1]==-1:
x=x+1
if i+1<kuan and j-1>=0 and b[i+1][j-1]==-1:
x=x+1
if i+1<kuan and b[i+1][j]==-1:
x=x+1
if i+1<kuan and j+1<gao and b[i+1][j+1]==-1:
x=x+1
b[i][j]=x
for i in range(kuan): # 生成方块
for j in range(gao):
x=3
y=1
a[i][j]= Button(width=x,height=y,background="grey",command=lambda x1=i,y1=j:f(x1,y1,kuan,gao,lei))
a[i][j].place(x=50+30*i, y=100+30*j) # 用按钮表示方块,把按钮紧密排列,就变成了一片扫雷阵
Button(text='退出', command=lambda:root.destroy()).pack(anchor='ne') # 建立一个退出按钮,不想玩了可以直接退出游戏
Button(text='重新开始', command=lambda: chongqi()).pack(anchor='ne') # 建立一个重新开始按钮,可以回到目录窗口重新选择难度
root.mainloop() # 生成扫雷阵之后,显示出来
a = [[0 for j in range(100)] for i in range(100)] # 这里开始才是主程序,定义了一些全局变量,方便后续的使用
b = [[0 for j in range(100)] for i in range(100)]
d = [[0 for j in range(100)] for i in range(100)]
root=0 # root表示游戏窗口
mulu=Tk() # 新建一个窗口
menu() # 先出现目录,再开始游戏
结果展示
以下是游戏的一些界面展示:
总结
这个扫雷是一个比较粗糙的实现。在一般的扫雷中,还有一些其它功能,如右键标记雷;单击已点开的数字格子,周边的格子会闪一下,若其周边的雷都被正确标记,则会自动显示出其周边不是雷的区域。由于tkinter中的按钮无法区分左键点击和右键点击,故该功能暂时无法实现。该程序仅仅提供了一个制作游戏的思路,毕竟谁会真的拿我的代码来玩扫雷呢。