赞
踩
from PIL import ImageGrab # 屏幕截图 import numpy as np # 用于图像处理 import win32gui # 用于Windows GUI操作 import win32con # API的常量定义 import win32api # 用于执行一些底层Windows操作 import random # 随机数生成库 import cv2 # 图像处理 import time # 用于控制时间间隔 # 窗体相关参数 WINDOW_TITLE = "QQ游戏 - 连连看角色版" # 时间间隔参数 MIN_TIME_INTERVAL = 0.00 # 最小时间间隔(秒) MAX_TIME_INTERVAL = 0.06 # 最大时间间隔(秒) # 游戏区域偏移参数 GAME_AREA_LEFT_MARGIN = 14 # 游戏区域左侧的偏移量 GAME_AREA_TOP_MARGIN = 181 # 游戏区域顶部的偏移量 # 游戏区域方块布局参数 HORIZONTAL_BLOCKS = 19 # 横向的方块数量 VERTICAL_BLOCKS = 11 # 纵向的方块数量 # 方块尺寸参数 BLOCK_WIDTH = 31 # 方块的宽度 BLOCK_HEIGHT = 35 # 方块的高度 # 特殊图像编号 EMPTY_BLOCK_ID = 0 # 空图像的编号 # 图像切片坐标参数 SLICE_LEFT_TOP_X = 8 # 切片左上角的x坐标 SLICE_RIGHT_BOTTOM_X = 27 # 切片右下角的x坐标 SLICE_LEFT_TOP_Y = 8 # 切片左上角的y坐标 SLICE_RIGHT_BOTTOM_Y = 27 # 切片右下角的y坐标 def get_game_window(): # 使用win32gui的FindWindow函数来查找具有指定标题的窗口 game_window_handle = win32gui.FindWindow(None, WINDOW_TITLE) # 初始化一个循环,用于不断尝试查找窗口,直到找到为止 while not game_window_handle: print('未能定位到游戏窗口,请确保游戏窗口已打开,并在10秒后重试...') time.sleep(10) # 等待10秒后重试查找 game_window_handle = win32gui.FindWindow(None, WINDOW_TITLE) # 再次尝试查找窗口 # 使用SetForegroundWindow函数将游戏窗口设置为前台窗口(即置顶) win32gui.SetForegroundWindow(game_window_handle) # 使用GetWindowRect函数获取窗口的位置和大小信息 game_window_rect = win32gui.GetWindowRect(game_window_handle) # 打印窗口的位置信息,包括左上角和右下角的坐标 print(f"游戏窗口位置:{game_window_rect}") # 返回窗口左上角的x坐标和y坐标 return game_window_rect[0], game_window_rect[1] def get_screen_image(): # 打印提示信息,告知用户正在截图 print('正在截图...') # 使用ImageGrab捕获整个屏幕的截图,并返回Image对象 screenshot_image = ImageGrab.grab() # 将PIL的Image对象转换为numpy数组 screenshot_np = np.array(screenshot_image) # 转换颜色通道顺序,因为PIL使用'RGB',而OpenCV使用'BGR' screenshot_cv = cv2.cvtColor(screenshot_np, cv2.COLOR_RGB2BGR) # 返回OpenCV格式的图像数组 return screenshot_cv def get_all_squares(screen_image, game_position): # 初始化游戏窗体边缘的偏移量 margin_left = GAME_AREA_LEFT_MARGIN # 左侧偏移量 margin_height = GAME_AREA_TOP_MARGIN # 高度偏移量 # 根据游戏窗体位置计算游戏区域的左上角坐标 game_x = game_position[0] + margin_left game_y = game_position[1] + margin_height # 初始化一个列表,用于存储所有切割后的小方块 all_squares = [] # 遍历游戏区域的每个小方块 for x in range(0, HORIZONTAL_BLOCKS): # 遍历水平方向的小方块 for y in range(0, VERTICAL_BLOCKS): # 遍历垂直方向的小方块 # 根据小方块的坐标切割屏幕图像 square = screen_image[ game_y + y * BLOCK_HEIGHT: game_y + (y + 1) * BLOCK_HEIGHT, game_x + x * BLOCK_WIDTH: game_x + (x + 1) * BLOCK_WIDTH ] all_squares.append(square) # 初始化一个列表,用于存储去除边缘后的小方块 final_squares = [] # 遍历所有小方块,去除边缘像素 for square in all_squares: # 假设SUB_LT_X, SUB_LT_Y, SUB_RB_X, SUB_RB_Y是预定义的边缘去除参数 # 去除每个小方块边缘的一圈像素 edge_removed_square = square[SLICE_LEFT_TOP_Y:SLICE_RIGHT_BOTTOM_Y, SLICE_LEFT_TOP_X:SLICE_RIGHT_BOTTOM_X] final_squares.append(edge_removed_square) return final_squares def is_image_exist(image, image_list): # 将传入的图像转换为numpy数组(如果它还不是numpy数组的话) image_array = np.asarray(image) # 遍历图像列表中的每张图像 for idx, existing_image in enumerate(image_list): # 确保要比较的图像也是numpy数组 existing_image_array = np.asarray(existing_image) # 计算两张图像之间的差异 difference = np.subtract(existing_image_array, image_array) # 如果所有像素的差异都是0,即两张图像相同 if not np.any(difference): return idx # 返回找到相同图像的索引 # 如果没有找到相同的图像 return -1 def get_all_square_types(all_squares): print("初始化方块类型...") # 存储不同方块类型的列表 square_types = [] # 存储每个类型出现次数的列表 type_counts = [] # 遍历所有小方块 for square in all_squares: # 检查当前方块是否已存在于类型列表中 square_index = is_image_exist(square, square_types) if square_index == -1: # 如果不存在,则添加到类型列表中,并将计数器初始化为1 square_types.append(square) type_counts.append(1) else: # 如果存在,则增加对应类型的计数器 type_counts[square_index] += 1 # 找到出现次数最多的方块类型的计数器 max_count = max(type_counts) # 找到出现次数最多的方块类型的索引 max_count_index = type_counts.index(max_count) # 更新全局变量 EMPTY_BLOCK_ID,假设 EMPTY_BLOCK_ID 表示空白块的索引 global EMPTY_BLOCK_ID EMPTY_BLOCK_ID = max_count_index print('空白块的ID是:' + str(EMPTY_BLOCK_ID)) return square_types def get_square_record(all_squares, square_types, v_num): print("转换地图...") # 存储最终记录 record = [] # 当前行的方块类型索引 current_line = [] for square in all_squares: found_match = False # 遍历所有方块类型 for type_idx, type_square in enumerate(square_types): # 计算当前方块与类型方块的差异 diff = np.abs(square - type_square) # 检查差异是否全为0(即是否找到完全匹配的方块) if np.all(diff == 0): current_line.append(type_idx) found_match = True break # 如果没有找到匹配项,则添加-1表示未找到匹配 if not found_match: current_line.append(-1) # 如果当前行已满或到达最后一个方块,则保存当前行并重置 if len(current_line) == v_num or square is all_squares[-1]: record.append(current_line) current_line = [] # 检查是否有未添加到记录的最后一行(包含-1的情况) if current_line: record.append(current_line) return record def can_connect(x1, y1, x2, y2, matrix): # 复制矩阵,避免修改原矩阵 grid = matrix[:] # 定义空点的常量,这里需要外部定义EMPTY_ID # EMPTY_ID = ... # 需要根据实际情况定义 # 如果两个点中有一个为空点,则直接返回False if grid[x1][y1] == EMPTY_BLOCK_ID or grid[x2][y2] == EMPTY_BLOCK_ID: return False # 如果两个点相同,则它们不是两个不同的点,直接返回False if x1 == x2 and y1 == y2: return False # 如果两个点所在的区域不同(即值不同),则它们不连通,返回False if grid[x1][y1] != grid[x2][y2]: return False # 判断横向是否连通 if is_horizontally_connected(x1, y1, x2, y2, grid): return True # 判断纵向是否连通 if is_vertically_connected(x1, y1, x2, y2, grid): return True # 判断是否可以通过一个拐点连通 if is_connected_with_one_turn(x1, y1, x2, y2, grid): return True # 判断是否可以通过两个拐点连通 if is_connected_with_two_turns(x1, y1, x2, y2, grid): return True # 不可连通,返回False return False # 判断横向是否连通 def is_horizontally_connected(x1, y1, x2, y2, grid): # 如果两点坐标相同,则它们不是两个不同的点,直接返回False if x1 == x2 and y1 == y2: return False # 如果两点不在同一行,则它们不是水平连通的,直接返回False if x1 != x2: return False # 获取两点中较小的y坐标作为起始点 start_y = min(y1, y2) # 获取两点中较大的y坐标作为结束点 end_y = max(y1, y2) # 如果两点之间只有一个点的距离,则它们是直接相邻的,返回True if (end_y - start_y) == 1: return True # 遍历两点之间的所有点 for i in range(start_y + 1, end_y): # 如果发现有一个点不是空的(不是EMPTY_ID),则两点不是水平连通的,返回False if grid[x1][i] != EMPTY_BLOCK_ID: return False # 如果所有点都是空的,则两点是水平连通的,返回True return True # 判断纵向是否连通 def is_vertically_connected(x1, y1, x2, y2, grid): # 如果两点坐标相同,则它们不是两个不同的点,直接返回False if x1 == x2 and y1 == y2: return False # 如果两点不在同一列,则它们不是垂直连通的,直接返回False if y1 != y2: return False # 获取两点中较小的x坐标作为起始点 start_x = min(x1, x2) # 获取两点中较大的x坐标作为结束点 end_x = max(x1, x2) # 如果两点之间只有一个点的距离,则它们是直接相邻的,返回True if end_x - start_x == 1: return True # 遍历两点之间的所有点 for current_x in range(start_x + 1, end_x): # 如果发现有一个点不是空的(不是EMPTY_ID),则两点不是垂直连通的,返回False if grid[current_x][y1] != EMPTY_BLOCK_ID: return False # 如果所有点都是空的,则两点是垂直连通的,返回True return True # 判断是否可以通过一个拐点连通 def is_connected_with_one_turn(x1, y1, x2, y2, grid): # 如果两点在同一行或同一列,则它们不需要转弯即可连通,直接返回False if x1 == x2 or y1 == y2: return False # 定义拐点的坐标 cx, cy = x1, y2 # 第一个可能的拐点 dx, dy = x2, y1 # 第二个可能的拐点 # 检查第一个拐点的情况 # 如果第一个拐点为空,并且从起点到拐点水平连通,从拐点到终点垂直连通,则返回True if grid[cx][cy] == EMPTY_BLOCK_ID: if is_horizontally_connected(x1, y1, cx, cy, grid) and is_vertically_connected(cx, cy, x2, y2, grid): return True # 检查第二个拐点的情况 # 如果第二个拐点为空,并且从起点到拐点垂直连通,从拐点到终点水平连通,则返回True if grid[dx][dy] == EMPTY_BLOCK_ID: if is_vertically_connected(x1, y1, dx, dy, grid) and is_horizontally_connected(dx, dy, x2, y2, grid): return True # 如果两种情况都不满足,则返回False return False # 判断是否可以通过两个拐点连通 def is_connected_with_two_turns(x1, y1, x2, y2, grid): # 如果起点和终点相同,则它们不需要转弯即可连通,直接返回False if x1 == x2 and y1 == y2: return False # 遍历整个数组找合适的拐点 for i in range(0, len(grid)): for j in range(0, len(grid[1])): # 不为空不能作为拐点 if grid[i][j] != EMPTY_BLOCK_ID: continue # 不和被选方块在同一行列的不能作为拐点 if i != x1 and i != x2 and j != y1 and j != y2: continue # 作为交点的方块不能作为拐点 if (i == x1 and j == y2) or (i == x2 and j == y1): continue if is_connected_with_one_turn(x1, y1, i, j, grid) and ( is_horizontally_connected(i, j, x2, y2, grid) or is_vertically_connected(i, j, x2, y2, grid)): return True if is_connected_with_one_turn(i, j, x2, y2, grid) and ( is_horizontally_connected(x1, y1, i, j, grid) or is_vertically_connected(x1, y1, i, j, grid)): return True return False def auto_release(grid, game_x, game_y): for i in range(len(grid)): # 遍历矩阵的行 for j in range(len(grid[0])): # 遍历矩阵的列 if grid[i][j] != EMPTY_BLOCK_ID: # 如果当前位置不是空 for m in range(len(grid)): # 再次遍历矩阵的行 for n in range(len(grid[0])): # 再次遍历矩阵的列 if grid[m][n] != EMPTY_BLOCK_ID: # 如果另一个位置也不是空 if can_connect(i, j, m, n, grid): # 如果两个位置可以连接消除 grid[i][j] = EMPTY_BLOCK_ID # 将两个位置设置为空 grid[m][n] = EMPTY_BLOCK_ID print(f'消除:{i + 1},{j + 1} 和 {m + 1},{n + 1}') # 计算鼠标操作的坐标 x1 = game_x + j * BLOCK_WIDTH y1 = game_y + i * BLOCK_HEIGHT x2 = game_x + n * BLOCK_WIDTH y2 = game_y + m * BLOCK_HEIGHT # 移动鼠标并点击消除两个位置 win32api.SetCursorPos((x1 + 15, y1 + 18)) click(x1 + 15, y1 + 18) time.sleep(random.uniform(MIN_TIME_INTERVAL, MAX_TIME_INTERVAL)) win32api.SetCursorPos((x2 + 15, y2 + 18)) click(x2 + 15, y2 + 18) time.sleep(random.uniform(MIN_TIME_INTERVAL, MAX_TIME_INTERVAL)) return True # 消除成功,返回True return False # 没有找到可消除的元素对,返回False def click(x, y): win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0) def auto_remove(game_board, game_window_position): # 计算游戏面板的左上角坐标 game_x = game_window_position[0] + GAME_AREA_LEFT_MARGIN game_y = game_window_position[1] + GAME_AREA_TOP_MARGIN # 初始化消除计数为0 elimination_count = 0 # 循环调用 auto_release 函数,直到没有可消除的方块对为止 while auto_release(game_board, game_x, game_y): # 假设 auto_release 在每次成功消除时返回 True,并且每次消除至少一个方块 # 因此,我们增加消除计数 elimination_count += 1 # 返回消除的总数量 return elimination_count def main(): # 设置随机数生成器的种子,确保结果的可复现性 random.seed() # 获取游戏窗口的位置 game_pos = get_game_window() # 获取屏幕图像 screen_image = get_screen_image() # 从屏幕图像中提取所有的方块列表 all_square_list = get_all_squares(screen_image, game_pos) # 获取所有方块的类型 types = get_all_square_types(all_square_list) # 根据方块列表、类型以及垂直方块规则,获取方块记录 result_list = get_square_record(all_square_list, types, VERTICAL_BLOCKS) # 将列表转换为NumPy数组,便于后续操作 result_array = np.array(result_list) # 设置合适的维度,用于重塑数组 m, n = 19, 11 # 将数组重塑为指定的形状 result_array_reshaped = result_array.reshape((m, n)) # 打印重塑后的数组,用于调试或展示 print(result_array_reshaped) # 对数组进行转置,可能用于适应游戏的特定逻辑 result_transposed = result_array_reshaped.T # 使用转置后的数组进行自动消除操作,并打印消除的总数量 elimination_amount = auto_remove(result_transposed, game_pos) # 打印消除的总数量 print('消除的总数量是', elimination_amount) if __name__ == '__main__': # 调用主函数 main()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。