Python tkinter项目实践——扫雷游戏

扫雷是一款经典的益智游戏,在这个教程中,我们将使用 Python 的tkinter库来创建一个扫雷游戏。这个游戏允许你设置行数、列数和地雷数量。当你点击一个方块时,如果它是地雷,游戏结束并显示所有地雷。如果不是地雷,它会显示周围地雷的数量。你可以使用右键标记你认为是地雷的方块。游戏有重新开始和退出按钮。已扫和标记地雷与未知方块有颜色区分。

一、导入所需库

import tkinter as tk
import random

这里我们导入了tkinter库用于创建图形用户界面,以及random库用于随机放置地雷。

二、创建扫雷游戏类

class MineSweeper:
    def __init__(self, rows, cols, mines):
        self.rows = rows
        self.cols = cols
        self.mines = mines
        self.root = tk.Tk()
        self.root.title("扫雷游戏")
        self.buttons = [[None for _ in range(cols)] for _ in range(rows)]
        self.create_widgets()
        self.place_mines()
        self.start_game()

__init__方法中,我们接受行数、列数和地雷数量作为参数。创建了一个Tk对象作为游戏的主窗口,并设置了窗口标题。初始化了一个二维列表来存储游戏中的按钮。然后调用create_widgets方法创建游戏界面,place_mines方法放置地雷,start_game方法开始游戏。

def create_widgets(self):
        for row in range(self.rows):
            for col in range(self.cols):
                button = tk.Button(self.root, width=2, height=1, command=lambda r=row, c=col: self.click(r, c))
                button.bind("<Button-3>", lambda event, r=row, c=col: self.right_click(r, c))
                button.grid(row=row, column=col)
                self.buttons[row][col] = button

        restart_button = tk.Button(self.root, text="重新开始", command=self.restart)
        restart_button.grid(row=self.rows, column=0, columnspan=int(self.cols/2))
        exit_button = tk.Button(self.root, text="退出", command=self.root.quit)
        exit_button.grid(row=self.rows, column=int(self.cols/2), columnspan=int(self.cols/2))

create_widgets方法创建游戏界面。它遍历行数和列数,为每个方块创建一个按钮,并将其添加到主窗口中。按钮的点击事件绑定到click方法,右键点击事件绑定到right_click方法。还创建了重新开始和退出按钮,并将它们添加到主窗口中。

def place_mines(self):
        self.mines_list = []
        while len(self.mines_list) < self.mines:
            row = random.randint(0, self.rows - 1)
            col = random.randint(0, self.cols - 1)
            if (row, col) not in self.mines_list:
                self.mines_list.append((row, col))

place_mines方法随机放置地雷。它创建一个列表来存储地雷的位置,然后不断随机生成位置,直到放置了指定数量的地雷。

def start_game(self):
        self.game_over = False
        self.flags = 0
        for row in range(self.rows):
            for col in range(self.cols):
                self.buttons[row][col]["text"] = ""
                self.buttons[row][col]["bg"] = "gray"

start_game方法开始游戏。它设置游戏未结束标志为False,标记地雷的数量为 0。然后将所有方块的文本清空,并设置背景颜色为灰色。

def click(self, row, col):
        if self.game_over:
            return
        button = self.buttons[row][col]
        if (row, col) in self.mines_list:
            button["text"] = "X"
            button["bg"] = "red"
            self.game_over = True
            self.show_mines()
        else:
            count = self.count_adjacent_mines(row, col)
            if count > 0:
                button["text"] = str(count)
                button["bg"] = "white"
            else:
                button["text"] = ""
                button["bg"] = "white"
                self.clear_zeros_non_recursive(row, col)
            if self.check_win():
                self.game_over = True
                for r in range(self.rows):
                    for c in range(self.cols):
                        if (r, c) not in self.mines_list:
                            self.buttons[r][c]["bg"] = "green"

click方法处理方块的点击事件。如果游戏已经结束,直接返回。如果点击的方块是地雷,显示地雷标志并设置背景颜色为红色,游戏结束并调用show_mines方法显示所有地雷。如果不是地雷,计算周围地雷的数量并显示在方块上。如果周围没有地雷,调用clear_zeros_non_recursive方法清理周围的方块。如果游戏胜利,将所有未标记为地雷的方块设置为绿色背景。

