欢迎来到 盹猫(>^ω^<)的博客
本篇文章主要介绍了
[从零实现Python扫雷游戏:完整开发指南与深度解析]
❤博主广交技术好友,喜欢文章的可以关注一下❤
扫雷作为Windows经典游戏,承载了许多人的童年回忆。本文将详细介绍如何使用Python和Tkinter库从零开始构建一个功能完整的扫雷游戏,涵盖游戏设计、算法实现和界面开发的全过程。
一、游戏架构设计
1.1 核心组件
我们的扫雷游戏由以下几个核心模块组成:
游戏逻辑模块:处理地雷生成、数字计算、胜负判断等核心逻辑
图形界面模块:使用Tkinter构建可视化界面
数据持久化模块:记录和读取最佳成绩
游戏控制模块:管理游戏状态和流程
1.2 类结构设计
class Minesweeper:
def __init__(self, master, width=10, height=10, mines=10, difficulty="medium"):
# 初始化游戏参数
self.width = width # 游戏板宽度
self.height = height # 游戏板高度
self.mines = mines # 地雷数量
self.difficulty = difficulty # 游戏难度
# 游戏数据结构
self.board = [] # 游戏板二维数组
self.buttons = [] # 按钮二维数组
self.mine_positions = set() # 地雷位置集合
self.flag_positions = set() # 标记位置集合
self.revealed_positions = set() # 已揭开位置集合
# 游戏状态
self.start_time = 0 # 游戏开始时间
self.highscores = {} # 最佳成绩记录
二、核心算法实现
2.1 地雷生成算法
地雷生成需要满足两个条件:
随机分布
数量精确
我们使用Python的random
模块实现:
def setup_board(self):
# 随机放置地雷
while len(self.mine_positions) < self.mines:
x = random.randint(0, self.width - 1)
y = random.randint(0, self.height - 1)
self.mine_positions.add((x, y))
self.board[y][x] = -1 # -1表示地雷
2.2 数字计算算法
每个非地雷格子需要计算周围8个格子中的地雷数量:
# 计算每个格子周围的地雷数
for y in range(self.height):
for x in range(self.width):
if self.board[y][x] == -1: # 跳过地雷格子
continue
# 检查周围8个格子
for nx, ny in [(x-1, y-1), (x, y-1), (x+1, y-1),
(x-1, y), (x+1, y),
(x-1, y+1), (x, y+1), (x+1, y+1)]:
if 0 <= nx < self.width and 0 <= ny < self.height and self.board[ny][nx] == -1:
self.board[y][x] += 1
2.3 空白区域展开算法
当玩家点击空白格子时,需要自动展开所有相邻的空白区域:
def reveal_neighbors(self, x, y):
# 检查周围8个格子
for nx, ny in [(x-1, y-1), (x, y-1), (x+1, y-1),
(x-1, y), (x+1, y),
(x-1, y+1), (x, y+1), (x+1, y+1)]:
# 确保坐标在范围内且未被揭开
if 0 <= nx < self.width and 0 <= ny < self.height and (nx, ny) not in self.revealed_positions:
self.on_click(nx, ny) # 递归揭开该格子
三、图形界面开发
3.1 主界面布局
使用Tkinter的网格布局管理器创建游戏板:
def create_widgets(self):
for y in range(self.height):
for x in range(self.width):
btn = tk.Button(
self.master,
text="",
width=2,
height=1,
bg="#f0f0f0",
relief=tk.RAISED,
font=("Helvetica", 10, "bold"),
command=lambda x=x, y=y: self.on_click(x, y)
)
btn.bind("<Button-3>", lambda e, x=x, y=y: self.on_right_click(x, y))
btn.grid(row=y, column=x)
self.buttons[y][x] = btn
3.2 交互事件处理
左键点击事件
def on_click(self, x, y):
if (x, y) in self.mine_positions: # 踩到地雷
self.game_over()
elif self.board[y][x] > 0: # 数字格子
self.reveal_number(x, y)
else: # 空白格子
self.reveal_neighbors(x, y)
self.check_victory()
右键点击事件
def on_right_click(self, x, y):
btn = self.buttons[y][x]
if btn['state'] != tk.DISABLED:
if btn['text'] == "": # 未标记
btn.config(text="🚩", fg="red")
self.flag_positions.add((x, y))
elif btn['text'] == "🚩": # 已标记
btn.config(text="", fg="black")
self.flag_positions.remove((x, y))
self.check_victory()
3.3 游戏状态显示
计时器实现:
def update_timer(self):
elapsed_time = int(time.time() - self.start_time)
self.timer_label.config(
text=f"时间: {elapsed_time}秒 | 最佳: {self.highscores.get(self.difficulty, 999)}秒"
)
self.master.after(1000, self.update_timer) # 每秒更新一次
四、游戏功能扩展
4.1 多难度级别支持
def change_difficulty(self, difficulty):
self.difficulty = difficulty
# 根据难度设置参数
if difficulty == "easy":
self.width, self.height, self.mines = 8, 8, 10
elif difficulty == "hard":
self.width, self.height, self.mines = 16, 16, 40
else: # medium
self.width, self.height, self.mines = 10, 10, 10
self.restart_game()
4.2 最佳成绩系统
使用JSON文件存储成绩:
def __init__(self, master, ...):
self.highscores_file = "minesweeper_scores.json"
if not os.path.exists(self.highscores_file):
with open(self.highscores_file, 'w') as f:
json.dump({"easy": 999, "medium": 999, "hard": 999}, f)
with open(self.highscores_file, 'r') as f:
self.highscores = json.load(f)
成绩更新逻辑:
def check_victory(self):
if self.is_win():
elapsed_time = int(time.time() - self.start_time)
if elapsed_time < self.highscores[self.difficulty]:
self.highscores[self.difficulty] = elapsed_time
with open(self.highscores_file, 'w') as f:
json.dump(self.highscores, f)
五、游戏测试与优化
5.1 常见问题与解决方案
第一次点击就踩雷:
解决方案:在第一次点击后再生成地雷,确保点击位置安全
递归展开堆栈溢出:
解决方案:对于大型游戏板,使用迭代代替递归
界面卡顿:
解决方案:减少不必要的界面刷新,使用双缓冲技术
5.2 性能优化建议
使用位运算加速邻居位置计算
预计算所有格子的邻居位置,避免重复计算
使用更高效的数据结构存储游戏状态
六、完整代码
"""
扫雷游戏主程序
使用tkinter实现的图形界面扫雷游戏
支持三种难度级别和最佳成绩记录
"""
import tkinter as tk
from tkinter import messagebox, ttk
import random # 用于随机生成地雷位置
import time # 用于游戏计时
import json # 用于读写最佳成绩
import os # 用于检查文件是否存在
class Minesweeper:
"""扫雷游戏主类"""
def __init__(self, master, width=10, height=10, mines=10, difficulty="medium"):
"""
初始化游戏
:param master: tkinter根窗口
:param width: 游戏板宽度(默认10)
:param height: 游戏板高度(默认10)
:param mines: 地雷数量(默认10)
:param difficulty: 游戏难度(easy/medium/hard)
"""
# 初始化最佳成绩文件
self.highscores_file = "minesweeper_scores.json"
# 如果成绩文件不存在,创建并初始化
if not os.path.exists(self.highscores_file):
with open(self.highscores_file, 'w') as f:
# 默认设置各难度最佳成绩为999秒
json.dump({"easy": 999, "medium": 999, "hard": 999}, f)
self.master = master # tkinter主窗口
self.difficulty = difficulty # 游戏难度
# 根据难度设置不同参数
if difficulty == "easy":
self.width, self.height, self.mines = 8, 8, 10 # 简单: 8x8, 10个地雷
elif difficulty == "hard":
self.width, self.height, self.mines = 16, 16, 40 # 困难: 16x16, 40个地雷
else: # medium
self.width, self.height, self.mines = width, height, mines # 中等: 默认参数
# 加载最佳成绩
with open(self.highscores_file, 'r') as f:
self.highscores = json.load(f)
# 设置不同数字对应的颜色
self.colors = {
-1: "red", # 地雷
1: "blue", # 1个地雷
2: "green", # 2个地雷
3: "red", # 3个地雷
4: "purple", # 4个地雷
5: "maroon", # 5个地雷
6: "turquoise", # 6个地雷
7: "black", # 7个地雷
8: "gray" # 8个地雷
}
# 初始化游戏板(二维数组)
self.board = [[0 for _ in range(self.width)] for _ in range(self.height)]
# 初始化按钮网格
self.buttons = [[None for _ in range(self.width)] for _ in range(self.height)]
self.mine_positions = set() # 地雷位置集合
self.flag_positions = set() # 标记位置集合
self.revealed_positions = set() # 已揭开位置集合
# 创建菜单栏
self.create_menu()
# 设置游戏板(放置地雷)
self.setup_board()
# 创建游戏界面控件
self.create_widgets()
# 记录游戏开始时间
self.start_time = time.time()
# 创建计时器标签
self.timer_label = tk.Label(
master,
text=f"时间: 0秒 | 最佳: {self.highscores.get(self.difficulty, 999)}秒",
font=("Helvetica", 10)
)
self.timer_label.grid(row=self.height, columnspan=self.width)
# 开始更新计时器
self.update_timer()
def create_menu(self):
"""创建游戏菜单栏"""
menubar = tk.Menu(self.master)
self.master.config(menu=menubar)
# 创建游戏菜单
game_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="游戏", menu=game_menu)
# 添加难度选项
game_menu.add_command(label="简单", command=lambda: self.change_difficulty("easy"))
game_menu.add_command(label="中等", command=lambda: self.change_difficulty("medium"))
game_menu.add_command(label="困难", command=lambda: self.change_difficulty("hard"))
game_menu.add_separator()
# 添加功能选项
game_menu.add_command(label="最佳成绩", command=self.show_highscores)
game_menu.add_command(label="重新开始", command=self.restart_game)
game_menu.add_command(label="退出", command=self.master.quit)
# 创建帮助菜单
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="帮助", menu=help_menu)
help_menu.add_command(label="游戏说明", command=self.show_help)
def show_help(self):
"""显示游戏帮助信息"""
help_text = """扫雷游戏规则:
1. 左键点击格子揭开它
2. 右键点击格子标记/取消标记地雷
3. 数字表示周围8个格子中的地雷数量
4. 标记所有地雷并揭开所有安全格子获胜
5. 踩到地雷游戏结束
难度说明:
- 简单:8x8 格子,10个地雷
- 中等:10x10 格子,10个地雷
- 困难:16x16 格子,40个地雷"""
messagebox.showinfo("游戏帮助", help_text)
def show_highscores(self):
"""显示各难度最佳成绩"""
with open(self.highscores_file, 'r') as f:
scores = json.load(f)
messagebox.showinfo("最佳成绩",
f"简单: {scores['easy']}秒\n"
f"中等: {scores['medium']}秒\n"
f"困难: {scores['hard']}秒")
def change_difficulty(self, difficulty):
"""
更改游戏难度
:param difficulty: 新难度级别(easy/medium/hard)
"""
self.difficulty = difficulty
# 重新加载最佳成绩
with open(self.highscores_file, 'r') as f:
self.highscores = json.load(f)
# 重新开始游戏
self.restart_game()
def restart_game(self):
"""重新开始游戏"""
# 清除现有按钮
for y in range(len(self.buttons)):
for x in range(len(self.buttons[0])):
if self.buttons[y][x]:
self.buttons[y][x].destroy()
# 根据当前难度重置游戏参数
if self.difficulty == "easy":
self.width, self.height, self.mines = 8, 8, 10
elif self.difficulty == "hard":
self.width, self.height, self.mines = 16, 16, 40
else:
self.width, self.height, self.mines = 10, 10, 10
# 重置游戏板状态
self.board = [[0 for _ in range(self.width)] for _ in range(self.height)]
self.buttons = [[None for _ in range(self.width)] for _ in range(self.height)]
self.mine_positions = set()
self.flag_positions = set()
self.revealed_positions = set()
# 重新初始化游戏
self.setup_board()
self.create_widgets()
self.start_time = time.time() # 重置计时器
def setup_board(self):
"""设置游戏板,随机放置地雷并计算周围地雷数"""
# 随机放置地雷
while len(self.mine_positions) < self.mines:
x = random.randint(0, self.width - 1)
y = random.randint(0, self.height - 1)
self.mine_positions.add((x, y))
self.board[y][x] = -1 # -1表示地雷
# 计算每个格子周围的地雷数
for y in range(self.height):
for x in range(self.width):
if self.board[y][x] == -1: # 跳过地雷格子
continue
# 检查周围8个格子
for nx, ny in [(x-1, y-1), (x, y-1), (x+1, y-1),
(x-1, y), (x+1, y),
(x-1, y+1), (x, y+1), (x+1, y+1)]:
# 确保坐标在游戏板范围内且是地雷
if 0 <= nx < self.width and 0 <= ny < self.height and self.board[ny][nx] == -1:
self.board[y][x] += 1 # 增加周围地雷计数
def create_widgets(self):
"""创建游戏界面按钮网格"""
for y in range(self.height):
for x in range(self.width):
# 创建按钮
btn = tk.Button(
self.master,
text="", # 初始无文本
width=2,
height=1,
bg="#f0f0f0", # 背景色
relief=tk.RAISED, # 3D凸起效果
font=("Helvetica", 10, "bold"), # 字体
command=lambda x=x, y=y: self.on_click(x, y) # 左键点击事件
)
# 绑定右键点击事件(标记地雷)
btn.bind("<Button-3>", lambda e, x=x, y=y: self.on_right_click(x, y))
# 将按钮放置在网格中
btn.grid(row=y, column=x)
# 保存按钮引用
self.buttons[y][x] = btn
def on_click(self, x, y):
"""
处理格子点击事件
:param x: 点击格子的x坐标
:param y: 点击格子的y坐标
"""
if (x, y) in self.mine_positions: # 点击到地雷
self.buttons[y][x].config(bg="red", text="*") # 显示地雷
messagebox.showinfo("游戏结束", "你踩到地雷了!")
self.reveal_all_mines() # 显示所有地雷
self.restart_game() # 重新开始游戏
elif (x, y) not in self.revealed_positions: # 未揭开的格子
self.revealed_positions.add((x, y)) # 标记为已揭开
btn = self.buttons[y][x]
if self.board[y][x] > 0: # 周围有地雷的格子
btn.config(
text=str(self.board[y][x]), # 显示地雷数
state=tk.DISABLED, # 禁用按钮
disabledforeground=self.colors[self.board[y][x]], # 设置数字颜色
relief=tk.SUNKEN # 凹陷效果
)
else: # 空白格子
btn.config(state=tk.DISABLED, relief=tk.SUNKEN, bg="#e0e0e0")
self.reveal_neighbors(x, y) # 自动揭开周围空白格子
self.check_victory() # 检查是否获胜
def on_right_click(self, x, y):
"""
处理右键点击事件(标记/取消标记地雷)
:param x: 点击格子的x坐标
:param y: 点击格子的y坐标
"""
btn = self.buttons[y][x]
if btn['state'] != tk.DISABLED: # 只有未禁用的按钮可以标记
if btn['text'] == "": # 未标记状态
btn.config(text="🚩", fg="red") # 添加旗帜标记
self.flag_positions.add((x, y)) # 记录标记位置
elif btn['text'] == "🚩": # 已标记状态
btn.config(text="", fg="black") # 取消标记
self.flag_positions.remove((x, y)) # 移除标记记录
self.check_victory() # 检查是否获胜
def reveal_neighbors(self, x, y):
"""
递归揭开周围的空白格子
:param x: 当前格子的x坐标
:param y: 当前格子的y坐标
"""
# 检查周围8个格子
for nx, ny in [(x-1, y-1), (x, y-1), (x+1, y-1),
(x-1, y), (x+1, y),
(x-1, y+1), (x, y+1), (x+1, y+1)]:
# 确保坐标在范围内且未被揭开
if 0 <= nx < self.width and 0 <= ny < self.height and (nx, ny) not in self.revealed_positions:
self.on_click(nx, ny) # 揭开该格子
def reveal_all_mines(self):
"""显示所有地雷位置"""
for x, y in self.mine_positions:
self.buttons[y][x].config(text="*", bg="red") # 红色背景显示地雷
def check_victory(self):
"""检查游戏是否胜利"""
# 条件1: 所有地雷都被正确标记
# 条件2: 所有非地雷格子都被揭开
if (self.flag_positions == self.mine_positions and
len(self.revealed_positions) == self.width * self.height - self.mines):
elapsed_time = int(time.time() - self.start_time) # 计算用时
# 检查是否打破记录
if elapsed_time < self.highscores[self.difficulty]:
self.highscores[self.difficulty] = elapsed_time # 更新记录
with open(self.highscores_file, 'w') as f:
json.dump(self.highscores, f) # 保存新记录
messagebox.showinfo("胜利", f"新纪录!用时: {elapsed_time}秒")
else:
messagebox.showinfo("胜利",
f"你赢了!用时: {elapsed_time}秒\n"
f"当前记录: {self.highscores[self.difficulty]}秒")
self.restart_game() # 重新开始游戏
def update_timer(self):
"""更新计时器显示"""
elapsed_time = int(time.time() - self.start_time)
self.timer_label.config(
text=f"时间: {elapsed_time}秒 | 最佳: {self.highscores.get(self.difficulty, 999)}秒"
)
# 1秒后再次更新
self.master.after(1000, self.update_timer)
if __name__ == "__main__":
"""程序入口"""
root = tk.Tk() # 创建主窗口
root.title("扫雷游戏") # 设置窗口标题
root.resizable(False, False) # 禁止调整窗口大小
game = Minesweeper(root) # 创建游戏实例
root.mainloop() # 启动主事件循环
6.1 效果图
下面是实现的效果图,看起来和原版还是非常相似的 。
七、继续完善的思路
添加音效系统:为点击、胜利、失败等事件添加音效
实现主题切换:支持多种颜色主题和皮肤
添加解谜模式:预生成有趣的谜题布局
网络对战功能:实现多人扫雷对战
结语
通过本项目,我们完整实现了一个功能丰富的扫雷游戏,涵盖了从算法设计到界面开发的全过程。这个项目不仅可以帮助理解游戏开发的基本原理,也是学习Python GUI编程的绝佳案例。你可以基于这个基础版本,继续扩展更多有趣的功能。
完整代码已在文中提供,建议亲自运行并尝试修改,这是学习编程的最佳方式。如果你有任何问题或改进建议,欢迎在评论区交流讨论!
如果你对区块链内容感兴趣可以查看我的专栏:小试牛刀-区块链
感谢您的关注和收藏!!!!!!