Python实现功能完整的扫雷小游戏

JSON 2026-01-06 17:31:27 388

一、游戏功能亮点

本次实现的扫雷游戏在基础玩法之上,新增了三大核心功能,提升游戏体验与挑战性:

计时器功能:游戏启动后自动计时,结束时显示通关或失败用时,增强竞技感;

难度选择功能:提供初级、中级、高级三种模式,对应不同的格子尺寸与地雷数量,适配不同水平的玩家;

排行榜功能:通过JSON文件实现数据持久化,记录各难度下的最快通关时间,支持实时查看与更新。

二、开发环境与核心技术

1. 开发环境

本项目基于Python 3.x开发,无需额外配置复杂环境,仅依赖Python标准库:

Tkinter:Python内置的GUI开发库,用于构建游戏窗口、按钮、状态栏等交互界面;

random:用于随机生成地雷的位置;

json与os:实现排行榜数据的持久化存储(读取/保存JSON文件)。

2. 核心技术逻辑

游戏的核心运行逻辑可拆解为以下几步:

初始化:创建游戏窗口、状态栏(旗子数量、计时器、功能按钮)、游戏网格;

地雷布置:随机在网格中放置指定数量的地雷,记录地雷位置;

交互响应:处理左键揭开格子、右键插旗/拔旗的操作,判断是否踩雷或通关;

辅助功能:计时器的启动/停止/更新、难度选择界面的切换、排行榜数据的加载/保存/展示。

三、详细实现步骤

1. 基础游戏框架搭建

首先定义扫雷游戏的核心类Minesweeper,在初始化方法中完成界面组件的创建与初始化:

状态栏:包含剩余旗子数量显示、重置按钮、计时器显示、排行榜按钮;

游戏网格:通过Tkinter的Button组件构建二维网格,绑定左键揭开、右键插旗事件;

数据初始化:初始化地雷位置集合、已揭开格子集合、旗子集合、计时器数据等。

2. 核心游戏逻辑实现

(1)地雷布置与相邻地雷计数

通过random.randint随机生成地雷位置,确保地雷数量符合当前难度要求;定义count_adjacent_mines方法,遍历当前格子的8个相邻格子,统计地雷数量,为揭开格子时的数字显示提供依据。

(2)格子揭开与胜负判断

左键点击格子时,若点击到地雷则游戏失败;若为安全格子,则显示相邻地雷数量,若数量为0则递归揭开周围所有安全格子;定义check_win方法,当已揭开格子数量等于总格子数减去地雷数时,判定为游戏胜利。

(3)右键插旗功能

右键点击格子时,切换插旗/拔旗状态,同时更新状态栏的剩余旗子数量显示,确保旗子数量不超过当前难度的地雷数量。

3. 新增功能实现

(1)计时器功能

通过tkinter.after方法实现定时更新,每秒递增计时时间并刷新状态栏显示;游戏结束(胜利/失败)时调用stop_timer方法停止计时,重置游戏时也需停止当前计时器。

(2)难度选择功能

定义show_difficulty_selection函数,创建独立的难度选择窗口,提供三个难度按钮,点击后传递对应参数(格子尺寸、地雷数量)启动游戏;重置游戏时,返回难度选择界面,支持重新选择难度。

(3)排行榜功能

使用JSON文件minesweeper_leaderboard.json存储排行榜数据,初始化时若文件不存在则创建并初始化各难度记录为999秒(表示未通关);游戏胜利时,对比当前用时与历史最快记录,若创造新纪录则更新数据并保存;点击状态栏“排行榜”按钮,弹出窗口展示各难度的最快通关时间。

四、完整代码实现


