Python扫雷并基于tkinter实现(附代码)

1. Tkinter 简介

1.1 什么是Tkinter

Tkinter 是 Python 的标准 GUI (Graphical User Interface) 库,提供了一系列用于创建窗口应用程序的工具和控件。它允许开发者设计和实现具有图形用户界面的应用程序,如文本编辑器、游戏、数据可视化工具等。Tkinter 是 Python 的一部分,因此不需要额外安装,可以直接导入使用。

1.2 Tkinter 的特点

Tkinter 具有以下特点:

  • 跨平台:Tkinter 支持多种操作系统,包括 Windows、macOS 和 Linux。
  • 简单易用:Tkinter 提供了丰富的控件和简单的 API,使得快速开发 GUI 应用程序变得容易。
  • 内置控件:Tkinter 提供了多种常用的控件,如按钮、文本框、标签、列表框等。
  • 事件驱动:Tkinter 支持事件驱动编程,可以响应用户操作,如点击、输入等。
  • 可扩展:虽然 Tkinter 自身功能有限,但可以通过扩展和自定义控件来实现更复杂的功能。

1.3 Tkinter 与 Python 的关系

Tkinter 是 Python 的标准 GUI 库,它是 Python 语言的一部分,由 Python 的创始人之一 Guido van Rossum 与他人合作开发。由于 Tkinter 是 Python 的内置库,因此它与 Python 语言紧密集成,使得 Python 开发者可以轻松地创建 GUI 应用程序。

Tkinter 的存在使得 Python 不仅适用于脚本编写、系统管理、网络编程等领域,还能够开发具有图形用户界面的应用程序,这大大扩展了 Python 的应用范围。

2. 扫雷游戏实现

2.1 游戏界面设计

游戏界面是玩家与游戏交互的直接媒介,因此一个直观、易用的设计至关重要。在本研究中,我们采用了 Tkinter 库来构建游戏界面,因为它提供了丰富的控件和简单的布局管理。

2.1.1 网格设计

游戏的核心是一个由按钮组成的网格,每个按钮代表一个格子。我们为每个按钮设置了合适的大小和颜色,以确保玩家可以清晰地看到每个格子的状态。按钮的背景色默认为“lightgray”,表示未打开的格子;当格子被打开后,背景色变为“white”。

2.1.2 信息栏设计

在网格的上方,我们设计了一个信息栏,用于显示剩余地雷数和游戏时间。这两项信息对于玩家制定策略至关重要。剩余地雷数随着玩家标记地雷而减少,而游戏时间则从玩家进行第一次点击后开始计算。

2.1.3 控制按钮设计

为了增加游戏的互动性,我们在信息栏中添加了“开始AI”和“停止AI”按钮,允许玩家控制 AI 的扫雷操作。这些按钮通过调用 AI 类中的方法来启动或停止自动扫雷。

2.2 游戏逻辑实现

游戏逻辑是扫雷游戏的核心,包括初始化游戏、处理玩家操作、判断胜负等。

2.2.1 初始化游戏

游戏初始化包括创建网格、随机布置地雷、初始化 AI 等步骤。我们使用随机数生成器来确定地雷的位置,并确保第一个被点击的格子不是地雷。

2.2.2 玩家操作

玩家操作包括左键点击和右键点击。左键点击用于打开格子或标记地雷,右键点击则用于标记地雷。我们通过绑定按钮的点击事件来实现这些操作。

2.2.3 判断胜负

游戏胜负的判断基于玩家是否触发了地雷或成功打开了所有非雷区格子。我们通过检查所有格子的状态来确定游戏是否结束,并相应地显示游戏结果。

2.3 游戏功能介绍

2.3.1 新游戏功能

玩家可以通过菜单栏中的“新游戏”选项来开始一个新的游戏。此外,我们还提供了快捷键(Ctrl+N)来快速开始新游戏。

2.3.2 难度选择功能

游戏提供了不同难度级别的选项,包括初级、中级、高级等。玩家可以根据个人偏好选择不同的难度。

2.3.3 自定义游戏功能

玩家可以通过“自定义”选项来设置游戏的行数、列数和雷数,从而创建个性化的游戏难度。

2.3.4 AI 扫雷功能

AI 扫雷功能是本游戏的一大特色。AI 通过分析当前局面来自动进行扫雷操作,提高了游戏的趣味性和挑战性。

2.3.5 调整方块大小功能

为了适应不同玩家的视觉需求,我们提供了调整方块大小的功能。玩家可以根据个人偏好设置方块的大小。

