基于 Python 的怀旧向扫雷:融合单人闯关、联机竞技与个性化皮肤的全栈实现
主播今天突发奇想扫雷为什么没有皮肤呢,而且为什么不能和好友联机一起玩,所以我查询了资料并且成功并运行了这款扫雷游戏。
扫雷作为 Windows 系统自带的经典小游戏,凭借简单的规则和富有挑战性的玩法,成为无数人的童年回忆。本文将带你用 Python 从零开始实现一款功能完备的扫雷游戏,不仅包含经典的单人模式,还加入了皮肤系统、成就系统和联机对战功能,让传统游戏焕发新活力。
开发环境与核心依赖
本次实现基于 Python 的标准 GUI 库tkinter,无需额外安装第三方图形库,确保代码的可移植性。核心依赖如下:
- Python 3.6+(推荐 3.9 版本,兼容所有标准库)
tkinter:用于构建游戏界面,Python 自带无需额外安装socket:实现联机对战的网络通信json:存储用户数据、游戏记录和配置信息
核心功能设计与实现
1. 游戏基础框架搭建
扫雷的核心是一个网格,每个格子可能是地雷或数字(表示周围地雷数量)。我们首先定义游戏的基础参数,包括不同难度的网格大小和地雷数量:
# 多难度配置(行、列、地雷数、窗口宽、窗口高)
DIFFICULTY = {
"入门": (5, 5, 3, 200, 250),
"简单": (9, 9, 10, 300, 350),
"中等": (16, 16, 40, 500, 550),
"困难": (30, 16, 99, 800, 550),
"地狱": (30, 30, 150, 900, 950)
}
主窗口类MainWindow负责游戏入口,包含单人游戏、联机游戏、皮肤商店和成就系统的入口按钮:
class MainWindow:
def __init__(self, root):
self.root = root
self.root.title("Python 扫雷游戏 - 终极版")
self.root.geometry("450x400")
self.user_data = init_user_data() # 初始化用户数据
self.create_menu() # 创建主菜单界面
def create_menu(self):
# 标题与功能按钮布局(省略具体UI代码)
ttk.Button(self.root, text="单人游戏", command=self.start_single_game).pack(pady=6)
ttk.Button(self.root, text="联机游戏", command=self.start_online_game).pack(pady=6)
# 其他按钮...
. 游戏核心逻辑实现
游戏逻辑的核心是MinesweeperGUI类,负责网格初始化、地雷生成、用户交互处理和胜负判断。
(1)网格与地雷初始化
首先需要随机生成地雷位置,并计算每个非地雷格子周围的地雷数量:
def init_game(self):
"""初始化游戏网格,生成地雷并计算周围雷数"""
self.grid = [[0 for _ in range(self.cols)] for _ in range(self.rows)]
# 随机生成地雷
mine_count = 0
while mine_count < self.mines:
x = random.randint(0, self.rows - 1)
y = random.randint(0, self.cols - 1)
if self.grid[x][y] != 9: # 9表示地雷
self.grid[x][y] = 9
mine_count += 1
self.update_around(x, y) # 更新周围格子的雷数
def update_around(self, x, y):
"""计算地雷周围8个方向的格子的雷数"""
directions = [(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1)]
for dx, dy in directions:
nx, ny = x + dx, y + dy
if 0 <= nx < self.rows and 0 <= ny < self.cols and self.grid[nx][ny] != 9:
self.grid[nx][ny] += 1
(2)用户交互处理
扫雷的核心操作是左键点击(翻开格子)和右键点击(标记地雷),需要分别处理:
def click_cell(self, x, y):
"""左键点击格子:翻开格子,处理胜利/失败逻辑"""
if self.game_over or self.buttons[x][y]["relief"] == tk.SUNKEN:
return # 游戏结束或已翻开的格子不响应
# 第一次点击开始计时
if self.start_time is None:
self.start_time = time.time()
self.update_timer()
# 踩中地雷:游戏失败
if self.grid[x][y] == 9:
self.buttons[x][y].config(text="💣", bg="red", relief=tk.SUNKEN)
self.game_over = True
self.win = False
self.show_result()
return
# 显示格子信息(数字或空白)
self.buttons[x][y].config(
bg=self.current_skin["cell_clicked"], # 使用当前皮肤
relief=tk.SUNKEN,
text=str(self.grid[x][y]) if self.grid[x][y] != 0 else "",
fg=self.current_skin["number"][self.grid[x][y]] # 数字颜色随皮肤变化
)
self.click_count += 1
# 递归展开空白区域(周围无地雷的格子)
if self.grid[x][y] == 0:
for dx, dy in directions:
self.click_cell(x+dx, y+dy)
# 所有非地雷格子被翻开:游戏胜利
if self.click_count == self.total_safe:
self.game_over = True
self.win = True
self.show_result()
def right_click_cell(self, x, y):
"""右键点击格子:标记/取消地雷旗帜"""
if self.game_over or self.buttons[x][y]["relief"] == tk.SUNKEN:
return
if self.buttons[x][y]["text"] == "": # 标记旗帜
self.buttons[x][y].config(text="🚩", fg="red")
self.flag_count += 1
elif self.buttons[x][y]["text"] == "🚩": # 取消标记
self.buttons[x][y].config(text="")
self.flag_count -= 1
self.mine_label.config(text=f"地雷:{self.mines - self.flag_count}") # 更新剩余地雷数
3. 特色功能:皮肤系统与成就系统
为了增强游戏趣味性,我们加入了皮肤切换和成就解锁功能,通过json文件存储用户数据。
(1)皮肤系统实现
皮肤配置定义了界面元素的颜色,用户可在商店中切换:
# 皮肤配置(背景、格子、数字颜色等)
SKINS = {
"默认皮肤": {
"bg": "#C0C0C0",
"cell_normal": "#C0C0C0",
"cell_clicked": "#DCDCDC",
"number": ["#000000", "#0000FF", "#008000", "#FF0000", "#000080", "#800000", "#008080", "#808080"]
},
"暗黑皮肤": {
"bg": "#2D2D2D",
"cell_normal": "#4A4A4A",
"cell_clicked": "#6E6E6E",
"number": ["#FFFFFF", "#87CEFA", "#98FB98", "#FFB6C1", "#DDA0DD", "#F0E68C", "#FFA07A", "#20B2AA"]
}
# 其他皮肤...
}
皮肤商店窗口SkinShopWindow允许用户预览和切换皮肤,切换后即时生效:
def use_skin(self):
"""应用选中的皮肤"""
selected_idx = self.skin_listbox.curselection()
if selected_idx:
skin_name = list(SKINS.keys())[selected_idx[0]]
self.user_data["current_skin"] = skin_name
save_user_data(self.user_data) # 保存到本地文件
messagebox.showinfo("使用成功", f"已切换至{skin_name}!新游戏将生效~")
(2)成就系统实现
成就系统通过触发条件判断是否解锁,解锁后奖励金币,增强游戏成就感:
# 成就配置(条件、奖励、描述)
ACHIEVEMENTS = {
"初露锋芒": {
"condition": lambda user: user["records"]["win_count"] >= 1, # 获胜1次
"reward": 50,
"desc": "获得1次游戏胜利"
},
"地狱征服者": {
"condition": lambda user: any(r["difficulty"] == "地狱" and r["win"] for r in user["history"]),
"reward": 500,
"desc": "在「地狱」难度中获胜1次"
}
# 其他成就...
}
def check_achievements(user_data):
"""检查并解锁成就,发放奖励"""
for name, ach in ACHIEVEMENTS.items():
if not user_data["achievements"][name] and ach["condition"](user_data):
user_data["achievements"][name] = True # 标记为已解锁
user_data["coins"] += ach["reward"] # 奖励金币
messagebox.showinfo("成就解锁!", f"🏆 解锁成就:{name}\n奖励金币:{ach['reward']}枚")
save_user_data(user_data)
4. 联机对战功能实现
通过socket库实现局域网内的联机对战,支持主机创建房间和客户端加入房间,先完成扫雷的一方获胜。
(1)主机创建房间
主机通过start_server函数启动 TCP 服务器,等待客户端连接:
def start_server(game_instance):
"""启动联机服务器(主机)"""
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((get_local_ip(), PORT)) # 绑定本地IP和端口
server_socket.listen(1)
game_instance.root.after(0, lambda: messagebox.showinfo(
"服务器已启动", f"本机IP:{get_local_ip()}\n等待好友连接..."
))
conn, addr = server_socket.accept() # 接受客户端连接
game_instance.peer_conn = conn # 保存连接对象
game_instance.net_label.config(text=f"联机状态:已连接 {addr[0]}", foreground="green")
# 启动消息接收线程
threading.Thread(target=receive_message, args=(game_instance,), daemon=True).start()
(2)客户端加入房间
客户端通过connect_client函数连接主机 IP,建立通信:
def connect_client(game_instance):
"""连接好友服务器(客户端)"""
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
client_socket.connect((game_instance.friend_ip, PORT)) # 连接主机
game_instance.peer_conn = client_socket
game_instance.net_label.config(text=f"联机状态:已连接 {game_instance.friend_ip}", foreground="green")
threading.Thread(target=receive_message, args=(game_instance,), daemon=True).start()
except Exception as e:
messagebox.showerror("连接失败", f"无法连接到 {game_instance.friend_ip}:{str(e)}")
(3)对战结果同步
游戏结束时,通过send_game_result发送结果给对方,先完成者获胜:
def send_game_result(self):
"""发送游戏结果给对手"""
if self.peer_conn:
result = f"{'WIN' if self.win else 'LOSE'}_{time.time() - self.start_time:.1f}"
self.peer_conn.send(result.encode("utf-8"))
def receive_game_result(self, result):
"""接收对手结果并处理"""
status, time_used = result.split("_")
self.game_over = True
if status == "WIN":
messagebox.showinfo("好友获胜", f"好友已获胜!耗时:{time_used}秒\n你挑战失败~")
else:
messagebox.showinfo("好友失败", f"好友踩中地雷!你获得胜利!")
游戏运行与扩展方向
运行方法
- 确保 Python 3.6 + 环境,无需安装额外依赖;
- 将代码保存为你的文件名
.py,运行即可启动游戏; - 首次运行会自动创建用户数据文件,存储金币、成就和皮肤设置。
总结
本文通过 Python 的tkinter和socket库,实现了一款功能丰富的扫雷游戏,涵盖了经典玩法与创新功能。代码采用模块化设计,从游戏逻辑到用户交互,再到数据存储和网络通信,每一部分都可独立扩展。无论是作为 Python GUI 编程的学习案例,还是经典游戏的复刻实践,都具有很好的参考价值。
通过这个项目,你不仅能掌握tkinter界面开发、事件处理、数据持久化等基础技能,还能了解网络编程和多线程协作的核心思想,为更复杂的游戏开发打下基础。
更多推荐


所有评论(0)