返回作品列表

网友作品 #000145 · 提交于 2026/08/11

Python扫雷小游戏:经典玩法三难度桌面版

★★★★☆零基础AI 编程工具国内其他模型python

作品作者

Mustang零基础
开发工具
AI 编程工具
AI 模型
国内其他模型
使用 Skill
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()