2.3.6 日志查看功能

游戏提供了日志查看功能,玩家可以查看历史游戏记录,包括游戏结果、难度、地雷数和用时等信息。

通过以上功能的介绍,我们可以看到 Tkinter 在构建具有丰富功能的 GUI 应用程序方面的强大能力。尽管如此,现有的 AI 智能程序仍有待提高,未来的研究可以集中在如何使 AI 更加智能,以及如何更好地将 AI 与用户交互结合起来,提供更加丰富和有趣的游戏体验。

3. AI 智能程序分析

3.1 AI 决策流程

AI 决策流程是 AI 智能程序的核心,它决定了 AI 如何在游戏中进行决策。在扫雷游戏中,AI 的决策流程主要包括以下几个步骤:

3.1.1 初始化决策状态

在游戏开始时,AI 需要初始化其决策状态,包括清除之前的游戏数据,重置标记的地雷和已知的安全格子。

3.1.2 第一次移动决策

AI 的第一次移动通常选择一个角落的格子进行点击,这是基于概率学的考虑,角落的格子周围地雷的可能性相对较低。

3.1.3 地雷标记决策

AI 会分析已打开的格子周围的格子,如果周围未打开的格子数量等于当前格子上显示的数字,那么这些未打开的格子可以被认定为地雷。

3.1.4 安全格子点击决策

如果一个格子周围所有的地雷都已经被标记,那么这个格子以及与之相邻的未打开的格子可以被认为是安全的,AI 将选择点击这些安全格子。

3.1.5 循环决策

AI 会不断重复上述决策流程,直到游戏结束或者用户停止 AI。

3.2 AI 策略分析

AI 策略分析涉及到 AI 如何根据当前的游戏状态来制定最优的行动策略。

3.2.1 概率评估策略

AI 会根据周围已知的地雷数量和未打开的格子数量来评估每个格子是地雷的概率。

3.2.2 模式识别策略

AI 会识别特定的模式,如孤立的数字或已知的地雷布局,来推断未知格子的状态。

3.2.3 风险评估策略

在复杂的游戏局面中,AI 需要评估每一步的风险,选择风险最小的行动。

3.2.4 效率优化策略

AI 会优化其决策流程,以提高扫雷的效率,减少不必要的计算。

3.3 AI 性能瓶颈

尽管 AI 智能程序能够进行基本的扫雷操作,但在某些情况下,其性能可能会遇到瓶颈。

3.3.1 复杂局面处理

在复杂的游戏局面中,AI 的决策效率可能会降低,因为它需要处理更多的信息和可能性。

3.3.2 计算资源限制

AI 的决策过程可能会消耗大量的计算资源,尤其是在大型网格和高难度设置中。

3.3.3 算法优化不足

现有的 AI 算法可能没有针对特定的游戏局面进行优化,导致在某些情况下无法做出最优决策。

3.3.4 用户交互冲突

AI 的操作可能会与玩家的操作发生冲突,尤其是在玩家和 AI 共享控制权的情况下。

为了解决这些性能瓶颈,未来的研究可以集中在算法优化、计算资源管理、用户交互设计等方面,以提高 AI 的智能程度和用户体验。

4. 代码解读

4.1 主要类和方法

4.1.1 AI 类

AI 类是扫雷游戏中智能程序的核心,负责自动扫雷的逻辑。

  • __init__(self, game): 构造函数,初始化 AI 实例,设置扫雷速度、标记集合等。
  • start_sweeping(self): 开始自动扫雷。
  • stop_sweeping(self): 停止自动扫雷。
  • sweep_loop(self): 自动扫雷的主循环,负责执行扫雷动作。
  • make_first_move(self): 执行第一次点击操作,通常选择角落的格子。
  • execute_action(self): 执行扫雷动作,如点击或标记。
  • get_next_actions(self): 获取下一步的扫雷动作。
  • analyze_cell(self, row, col): 分析指定格子,确定是否为地雷或安全格子。
  • get_surrounding_cells(self, row, col): 获取指定格子周围的格子。

4.1.2 CustomGameDialog 类

CustomGameDialog 类用于创建自定义游戏对话框,允许用户设置游戏的难度。

  • __init__(self, parent, title=None): 构造函数,初始化对话框。
  • create_widgets(self): 创建对话框中的控件,如文本框和按钮。
  • on_ok(self): 确定按钮的回调函数,验证用户输入并创建新游戏。
  • on_cancel(self): 取消按钮的回调函数,关闭对话框。

