文章目录
- 定制按钮
- 生成雷区
- 主流程
基础知识:
- StringVar
- tkinter布局
定制按钮
学会了布局和绑定事件,就可以开发一些简单的应用,比如扫雷小游戏。从外观来看,扫雷就是一个按钮矩阵,左键点击按钮,如果按钮里埋了雷,那么就游戏结束,否则继续游戏。
所以第一步,是对按钮进行定制
import tkinter as tk
from itertools import product
def clickRight(evt, txt):
if txt.get() == "🚩":
txt.set("")
else:
txt.set("🚩")
def clickLeft(evt, txt, isMine):
if isMine:
txt.set("💥")
else:
txt.set(" ")
def setMine(root, i, j, isMine=False):
txt = tk.StringVar()
btn = tk.Button(root, width=5, height=2,
textvariable=txt)
btn.grid(row=i,column=j)
btn.bind("<Button-3>", lambda evt: clickRight(evt, txt))
btn.bind("<Button-1>", lambda evt: clickLeft(evt, txt, isMine))
root = tk.Tk()
root.title("扫雷")
for i,j in product(range(5), range(8)):
setMine(root, i, j, True)
root.mainloop()
效果如下,总共设置了40个按钮,每个按钮都是雷。
生成雷区
接下来要做的有两件事,一是随机生成一片雷区,二是引发雷的连锁反应,当点击一个按钮,如果这个按钮不是雷,那么会显示这个按钮周围的雷的个数。
随机雷区可以通过矩阵实现
import numpy as np
def setMineMat(M, N, r):
mat = np.random.rand(M, N)
return mat > r
其中mat
是一个范围在0到1之间均匀分布的矩阵,其返回值是一个布尔型矩阵,True
为雷,False
为非雷,所以r
越大,则True
值越少,雷也就越少,也就越简单。
然后实现第二个需求,当左键点击按钮后,按钮显示的值,
def mineNumber(mat, i, j):
if mat[i,j] == True:
return "💥"
M, N = mat.shape
i0, i1 = max(0, i-1), min(M, i+2)
j0, j1 = max(0, j-1), min(N, j+2)
num = np.sum(mat[i0:i1, j0:j1])
return str(num)
在这个基础上,更改左键单击的逻辑,除了点击之后显示的内容发生变化之外,若该点为雷,则弹出失败框;若该点为0,则将该点周围所有点全部翻面。
from tkinter.messagebox import showerror
def clickRight(evt, txt):
if txt.get() == "🚩": txt.set("")
else: txt.set("🚩")
def clickLeft(i, j):
if txtLst[i][j].get() != "":
return
flag = mineNumber(mat, i, j)
txtLst[i][j].set(flag)
if flag == "💥":
showerror("", "你输了!")
if flag != "0":
return
i0, i1 = max(0, i-1), min(M, i+2)
j0, j1 = max(0, j-1), min(N, j+2)
for i,j in product(range(i0,i1), range(j0, j1)):
clickLeft(i, j)
重新写一下生成雷区的逻辑,按钮绑定了两个事件,分别在左键点击和右键点击时触发。
def setMine(root, i, j):
txt = tk.StringVar()
btn = tk.Button(root, width=5, height=2,
textvariable=txt)
btn.grid(row=i,column=j)
btn.bind("<Button-3>", lambda evt: clickRight(evt, txt))
btn.bind("<Button-1>", lambda evt: clickLeft(i, j))
return txt
主流程
最后,写一下主流程
root = tk.Tk()
root.title("扫雷")
M, N, r = 6, 10, 0.8
mat = setMineMat(M, N, r)
txtLst = [[] for _ in range(M)]
for i,j in product(range(M), range(N)):
txt = setMine(root, i, j)
txtLst[i].append(txt)
root.mainloop()
效果为
这个扫雷还有一些不足之处,最显而易见的就是旗帜和雷的颜色,这一点其实很好办,只要改下前景就行。另外一点就是,并没有提供一个按钮用于改变雷区和难度,对于这点,最简单的方法既是来一个参数对话框,这个内容接下来就讲。