PyAutoGUI实战:用Python写个游戏外挂?不,我们来做个自动玩《扫雷》的AI脚本
2026/5/28 12:54:46 网站建设 项目流程

PyAutoGUI实战:用Python打造《扫雷》自动解谜AI

Windows自带的《扫雷》游戏是许多人童年的记忆。这款看似简单的游戏背后隐藏着严谨的逻辑推理,恰好为编程爱好者提供了一个绝佳的练手项目。今天,我们将使用Python的PyAutoGUI库,从零开始构建一个能自动玩《扫雷》的AI脚本。这不仅是图像识别和自动化控制的完美结合,更是一次逻辑思维的编程实践。

1. 环境准备与基础配置

在开始编写扫雷AI之前,我们需要确保开发环境配置正确。PyAutoGUI是一个跨平台的GUI自动化库,支持Windows、macOS和Linux系统。安装过程非常简单:

pip install pyautogui opencv-python numpy pillow

安装完成后,建议先测试PyAutoGUI的基本功能是否正常工作。创建一个简单的测试脚本:

import pyautogui print(pyautogui.size()) # 获取屏幕分辨率 pyautogui.moveTo(100, 100, duration=1) # 移动鼠标到(100,100)位置

注意:PyAutoGUI的坐标系统以屏幕左上角为原点(0,0),x轴向右增加,y轴向下增加。

为了确保脚本能准确识别扫雷窗口,我们需要先确定游戏窗口的位置和大小。最简单的方法是使用PyAutoGUI的截图功能:

# 获取游戏窗口截图 game_window = pyautogui.screenshot(region=(x, y, width, height))

2. 游戏界面识别技术

扫雷游戏的核心识别任务包括:数字识别、未点击方块识别和旗帜标记识别。PyAutoGUI提供了强大的图像识别功能,我们可以利用locateOnScreen()方法来实现这些功能。

2.1 数字模板准备

首先,我们需要为每个数字(1-8)创建模板图像。这些模板将用于后续的图像匹配:

  1. 截取游戏中数字1-8的清晰图像样本
  2. 使用图像编辑工具裁剪出单个数字
  3. 保存为png格式,命名如"1.png", "2.png"等
# 数字识别函数示例 def recognize_number(image): numbers = ['1', '2', '3', '4', '5', '6', '7', '8'] for num in numbers: template = f'templates/{num}.png' location = pyautogui.locate(template, image, confidence=0.9) if location: return int(num) return None

2.2 游戏状态分析

扫雷游戏界面由多个方块组成,每个方块可能处于以下状态之一:

状态类型描述识别方法
未点击灰色方块颜色匹配
已点击显示数字或空白数字识别
旗帜小红旗标记图像匹配
地雷游戏结束时显示图像匹配

为了提高识别准确率,我们可以结合多种方法:

def get_cell_state(cell_image): # 检查是否为旗帜 if pyautogui.locate('flag_template.png', cell_image, confidence=0.85): return 'flag' # 检查是否为未点击方块 if is_unopened(cell_image): return 'unopened' # 尝试识别数字 number = recognize_number(cell_image) if number is not None: return str(number) # 空白方块 return 'empty'

3. 游戏逻辑与AI策略

有了基本的识别能力后,我们需要为AI设计游戏策略。扫雷的核心逻辑是根据已知数字推断周围方块的安全性。

3.1 基础安全判断

最简单的策略是寻找绝对安全的点击:

  1. 如果一个已打开的数字方块周围恰好有相应数量的旗帜,则剩余未点击的方块一定是安全的
  2. 如果一个数字等于其周围未点击方块的数量,则所有这些方块都必须是地雷
def find_safe_clicks(board): safe_clicks = [] for y in range(board.height): for x in range(board.width): cell = board.get_cell(x, y) if cell.isdigit(): number = int(cell) neighbors = board.get_neighbors(x, y) flagged = sum(1 for n in neighbors if n.state == 'flag') unopened = [n for n in neighbors if n.state == 'unopened'] # 规则1:数字等于周围旗帜数,剩余未点击是安全的 if number == flagged and unopened: safe_clicks.extend(unopened) # 规则2:数字等于未点击数,所有未点击都是地雷 if number - flagged == len(unopened) and unopened: for cell in unopened: cell.mark_as_mine() return safe_clicks

3.2 高级概率分析

对于更复杂的情况,我们可以引入概率计算:

  1. 计算每个未点击方块成为地雷的概率
  2. 选择概率最低的方块进行点击
