zuobia

import cv2 import numpy as np import time import win32gui import win32con from PIL import ImageGrab import win32api import os import random from image_processor import ImageProcessor from mouse_controller import MouseController from window_handler import WindowHandler from logger import Logger class GameWindowLostError(Exception): """游戏窗口丢失异常 - 用于终止任务执行""" pass class Assist: def __init__(self, 设置页面, 日志输出=None,延时=None): self.hwnd = None self.日志输出 = 日志输出 self.img_processor = ImageProcessor( self.hwnd,self.日志输出) self.mouse = MouseController( self.hwnd,self.日志输出) self.延时 = 延时 self.window_handler = WindowHandler( self.日志输出,game_add =设置页面['gameaddress']) self.设置页面 ={ 'duanxinchonglian': 设置页面['duanxinchonglian'], #断线重连是否开启 'chongliantime': 设置页面['chongliantime'], #断线重连时间 'gameaddress': 设置页面['gameaddress'], #游戏地址 } # 🔥 添加终止任务相关属性 self.终止标志 = False # 终止标志 self.stop_callback = None # 停止回调函数 self.main_thread_closer = None # 主线程关闭函数回调 #print("EnvironmentDetector实例已创建") def 前往目的地(self,目的地): imgpath ={} if 目的地 == 'sj': mudidi = '世界' imgpath['cs'] = 'img/cs.bmp' imgpath['cs2'] = 'img/cs2.bmp' imgpath['cs3'] = 'img/cs3.bmp' imgpath['cs4'] = 'img/cs4.bmp' elif 目的地 == 'cs': imgpath['sj'] = 'img/sj.bmp' imgpath['sj2'] = 'img/sj2.bmp' mudidi = '城市' # 在 1113,583 1278,745中查找 pont = (6, 583, 1278, 745) sj_location = self.img_processor.find_images(imgpath,region = pont) if sj_location != None: #print(f"找到{mudidi}按钮,正在点击") self.日志输出.info(f"找到{mudidi}按钮,正在点击") self.mouse.click_position(sj_location) self.延时.delay() self.移动开鼠标() return True return False def 是否抵达目的地(self,目的地): imgpath ={} imgpaths ={ 'cs': 'img/cs.bmp', 'cs2': 'img/cs2.bmp', 'cs3': 'img/cs3.bmp', 'cs4': 'img/cs4.bmp', 'sj': 'img/sj.bmp', 'sj2': 'img/sj2.bmp', } if 目的地 == 'sj': mudidi = '世界' imgpath['cs'] = 'img/cs.bmp' imgpath['cs2'] = 'img/cs2.bmp' imgpath['cs3'] = 'img/cs3.bmp' imgpath['cs4'] = 'img/cs4.bmp' elif 目的地 == 'cs': imgpath['sj'] = 'img/sj.bmp' imgpath['sj2'] = 'img/sj2.bmp' mudidi = '城市' # 在 1113,583 1278,745中查找 pont = (6, 583, 1278, 745) # 查找城市 或者世界按钮 #判断管理箭头是否存在 cs_location = self.img_processor.find_images(imgpaths,0.85,region = pont) if cs_location !=None : if 目的地=='cs': if cs_location['key']=='cs' or cs_location['key']=='cs2' or cs_location['key']=='cs3' or cs_location['key']=='cs4': #print(f"已抵达{mudidi}") self.日志输出.info(f"已抵达{mudidi}") return True elif 目的地=='sj': if cs_location['key']=='sj' or cs_location['key']=='sj2': #print(f"已抵达{mudidi}") self.日志输出.info(f"已抵达{mudidi}") return True return False def gotocs(self,addres): """ 执行回到城市的操作 sj 世界 cs 城市 1.首先判断现在是在cs 还是世界 如果已经在则返回 2.如果没在 """ for i in range(3): if self.是否抵达目的地(addres): self.日志输出.info(f"已抵达{'城市' if addres=='cs' else '世界'}") return True if self.前往目的地(addres): return True self.延时.delay() self.清理对话框() self.移动开鼠标 self.日志输出.info(f"回到{'城市' if addres=='cs' else '世界'}失败") return False def 世界或者城市(self): """判断 是在世界 还是城市""" imgpaths ={ 'cs': 'img/cs.bmp', 'cs2': 'img/cs2.bmp', 'cs3': 'img/cs3.bmp', 'cs4': 'img/cs4.bmp', 'sj': 'img/sj.bmp', 'sj2': 'img/sj2.bmp', } 世界城市 = self.img_processor.find_images(imgpaths,threshold=0.75) if not 世界城市: return False elif 世界城市['key'] == 'cs' or 世界城市['key'] == 'cs2' or 世界城市['key'] == 'cs3' or 世界城市['key'] == 'cs4': return 'cs' elif 世界城市['key'] == 'sj' or 世界城市['key'] == 'sj2': return 'sj' return None def get_window_rect(self): """获取窗口位置和大小""" rect = win32gui.GetWindowRect(self.hwnd) return rect def get_duilie_count(self): """获取队列数量 全部没有出征 返回6 否则返回对应数量""" if not self.check_window_and_mouse(): self.日志输出.info("窗口不存在或不是当前窗口") return False if not self.清理对话框(): self.日志输出.info("清理对话框失败") #首次查找gang是否存在 如果不存在 查找 dljt是否存在 如果不存在 dljt_location = self.img_processor.find_image('img/dljt.bmp',threshold=0.75) if dljt_location == None: #print("没有队伍出征还能派出很多") self.日志输出.info("没有队伍出征") return 8 #查找最大序列 max_dl = self.get_dl_count(type = 'zd') if max_dl == None: return None #print(f"最大序列为: {max_dl}") self.日志输出.info(f"最大序列为: {max_dl}") dq_dl_count = self.get_dl_count(type ='dq') #print(f"当前出征队列数量为: {dq_dl_count}") self.日志输出.info(f"当前出征队列数量为: {dq_dl_count}") if max_dl == None: #print("获取最大序列失败") self.日志输出.info("获取最大序列失败") return None if max_dl == 0: #print("没有队列出征,可以尽情的派遣") self.日志输出.info("没有队列出征,可以尽情的派遣") return max_dl if dq_dl_count == None: #print("获取当前队列数量失败") self.日志输出.info("获取当前队列数量失败") return None return max_dl - dq_dl_count #获得最大序列 def get_dl_count(self,type='dq'): """序列数量 type=dq 为获取当前队列数量 type=max为获取当前最大序列数量 dq 为获取当前出征队列 zd为最大可出征的队列 """ self.gotocs('cs') region = self.img_processor.find_image('img/dljt.bmp',threshold=0.75) if region == None: return 0 newregin = None if type =='zd': newregin = (region['x']+region['width']+32, region['y'],region['x']+region['width']+42 , region['y']+15) # print(f"get_dl_count中获取当前最大序列数量截图范围:{region}") else: newregin = (region['x']+region['width']+21, region['y'],region['x']+region['width']+30 , region['y']+15) # print(f"get_dl_count获取当前队列数量:截图范围{newregin}") dlimgs={ 'dl1': 'img/dl1.bmp', 'dl11': 'img/dl11.bmp', 'dl2': 'img/dl2.bmp', 'dl21': 'img/dl21.bmp', 'dl3': 'img/dl3.bmp', 'dl31': 'img/dl31.bmp', 'dl32': 'img/dl32.bmp', 'dl4': 'img/dl4.bmp', 'dl41': 'img/dl41.bmp', 'dl5': 'img/dl5.bmp', 'dl51': 'img/dl51.bmp', 'dl6': 'img/dl6.bmp', 'dl7': 'img/dl7.bmp', 'dl62': 'img/dl62.bmp', } # window_image = self.img_processor.capture_window(newregin) # # 保存截图到seedpic目录 # os.makedirs("seedpic", exist_ok=True) # timestamp = time.strftime("%Y%m%d_%H%M%S") # save_path = f"seedpic/find_images_{timestamp}.png" # cv2.imwrite(save_path, window_image) max_count = None max_sequence = self.img_processor.find_images( dlimgs,threshold=0.75,region=newregin) if type =='dq': self.日志输出.info(f"获取当前序列") if max_sequence: if max_sequence['key']=='dl1' or max_sequence['key']=='dl11': max_count = 1 elif max_sequence['key']=='dl2' or max_sequence['key']=='dl21': max_count = 2 elif max_sequence['key']=='dl3' or max_sequence['key']=='dl31' or max_sequence['key']=='dl32': max_count = 3 elif max_sequence['key']=='dl4' or max_sequence['key']=='dl41': max_count = 4 elif max_sequence['key']=='dl5' or max_sequence['key']=='dl51': max_count = 5 elif max_sequence['key']=='dl6' or max_sequence['key']=='dl62': max_count = 6 elif max_sequence['key']=='dl7': max_count = 7 if max_count == None: #print("获取序列数量失败") if type =='dq': self.日志输出.info(f"当前没有出征队列") else: self.日志输出.info(f"队列没有识别到,怀疑未出证") return None # print(f"找到{type}序列数量为: {max_count}") return max_count else: # print(f"未找到{type}序列序列") #日志输出 如果 type = dq 输出当前 如果 type = zd 输出最大 if type =='dq': self.日志输出.info(f"当前没有出征队列") else: self.日志输出.info(f"当前最大序列为{max_count}") return None def 移动开鼠标(self,操作='移动'): """操作 移动 或者 移动点击""" 窗口 = self.get_window_rect() #print(f"窗口{窗口}") #获取两数直接的随机整数 随机x = random.randint(100, 窗口[2]-窗口[0]-200) 随机y = random.randint(100, 窗口[3]-窗口[1]-200) #print(f"鼠标移动到窗口的{随机x} {随机y}") if 操作 =='移动': self.mouse.move_to_xy({'x':随机x , 'y': 随机y}) elif 操作 =='移动点击': # 移动鼠标到指定位置 win32api.SetCursorPos((随机x, 随机y)) # 模拟鼠标左键点击 win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 随机x, 随机y, 0, 0) #随机延时 self.延时.delay() win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 随机x, 随机y, 0, 0) #延时 self.延时.delay() def 检测游戏是否掉线(self): """ 检测游戏是否掉线并处理 流程: 1. 检测游戏窗口是否存在,如果不存在则启动游戏并调用 self.游戏重新登录() 2. 检测是否有顶号或掉线提示,如果都没有则返回 False 3. 如有提示,点击确认按钮 4. 如果是顶号,判断断线重连功能是否开启,如开启则启动游戏并调用 self.游戏重新登录() 5. 游戏启动成功后,获取窗口句柄并更新到相关组件 Returns: bool: 检测到掉线或顶号并成功处理返回 True,否则返回 False """ try: # 记录详细的连接检测日志 self.日志输出.info("开始检测游戏连接状态", show_in_ui=False) # 1. 检测游戏窗口是否存在 hwnd = self.window_handler.is_window_exists() if not hwnd: self.日志输出.warning("游戏窗口不存在") # 🔥 无论是否开启重连,都需要处理窗口丢失的情况 if self.设置页面['duanxinchonglian'] != True: self.关闭线程() # 如果开启了重连,执行重连逻辑 # 调用游戏重新登录函数 login_result = self.游戏重新登录("登录") if login_result: # 重新获取窗口句柄并更新 new_hwnd = self.window_handler.is_window_exists() if new_hwnd: self._update_window_handle(new_hwnd) self.日志输出.info("游戏窗口重新启动成功") return True else: self.日志输出.error("游戏窗口重新启动失败") else: # 重要:窗口存在时也需要更新句柄 self._update_window_handle(hwnd) # 2. 检测顶号或掉线提示 img = { '顶号': 'img/dinghao.bmp', '掉线': 'img/diaoxian.bmp', } 顶号 = self.img_processor.find_images(img, threshold=0.75) if not 顶号: # 没有检测到断线提示,游戏连接正常 return False # 🔥 无论是否开启重连,都需要处理窗口丢失的情况 if self.设置页面['duanxinchonglian'] != True: self.关闭线程() # 3. 处理检测到的情况 - 增强日志记录 if 顶号['key'] == '顶号': self.日志输出.warning("检测到账号被顶号,连接已断开") # 4. 判断是否开启断线重连 elif 顶号['key'] == '掉线': self.日志输出.warning("检测到网络掉线,连接已断开") self.延时.delay() # 5. 查找并点击确认按钮 确认图片 = { '确认1': 'img/jgqueren.bmp', '确认2': 'img/jgwjqueren.bmp', } 确认返回 = self.img_processor.find_images(确认图片, threshold=0.75) if 确认返回: self.日志输出.info(f"点击断线确认按钮: {确认返回['key']}") self.mouse.click_position(确认返回) self.延时.delay() self.延时.delay() self.延时.delay() # 6. 根据设置决定是否重连 if self.设置页面['duanxinchonglian']: self.日志输出.info("断线重连功能已开启,开始执行重新登录流程") login_result = self.游戏重新登录() if login_result: new_hwnd = self.window_handler.is_window_exists() if new_hwnd: self._update_window_handle(new_hwnd) self.日志输出.info("断线重连成功,游戏已重新连接") return True else: self.日志输出.error("断线重连失败,无法获取游戏窗口句柄") else: self.日志输出.error("断线重连失败,游戏启动不成功") else: self.日志输出.warning("断线重连功能未开启,需要手动重新连接游戏") return True except Exception as e: self.日志输出.error(f"检测游戏连接状态时发生错误: {str(e)}") return False def 关闭线程(self): """真正关闭主线程 - 和F12效果一样""" self.日志输出.warning("未开启重连,程序即将停止") # 方法1: 通过返回特殊值通知调用者终止 self.终止标志 = True # 方法2: 如果有主线程关闭函数,直接调用(优先级最高) if hasattr(self, 'main_thread_closer') and callable(self.main_thread_closer): try: #self.日志输出.info("正在调用主线程关闭函数...") self.main_thread_closer() # 直接调用主程序的关闭线程函数 #self.日志输出.info("主线程关闭函数已调用") return # 主线程关闭函数会处理一切,不需要继续 except Exception as e: self.日志输出.error(f"线程关闭失败: {e}") # 方法3: 如果有业务逻辑停止回调,通知主程序停止 if hasattr(self, 'stop_callback') and callable(self.stop_callback): try: self.stop_callback() self.日志输出.info("正在停止") except Exception as e: self.日志输出.error(f"停止失败: {e}") # 方法4: 抛出特殊异常来强制终止 raise GameWindowLostError("未开启重连功能,任务终止") def _update_window_handle(self, hwnd): """ 更新窗口句柄到相关组件 Args: hwnd: 窗口句柄 """ try: self.hwnd = hwnd if hasattr(self, 'img_processor'): self.img_processor.hwnd = hwnd if hasattr(self, 'mouse'): self.mouse.hwnd = hwnd self.日志输出.debug(f"更新窗口句柄成功: {hwnd}") except Exception as e: self.日志输出.error(f"更新窗口句柄时发生错误: {str(e)}") def 游戏重新登录(self,类型="等待登录"): """ 类型 登录 2 等待登录 执行登录游戏 1.如果类型为等待登录 创建一个定时器 等待时间为 self.chongliantime + 0 或者(self.chongliantime *self.延时.random_factor) 2.调用 self.window_handler.启动游戏() """ try: self.日志输出.info(f"开始执行游戏重新登录,类型: {类型}") # 检查断线重连是否开启 # 处理等待登录模式 if 类型 == "等待登录": # 计算等待时间 if hasattr(self, '设置页面') and 'chongliantime' in self.设置页面: # 添加类型转换,确保wait_time为数字类型 wait_time = float(self.设置页面['chongliantime']) # 如果有延时模块并且有随机因子,则应用随机因子 if hasattr(self, '延时') and hasattr(self.延时, 'random_factor'): #随机 wait_time 到 wait_time * self.延时.random_factor wait_time += random.uniform(0, wait_time * self.延时.random_factor) wait_time = int(wait_time * 60) self.日志输出.info(f"等待 {wait_time:.2f} 秒后开始重新登录") # 使用时间戳循环检测替代time.sleep,定期提示剩余时间 start_time = time.time() end_time = start_time + wait_time last_notify_time = start_time notify_interval = 10 # 每10秒提示一次剩余时间 while time.time() < end_time: current_time = time.time() remaining_time = end_time - current_time # 定期提示剩余时间 if current_time - last_notify_time >= notify_interval: self.日志输出.info(f"等待重新登录,还剩 {int(remaining_time)} 秒") last_notify_time = current_time # 短暂休眠,减少CPU占用但不阻塞界面 time.sleep(0.5) self.日志输出.info("等待时间结束,开始重新登录") else: self.日志输出.warning("未找到等待时间配置,使用默认等待时间 20秒") # 对于默认等待时间也使用循环检测方式 start_time = time.time() end_time = start_time + 20 while time.time() < end_time: remaining_time = end_time - time.time() if int(remaining_time) % 10 == 0 and remaining_time > 0: self.日志输出.info(f"使用默认等待时间,还剩 {int(remaining_time)} 秒") time.sleep(0.5) # 调用启动游戏方法 if hasattr(self, 'window_handler') and hasattr(self.window_handler, '启动游戏'): self.日志输出.info("开始启动游戏") result = self.window_handler.启动游戏() if result: self.日志输出.info("游戏启动成功") else: self.日志输出.error("游戏启动失败") return result except Exception as e: self.日志输出.error(f"游戏重新登录过程中发生错误: {str(e)}") return False def 清理对话框(self): """ 判断窗口是否被未知对话框遮挡 如果被遮挡 清理一下 """ #首先判断 gljiantou.bmp 和 'sj': 'img/sj.bmp','sj2': 'img/sj2.bmp', self.检测游戏是否掉线() imgpaths ={ 'cs': 'img/cs.bmp', 'cs2': 'img/cs2.bmp', 'cs3': 'img/cs3.bmp', 'cs4': 'img/cs4.bmp', 'sj': 'img/sj.bmp', 'sj2': 'img/sj2.bmp' } 无遮挡图片={ 'xing': 'img/zuobiaoxing.bmp', 'guanli': 'img/gljiantou.bmp', } pont = (6, 583, 1278, 745) zuobia =(1,1,380,100) for i in range(3): 无遮挡返回 =self.img_processor.find_images(无遮挡图片, threshold=0.85,region=zuobia) 对话框返回 =self.img_processor.find_images(imgpaths, threshold=0.85,region=pont) if 无遮挡返回 and 对话框返回: self.日志输出.info("窗口正常无遮挡") return True self.日志输出.info("关闭对话框") # 使用win32con模块中的ESC虚拟键码 self.mouse.press_key(win32con.VK_ESCAPE) self.延时.delay() return False def check_window_and_mouse(self): """检查窗口是否存在、是否为当前窗口,并处理鼠标位置""" # 1. 判断窗口是否存在 if not win32gui.IsWindow(self.hwnd): self.日志输出.info("窗口不存在") return False # 2. 判断是否为当前窗口,如果不是则设为当前 current_hwnd = win32gui.GetForegroundWindow() if current_hwnd != self.hwnd: self.日志输出.info("窗口不是当前窗口,正在设置为当前窗口") # 尝试方法1: 发送ALT键事件以绕过焦点限制 win32api.keybd_event(18, 0, win32con.KEYEVENTF_EXTENDEDKEY, 0) time.sleep(0.1) win32api.keybd_event(18, 0, win32con.KEYEVENTF_EXTENDEDKEY | win32con.KEYEVENTF_KEYUP, 0) time.sleep(0.1) # 尝试方法2: 使用SetForegroundWindow设置为当前窗口 try: result = win32gui.SetForegroundWindow(self.hwnd) if result == 0: self.日志输出.warning("SetForegroundWindow调用失败,尝试使用SetWindowPos") # 尝试方法3: 设置窗口为最顶层 win32gui.SetWindowPos(self.hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE) time.sleep(0.2) # 再次尝试SetForegroundWindow win32gui.SetForegroundWindow(self.hwnd) # 取消最顶层设置 win32gui.SetWindowPos(self.hwnd, win32con.HWND_NOTOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE) except Exception as e: self.日志输出.error(f"设置窗口为当前窗口时出错: {str(e)}") self.延时.delay() # 3. 判断鼠标是否在窗口内,如果不在则调用移动开鼠标 window_rect = self.get_window_rect() mouse_pos = win32api.GetCursorPos() # 检查鼠标是否在窗口内 if not (window_rect[0] <= mouse_pos[0] <= window_rect[2] and window_rect[1] <= mouse_pos[1] <= window_rect[3]): self.日志输出.info("鼠标不在窗口内,正在移动鼠标") self.移动开鼠标() return True
11-01
先展示下效果 https://pan.quark.cn/s/a4b39357ea24 遗传算法 - 简书 遗传算法的理论是根据达尔文进化论而设计出来的算法: 人类是朝着好的方向(最优解)进化,进化过程中,会自动选择优良基因,淘汰劣等基因。 遗传算法(英语:genetic algorithm (GA) )是计算数学中用于解决最佳化的搜索算法,是进化算法的一种。 进化算法最初是借鉴了进化生物学中的一些现象而发展起来的,这些现象包括遗传、突变、自然选择、杂交等。 搜索算法的共同特征为: 首先组成一组候选解 依据某些适应性条件测算这些候选解的适应度 根据适应度保留某些候选解,放弃其他候选解 对保留的候选解进行某些操作,生成新的候选解 遗传算法流程 遗传算法的一般步骤 my_fitness函数 评估每条染色体所对应个体的适应度 升序排列适应度评估值,选出 前 parent_number 个 个体作为 待选 parent 种群(适应度函数的值越小越好) 从 待选 parent 种群 中随机选择 2 个个体作为父方和母方。 抽取父母双方的染色体,进行交叉,产生 2 个子代。 (交叉概率) 对子代(parent + 生成的 child)的染色体进行变异。 (变异概率) 重复3,4,5步骤,直到新种群(parentnumber + childnumber)的产生。 循环以上步骤直至找到满意的解。 名词解释 交叉概率:两个个体进行交配的概率。 例如,交配概率为0.8,则80%的“夫妻”会生育后代。 变异概率:所有的基因中发生变异的占总体的比例。 GA函数 适应度函数 适应度函数由解决的问题决定。 举一个平方和的例子。 简单的平方和问题 求函数的最小值,其中每个变量的取值区间都是 [-1, ...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值