import tkinter as tk from tkinter import messagebox, simpledialog import random import json import os # 排行榜文件路径 LEADERBOARD_PATH = "minesweeper_leaderboard.json" class MinesweeperGame: def __init__(self, root, rows=10, cols=10, mines=10, level="初级"): self.root = root self.rows = rows self.cols = cols self.mines_total = mines self.level = level self.grid_buttons = [] self.mines_pos = set() self.opened_cells = set() self.flags = set() self.time = 0 self.timer_task = None self.leaderboard = self._load_leaderboard() # 初始化界面 self._init_ui() self._place_mines() self._start_timer() def _init_ui(self): """初始化游戏界面""" # 状态栏 status_bar = tk.Frame(self.root) status_bar.pack(pady=5) # 剩余旗子显示 self.flag_text = tk.StringVar(value=f"剩余旗子: {self.mines_total}") tk.Label(status_bar, textvariable=self.flag_text, font=("Arial", 12)).pack(side=tk.LEFT, padx=10) # 功能按钮 tk.Button(status_bar, text="重置", command=self._restart_game, font=("Arial", 12)).pack(side=tk.LEFT, padx=10) tk.Button(status_bar, text="排行榜", command=self._show_leaderboard, font=("Arial", 12)).pack(side=tk.LEFT, padx=10) # 计时器显示 self.timer_text = tk.StringVar(value="时间: 0s") tk.Label(status_bar, textvariable=self.timer_text, font=("Arial", 12)).pack(side=tk.LEFT, padx=10) # 游戏网格 game_grid = tk.Frame(self.root) game_grid.pack() # 创建网格按钮 for r in range(self.rows): row_buttons = [] for c in range(self.cols): btn = tk.Button( game_grid, width=2, height=1, font=("Arial", 14), command=lambda x=r, y=c: self._open_cell(x, y) ) btn.bind("", lambda e, x=r, y=c: self._toggle_flag(x, y)) btn.grid(row=r, column=c, padx=1, pady=1) row_buttons.append(btn) self.grid_buttons.append(row_buttons) def _place_mines(self): """随机放置地雷""" while len(self.mines_pos) < self.mines_total: r = random.randint(0, self.rows - 1) c = random.randint(0, self.cols - 1) self.mines_pos.add((r, c)) def _count_mines_around(self, r, c): """计算指定格子周围的地雷数量""" count = 0 # 遍历8个相邻格子 for dr in (-1, 0, 1): for dc in (-1, 0, 1): if dr == 0 and dc == 0: continue nr, nc = r + dr, c + dc if 0 <= nr < self.rows and 0 <= nc < self.cols: if (nr, nc) in self.mines_pos: count += 1 return count def _open_cell(self, r, c): """打开指定格子""" # 跳过已打开或插旗的格子 if (r, c) in self.opened_cells or (r, c) in self.flags: return # 踩到地雷,游戏结束 if (r, c) in self.mines_pos: self._game_over(win=False) return # 标记为已打开 self.opened_cells.add((r, c)) mine_count = self._count_mines_around(r, c) # 更新按钮状态 self.grid_buttons[r][c].config( text=str(mine_count) if mine_count > 0 else "", state="disabled", bg="#f0f0f0" ) # 周围无地雷时,递归打开相邻格子 if mine_count == 0: for dr in (-1, 0, 1): for dc in (-1, 0, 1): if dr == 0 and dc == 0: continue nr, nc = r + dr, c + dc if 0 <= nr < self.rows and 0 <= nc < self.cols: self._open_cell(nr, nc) # 检查是否胜利 self._check_win() def _toggle_flag(self, r, c): """右键插旗/拔旗""" if (r, c) in self.opened_cells: return if (r, c) in self.flags: # 移除旗子 self.flags.remove((r, c)) self.grid_buttons[r][c].config(text="", bg="SystemButtonFace") else: # 添加旗子(不超过地雷总数) if len(self.flags) < self.mines_total: self.flags.add((r, c)) self.grid_buttons[r][c].config(text="", bg="orange") # 更新剩余旗子数量 self.flag_text.set(f"剩余旗子: {self.mines_total - len(self.flags)}") def _check_win(self): """检查是否胜利""" if len(self.opened_cells) == self.rows * self.cols - self.mines_total: self._game_over(win=True) def _game_over(self, win): """游戏结束处理""" self._stop_timer() # 禁用所有按钮,显示地雷位置 for r in range(self.rows): for c in range(self.cols): if (r, c) in self.mines_pos: self.grid_buttons[r][c].config(text="", bg="red") self.grid_buttons[r][c].config(state="disabled") # 胜利提示与排行榜更新 if win: record = self.leaderboard.get(self.level, 999) if self.time < record: self.leaderboard[self.level] = self.time self._save_leaderboard() messagebox.showinfo("胜利", f"恭喜通关!用时: {self.time} 秒\n 刷新纪录!") else: messagebox.showinfo("胜利", f"恭喜通关!用时: {self.time} 秒") else: messagebox.showerror("失败", f"踩到地雷!游戏结束\n用时: {self.time} 秒") def _start_timer(self): """启动计时器""" self.time = 0 self._update_timer() def _update_timer(self): """更新计时器""" self.time += 1 self.timer_text.set(f"时间: {self.time}s") self.timer_task = self.root.after(1000, self._update_timer) def _stop_timer(self): """停止计时器""" if self.timer_task: self.root.after_cancel(self.timer_task) def _restart_game(self): """重置游戏""" self._stop_timer() self.root.destroy() show_level_selection() def _load_leaderboard(self): """加载排行榜数据""" if os.path.exists(LEADERBOARD_PATH): with open(LEADERBOARD_PATH, "r", encoding="utf-8") as f: return json.load(f) # 初始化排行榜(999表示未通关) init_data = {"初级": 999, "中级": 999, "高级": 999} self._save_leaderboard(init_data) return init_data def _save_leaderboard(self, data=None): """保存排行榜数据""" data = data or self.leaderboard with open(LEADERBOARD_PATH, "w", encoding="utf-8") as f: json.dump(data, f, indent=4, ensure_ascii=False) def _show_leaderboard(self): """显示排行榜窗口""" lb_win = tk.Toplevel(self.root) lb_win.title("扫雷排行榜") lb_win.geometry("300x200") tk.Label(lb_win, text="扫雷排行榜", font=("Arial", 16)).pack(pady=10) for level, time in self.leaderboard.items(): display = f"{time} 秒" if time != 999 else "未通关" tk.Label(lb_win, text=f"{level}: {display}", font=("Arial", 12)).pack(pady=5) def show_level_selection(): """显示难度选择界面""" def start_game(rows, cols, mines, level): select_win.destroy() root = tk.Tk() root.title(f"扫雷 - {level}") MinesweeperGame(root, rows, cols, mines, level) root.mainloop() # 难度选择窗口 select_win = tk.Tk() select_win.title("选择难度") select_win.geometry("300x200") tk.Label(select_win, text="请选择游戏难度", font=("Arial", 14)).pack(pady=20) # 难度按钮 levels = [ ("初级 (9x9, 10个地雷)", 9, 9, 10, "初级"), ("中级 (16x16, 40个地雷)", 16, 16, 40, "中级"), ("高级 (16x30, 99个地雷)", 16, 30, 99, "高级") ] for text, r, c, m, lvl in levels: tk.Button( select_win, text=text, font=("Arial", 12), command=lambda x=r, y=c, z=m, lv=lvl: start_game(x, y, z, lv) ).pack(pady=5) select_win.mainloop() if __name__ == "__main__": show_level_selection()