4.1.3 Minesweeper 类

Minesweeper 类负责游戏的主要逻辑和界面。

  • __init__(self, master): 构造函数,初始化游戏界面和变量。
  • create_menu(self): 创建游戏菜单,包括新游戏、难度选择等。
  • change_difficulty(self, rows, cols, mines): 更改游戏难度。
  • create_game(self, rows, cols, mines): 创建新游戏,初始化游戏界面和逻辑。
  • ai_stopped(self): AI 停止时的回调函数。
  • set_ai_speed(self): 设置 AI 扫雷速度。
  • update_block_size(self): 更新方块大小。
  • adjust_block_size(self): 调整方块大小。
  • view_log(self): 查看游戏日志。
  • place_mines(self, first_row, first_col): 在游戏中放置地雷。
  • left_click_press(self, event, row, col): 处理左键按下事件。
  • left_click_release(self, event, row, col): 处理左键释放事件。
  • right_click(self, event, row, col): 处理右键点击事件。
  • click(self, row, col): 处理点击事件。
  • clear_empty_cells(self, row, col): 打开空白区域的格子。
  • chord_click(self, row, col): 处理同时按下左键和右键的事件。
  • toggle_flag(self, row, col): 切换标记地雷。
  • check_win(self): 检查是否赢得游戏。
  • reveal_all(self): 显示所有地雷。
  • update_time(self): 更新游戏时间。
  • log_game_result(self, result): 记录游戏结果。
  • new_game(self): 开始新游戏。
  • new_game_shortcut(self, event): 快捷键开始新游戏。
  • custom_game(self): 打开自定义游戏对话框。

4.2 关键功能实现

4.2.1 游戏初始化

游戏初始化包括创建网格、随机布置地雷、初始化 AI 等步骤。通过 create_game 方法实现,该方法首先创建游戏界面,然后初始化游戏逻辑。

4.2.2 玩家操作处理

玩家操作包括左键点击和右键点击,通过绑定按钮的点击事件来实现。左键点击用于打开格子或标记地雷,右键点击则用于标记地雷。

4.2.3 AI 扫雷逻辑

AI 扫雷逻辑通过 AI 类实现,包括自动扫雷的主循环、第一次移动决策、地雷标记决策、安全格子点击决策等。

4.2.4 胜负判断

胜负判断基于玩家是否触发了地雷或成功打开了所有非雷区格子。通过 check_win 方法实现,该方法检查所有格子的状态来确定游戏是否结束。

4.2.5 日志记录

游戏提供了日志记录功能,通过 log_game_result 方法实现,记录游戏结果、难度、地雷数和用时等信息。

4.3 代码优化建议

4.3.1 算法优化

AI 的决策算法可以进一步优化,引入更复杂的算法,如机器学习、深度学习等,提高 AI 的决策能力。

4.3.2 性能优化

可以通过代码优化、并行计算等方式提高 AI 的性能,尤其是在大型网格和高难度设置中。

4.3.3 用户交互改进

改进 AI 与用户的交互方式,提高用户体验,例如通过设置 AI 的操作模式,允许玩家选择 AI 的行为。

4.3.4 异常处理

增强代码的异常处理能力,确保在用户输入无效数据或进行不合理操作时,程序能够给出友好的提示并保持稳定运行。

4.3.5 代码重构

对现有代码进行重构,提高代码的可读性和可维护性,例如将一些复杂的函数拆分成更小的模块,或者使用设计模式来优化代码结构。

通过以上分析和建议,我们可以进一步提升扫雷游戏的 AI 智能程度和用户体验。

5. 扩展功能讨论

5.1 增加游戏难度

为了提高游戏的挑战性和可玩性,增加游戏难度是一个有效的途径。可以通过以下几种方式来增加扫雷游戏的难度:

5.1.1 扩大游戏区域

增加网格的行数和列数,使得游戏区域变大,玩家需要更加仔细地进行判断和操作。

5.1.2 增加地雷数量

在保持游戏区域不变的情况下,增加地雷的数量,提高踩雷的概率。

5.1.3 引入计时机制

设置游戏时间限制,玩家需要在规定的时间内完成游戏,增加游戏的紧迫感。

5.1.4 限制操作次数

限制玩家的点击次数,超过一定次数后游戏失败,要求玩家更加精准地进行操作。

5.1.5 动态难度调整

