本程序实现了扫雷功能,实现了左键打开地块,右键标棋,中键范围打开的功能,采用tkinter图形化,事件绑定实现功能。
代码如下:
import tkinter as tk
from random import randint
import tkinter.simpledialog
from tkinter import messagebox# 一般左键打开
def ordinary_Left(t, xx, yy, x, y):global Minefield, Bif Minefield[xx][yy] == -1: # 踩到了雷B[xx][yy]['bg'] = 'red' # 标出踩到的雷l2['fg'] = 'black' # 游戏结束for i in range(x): # 打开所有雷for j in range(y):B[i][j].bind('<Button-1>', end)B[i][j].bind('<Button-2>', end)B[i][j].bind('<Button-3>', end)if Minefield[i][j] == -1:if not B[i][j]['bg'] == 'red':B[i][j]['bg'] = 'whitesmoke' # 换颜色B[i][j]['text'] = '☢' # 换成雷图标elif Minefield[xx][yy] == 0: # 空位vacancy(xx, yy, x, y) # 递归开附近空位else:open(xx, yy, x, y)# 开空位
def vacancy(xx, yy, x, y):try:global Minefield, Bif Minefield[xx][yy] == 0: # 是空位,下一步for i in [0, -1, 1]:if (xx + i) < 0 or (xx + i) == x: # x越界跳过continuefor j in [0, -1, 1]:if (yy + j) < 0 or (yy + j) == y: # y越界跳过continue# 打开过也跳过if B[xx + i][yy + j]['bg'] == 'whitesmoke':continueopen(xx + i, yy + j, x, y) # 空位周围随便开if Minefield[xx + i][yy + j] == 0: # 如果是空位vacancy(xx + i, yy + j, x, y) # 递归except:messagebox.showwarning('警告', '报错,可能是空位过多,递归超限')return# 一般右键标雷
def ordinary_Right(t, xx, yy, x, y):t = 0global mine_2mine_2 -= 1var.set(mine_2)B[xx][yy].bind('<Button-1>', end)B[xx][yy].bind('<Button-2>', end)B[xx][yy].bind('<Button-3>',lambda t=t, xx=xx, yy=yy, x=x, y=y: Flag_Right(t, xx, yy, x, y))B[xx][yy]['text'] = '⚑' # 换成旗帜图标# 右键取消
def Flag_Right(t, xx, yy, x, y):t = 0global mine_2mine_2 += 1var.set(mine_2)B[xx][yy].bind('<Button-1>',lambda t=t, xx=xx, yy=yy, x=x, y=y: ordinary_Left(t, xx, yy, x, y))B[xx][yy].bind('<Button-2>', end)B[xx][yy].bind('<Button-3>',lambda t=t, xx=xx, yy=yy, x=x, y=y: ordinary_Right(t, xx, yy, x, y))B[xx][yy]['text'] = '' # 换成空图标# 打开:
def open(xx, yy, x, y):global B, Minefield, colour, lie, mine_1, l2t = 0B[xx][yy].bind('<Button-1>', end) # 开空位B[xx][yy].bind('<Button-3>', end)if Minefield[xx][yy] == -1: # 雷B[xx][yy]['bg'] = 'whitesmoke' # 换颜色B[xx][yy]['text'] = '☢' # 换成雷图标elif not Minefield[xx][yy] == 0:# 中键操作B[xx][yy].bind('<Button-2>', lambda t=t, xx=xx, yy=yy, x=x, y=y: open_Middle(t, xx, yy, x, y))B[xx][yy]['bg'] = 'whitesmoke' # 换颜色B[xx][yy]['text'] = Minefield[xx][yy] # 换成数字B[xx][yy]['fg'] = colour[Minefield[xx][yy]] # 文字颜色else: # 空位B[xx][yy]['bg'] = 'whitesmoke' # 换颜色lie -= 1 # 开一个少一个格子if lie == mine_1 and not l2['fg'] == 'break': # 小于等于雷数,游戏结束l2['fg'] = 'black'for i in range(x): # 打开所有雷for j in range(y):B[i][j].bind('<Button-1>', end)B[i][j].bind('<Button-2>', end)B[i][j].bind('<Button-3>', end)if Minefield[i][j] == -1:B[i][j]['bg'] = 'whitesmoke' # 换颜色B[i][j]['text'] = '☢' # 换成雷图标# 打开中键范围开启
def open_Middle(t, xx, yy, x, y):global Minefieldn = 0for i in [-1, 0, 1]:if (xx + i) < 0 or (xx + i) == x: # x越界跳过continuefor j in [-1, 0, 1]:if (yy + j) < 0 or (yy + j) == y: # y越界跳过continue# 如果是旗帜if B[xx + i][yy + j]['text'] == '⚑':n += 1if n == Minefield[xx][yy]: # 标雷数相同for i in [-1, 0, 1]:if (xx + i) < 0 or (xx + i) == x: # x越界跳过continuefor j in [-1, 0, 1]:if (yy + j) < 0 or (yy + j) == y: # y越界跳过continue# 打开过也跳过if not B[xx + i][yy + j]['bg'] == 'whitesmoke':# 旗帜跳过if not B[xx + i][yy + j]['text'] == '⚑':ordinary_Left(0, xx + i, yy + j, x, y)# 覆盖用方法
def end(t):pass# 生成游戏,长x,宽y,n个雷。
def generate(x, y, n):global Minefield, mir, main_window, B, mine_1, mine_2, lie, l2, varmine_1 = n # 真实地雷数mine_2 = n # 地雷数-旗帜数var.set(mine_2)lie = x * y # 地块大小Minefield = [[0 for i in range(y)] for i in range(x)]while n: # 埋下n个雷xx = randint(0, x - 1) # 生成一个随机坐标yy = randint(0, y - 1)if not (Minefield[xx][yy] < 0): # 如果该位置没有雷Minefield[xx][yy] = -1 # 埋下一个雷n -= 1 # 雷数-1for i in [-1, 0, 1]:if (xx + i) < 0 or (xx + i) == x: # x越界跳过continuefor j in [-1, 0, 1]:if (yy + j) < 0 or (yy + j) == y: # y越界跳过continueif not Minefield[xx + i][yy + j] == -1: # 如果不是埋雷地点Minefield[xx + i][yy + j] += 1 # 数值加1# 雷区生成完毕,生成按钮l2['fg'] = 'whitesmoke'mir.destroy() # 删除上次按钮mir = tk.Frame(main_window) # 放雷区的框架mir.grid(row=0, column=0, sticky="nsew")B = [] # 按钮组t = 0for i in range(x):B.append([])mir.grid_rowconfigure(i, weight=1) # row为i,缩放比为1for j in range(y):mir.grid_columnconfigure(j, weight=1) # column为i,缩放比为1B[i].append(tk.Button(mir, text="", width=2, height=2, bg='lightgray')) # 添加按钮# 绑定左键和右键方法B[i][j].bind('<Button-1>', lambda t=t, i=i, j=j, x=x, y=y: ordinary_Left(t, i, j, x, y))B[i][j].bind('<Button-3>', lambda t=t, i=i, j=j, x=x, y=y: ordinary_Right(t, i, j, x, y))B[i][j].grid(row=i, column=j, sticky=tk.N + tk.S + tk.W + tk.E) # 添加到主窗口显示# 自定义参数对话框
def custom():def determine(x, y, n): # 确定函数try:x, y, n = int(x), int(y), int(n) # str转intexcept:messagebox.showwarning('警告', '请输入正确的数字')return# 判断数字是否正确if x > 30 or y > 50 or x < 1 or y < 1 or n < 1 or n >= x * y:messagebox.showwarning('警告', '请输入正确的数字')returngenerate(x, y, n)cusrom_window.destroy() # 关闭窗口global main_windowcusrom_window = tk.Toplevel(main_window)cusrom_window.title("自定义")cusrom_window.geometry("200x120") # 大小yl = tk.Label(cusrom_window, text='长(1~50):') # 输入框前的字xl = tk.Label(cusrom_window, text='宽(1~30):')nl = tk.Label(cusrom_window, text='雷(xy>n>0):')var_y = tkinter.StringVar()var_x = tkinter.StringVar()var_n = tkinter.StringVar()y = tk.Entry(cusrom_window, textvariable=var_y) # 输入框x = tk.Entry(cusrom_window, textvariable=var_x)n = tk.Entry(cusrom_window, textvariable=var_n)yl.grid(column=0, row=0) # 布局xl.grid(column=0, row=2)nl.grid(column=0, row=4)y.grid(column=1, row=0)x.grid(column=1, row=2)n.grid(column=1, row=4)B = tk.Button(cusrom_window, text="确定", command=lambda: determine(x.get(), y.get(), n.get()))B.grid(column=0, row=5)def mine():global l2, mine_2, varmine_window = tk.Toplevel(main_window) # 信息窗口mine_window.title("地雷数")mine_window.geometry("200x100+200+200") # 大小mine_window.resizable(0, 0) # 不允许拉伸改变大小l1 = tkinter.Label(mine_window, text='剩余地雷:', fg='blue')l2 = tkinter.Label(mine_window, text='game over', fg='whitesmoke')var = tkinter.StringVar()var.set(mine_2)mine = tkinter.Label(mine_window, textvariable=var, fg='blue')l1.grid(row=0, column=0, )mine.grid(row=0, column=1, )l2.grid(row=1, column=0, )# 雷区,主窗口,雷场,颜色,炸弹数
global Minefield, main_window, mir, colour,mine_2
mine_2 = 0
colour = ['w', 'blue', 'green', 'red', 'navy', 'maroon', 'teal', 'black', 'indigo'] # 文字颜色
main_window = tk.Tk() # 调用Tk()创建主窗口
main_window.title("扫雷") # 给主窗口起一个名字
main_window.geometry("450x450+400+200") # 大小
menubar = tk.Menu(main_window) # 菜单栏
# 难度菜单是菜单栏的子菜单,且不能窗口化
difficulty = tk.Menu(menubar, tearoff=False)
difficulty.add_command(label='简单', command=lambda: generate(9, 9, 10)) # 难度子菜单添加选项
difficulty.add_command(label='中等', command=lambda: generate(16, 16, 40)) # command为要调用的函数
difficulty.add_command(label='困难', command=lambda: generate(16, 30, 99))
difficulty.add_separator() # 分割线
difficulty.add_command(label='自定义', command=custom)
menubar.add_cascade(label='难度', menu=difficulty) # 菜单难度选项
menubar.add_cascade(label='地雷数', command=mine) # 菜单难度选项
mine()
main_window.config(menu=menubar) # 窗口与菜单关联
main_window.grid_rowconfigure(0, weight=1) # row为0,缩放比为1
main_window.grid_columnconfigure(0, weight=1) # column为0,缩放比为1
mir = tk.Frame(main_window) # 放雷区的框架
mir.grid(row=0, column=0, sticky="nsew") # 放置框架
generate(9, 9, 10)
main_window.mainloop() # 开启主循环,让窗口处于显示状态
结果示例:
改进方案:
因为是先生成雷区,玩家再进行扫雷,有可能会第一次就踩雷,可以先生成所有按钮,每个按钮左键绑定一个函数,触发这个函数后,再进行雷区生成,生成的时候避开第一个点击的按钮位置就行。
对于雷区大雷少的情况,空位比较多,递归的时候可能会越界,可以用sys库中的sys.setrecursionlimit(1500)将递归深度改大,我这里不想导那么多库,就没整,你们可以试试,就两行的事
其他:此程序为新学期练手用,欢迎找bug