Python 使用内置模块 tkinter 实现扫雷游戏
扫雷是一款经典的单人电脑游戏,玩家通过点击方块来揭示隐藏在方块下面的地雷,目标是在揭示所有非地雷方块的同时避免点击到地雷。
在 Python 中可以使用内置模块tkinter
和random
搭配来实现扫雷游戏,如下是实现步骤:
- 导入所需的
tkinter
和random
模块。 - 定义函数
get_image
,用于获取游戏图片,如果图片不存在则返回None
。 - 定义
Sweep
类,继承tkinter.Button
类,用于创建每个方块的按钮,并处理按钮点击事件和右键点击事件。 - 在
Sweep
类中定义类属性,包括地图宽度、高度、地雷个数、地图相对于主窗口的起点、扫除计数、游戏状态、地图方块大小等。 - 在
Sweep
类中定义事件处理的方法,create_map
方法用于创建地图,init_map
方法用于初始化地图,reset_map
方法用于重置地图,clicked
方法处理按钮左键点击事件,right_clicked
方法处理按钮右键点击事件,setPos
方法用于设置按钮的位置,map_mine
方法用于处理地图中数据,update_style
方法用于更新按钮样式,auto_sweep
方法用于自动扫描周围方块,gameover
方法用于处理游戏结束,victory
方法用于处理过关。 - 在主程序中,创建
tkinter
窗口对象root
,调用Sweep
类的create_map
函数创建地图,并调用init_map
函数初始化地图。 - 在主程序中,创建菜单栏和游戏菜单,使用
add_cascade
方法将游戏菜单添加到菜单栏中,并添加不同难度的选项和重新开始选项。 - 在主程序中,调用
tkinter
的mainloop
方法启动窗口,等待用户操作。
如下是代码示例:
import tkinter
import tkinter.messagebox
import random
# 通过路径获取图片
def get_image(filename, w=None, h=None):
try:
# 加载图片
im = tkinter.PhotoImage(file=filename)
if w != None and h != None:
im = im.subsample(im.width() // w, im.height() // h)
return im
except:
# 未找到图片加载未空
return None
class Sweep(tkinter.Button):
# 定义地图宽度,高度,地雷个数,地图相对于主窗口的起点,扫除计数,游戏状态,地图方块大小
map,w,h,mine,x0,y0,count,state,mine_size = [],7,8,5,30,50,0,0,40
# 数字 0 ~ 9 的字体颜色 rgb
fgs = [(255, 255, 255), (9, 147, 62), (0, 187, 187), (240, 78, 0), (166, 19, 188)
, (185, 122, 87), (136, 0, 21), (163, 73, 164), (0, 0, 0), (0, 0, 0)]
# 方块状态颜色与背景图片
bgs = [(128, 128, 128), (255, 255, 255), (0, 255, 0), (255, 0, 0), (255, 200, 0), (0, 255, 0), (163, 73, 164)]
images = ['area.png', 'opened.png', 'flag.png', 'doubt.png', 'boom.png', 'sweeped.png', 'mistake.png']
pic = []
# 创建地图
def create_map(w=w, h=h, mine=mine, x0=x0, y0=x0,
mine_size=mine_size, fgs=fgs, bgs=bgs, images=images):
# 加载地图信息
Sweep.w = w
Sweep.h = h
Sweep.mine = mine
Sweep.x0 = x0
Sweep.y0 = y0
Sweep.mine_size = mine_size
Sweep.fgs = fgs
Sweep.bgs = bgs
Sweep.images = images
Sweep.state = 0
Sweep.count = 0
# 加载背景图片
if not Sweep.pic:
for filename in Sweep.images:
image = get_image(filename, Sweep.mine_size, Sweep.mine_size)
Sweep.pic.append(image)
# 处理地图中数据
def init_map(root):
size_str = '{}x{}+400+80'.format(Sweep.w * Sweep.mine_size + 80, Sweep.h * Sweep.mine_size + 100)
root.geometry(size_str)
Sweep.state = 0
Sweep.count = 0
# 判断按钮点击位置
for button in Sweep.map:
button.destroy()
Sweep.map.clear()
# 更新界面
root.update()
random_numbers = random.sample(range(0, Sweep.w * Sweep.h), Sweep.mine)
for i in range(Sweep.w * Sweep.h):
r = i // Sweep.w
c = i % Sweep.w
n = 9 if i in random_numbers else 0
# 操作按钮
button = Sweep(root)
button.place(x=c * Sweep.mine_size + Sweep.x0, y=r * Sweep.mine_size + Sweep.y0
, width=Sweep.mine_size, height=Sweep.mine_size)
button.setPos(r=r, c=c, n=n)
# 遍历地图每一个单元格
for mine in Sweep.map:
mine.map_mine()
root.update()
def reset_map(self):
Sweep.state = 0
for mine in Sweep.map:
mine.state = 0
mine.update_style()
def __init__(self, master=None, cnf={}, **kw):
# 继承属性
super().__init__(master, cnf, **kw)
self.text = self['text']
self.command = self['command']
self.bind('<Button-1>', self.clicked)
self.bind('<Button-3>', self.right_clicked)
self.r = 0
self.c = 0
self.n = 0
# 0没有被打开 1已经被打开 2被标志 3被质疑 4打开是雷被爆炸 5被扫除 6标志错误
self.state = 0
self.update_style()
self.__class__.map.append(self)
def clicked(self, event):
# 判断点击实际
if self.__class__.state != 0:
return
if self.state == 1: return
if self.n == 9:
self.gameover()
return
if self.n == 0:
self.auto_sweep()
self.vectory()
return
self.__class__.count += 1
self.state = 1
# 更新样式
self.update_style()
self.vectory()
# 处理右键点击
def right_clicked(self, event):
# 判断该方块周围雷数
if self.state == 1: return
if self.state == 0:
self.state = 2
elif self.state == 2:
self.state = 3
elif self.state == 3:
self.state = 0
# 更新样式
self.update_style()
# 设置位置
def setPos(self, r, c, n):
self.r = r
self.c = c
self.n = n
# 处理每一行
def map_mine(self):
if self.n != 9:
return
# 将(r,c)的邻居的位置都找出来
neighbors = [(self.r + i, self.c + j) for i in range(-1, 2) for j in range(-1, 2) if i != 0 or j != 0]
for r, c in neighbors:
for mine in self.__class__.map:
if mine.r == r and mine.c == c and mine.n != 9:
mine.n += 1
# #根据按钮自身的不同状态去显示按钮的样式
def update_style(self):
text = str("")
if self.n != 0 and self.n != 9 and self.state == 1: text = str(self.n)
hex_fg = '#{:02x}{:02x}{:02x}'.format(self.__class__.fgs[self.n][0]
, self.__class__.fgs[self.n][1],
self.__class__.fgs[self.n][2])
hex_bg = '#{:02x}{:02x}{:02x}'.format(self.__class__.bgs[self.state][0]
, self.__class__.bgs[self.state][1],
self.__class__.bgs[self.state][2])
image = self.__class__.pic[self.state]
# 加载图片
self.image = image
if self.state == 2:
text = str("!")
hex_fg = "red"
elif self.state == 3:
text = str("?")
hex_fg = str("yellow")
elif self.state == 6:
text = str("×")
hex_fg = str("red")
if image != None:
hex_bg = None
# 加载怕配置
self.configure(bg=hex_bg, fg=hex_fg, image=image, text=text, compound=tkinter.CENTER)
# 处理过关后自动清除页面
def auto_sweep(self):
if self.state == 1: return
self.state = 1
# 更新样式
self.update_style()
self.__class__.count += 1
if self.n != 0:
return
# # 将(r,c)的邻居的位置都找出来
neighbors = [(self.r + i, self.c + j) for i in range(-1, 2) for j in range(-1, 2) if i != 0 or j != 0]
for r, c in neighbors:
for mine in self.__class__.map:
if mine.r == r and mine.c == c and mine.n != 9:
mine.clicked(None)
# 处理游戏结束
def gameover(self):
self.state = 4
self.__class__.state = 2
# 根据选中状态和地图判断每一个单元格
for mine in self.__class__.map:
if mine.n == 9 and mine.state != 2:
mine.state = 4
mine.update_style()
elif mine.n != 9 and mine.state == 2:
mine.state = 6
mine.update_style()
# 显示失败页面
result = tkinter.messagebox.showinfo(parent=self, title="游戏结束!", message="失败!")
# 关闭页面
if result=="ok":
root.quit()
# 处理过关
def vectory(self):
if self.__class__.count == (self.__class__.h * self.__class__.w - self.__class__.mine):
if self.__class__.state != 1:
tkinter.messagebox.showinfo("游戏结束!", "恭喜过关!!!")
self.__class__.state = 1
root = tkinter.Tk()
# 处理难度选择的函数
def del_menu(args):
# 根据选择生成对应的地图
if args == "入门":
Sweep.create_map(w=6, h=5, mine=1)
elif args == "简单":
Sweep.create_map(w=10, h=10, mine=15)
elif args == "一般":
Sweep.create_map(w=16, h=16, mine=40)
elif args == "困难":
Sweep.create_map(w=20, h=16, mine=60)
elif args == "重新开始":
pass
# 创建地图
Sweep.init_map(root)
if __name__ == '__main__':
root.title('扫雷')
# 创建地图
Sweep.create_map(w=6, h=5, mine=3)
Sweep.init_map(root)
menu_bar = tkinter.Menu(root)
# 加载配置
root.config(menu=menu_bar)
game_menu = tkinter.Menu(menu_bar, tearoff=False)
# 加载标题和难度
menu_bar.add_cascade(label="游戏", menu=game_menu)
game_menu.add_command(label="入门", command=lambda: del_menu("入门"))
game_menu.add_command(label="简单", command=lambda: del_menu("简单"))
game_menu.add_command(label="一般", command=lambda: del_menu("一般"))
game_menu.add_command(label="困难", command=lambda: del_menu("困难"))
game_menu.add_command(label="重新开始", command=lambda: del_menu("重新开始"))
# 启动窗口
root.mainloop()
执行上述代码后将开启扫雷游戏的界面,游戏开始时,地雷会随机分布在地图上,你需要通过左键点击按钮来扫除方块,如果点击到地雷,则游戏结束;否则,方块会显示周围地雷的数量。你可以通过右键点击标记地雷。游戏菜单提供了不同难度的选项和重新开始选项,你可以根据自己的喜好选择难度。
注意:这只是一个简单的示例,扫雷游戏具有很多实现方式,你可以根据自己的需求修改代码来实现你想要的玩法。