Python实现功能完整的扫雷小游戏
一、游戏功能亮点
本次实现的扫雷游戏在基础玩法之上,新增了三大核心功能,提升游戏体验与挑战性:
计时器功能:游戏启动后自动计时,结束时显示通关或失败用时,增强竞技感;
难度选择功能:提供初级、中级、高级三种模式,对应不同的格子尺寸与地雷数量,适配不同水平的玩家;
排行榜功能:通过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
转载时必须以链接形式注明原始出处及本声明。
如果本文对你有帮助,那么请你赞助我,让我更有激情的写下去,帮助更多的人。
