当前位置:   article > 正文

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

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






  • 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
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/854299
推荐阅读
相关标签
  

闽ICP备14008679号