RM 连连看小程序 Python 脚本实现
RM 连连看小程序 Python 脚本实现

RM 连连看小程序 Python 脚本实现

一、 初始化方块所有类型并处理图像

注意方块类型数量

建议大小为 50 * 50 的方块。

# 获取所有的方块类型
def getAllSquareTypes():
    print("将图像矩阵按类型归入类型列表...")
    types = []

    for i in range(0, 8):
        empty_img = cv2.imread("ss/" + str(i) + ".png")
        empty_img = np.array(empty_img)
        empty_gray = cv2.cvtColor(empty_img, cv2.COLOR_BGR2GRAY)
        empty_GB = cv2.GaussianBlur(empty_gray, (3, 3), 0)
        empty_GB = cv2.threshold(empty_GB, 100, 255, cv2.THRESH_BINARY_INV)[1]
        empty_GB = cv2.dilate(empty_GB, None, iterations=2)
        
        types.append(empty_GB)
        
    return types

二、 获取窗口在屏幕上位置

返回窗口左上角坐标。

# 获取窗体坐标位置(左上)
def getGameWindowPosition():
    # FindWindow(lpClassName=None, lpWindowName=None)  窗口类名 窗口标题名
    window = win32gui.FindWindow(None,WINDOW_TITLE)
    # 没有定位到游戏窗体
    while not window:
        print('定位游戏窗体失败,5秒后重试...')
        time.sleep(5)
        window = win32gui.FindWindow(None,WINDOW_TITLE)
    # 定位到游戏窗体
    win32gui.SetForegroundWindow(window) # 将窗体顶置
    pos = win32gui.GetWindowRect(window)
    print("定位到游戏窗体:" + str(pos))
    return (pos[0],pos[1])

三、截取屏幕并保存为 png 格式

输入你想要的名字(英文)即可保存截屏。

def window_capture(filename):
  hwnd = 0 # 窗口的编号,0号表示当前活跃窗口
  # 根据窗口句柄获取窗口的设备上下文DC(Divice Context)
  hwndDC = win32gui.GetWindowDC(hwnd)
  # 根据窗口的DC获取mfcDC
  mfcDC = win32ui.CreateDCFromHandle(hwndDC)
  # mfcDC创建可兼容的DC
  saveDC = mfcDC.CreateCompatibleDC()
  # 创建bigmap准备保存图片
  saveBitMap = win32ui.CreateBitmap()
  # 获取监控器信息
  MoniterDev = win32api.EnumDisplayMonitors(None, None)
  w = MoniterDev[0][2][2]
  h = MoniterDev[0][2][3]
  # print w,h   #图片大小
  # 为bitmap开辟空间
  saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
  # 高度saveDC,将截图保存到saveBitmap中
  saveDC.SelectObject(saveBitMap)
  # 截取从左上角(0,0)长宽为(w,h)的图片
  saveDC.BitBlt((0, 0), (w, h), mfcDC, (0, 0), win32con.SRCCOPY)
  saveBitMap.SaveBitmapFile(saveDC, filename)

四、读取图片并切割需要识别的部分

注意:此处需要修改的部分为:

MARGIN_LEFT、MARGIN_HEIGHT为

窗口左上角坐标系到左上角第一个方块的宽和高

SQUARE_HEIGHT、 SQUARE_WIDTH为

方块左上角到下一个方块的左上角(即包含方块与方块之间的间隙)

# 从屏幕截图中切割
def getAllSquare(screen_image,game_pos):
    print('图像切片处理...')
    # 通过游戏窗体,找到连连看连接的区域:
    game_x = game_pos[0] + MARGIN_LEFT
    game_y = game_pos[1] + MARGIN_HEIGHT
    # 从连接区域左上开始,把图像切割成一个一个的小块,切割标准是按照小块的横纵坐标。
    all_square = []
    for x in range(0,H_NUM):
        # line_square = []
        for y in range(0,V_NUM):
            # ndarray的切片方法,[纵坐标起始位置:纵坐标结束为止,横坐标起始位置:横坐标结束位置]
            square = screen_image[game_y + y * SQUARE_HEIGHT :game_y + (y+1) * SQUARE_HEIGHT,game_x + x * SQUARE_WIDTH:game_x + (x+1) * SQUARE_WIDTH]
            # cv2.rectangle(screen_image, (game_x + x * SQUARE_WIDTH , game_y + y * SQUARE_HEIGHT), 
            #                             (game_x + (x+1) * SQUARE_WIDTH, game_y + (y+1) * SQUARE_HEIGHT),
            #                             (0, 0, 255),
            #                             3, 
            #                             8)
            # cv2.imshow("ss", screen_image)
            # cv2.waitKey(0)
            all_square.append(square)
    # 因为有些图片的边缘不一致造成干扰(主要是空白区域的切图),所以把每张小方块向内缩小一部分再
    # 对所有的方块进行处理屏蔽掉外边缘 然后返回
    # return list(map(lambda square : square[SUB_LT_Y:SUB_RB_Y,SUB_LT_X:SUB_RB_X],all_square))
    return [square[SUB_LT_Y:SUB_RB_Y, SUB_LT_X:SUB_RB_X] for square in all_square]