五、游戏使用说明

1. 运行游戏

将上述代码保存为minesweeper.py,使用Python 3.x环境运行该文件,即可启动游戏。

2. 难度选择

游戏启动后,首先进入难度选择界面,三种难度参数如下:

初级:9×9格子,10个地雷,适合新手入门;

中级:16×16格子,40个地雷,难度适中;

高级:16×30格子,99个地雷,挑战高阶玩家。

3. 核心操作

左键点击:揭开当前格子,若为地雷则游戏失败,若为安全格子则显示相邻地雷数量;

右键点击:在未揭开的格子上插旗/拔旗,用于标记疑似地雷的位置,旗子数量不超过当前难度的地雷数量;

重置按钮:停止当前游戏,返回难度选择界面,可重新选择难度开始游戏;

排行榜按钮:随时查看各难度下的最快通关时间。

4. 胜负判定与排行榜更新

胜利条件:揭开所有非地雷格子,游戏自动停止计时,若用时刷新当前难度记录,会弹出“新纪录”提示;

失败条件:左键点击到地雷,游戏自动停止计时,弹出失败提示;

排行榜数据:保存在minesweeper_leaderboard.json文件中,关闭游戏后记录不丢失,可手动打开文件查看或修改。

六、拓展与优化方向

若想进一步完善游戏,可参考以下拓展方向:

