网友作品 #000145 · 提交于 2026/08/11
Python扫雷小游戏:经典玩法三难度桌面版

作品简介
作者借助 AI 编程工具和 Python Skill,用 Python 标准库 tkinter 从零复刻了经典扫雷游戏。游戏包含难度菜单、剩余雷数、计时器、重开按钮等经典界面元素,玩法与原版一致:左键翻格子、右键插旗。开发中经常出现小 bug,经过反复调试,界面从粗糙逐步打磨到相对精良,作者也在这个过程中熟悉了用 Skill 编程的完整流程。
核心亮点
- 初级(9×9、10 雷)、中级(16×16、40 雷)、高级(16×30、99 雷)三档难度
- 左键翻开格子、右键插旗或取消标记的经典操作逻辑
- 空白区域自动连锁展开,数字采用经典扫雷配色
- 首点保护:第一次点击永远不会踩雷
- 剩余雷数统计、计时器、重新开始按钮齐备,胜负判定与提示明确,胜利时自动标记所有地雷
- 可打包为单文件 exe 分发
技术与工具
- 编程语言:Python,界面用标准库 tkinter 实现,无第三方依赖
- 开发方式:AI 编程类工具 + Python Skill,经历多轮 bug 修复
- 水平参考:作者零基础
开发过程
从第一个可运行版本开始,作者在不断修复小 bug 的过程中迭代:游戏逻辑逐步完善,界面从粗糙到相对精良,最终成为一款功能完整的桌面小游戏,也是一次熟悉 Skill 编程流程的完整练手。
适用场景
- 编程入门的实践案例,帮助新手理解 GUI 界面与事件交互的基础实现
- 办公场景的轻量休闲解压小游戏
原始提交内容(点开查看,可复制全文去问 AI)
-- coding: utf-8 --
“”” 扫雷 (Minesweeper) - tkinter 实现
一个使用 Python 标准库 tkinter 编写的经典扫雷游戏。
功能特性:
- 三种难度:初级(9x9, 10雷) / 中级(16x16, 40雷) / 高级(16x30, 99雷)
- 左键翻开格子,右键插旗(标记地雷)/再次右键取消
- 自动展开空白区域(连锁翻开)
- 顶部状态栏:剩余雷数、计时器、笑脸重开按钮
- 首点保护:第一次点击永远不会踩雷
- 游戏胜利 / 失败判定与提示
运行方式: python 扫雷.py 或:pyinstaller -F -w 扫雷.py (打包成单文件 exe)
作者:sugon “””
import random import time import tkinter as tk from tkinter import messagebox
难度配置:(行, 列, 雷数)
DIFFICULTIES = { “初级”: (9, 9, 10), “中级”: (16, 16, 40), “高级”: (16, 30, 99), }
数字颜色(1~8),与经典扫雷风格一致
NUM_COLORS = { 1: “#0000ff”, 2: “#008000”, 3: “#ff0000”, 4: “#000080”, 5: “#800000”, 6: “#008080”, 7: “#000000”, 8: “#808080”, }
CELL_SIZE = 30 # 每格像素大小 PAD = 4 # 边框内边距
class Minesweeper: def init(self, root: tk.Tk): self.root = root self.root.title(“扫雷”) self.root.resizable(False, False)
# 当前难度
self.rows, self.cols, self.mines = DIFFICULTIES["初级"]
# 运行时状态
self.board = [] # 每格的真实内容:0~8 表示周围雷数,-1 表示是雷
self.revealed = [] # 是否已翻开
self.flagged = [] # 是否已插旗
self.buttons = [] # 按钮控件矩阵
self.first_click = True # 首点保护标记
self.game_over = False
self.start_time = 0.0
self.timer_running = False
self._build_menu()
self._build_widgets()
self.new_game()
# ---------------------- 界面构建 ----------------------
def _build_menu(self):
menubar = tk.Menu(self.root)
difficulty_menu = tk.Menu(menubar, tearoff=0)
for name in DIFFICULTIES:
difficulty_menu.add_command(
label=name, command=lambda n=name: self.change_difficulty(n)
)
menubar.add_cascade(label="难度", menu=difficulty_menu)
menubar.add_command(label="重新开始", command=self.new_game)
menubar.add_command(label="退出", command=self.root.quit)
self.root.config(menu=menubar)
def _build_widgets(self):
# 顶部状态栏
self.status_frame = tk.Frame(self.root, relief=tk.RAISED, bd=2)
self.status_frame.pack(fill=tk.X, padx=PAD, pady=PAD)
self.mine_label = tk.Label(
self.status_frame, text="", font=("Courier", 16, "bold"),
bg="black", fg="red", width=4,
)
self.mine_label.pack(side=tk.LEFT, padx=4)
self.face_button = tk.Button(
self.status_frame, text="", font=("Arial", 16),
width=3, command=self.new_game,
)
self.face_button.pack(side=tk.LEFT, expand=True)
self.time_label = tk.Label(
self.status_frame, text="000", font=("Courier", 16, "bold"),
bg="black", fg="red", width=4,
)
self.time_label.pack(side=tk.RIGHT, padx=4)
# 棋盘容器
self.board_frame = tk.Frame(self.root, relief=tk.SUNKEN, bd=2)
self.board_frame.pack(padx=PAD, pady=(0, PAD))
# ---------------------- 游戏逻辑 ----------------------
def change_difficulty(self, name: str):
self.rows, self.cols, self.mines = DIFFICULTIES[name]
self.new_game()
def new_game(self):
"""重置棋局并重新生成界面。"""
self.first_click = True
self.game_over = False
self.timer_running = False
self.start_time = 0.0
# 初始化数据矩阵
self.board = [[0] * self.cols for _ in range(self.rows)]
self.revealed = [[False] * self.cols for _ in range(self.rows)]
self.flagged = [[False] * self.cols for _ in range(self.rows)]
# 重建按钮矩阵
for widget in self.board_frame.winfo_children():
widget.destroy()
self.buttons = []
for r in range(self.rows):
row_buttons = []
for c in range(self.cols):
btn = tk.Button(
self.board_frame, width=2, height=1,
font=("Arial", 12, "bold"),
relief=tk.RAISED, bd=2,
command=lambda rr=r, cc=c: self.on_left_click(rr, cc),
)
btn.bind(
"<Button-3>",
lambda e, rr=r, cc=c: self.on_right_click(rr, cc),
)
btn.grid(row=r, column=c)
row_buttons.append(btn)
self.buttons.append(row_buttons)
self.face_button.config(text="")
self._update_mine_counter()
self.time_label.config(text="000")
def _place_mines(self, safe_r: int, safe_c: int):
"""在排除首点及其邻格的位置随机布雷。"""
forbidden = set()
for dr in (-1, 0, 1):
for dc in (-1, 0, 1):
nr, nc = safe_r + dr, safe_c + dc
if 0 <= nr < self.rows and 0 <= nc < self.cols:
forbidden.add((nr, nc))
positions = [
(r, c)
for r in range(self.rows)
for c in range(self.cols)
if (r, c) not in forbidden
]
mine_spots = random.sample(positions, self.mines)
for r, c in mine_spots:
self.board[r][c] = -1 # -1 代表地雷
# 计算每格周围雷数
for r in range(self.rows):
for c in range(self.cols):
if self.board[r][c] == -1:
continue
count = 0
for dr in (-1, 0, 1):
for dc in (-1, 0, 1):
nr, nc = r + dr, c + dc
if 0 <= nr < self.rows and 0 <= nc < self.cols:
if self.board[nr][nc] == -1:
count += 1
self.board[r][c] = count
def _start_timer(self):
if not self.timer_running:
self.timer_running = True
self.start_time = time.time()
self._tick()
def _tick(self):
if not self.timer_running or self.game_over:
return
elapsed = int(time.time() - self.start_time)
self.time_label.config(text=f"{min(elapsed, 999):03d}")
self.root.after(200, self._tick)
def _update_mine_counter(self):
remaining = self.mines - sum(
self.flagged[r][c]
for r in range(self.rows)
for c in range(self.cols)
)
self.mine_label.config(text=f"{max(remaining, -99):03d}")
# ---------------------- 交互处理 ----------------------
def on_left_click(self, r: int, c: int):
if self.game_over or self.flagged[r][c]:
return
if self.first_click:
self._place_mines(r, c)
self.first_click = False
self._start_timer()
if self.board[r][c] == -1:
self._reveal_all_mines(hit=(r, c))
self._lose()
return
self._reveal(r, c)
self._check_win()
def on_right_click(self, r: int, c: int):
if self.game_over or self.revealed[r][c]:
return
self.flagged[r][c] = not self.flagged[r][c]
btn = self.buttons[r][c]
btn.config(text="" if self.flagged[r][c] else "", fg="black")
self._update_mine_counter()
def _reveal(self, r: int, c: int):
"""翻开单格,若为 0 则连锁展开。"""
stack = [(r, c)]
while stack:
cr, cc = stack.pop()
if self.revealed[cr][cc] or self.flagged[cr][cc]:
continue
self.revealed[cr][cc] = True
btn = self.buttons[cr][cc]
btn.config(relief=tk.SUNKEN, bd=1)
value = self.board[cr][cc]
if value == 0:
btn.config(text="", bg="#c0c0c0")
for dr in (-1, 0, 1):
for dc in (-1, 0, 1):
nr, nc = cr + dr, cc + dc
if 0 <= nr < self.rows and 0 <= nc < self.cols:
if not self.revealed[nr][nc]:
stack.append((nr, nc))
else:
btn.config(text=str(value), fg=NUM_COLORS[value], bg="#c0c0c0")
def _reveal_all_mines(self, hit=None):
for r in range(self.rows):
for c in range(self.cols):
if self.board[r][c] == -1:
btn = self.buttons[r][c]
if hit and (r, c) == hit:
btn.config(text="", bg="red")
else:
btn.config(text="", bg="#c0c0c0")
elif self.flagged[r][c] and self.board[r][c] != -1:
# 插错旗的格子上标叉
self.buttons[r][c].config(text="", bg="#c0c0c0")
def _lose(self):
self.game_over = True
self.timer_running = False
self.face_button.config(text="")
def _check_win(self):
safe_total = self.rows * self.cols - self.mines
opened = sum(
self.revealed[r][c]
for r in range(self.rows)
for c in range(self.cols)
)
if opened == safe_total:
self.game_over = True
self.timer_running = False
self.face_button.config(text="")
# 自动给所有雷插旗
for r in range(self.rows):
for c in range(self.cols):
if self.board[r][c] == -1 and not self.flagged[r][c]:
self.flagged[r][c] = True
self.buttons[r][c].config(text="")
self._update_mine_counter()
messagebox.showinfo("胜利", " 恭喜,你扫清了所有地雷!")
def main(): root = tk.Tk() Minesweeper(root) root.mainloop()
if name == “main”: main()