根据玩家的胜率和游戏表现动态调整难度,保持游戏的挑战性。

5.2 排行榜功能

排行榜功能可以激发玩家的竞争欲望,提高游戏的粘性。实现排行榜功能需要考虑以下几个方面:

5.2.1 数据存储

需要一个后端数据库来存储玩家的得分和游戏时间等信息。

5.2.2 玩家识别

设计一个玩家登录和注册系统,以便记录和识别玩家。

5.2.3 得分计算

设计一个得分计算规则,可以基于游戏难度、完成时间、剩余地雷数等因素。

5.2.4 排行榜更新

实现一个排行榜更新机制,可以实时更新玩家的排名。

5.2.5 前端展示

设计一个直观的排行榜界面,展示玩家的排名和得分。

5.3 网络对战模式

网络对战模式可以允许多个玩家同时在线游戏,增加了游戏的互动性和趣味性。实现网络对战模式需要考虑以下几个方面:

5.3.1 网络通信

实现一个稳定的网络通信机制,确保玩家的操作可以实时同步。

5.3.2 房间系统

设计一个房间系统,玩家可以创建房间或加入房间进行对战。

5.3.3 同步机制

设计一个同步机制,确保所有玩家的游戏状态一致。

5.3.4 作弊预防

采取措施防止玩家作弊,保证游戏的公平性。

5.3.5 断线重连

实现断线重连功能,玩家在网络不稳定时可以重新连接游戏。

通过实现这些扩展功能,可以大大提升扫雷游戏的可玩性和用户体验,同时也为 AI 智能程序提供了更多的挑战和应用场景。

6. 总结

本文通过分析基于Python的扫雷游戏实现,探讨了Tkinter在GUI应用程序开发中的应用,以及AI智能程序在游戏中的应用和局限性。

6.1 Tkinter应用总结

Tkinter作为Python的标准GUI库,为开发者提供了丰富的控件和简单的API,使得创建图形用户界面变得容易。在扫雷游戏的实现中,Tkinter不仅用于构建游戏界面,还用于处理用户输入和响应事件,展示了其在GUI开发中的实用性和高效性。

6.2 游戏设计与实现

扫雷游戏的设计包括了网格设计、信息栏设计和控制按钮设计,这些设计共同构成了玩家与游戏交互的界面。游戏逻辑的实现涉及到初始化游戏、处理玩家操作和判断胜负等关键步骤,这些逻辑的实现保证了游戏的正常运行和玩家的游戏体验。

6.3 AI智能程序分析

AI智能程序的设计包括了决策流程、策略分析和性能瓶颈的分析。AI的决策流程包括初始化决策状态、第一次移动决策、地雷标记决策和安全格子点击决策等。AI策略分析涉及到概率评估、模式识别和风险评估等策略。性能瓶颈分析则指出了现有AI在复杂局面处理、计算资源限制和算法优化方面的不足。

6.4 代码解读与优化

对主要类和方法的解读揭示了游戏逻辑的实现细节,而关键功能的实现则展示了Tkinter在处理用户操作和AI扫雷逻辑中的应用。代码优化建议包括算法优化、性能优化、用户交互改进、异常处理和代码重构,这些建议旨在提高AI的智能程度和用户体验。

6.5 扩展功能讨论

扩展功能的讨论包括增加游戏难度、排行榜功能和网络对战模式的实现。这些功能的实现可以提高游戏的挑战性、互动性和趣味性,同时也为AI智能程序提供了更多的应用场景。

6.6 未来研究方向

未来的研究可以集中在以下几个方向:

  • 算法优化:研究和引入更先进的算法,如机器学习、深度学习,以提高AI的决策能力。
  • 性能提升:通过代码优化和并行计算等技术提高AI的性能。
  • 用户体验:改进AI与用户的交互方式,提高用户体验。
  • 功能扩展:开发新的游戏功能,如排行榜、网络对战模式,以增加游戏的可玩性和互动性。

通过本文的研究,我们可以看到Python和Tkinter在GUI应用程序开发中的强大能力,以及AI在游戏中的应用潜力。尽管现有的AI智能程序存在一些局限性,但通过不断的研究和优化,我们可以期待未来更加智能和有趣的游戏体验。

完整代码:

import tkinter as tk
from tkinter import messagebox, simpledialog, scrolledtext
import random
import time
import logging
from queue import Queue
import threading
from itertools import combinations

