本程序实现了扫雷功能,实现了左键打开地块,右键标棋,中键范围打开的功能,采用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, B
    if 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, B
        if Minefield[xx][yy] == 0:  # 是空位,下一步
            for i in [0, -1, 1]:
                if (xx + i) < 0 or (xx + i) == x:  # x越界跳过
                    continue
                for j in [0, -1, 1]:
                    if (yy + j) < 0 or (yy + j) == y:  # y越界跳过
                        continue
                    # 打开过也跳过
                    if B[xx + i][yy + j]['bg'] == 'whitesmoke':
                        continue
                    open(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 = 0
    global mine_2
    mine_2 -= 1
    var.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 = 0
    global mine_2
    mine_2 += 1
    var.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, l2
    t = 0
    B[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 Minefield
    n = 0
    for i in [-1, 0, 1]:
        if (xx + i) < 0 or (xx + i) == x:  # x越界跳过
            continue
        for j in [-1, 0, 1]:
            if (yy + j) < 0 or (yy + j) == y:  # y越界跳过
                continue
            # 如果是旗帜
            if B[xx + i][yy + j]['text'] == '⚑':
                n += 1
    if n == Minefield[xx][yy]:  # 标雷数相同
        for i in [-1, 0, 1]:
            if (xx + i) < 0 or (xx + i) == x:  # x越界跳过
                continue
            for 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, var
    mine_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  # 雷数-1
            for i in [-1, 0, 1]:
                if (xx + i) < 0 or (xx + i) == x:  # x越界跳过
                    continue
                for j in [-1, 0, 1]:
                    if (yy + j) < 0 or (yy + j) == y:  # y越界跳过
                        continue
                    if 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 = 0
    for i in range(x):
        B.append([])
        mir.grid_rowconfigure(i, weight=1)  # row为i,缩放比为1
        for j in range(y):
            mir.grid_columnconfigure(j, weight=1)  # column为i,缩放比为1
            B[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转int
        except:
            messagebox.showwarning('警告', '请输入正确的数字')
            return
        # 判断数字是否正确
        if x > 30 or y > 50 or x < 1 or y < 1 or n < 1 or n >= x * y:
            messagebox.showwarning('警告', '请输入正确的数字')
            return
        generate(x, y, n)
        cusrom_window.destroy()  # 关闭窗口

    global main_window
    cusrom_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, var
    mine_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()  # 开启主循环,让窗口处于显示状态

结果示例:




python扫雷小游戏的背景 tkinter扫雷小游戏代码_python扫雷小游戏的背景


python扫雷小游戏的背景 tkinter扫雷小游戏代码_tkinter_02


python扫雷小游戏的背景 tkinter扫雷小游戏代码_递归_03


改进方案:

因为是先生成雷区,玩家再进行扫雷,有可能会第一次就踩雷,可以先生成所有按钮,每个按钮左键绑定一个函数,触发这个函数后,再进行雷区生成,生成的时候避开第一个点击的按钮位置就行。

对于雷区大雷少的情况,空位比较多,递归的时候可能会越界,可以用sys库中的sys.setrecursionlimit(1500)将递归深度改大,我这里不想导那么多库,就没整,你们可以试试,就两行的事