def right_click(self, row, col):
        if self.game_over:
            return
        button = self.buttons[row][col]
        if button["text"] == "":
            if self.flags < self.mines:
                button["text"] = "*"
                button["bg"] = "yellow"
                self.flags += 1
            else:
                return
        elif button["text"] == "*":
            button["text"] = ""
            button["bg"] = "gray"
            self.flags -= 1

right_click方法处理方块的右键点击事件。如果游戏已经结束,直接返回。如果方块上没有文本,并且标记地雷的数量小于总地雷数量,将方块标记为地雷并设置背景颜色为黄色,标记地雷的数量加 1。如果方块已经标记为地雷,取消标记并设置背景颜色为灰色,标记地雷的数量减 1。

def count_adjacent_mines(self, row, col):
        count = 0
        for r in range(max(0, row - 1), min(row + 2, self.rows)):
            for c in range(max(0, col - 1), min(col + 2, self.cols)):
                if (r, c) in self.mines_list:
                    count += 1
        return count

count_adjacent_mines方法计算给定方块周围的地雷数量。它遍历周围的方块,如果是地雷,则计数器加 1。

def clear_zeros_non_recursive(self, row, col):
        visited = set()
        queue = [(row, col)]
        while queue:
            r, c = queue.pop(0)
            if (r, c) in visited:
                continue
            visited.add((r, c))
            button = self.buttons[r][c]
            if button["text"] == "" and (r, c) not in self.mines_list:
                count = self.count_adjacent_mines(r, c)
                if count > 0:
                    button["text"] = str(count)
                    button["bg"] = "white"
                else:
                    button["text"] = ""
                    button["bg"] = "white"
                    for r2 in range(max(0, r - 1), min(r + 2, self.rows)):
                        for c2 in range(max(0, c - 1), min(c + 2, self.cols)):
                            if (r2, c2) not in self.mines_list:
                                queue.append((r2, c2))

clear_zeros_non_recursive方法使用广度优先搜索清理周围没有地雷的方块。它使用一个集合来记录已经访问过的方块,避免重复处理。使用一个队列来存储待处理的方块。从给定的方块开始,不断从队列中取出方块进行处理。如果方块上没有文本并且不是地雷,计算周围地雷的数量并显示在方块上。如果周围没有地雷,将方块的文本清空并设置背景颜色为白色,然后将周围的方块加入队列。

def show_mines(self):
        for mine in self.mines_list:
            self.buttons[mine[0]][mine[1]]["text"] = "X"
            self.buttons[mine[0]][mine[1]]["bg"] = "red"

show_mines方法显示所有地雷。它遍历地雷列表,将每个地雷方块的文本设置为“X”,并设置背景颜色为红色。

def check_win(self):
        return self.flags == self.mines and all(button["text"]!= "" or (row, col) in self.mines_list for row, row_buttons in enumerate(self.buttons) for col, button in enumerate(row_buttons))

check_win方法检查游戏是否胜利。如果标记地雷的数量等于总地雷数量,并且所有未标记为地雷的方块都已经被打开或者是地雷,则游戏胜利。

def restart(self):
        self.start_game()
        self.place_mines()

restart方法重新开始游戏。它调用start_game方法和place_mines方法,重新初始化游戏状态。

三、主程序部分

if __name__ == "__main__":
    rows = 10
    cols = 10
    mines = 15
    game = MineSweeper(rows, cols, mines)
    game.root.mainloop()

四、完整代码

import tkinter as tk
import random