class AI:
    def __init__(self, game):
        self.game = game
        self.sweep_speed = 50  # Default speed in milliseconds
        self.is_sweeping = False
        self.sweep_queue = Queue()
        self.flagged_cells = set()  # Keep track of cells flagged by AI
        self.safe_cells = set()  # Keep track of cells known to be safe
        self.first_move = True
        self.game_started = False

    def start_sweeping(self):
        if not self.is_sweeping:
            self.is_sweeping = True
            self.sweep_thread = threading.Thread(target=self.sweep_loop)
            self.sweep_thread.start()

    def stop_sweeping(self):
        self.is_sweeping = False
        self.sweep_queue.put(None)  # Signal to stop the sweep loop

    def sweep_loop(self):
        while self.is_sweeping:
            if self.first_move:
                self.make_first_move()
            elif self.game_started:
                actions = self.get_next_actions()
                if actions:
                    for action in actions:
                        if self.is_sweeping:  # Check if still sweeping before each action
                            self.sweep_queue.put(action)
                            self.game.master.after(self.sweep_speed, self.execute_action)
                        else:
                            break
                else:
                    self.stop_sweeping()
                    self.game.master.after(0, self.game.ai_stopped)
                    break
            time.sleep(self.sweep_speed / 1000)  # Wait for the specified sweep speed

    def make_first_move(self):
        # Choose a corner for the first move
        row, col = 0, 0
        self.sweep_queue.put((row, col, 'click'))
        self.game.master.after(self.sweep_speed, self.execute_action)
        self.first_move = False
        self.game_started = True

    def execute_action(self):
        if not self.is_sweeping:
            return
        action = self.sweep_queue.get()
        if action is None:
            return
        row, col, action_type = action
        if action_type == 'click':
            self.game.click(row, col)
        elif action_type == 'flag':
            self.game.toggle_flag(row, col)
            self.flagged_cells.add((row, col))  # Add to set of flagged cells

    def get_next_actions(self):
        actions = []
        revealed_cells = self.get_revealed_cells()

        # First pass: identify all certain flags and safe cells
        for row, col in revealed_cells:
            flag_actions, safe_cells = self.analyze_cell(row, col)
            actions.extend(flag_actions)
            self.safe_cells.update(safe_cells)

        # Second pass: click on all known safe cells
        for cell in list(self.safe_cells):
            if self.game.buttons[cell[0]][cell[1]]['state'] != 'disabled':
                actions.append((cell[0], cell[1], 'click'))
                self.safe_cells.remove(cell)

        return actions

    def get_revealed_cells(self):
        return [(row, col) for row in range(self.game.rows) for col in range(self.game.cols)
                if self.game.buttons[row][col]['state'] == 'disabled' and self.game.board[row][col] != 'X' and
                self.game.board[row][col] != 0]

    def analyze_cell(self, row, col):
        surrounding_cells = self.get_surrounding_cells(row, col)
        unopened_cells = [cell for cell in surrounding_cells if
                          self.game.buttons[cell[0]][cell[1]]['state'] != 'disabled' and cell not in self.flagged_cells]
        flagged_cells = [cell for cell in surrounding_cells if cell in self.flagged_cells]

        remaining_mines = self.game.board[row][col] - len(flagged_cells)

        flag_actions = []
        safe_cells = []

        if len(unopened_cells) == remaining_mines and remaining_mines > 0:
            # All unopened cells are certainly mines
            for cell in unopened_cells:
                if cell not in self.flagged_cells:
                    flag_actions.append((cell[0], cell[1], 'flag'))
        elif remaining_mines == 0:
            # All unopened cells are safe
            safe_cells = unopened_cells

        return flag_actions, safe_cells

    def get_surrounding_cells(self, row, col):
        return [(r, c) for r in range(max(0, row - 1), min(self.game.rows, row + 2))
                for c in range(max(0, col - 1), min(self.game.cols, col + 2))
                if (r, c) != (row, col)]

    def set_sweep_speed(self, speed):
        self.sweep_speed = speed

    def reset(self):
        self.stop_sweeping()
        self.flagged_cells.clear()
        self.safe_cells.clear()
        self.first_move = True
        self.game_started = False
        self.sweep_queue = Queue()  # Reset the queue

    def hard_reset(self):
        self.stop_sweeping()
        self.flagged_cells.clear()
        self.safe_cells.clear()
        self.first_move = True
        self.game_started = False
        self.sweep_queue = Queue()
        self.is_sweeping = False