def calculate_probabilities(board): probabilities = {} unopened_cells = [cell for row in board.cells for cell in row if cell.state == 'unopened'] for cell in unopened_cells: # 获取所有影响此方块的数字单元格 influencing_numbers = get_influencing_numbers(cell, board) if not influencing_numbers: probabilities[cell] = 0.2 # 默认概率 continue # 基于约束条件计算概率 total_mines = sum(int(num) for num in influencing_numbers) flagged = sum(1 for num in influencing_numbers if num.flagged_neighbors) remaining_mines = total_mines - flagged possible_locations = sum(1 for num in influencing_numbers for neighbor in num.neighbors if neighbor.state == 'unopened') if possible_locations > 0: probabilities[cell] = remaining_mines / possible_locations else: probabilities[cell] = 0 return probabilities

4. 脚本实现与优化

将上述组件整合成一个完整的自动化脚本需要考虑执行效率、错误处理和用户交互。

4.1 主循环结构

def main(): initialize_game() # 确保游戏在正确位置启动 while not game_over(): try: # 获取当前游戏状态 board = capture_game_board() # 寻找安全点击 safe_clicks = find_safe_clicks(board) if safe_clicks: for cell in safe_clicks: click_cell(cell) continue # 如果没有确定的安全点击,使用概率分析 probabilities = calculate_probabilities(board) if probabilities: safest = min(probabilities.items(), key=lambda x: x[1])[0] click_cell(safest) continue # 没有可用策略,随机点击 random_click(board) except Exception as e: handle_error(e) break

4.2 性能优化技巧

  1. 局部截图:只截取游戏区域而非整个屏幕
  2. 缓存机制:记住已识别的方块状态,减少重复识别
  3. 多线程处理:将图像识别和逻辑计算分离到不同线程
  4. 失败恢复:检测游戏失败/胜利状态并相应处理
# 优化后的截图函数 def capture_game_board(): if not hasattr(capture_game_board, 'last_board'): # 首次运行,截取整个游戏区域 full_image = pyautogui.screenshot(region=GAME_REGION) board = analyze_full_image(full_image) capture_game_board.last_board = board return board else: # 增量更新,只检查可能变化的区域 changed_cells = find_changed_cells(capture_game_board.last_board) for cell in changed_cells: cell_image = pyautogui.screenshot(region=get_cell_region(cell)) cell.state = get_cell_state(cell_image) return capture_game_board.last_board

5. 进阶功能与扩展思路

一个完整的扫雷AI还可以加入更多智能功能和用户体验改进。

5.1 学习与适应机制

通过记录游戏历史,AI可以学习玩家的习惯或调整自己的策略:

class LearningAI: def __init__(self): self.memory = {} # 存储位置模式及其结果 def remember_pattern(self, pattern, was_mine): if pattern not in self.memory: self.memory[pattern] = {'mines': 0, 'safe': 0} if was_mine: self.memory[pattern]['mines'] += 1 else: self.memory[pattern]['safe'] += 1 def predict_from_memory(self, pattern): if pattern in self.memory: stats = self.memory[pattern] total = stats['mines'] + stats['safe'] return stats['mines'] / total return None

5.2 可视化调试界面

为帮助调试和理解AI的决策过程,可以添加可视化功能:

def draw_analysis_overlay(board, probabilities): # 创建一个透明覆盖层 overlay = Image.new('RGBA', (board.width*CELL_SIZE, board.height*CELL_SIZE), (0,0,0,0)) draw = ImageDraw.Draw(overlay) # 为每个方块绘制概率 for y in range(board.height): for x in range(board.width): cell = board.get_cell(x, y) if cell.state == 'unopened' and cell in probabilities: prob = probabilities[cell] color = (255, int(255*(1-prob)), int(255*(1-prob)), 150) draw.rectangle([x*CELL_SIZE, y*CELL_SIZE, (x+1)*CELL_SIZE, (y+1)*CELL_SIZE], fill=color) text = f"{prob:.0%}" draw.text((x*CELL_SIZE+5, y*CELL_SIZE+5), text, fill=(0,0,0,255)) # 显示覆盖层 overlay.show()

在实际项目中,我发现最难处理的是游戏速度的适应性。不同电脑上扫雷的动画速度可能不同,需要仔细调整点击间隔。一个实用的技巧是开始时进行几次测试点击,测量动画完成的时间,然后基于这个时间设置合理的延迟参数。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询