class MineSweeper:
    def __init__(self, rows, cols, mines):
        self.rows = rows
        self.cols = cols
        self.mines = mines
        self.root = tk.Tk()
        self.root.title("扫雷游戏")
        self.buttons = [[None for _ in range(cols)] for _ in range(rows)]
        self.create_widgets()
        self.place_mines()
        self.start_game()

    def create_widgets(self):
        for row in range(self.rows):
            for col in range(self.cols):
                button = tk.Button(self.root, width=2, height=1, command=lambda r=row, c=col: self.click(r, c))
                button.bind("<Button-3>", lambda event, r=row, c=col: self.right_click(r, c))
                button.grid(row=row, column=col)
                self.buttons[row][col] = button

        restart_button = tk.Button(self.root, text="重新开始", command=self.restart)
        restart_button.grid(row=self.rows, column=0, columnspan=int(self.cols/2))
        exit_button = tk.Button(self.root, text="退出", command=self.root.quit)
        exit_button.grid(row=self.rows, column=int(self.cols/2), columnspan=int(self.cols/2))

    def place_mines(self):
        self.mines_list = []
        while len(self.mines_list) < self.mines:
            row = random.randint(0, self.rows - 1)
            col = random.randint(0, self.cols - 1)
            if (row, col) not in self.mines_list:
                self.mines_list.append((row, col))

    def start_game(self):
        self.game_over = False
        self.flags = 0
        for row in range(self.rows):
            for col in range(self.cols):
                self.buttons[row][col]["text"] = ""
                self.buttons[row][col]["bg"] = "gray"

    def click(self, row, col):
        if self.game_over:
            return
        button = self.buttons[row][col]
        if (row, col) in self.mines_list:
            button["text"] = "X"
            button["bg"] = "red"
            self.game_over = True
            self.show_mines()
        else:
            count = self.count_adjacent_mines(row, col)
            if count > 0:
                button["text"] = str(count)
                button["bg"] = "white"
            else:
                button["text"] = ""
                button["bg"] = "white"
                self.clear_zeros_non_recursive(row, col)
            if self.check_win():
                self.game_over = True
                for r in range(self.rows):
                    for c in range(self.cols):
                        if (r, c) not in self.mines_list:
                            self.buttons[r][c]["bg"] = "green"

    def right_click(self, row, col):
        if self.game_over:
            return
        button = self.buttons[row][col]
        if button["text"] == "":
            if self.flags < self.mines:
                button["text"] = "*"
                button["bg"] = "yellow"
                self.flags += 1
            else:
                return
        elif button["text"] == "*":
            button["text"] = ""
            button["bg"] = "gray"
            self.flags -= 1

    def count_adjacent_mines(self, row, col):
        count = 0
        for r in range(max(0, row - 1), min(row + 2, self.rows)):
            for c in range(max(0, col - 1), min(col + 2, self.cols)):
                if (r, c) in self.mines_list:
                    count += 1
        return count

    def clear_zeros(self, row, col):
        queue = [(row, col)]
        while queue:
            r, c = queue.pop(0)
            button = self.buttons[r][c]
            if button["text"] == "" and (r, c) not in self.mines_list:
                count = self.count_adjacent_mines(r, c)
                if count > 0:
                    button["text"] = str(count)
                    button["bg"] = "white"
                else:
                    button["text"] = ""
                    button["bg"] = "white"
                    for r2 in range(max(0, r - 1), min(r + 2, self.rows)):
                        for c2 in range(max(0, c - 1), min(c + 2, self.cols)):
                            if (r2, c2) not in self.mines_list and self.buttons[r2][c2]["text"] == "" and (r2, c2) not in queue:
                                queue.append((r2, c2))
    def clear_zeros_non_recursive(self, row, col):
        visited = set()
        queue = [(row, col)]
        while queue:
            r, c = queue.pop(0)
            if (r, c) in visited:
                continue
            visited.add((r, c))
            button = self.buttons[r][c]
            if button["text"] == "" and (r, c) not in self.mines_list:
                count = self.count_adjacent_mines(r, c)
                if count > 0:
                    button["text"] = str(count)
                    button["bg"] = "white"
                else:
                    button["text"] = ""
                    button["bg"] = "white"
                    for r2 in range(max(0, r - 1), min(r + 2, self.rows)):
                        for c2 in range(max(0, c - 1), min(c + 2, self.cols)):
                            if (r2, c2) not in self.mines_list:
                                queue.append((r2, c2))

    def show_mines(self):
        for mine in self.mines_list:
            self.buttons[mine[0]][mine[1]]["text"] = "X"
            self.buttons[mine[0]][mine[1]]["bg"] = "red"

    def check_win(self):
        return self.flags == self.mines and all(button["text"]!= "" or (row, col) in self.mines_list for row, row_buttons in enumerate(self.buttons) for col, button in enumerate(row_buttons))

    def restart(self):
        self.start_game()
        self.place_mines()

if __name__ == "__main__":
    rows = 10
    cols = 10
    mines = 15
    game = MineSweeper(rows, cols, mines)
    game.root.mainloop()
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

elvis_z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值