diff --git a/第23讲扫雷/课堂成果/扫雷.py b/第23讲扫雷/课堂成果/扫雷.py index 2a19ba2..5d29501 100644 --- a/第23讲扫雷/课堂成果/扫雷.py +++ b/第23讲扫雷/课堂成果/扫雷.py @@ -2,135 +2,153 @@ import tkinter as tk from tkinter import messagebox import random +# 扫雷游戏类 class MineSweeper: def __init__(self, master, rows, cols, mines): - self.master = master - self.rows = rows - self.cols = cols - self.mines = mines - self.buttons = {} - self.board = [[0 for _ in range(cols)] for _ in range(rows)] - self.revealed = [[False for _ in range(cols)] for _ in range(rows)] - self.flags = [[False for _ in range(cols)] for _ in range(rows)] - self.game_over = False + self.master = master # 主窗口 + self.rows = rows # 行数 + self.cols = cols # 列数 + self.mines = mines # 雷的数量 + self.buttons = {} # 存放每个按钮控件的字典 {(r, c): Button} + self.board = [[0 for _ in range(cols)] for _ in range(rows)] # 雷区,-1 表示地雷,其他为周围雷的数量 + self.revealed = [[False for _ in range(cols)] for _ in range(rows)] # 是否已翻开 + self.flags = [[False for _ in range(cols)] for _ in range(rows)] # 是否标记为旗帜 + self.game_over = False # 游戏是否结束 - self.generate_mines() - self.create_widgets() + self.generate_mines() # 随机布雷并更新数字 + self.create_widgets() # 创建按钮控件界面 + # 布雷并更新周围格子的数字 def generate_mines(self): count = 0 while count < self.mines: r = random.randint(0, self.rows - 1) c = random.randint(0, self.cols - 1) if self.board[r][c] == -1: - continue + continue # 避免重复布雷 self.board[r][c] = -1 count += 1 - # 更新周围数字 + # 更新周围8格的数字 for i in range(r - 1, r + 2): for j in range(c - 1, c + 2): if 0 <= i < self.rows and 0 <= j < self.cols and self.board[i][j] != -1: self.board[i][j] += 1 + # 创建游戏按钮界面 def create_widgets(self): for r in range(self.rows): for c in range(self.cols): + # 每个格子是一个 Button,绑定左键点击翻格子、右键标旗 b = tk.Button(self.master, width=2, height=1, font=('Arial', 14), command=lambda r=r, c=c: self.reveal_cell(r, c)) b.bind("", lambda e, r=r, c=c: self.toggle_flag(r, c)) b.grid(row=r, column=c) self.buttons[(r, c)] = b + # 翻开一个格子 def reveal_cell(self, r, c): if self.game_over or self.revealed[r][c] or self.flags[r][c]: - return + return # 游戏结束、已翻开或已标旗不响应 if self.board[r][c] == -1: + # 踩到地雷 self.buttons[(r, c)].config(text="💣", bg="red") self.game_over = True self.show_all_mines() messagebox.showerror("游戏结束", "你踩到雷啦!") return + # 否则开始翻格子(递归展开空格) self.flood_fill(r, c) self.check_win() + # 类似于 BFS 的空白格子展开 def flood_fill(self, r, c): if not (0 <= r < self.rows and 0 <= c < self.cols): - return + return # 越界 if self.revealed[r][c] or self.flags[r][c]: - return + return # 已翻开或被标记不处理 self.revealed[r][c] = True val = self.board[r][c] btn = self.buttons[(r, c)] + # 设置按钮为灰色、不可点击、显示数字(或空) btn.config(relief=tk.SUNKEN, text=str(val) if val > 0 else "", state=tk.DISABLED, bg="lightgray") if val == 0: + # 如果是 0,继续向四周递归展开 for dr in (-1, 0, 1): for dc in (-1, 0, 1): if dr != 0 or dc != 0: self.flood_fill(r + dr, c + dc) + # 标记旗帜(右键) def toggle_flag(self, r, c): if self.revealed[r][c] or self.game_over: return if self.flags[r][c]: self.flags[r][c] = False - self.buttons[(r, c)].config(text="") + self.buttons[(r, c)].config(text="") # 取消旗帜 else: self.flags[r][c] = True - self.buttons[(r, c)].config(text="🚩") + self.buttons[(r, c)].config(text="🚩") # 添加旗帜 + # 显示所有地雷 def show_all_mines(self): for r in range(self.rows): for c in range(self.cols): if self.board[r][c] == -1: self.buttons[(r, c)].config(text="💣") + # 检查是否胜利 def check_win(self): for r in range(self.rows): for c in range(self.cols): if self.board[r][c] != -1 and not self.revealed[r][c]: - return + return # 还有未翻开的非雷格子 self.game_over = True messagebox.showinfo("胜利!", "恭喜你,排雷成功!") -# 自定义参数 +# 游戏入口,获取参数并启动游戏 def start_game(): try: + # 从输入框获取参数并转为整数 rows = int(entry_rows.get()) cols = int(entry_cols.get()) mines = int(entry_mines.get()) - if rows <= 0 or cols <= 0 or mines <= 0 or mines >= rows * cols: + # 检查输入合理性:不能为负、雷不能太多、不能太大 + if rows <= 0 or cols <= 0 or mines <= 0 or mines*4 >= rows * cols or rows >= 20 or cols >= 20: raise ValueError except: messagebox.showerror("输入错误", "请输入合理的行列和雷数") return + # 关闭设置窗口,打开主游戏窗口 settings_window.destroy() root = tk.Tk() root.title("扫雷") MineSweeper(root, rows, cols, mines) root.mainloop() -# 设置窗口 +# 设置窗口(输入行、列、雷数) settings_window = tk.Tk() settings_window.title("扫雷设置") tk.Label(settings_window, text="行数:").grid(row=0, column=0) entry_rows = tk.Entry(settings_window) -entry_rows.insert(0, "10") +entry_rows.insert(0, "20") # 默认行数 entry_rows.grid(row=0, column=1) tk.Label(settings_window, text="列数:").grid(row=1, column=0) entry_cols = tk.Entry(settings_window) -entry_cols.insert(0, "10") +entry_cols.insert(0, "10") # 默认列数 entry_cols.grid(row=1, column=1) tk.Label(settings_window, text="雷数:").grid(row=2, column=0) entry_mines = tk.Entry(settings_window) -entry_mines.insert(0, "10") +entry_mines.insert(0, "10") # 默认雷数 entry_mines.grid(row=2, column=1) +# 开始游戏按钮 tk.Button(settings_window, text="开始游戏", command=start_game).grid(row=3, column=0, columnspan=2) +# 启动设置窗口主循环 settings_window.mainloop()