引言 上一篇文章中介绍了扫雷游戏的开发,本文将详细介绍如何通过Python实现扫雷游戏的窗口识别、屏幕截图和鼠标点击操作,这是实现扫雷的基础组件。
系统架构 我们的实现分为两个核心模块:
CursorClick.py - 负责底层鼠标操作WindowManager.py - 负责窗口管理和图像处理一、鼠标点击模块实现 1.1 核心功能 CursorClick.py
模块封装了Windows系统级的鼠标操作,主要特点包括:
支持左键和右键两种点击方式 点击后自动恢复鼠标原位置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import win32apiimport win32conimport timefrom mylogger import loggerdef click (px, py, type ): try : Pos = win32api.GetCursorPos() win32api.SetCursorPos((int (px), int (py))) if type == 'left' : win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0 , 0 , 0 , 0 ) time.sleep(0.1 ) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0 , 0 , 0 , 0 ) elif type == 'right' : win32api.mouse_event(win32con.MOUSEEVENTF_RIGHTDOWN, 0 , 0 , 0 , 0 ) time.sleep(0.1 ) win32api.mouse_event(win32con.MOUSEEVENTF_RIGHTUP, 0 , 0 , 0 , 0 ) win32api.SetCursorPos(Pos) time.sleep(0.1 ) logger.info(f"点击成功!坐标: ({px} , {py} )" ) except Exception as e: logger.error(f"点击失败!错误信息: {e} " )
1.2 技术细节 坐标系统 :使用Windows API获取和设置鼠标位置,坐标单位为屏幕像素点击实现 :通过mouse_event
函数模拟鼠标按下和释放动作稳定性保障 :每次操作前后添加100ms延迟 点击后恢复鼠标原位置避免干扰用户 错误处理 :捕获所有异常并通过日志系统记录二、窗口管理模块实现 2.1 核心功能 WindowManager.py
模块负责游戏窗口的识别和管理,主要功能包括:
查找和激活游戏窗口 检测游戏状态(胜利/失败) 截取游戏区域图像 将游戏区域切分为16×16的网格 在指定网格位置执行点击操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import sysimport win32guifrom PIL import ImageGrabfrom CursorClick import clickfrom mylogger import logger zoomRate = 1.25 grid_origin_width = 20 grid_origin_height = 20 grid_width = int (zoomRate * grid_origin_width) grid_height = int (zoomRate * grid_origin_height)def crop_grid_img (img, x, y ): rect = (x * grid_width, y * grid_height, (x + 1 ) * grid_width, (y + 1 ) * grid_height) return img.crop(rect)"""窗口管理器,负责管理游戏窗口和点击""" class WindowManager : def set_window_foreground (self ): if not self.hwndMain: self.find_main_window() try : win32gui.SetForegroundWindow(self.hwndMain) except Exception as e: logger.error("置顶窗口过程发生错误" , e) sys.exit()
2.2 窗口管理实现 2.2.1 窗口查找与置顶 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def find_main_window (self ): self.hwndMain = win32gui.FindWindow(self.class_name, self.title_name) if not self.hwndMain: logger.error("未找到主窗口" ) sys.exit() logger.info("找到主窗口" ) leftMain, topMain, rightMain, bottomMain = win32gui.GetWindowRect(self.hwndMain) logger.info(f"主窗口坐标:{leftMain} {rightMain} {topMain} {bottomMain} " )def find_game_window (self ): hwndChild = win32gui.FindWindowEx(self.hwndMain, 0 , "TkChild" , None ) if not hwndChild: logger.error("未找到子窗口" ) sys.exit() logger.info("找到子窗口" ) self.leftGame, self.topGame, self.rightGame, self.bottomGame = win32gui.GetWindowRect(hwndChild) logger.info(f"子窗口坐标:{self.leftGame} {self.rightGame} {self.topGame} {self.bottomGame} " )
技术要点 :
使用FindWindow
和FindWindowEx
查找窗口句柄 通过GetWindowRect
获取窗口位置和尺寸 分层查找:先找主窗口,再找游戏区域子窗口 2.2.2 游戏状态检测 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 def check_main_window (self ): flag = self.find_fail_window() if flag: return True flag = self.find_success_window() if flag: return True self.set_window_foreground() return False def find_fail_window (self ): hwndFail = win32gui.FindWindow("#32770" , "失败" ) if hwndFail: hwndButton = win32gui.FindWindowEx(hwndFail, 0 , "Button" , "确定" ) if hwndButton: left, top, right, bottom = win32gui.GetWindowRect(hwndButton) click((left + right) / 2 , (top + bottom) / 2 , "left" ) logger.info("扫雷失败" ) sys.exit() return False def find_success_window (self ): hwndSuccess = win32gui.FindWindow("#32770" , "成功" ) if hwndSuccess: hwndButton = win32gui.FindWindowEx(hwndSuccess, 0 , "Button" , "确定" ) if hwndButton: left, top, right, bottom = win32gui.GetWindowRect(hwndButton) click((left + right) / 2 , (top + bottom) / 2 , "left" ) logger.info("扫雷成功" ) sys.exit() return False
技术要点 :
2.3 图像处理实现 2.3.1 游戏区域截图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def get_img_blob (self ): try : self.set_window_foreground() self.find_game_window() (left, top, right, bottom) = rect = ( round (self.leftGame * zoomRate + 17 * zoomRate), round (self.topGame * zoomRate + 46 * zoomRate), round (self.leftGame * zoomRate + 17 * zoomRate + grid_width * 16 ), round (self.topGame * zoomRate + 46 * zoomRate + grid_height * 16 ) ) img = ImageGrab.grab() cropped_img = img.crop(rect) except Exception as e: logger.error(f"处理窗口或截图过程发生错误:{e} " ) return blocks_x = (right - left) // grid_width blocks_y = (bottom - top) // grid_height blocks_img = [[crop_grid_img(cropped_img, x, y) for x in range (blocks_x)] for y in range (blocks_y)] return blocks_img
技术要点 :
使用PIL库的ImageGrab
截取屏幕 根据游戏窗口位置计算游戏区域坐标 考虑屏幕缩放因素(zoomRate) 将游戏区域切分为16×16网格 2.3.2 网格点击操作 1 2 3 4 5 6 7 8 9 10 11 12 def click_grid (self, x, y, type ): logger.info(f"点击格子:{x} {y} {type } " ) self.set_window_foreground() self.find_game_window() (left, top, right, bottom) = rect = ( round (self.leftGame + 17 ), round (self.topGame + 46 ), round (self.leftGame + 17 + grid_origin_width * 16 ), round (self.topGame + 46 + grid_origin_height * 16 ) ) click(left + (y+0.5 ) * grid_origin_width, top + (x+0.5 ) * grid_origin_height, type ) self.check_main_window()
技术要点 :
将网格坐标转换为屏幕坐标 点击网格中心点(坐标+0.5) 操作后检查游戏状态 三、关键技术点 3.1 屏幕缩放处理 现代高分辨率显示器常使用缩放功能,我们通过zoomRate
参数(1.25)来解决这个问题:
1 2 3 4 5 zoomRate = 1.25 grid_origin_width = 20 grid_origin_height = 20 grid_width = int (zoomRate * grid_origin_width) grid_height = int (zoomRate * grid_origin_height)
3.2 坐标计算 游戏区域定位基于经验值(17,46),这是扫雷游戏的标准布局:
1 2 3 leftGame * zoomRate + 17 * zoomRate topGame * zoomRate + 46 * zoomRate
3.3 错误处理与日志 整个系统采用统一的错误处理策略:
使用try-catch捕获所有异常 通过logger记录操作日志和错误信息 关键错误直接退出程序(sys.exit) 四、总结 本文实现的扫雷游戏自动化模块具有以下特点:
稳定性 :完善的错误处理和操作延迟确保可靠运行灵活性 :可适应不同的屏幕缩放设置模块化 :清晰的职责分离,便于扩展下一篇将介绍使用TensorFlow实现扫雷游戏中雷块内容的识别
本文窗口管理、截图和点击操作的实现思路也可以应用于其他Windows应用的自动化测试和操作。