五、讲切片后的图像和第一步初始化的图像进行对比

对比原理:将图片都处理成二值化图后,利用 absdiff (做差求绝对值) 比较两个图片的差异,差异越多,图片二值图显示出来白色部分越多。再选取最小差异,即可判断出图片类型。

# 将所有的方块与类型进行比较,转置成数字
def getAllSquareRecord(all_square_list,types):
    print("将所有的方块与类型进行比较,转置成数字矩阵...")
    record = []  # 整个记录的二维数组
    line = []   # 记录一行
    for square in all_square_list:   # 把所有的方块和保存起来的所有类型做对比
        square = cv2.resize(square, (50, 50))
        square = np.array(square)
        square_gray = cv2.cvtColor(square, cv2.COLOR_BGR2GRAY)
        square_GB = cv2.GaussianBlur(square_gray, (3, 3), 0)
        thresh = cv2.threshold(square_GB, 100, 255, cv2.THRESH_BINARY_INV)[1]
        thresh = cv2.dilate(thresh, None, iterations=2)
        num = 0
        j = 0
        min = 10000
        for type in types:    # 所有类型
            
            edg = cv2.absdiff(type, thresh)
            contours, hierarchy = cv2.findContours(edg.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
            area = 0
            for c in contours:
                area += cv2.contourArea(c)
            if area < min:
                min = area
                j = num
            num += 1                    # 如果没有匹配上,则类型数加1
        line.append(j)                  # 将类型的数字记录进这一行
        if len(line) == V_NUM:          # 如果校验完这一行已经有了 V_NUM 个数据,则另起一行
            record.append(line)
            line = []
    return np.transpose(record)

六、循环遍历消除方块

def autoRemove(squares,game_pos):
    # 每次消除一对儿,QQ的连连看最多105对儿
    game_x = game_pos[0] + MARGIN_LEFT
    game_y = game_pos[1] + MARGIN_HEIGHT
    # 判断是否消除完了?如果没有的话,点击重列后继续消除
    for i in range(0,108):
        autoRelease(squares,game_x,game_y)
# 自动消除
def autoRelease(result,game_x,game_y):
    for i in range(0,len(result)):
        for j in range(0,len(result[0])):
            # 以上两个for循环,定位第一个选中点
            if result[i][j] != 0:
                for m in range(0,len(result)):
                    for n in range(0,len(result[0])):
                        if result[m][n] != 0:
                            # 后两个for循环定位第二个选中点
                            if matching.canConnect(i,j,m,n,result):
                            # 执行消除算法并返回
                                result[i][j] = 0
                                result[m][n] = 0
                                # print('可消除点:'+ str(i+1) + ',' + str(j+1) + '和' + str(m+1) + ',' + str(n+1))
                                x1 = game_x + j * SQUARE_WIDTH
                                y1 = game_y + i * SQUARE_HEIGHT
                                x2 = game_x + n * SQUARE_WIDTH
                                y2 = game_y + m * SQUARE_HEIGHT
                                win32api.SetCursorPos((x1 + 15,y1 + 18))
                                win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x1+15, y1+18, 0, 0)
                                win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x1+15, y1+18, 0, 0)
                                time.sleep(0.15)

                                win32api.SetCursorPos((x2 + 15, y2 + 18))
                                win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x2 + 15, y2 + 18, 0, 0)
                                win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x2 + 15, y2 + 18, 0, 0)
                                time.sleep(0.2)
                                return True
    return False

七、最终效果视频

完结撒花