python-QQ游戏-连连看-秒杀

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()






评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值