class CustomGameDialog(tk.Toplevel):
    def __init__(self, parent, title=None):
        super().__init__(parent)
        self.parent = parent
        if title:
            self.title(title)
        self.result = None
        self.create_widgets()

    def create_widgets(self):
        self.frame = tk.Frame(self)
        self.frame.pack(padx=20, pady=20)

        tk.Label(self.frame, text="行数:").grid(row=0, column=0, sticky="e")
        self.rows_entry = tk.Entry(self.frame)
        self.rows_entry.grid(row=0, column=1)
        self.rows_entry.insert(0, "9")

        tk.Label(self.frame, text="列数:").grid(row=1, column=0, sticky="e")
        self.cols_entry = tk.Entry(self.frame)
        self.cols_entry.grid(row=1, column=1)
        self.cols_entry.insert(0, "9")

        tk.Label(self.frame, text="雷数:").grid(row=2, column=0, sticky="e")
        self.mines_entry = tk.Entry(self.frame)
        self.mines_entry.grid(row=2, column=1)
        self.mines_entry.insert(0, "10")

        button_frame = tk.Frame(self)
        button_frame.pack(pady=10)
        tk.Button(button_frame, text="确定", command=self.on_ok).pack(side="left", padx=5)
        tk.Button(button_frame, text="取消", command=self.on_cancel).pack(side="left", padx=5)

    def on_ok(self):
        try:
            rows = int(self.rows_entry.get())
            cols = int(self.cols_entry.get())
            mines = int(self.mines_entry.get())
            if 5 <= rows <= 30 and 5 <= cols <= 60 and 1 <= mines <= rows * cols - 1:
                self.result = (rows, cols, mines)
                self.destroy()
            else:
                messagebox.showerror("错误", "请输入有效的数值:\n行数和列数: 5-30\n雷数: 1到(行数*列数-1)")
        except ValueError:
            messagebox.showerror("错误", "请输入有效的数字")

    def on_cancel(self):
        self.result = None
        self.destroy()