增加自定义难度功能:允许玩家手动设置格子尺寸与地雷数量;

优化界面样式:为不同数量的相邻地雷设置不同颜色,提升视觉体验;

增加音效反馈:在揭开格子、踩雷、胜利时添加对应的音效;

完善排行榜:支持记录玩家昵称,显示历史前3名记录。

通过本项目的实现,不仅能掌握Tkinter GUI开发、数据持久化等基础技术,还能深入理解游戏的交互逻辑与状态管理,是Python实战练习的优质案例。快来尝试运行代码,挑战自己的扫雷最快记录吧!



版权所属:SO JSON在线解析

原文地址:https://www.sojson.com/blog/556.html

转载时必须以链接形式注明原始出处及本声明。

本文主题:

如果本文对你有帮助,那么请你赞助我,让我更有激情的写下去,帮助更多的人。

相关文章
微信支付功能--PC端生成二维码,实现描支付功能
Python元组剖析
Python print() 函数
url怎么加密?方法有哪些?
Python print() 函数
DNS健康检测的原理?它是如何实现的?
ik分词器如何实现模糊匹配
python之numpy常用的100种数值相关方法及代码示例
Python元组剖析
Springboot 集成Aliyun MQ消息队列,Aliyun 消息队列配置及代码实现
最新文章
文件上传漏洞与防御 4058
前端构建工具选型指南:Webpack、Vite、Rollup、esbuild 深度对比 1444
物联网时代2026年时序数据库选型指南 1151
SaaS行业面临AI挑战:从“无限复用”到“灵活适应” 1269
神经网络:从构造到模型训练全链路解析 1168
一文吃透 Redis 核心存储结构:ziplist、listpack 与哈希表扩容 / 并发查询 1593
Linux sudo提权完整指南:从基础用法到生产级安全配置 691
XSS 和 CSRF 的本质区别及开发防御全解析 772
JVM垃圾回收(GC)全维度解析:从原理到调优实战 813
Linux动静态库与ELF加载全解析:从实操制作到底层原理 912
最热文章
免费天气API,天气JSON API,不限次数获取十五天的天气预报 783114
最新MyEclipse8.5注册码,有效期到2020年 (已经更新) 711464
苹果电脑Mac怎么恢复出厂系统?苹果系统怎么重装系统? 679993
Jackson 时间格式化,时间注解 @JsonFormat 用法、时差问题说明 562673
我为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比 512621
Elasticsearch教程(四) elasticsearch head 插件安装和使用 484794
Jackson 美化输出JSON,优雅的输出JSON数据,格式化输出JSON数据... ... 302947
Java 信任所有SSL证书,HTTPS请求抛错,忽略证书请求完美解决 247433
Elasticsearch教程(一),全程直播(小白级别) 233097
谈谈斐讯路由器劫持,你用斐讯路由器,你需要知道的事情 228329
支付扫码

所有赞助/开支都讲公开明细,用于网站维护:赞助名单查看

查看我的收藏

正在加载... ...