扫雷是一款经典的益智游戏,在这个教程中,我们将使用 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()