主播今天突发奇想扫雷为什么没有皮肤呢,而且为什么不能和好友联机一起玩,所以我查询了资料并且成功并运行了这款扫雷游戏。

扫雷作为 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"好友踩中地雷!你获得胜利!")

游戏运行与扩展方向

运行方法

  1. 确保 Python 3.6 + 环境,无需安装额外依赖;
  2. 将代码保存为你的文件名.py,运行即可启动游戏;
  3. 首次运行会自动创建用户数据文件,存储金币、成就和皮肤设置。

总结

本文通过 Python 的tkintersocket库,实现了一款功能丰富的扫雷游戏,涵盖了经典玩法与创新功能。代码采用模块化设计,从游戏逻辑到用户交互,再到数据存储和网络通信,每一部分都可独立扩展。无论是作为 Python GUI 编程的学习案例,还是经典游戏的复刻实践,都具有很好的参考价值。

通过这个项目,你不仅能掌握tkinter界面开发、事件处理、数据持久化等基础技能,还能了解网络编程和多线程协作的核心思想,为更复杂的游戏开发打下基础。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