class Minesweeper:
    def __init__(self, master):
        self.master = master
        self.master.title("扫雷")
        self.game_over = False
        self.flags = 0
        self.correct_flags = 0
        self.start_time = None
        self.first_click = True
        self.left_click_pressed = False
        self.block_size = 30  # 默认方块大小

        # 定义颜色
        self.unopened_color = 'lightgray'
        self.opened_color = 'white'
        self.number_colors = ['blue', 'green', 'red', 'purple', 'maroon', 'turquoise', 'black', 'gray']

        self.ai = AI(self)

        self.create_menu()
        self.create_game(9, 9, 10)  # 默认初级难度

        # 设置日志
        logging.basicConfig(filename='minesweeper.log', level=logging.INFO,
                            format='%(asctime)s - %(message)s')

        # 绑定快捷键
        self.master.bind('<Control-n>', self.new_game_shortcut)

    def create_menu(self):
        menubar = tk.Menu(self.master)
        self.master.config(menu=menubar)

        game_menu = tk.Menu(menubar, tearoff=0)
        game_menu.add_command(label="新游戏 (Ctrl+N)", command=self.new_game)
        game_menu.add_command(label="初级", command=lambda: self.change_difficulty(9, 9, 10))
        game_menu.add_command(label="中级", command=lambda: self.change_difficulty(16, 16, 40))
        game_menu.add_command(label="高级", command=lambda: self.change_difficulty(16, 30, 80))
        game_menu.add_command(label="特级", command=lambda: self.change_difficulty(24, 30, 100))
        game_menu.add_command(label="超级", command=lambda: self.change_difficulty(27, 40, 150))
        game_menu.add_command(label="大师", command=lambda: self.change_difficulty(27, 50, 200))
        game_menu.add_command(label="自定义", command=self.custom_game)
        game_menu.add_separator()
        game_menu.add_command(label="退出", command=self.master.quit)
        menubar.add_cascade(label="游戏", menu=game_menu)

        settings_menu = tk.Menu(menubar, tearoff=0)
        settings_menu.add_command(label="调整方块大小", command=self.adjust_block_size)
        settings_menu.add_command(label="设置自动扫雷速度", command=self.set_ai_speed)
        menubar.add_cascade(label="设置", menu=settings_menu)

        log_menu = tk.Menu(menubar, tearoff=0)
        log_menu.add_command(label="查看历史记录", command=self.view_log)
        menubar.add_cascade(label="日志", menu=log_menu)

    def change_difficulty(self, rows, cols, mines):
        self.ai.hard_reset()  # 重置 AI
        self.create_game(rows, cols, mines)

    def create_game(self, rows, cols, mines):
        self.rows = rows
        self.cols = cols
        self.mines = mines
        self.flags = 0
        self.correct_flags = 0
        self.game_over = False
        self.start_time = None
        self.first_click = True

        # 创建主框架
        if hasattr(self, 'main_frame'):
            self.main_frame.destroy()
        self.main_frame = tk.Frame(self.master)
        self.main_frame.pack()

        # 创建顶部信息栏
        self.info_frame = tk.Frame(self.main_frame)
        self.info_frame.grid(row=0, column=0, columnspan=self.cols, sticky="ew")
        self.mine_label = tk.Label(self.info_frame, text=f"剩余雷数: {self.mines}")
        self.mine_label.grid(row=0, column=0, sticky="w")
        self.time_label = tk.Label(self.info_frame, text="时间: 0")
        self.time_label.grid(row=0, column=1, sticky="e")

        # 添加开始和停止AI按钮
        self.start_ai_button = tk.Button(self.info_frame, text="开始AI", command=self.ai.start_sweeping)
        self.start_ai_button.grid(row=0, column=2, sticky="e")
        self.stop_ai_button = tk.Button(self.info_frame, text="停止AI", command=self.ai.stop_sweeping)
        self.stop_ai_button.grid(row=0, column=3, sticky="e")

        # 创建按钮网格
        self.buttons = []
        for i in range(self.rows):
            row = []
            for j in range(self.cols):
                button = tk.Button(self.main_frame, width=2, height=1, bg=self.unopened_color)
                button.grid(row=i + 1, column=j)
                button.bind('<Button-1>', lambda e, x=i, y=j: self.left_click_press(e, x, y))
                button.bind('<ButtonRelease-1>', lambda e, x=i, y=j: self.left_click_release(e, x, y))
                button.bind('<Button-3>', lambda e, x=i, y=j: self.right_click(e, x, y))
                row.append(button)
            self.buttons.append(row)

        self.update_block_size()

        # 初始化空白棋盘
        self.board = [[0 for _ in range(self.cols)] for _ in range(self.rows)]

        # 重置 AI
        self.ai.hard_reset()

    def ai_stopped(self):
        messagebox.showinfo("AI 停止", "AI 无法继续决策,已自动停止。")

    def set_ai_speed(self):
        speed = simpledialog.askinteger("设置自动扫雷速度", "请输入自动扫雷的速度(毫秒):",
                                        minvalue=100, maxvalue=5000, initialvalue=self.ai.sweep_speed)
        if speed:
            self.ai.set_sweep_speed(speed)

    def update_block_size(self):
        for i in range(self.rows):
            for j in range(self.cols):
                self.buttons[i][j].config(width=self.block_size // 10, height=self.block_size // 20)

    def adjust_block_size(self):
        new_size = simpledialog.askinteger("调整方块大小", "请输入新的方块大小 (像素):",
                                           minvalue=20, maxvalue=100, initialvalue=self.block_size)
        if new_size:
            self.block_size = new_size
            self.update_block_size()

    def view_log(self):
        log_window = tk.Toplevel(self.master)
        log_window.title("历史记录")
        log_window.geometry("600x400")

        log_text = scrolledtext.ScrolledText(log_window, wrap=tk.WORD, width=70, height=20)
        log_text.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

        try:
            with open('minesweeper.log', 'r') as log_file:
                log_content = log_file.read()
                log_text.insert(tk.END, log_content)
        except FileNotFoundError:
            log_text.insert(tk.END, "没有找到日志文件。")

        log_text.config(state=tk.DISABLED)  # 设置为只读

    def place_mines(self, first_row, first_col):
        mine_positions = random.sample([(r, c) for r in range(self.rows) for c in range(self.cols) if (r, c) != (first_row, first_col)], self.mines)
        for row, col in mine_positions:
            self.board[row][col] = 'X'
            for i in range(max(0, row-1), min(self.rows, row+2)):
                for j in range(max(0, col-1), min(self.cols, col+2)):
                    if self.board[i][j] != 'X':
                        self.board[i][j] += 1

    def left_click_press(self, event, row, col):
        self.left_click_pressed = True

    def left_click_release(self, event, row, col):
        if self.left_click_pressed:
            self.left_click_pressed = False
            self.click(row, col)

    def right_click(self, event, row, col):
        if self.game_over or self.first_click:
            return
        if self.left_click_pressed:
            self.chord_click(row, col)
        else:
            self.toggle_flag(row, col)

    def click(self, row, col):
        if self.game_over or self.buttons[row][col]['text'] == '🚩':
            return
        if self.first_click:
            self.place_mines(row, col)
            self.first_click = False
            self.start_time = time.time()
            self.update_time()
        if self.board[row][col] == 'X':
            self.game_over = True
            self.reveal_all()
            messagebox.showinfo("游戏结束", "很遗憾,你踩到了地雷!")
            self.log_game_result("失败")
        elif self.board[row][col] > 0:
            self.buttons[row][col].config(text=str(self.board[row][col]),
                                          state="disabled",
                                          bg=self.opened_color,
                                          fg=self.number_colors[self.board[row][col]-1])
        else:
            self.clear_empty_cells(row, col)
        self.check_win()

    def clear_empty_cells(self, row, col):
        if row < 0 or row >= self.rows or col < 0 or col >= self.cols:
            return
        if self.buttons[row][col]['state'] == 'disabled':
            return
        self.buttons[row][col].config(state="disabled", bg=self.opened_color)
        if self.board[row][col] == 0:
            for i in range(row-1, row+2):
                for j in range(col-1, col+2):
                    self.clear_empty_cells(i, j)
        else:
            self.buttons[row][col].config(text=str(self.board[row][col]),
                                          fg=self.number_colors[self.board[row][col]-1])

    def chord_click(self, row, col):
        if self.game_over or self.first_click:
            return
        surrounding_flags = sum(1 for i in range(row-1, row+2) for j in range(col-1, col+2)
                                if 0 <= i < self.rows and 0 <= j < self.cols and self.buttons[i][j]['text'] == '🚩')
        if surrounding_flags == self.board[row][col] or self.buttons[row][col]['state'] != 'disabled':
            for i in range(row-1, row+2):
                for j in range(col-1, col+2):
                    if 0 <= i < self.rows and 0 <= j < self.cols and self.buttons[i][j]['text'] != '🚩':
                        self.click(i, j)

    def toggle_flag(self, row, col):
        if self.buttons[row][col]['state'] == 'disabled':
            return
        if self.buttons[row][col]['text'] == '🚩':
            # Only allow removing flags placed by the player, not by AI
            if (row, col) not in self.ai.flagged_cells:
                self.buttons[row][col].config(text='')
                self.flags -= 1
                if self.board[row][col] == 'X':
                    self.correct_flags -= 1
        else:
            self.buttons[row][col].config(text='🚩')
            self.flags += 1
            if self.board[row][col] == 'X':
                self.correct_flags += 1
        self.mine_label.config(text=f"剩余雷数: {self.mines - self.flags}")
        self.check_win()

    def check_win(self):
        if self.correct_flags == self.mines and self.flags == self.mines:
            self.game_over = True
            self.ai.stop_sweeping()
            messagebox.showinfo("恭喜", "你赢了!")
            self.log_game_result("胜利")

    def reveal_all(self):
        self.ai.stop_sweeping()
        for i in range(self.rows):
            for j in range(self.cols):
                if self.board[i][j] == 'X':
                    self.buttons[i][j].config(text='💣', state="disabled", bg=self.opened_color)
                elif self.board[i][j] > 0:
                    self.buttons[i][j].config(text=str(self.board[i][j]),
                                              state="disabled",
                                              bg=self.opened_color,
                                              fg=self.number_colors[self.board[i][j]-1])

    def update_time(self):
        if self.start_time and not self.game_over:
            elapsed_time = int(time.time() - self.start_time)
            self.time_label.config(text=f"时间: {elapsed_time}")
            self.master.after(1000, self.update_time)

    def log_game_result(self, result):
        elapsed_time = int(time.time() - self.start_time)
        logging.info(f"游戏结果: {result}, 难度: {self.rows}x{self.cols}, 地雷数: {self.mines}, 用时: {elapsed_time}秒")

    def new_game(self):
        self.ai.hard_reset()
        self.create_game(self.rows, self.cols, self.mines)

    def new_game_shortcut(self, event):
        self.new_game()

    def custom_game(self):
        dialog = CustomGameDialog(self.master, "自定义游戏")
        self.master.wait_window(dialog)
        if dialog.result:
            rows, cols, mines = dialog.result
            self.change_difficulty(rows, cols, mines)

if __name__ == "__main__":
    root = tk.Tk()
    game = Minesweeper(root)
    root.mainloop()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值