Python 实现auto linlink 连连看
先上知乎上大神所写:
你看过/写过哪些有意思的代码?
然后别急着看blog,玩一把游戏再说!
看知乎评论,有人说他用了一个下午时间就写出来了. wo kao!!! 我断断续续写了一周的下午才搞定,然后又用了4个小时将近一个下午才将代码搬到博客园. 要说这个自动连接连连看说简单也简单,说不简单也不简单.反正是没你想的那么简单也没你想的那么复杂(喜欢说废话的人).做这个小程序遇到了好几个问题,一个一个搞定还蛮有成就感.
我也先放一个视频来看看程序跑起来的效果,(话说博客园不能上传视频,只能上传swf) mp4 转swf 失真的太厉害啦! 就忍着这样看吧.......
连连看的算法
连连看顾名思义, 将相同的图案连接起来,规则是什么呢? 很简单, 在三条线以内完成连接即可,换句话说,在小于等于两次拐角的情况下能把图案相同的连接起来就是可联通点.所以思路很清晰了,只要满足条件就能联通,3条线以内可分三种情况.
情况一:直线连接, 也就是两个点同在X轴 或同在Y轴, 这种情况下,我们先判断是在X轴还是在Y轴, 然后计算出同一个轴上的最大值与最小值,最大值MAX减去最小值Min就是两个点之间的距离,然后在判断两点之间的距离是否为空,为空即为可联通的点,不为空表示之间有其他不同的图案点.
情况二:一次拐角连接, 一次拐角就相当于是用了两条线进行联通,也就是两个点肯定不在同一个X轴或肯定不在同一个Y轴,那两个点的沿(长)线上肯定会有交集, 而且肯定是两个交集, 一个是点1的X轴与点2的Y轴交集 暂叫该点为点C, 另外一个是点1的Y轴与点2的X轴 暂叫该点为点D,那问题就很简单了,一次拐角的连接其实就是两个直线的连接, 只是这个拐角的连接点变成了两点直接的两个交集, 只要计算两点与交集是否能同时联通, 同时联通就认为一次拐角可联通.比如:点1与C可联通,但点C与点2不可联通,视为不可联通, 如果点1与点D可联通,且点D与点2可联通,即视为点1与点2一次拐角可联通.
情况三:两次拐角连接,刚才说过一次拐角连接的情况其实就是两个直线连接的and情况, 那两次拐角连接是不是三个直线的and情况呢, 对的,事实就是这样的,只不过一次拐角连接的情况我们能知道两点的交集点,也就是上面刚才所说的点C与点D,两次拐角的情况我们就不知道这个点在哪了,所以我们就要先选择一个点, 就它了---->点1, 遍历点1在一条直线上的点,这条直线可能是在点1的X轴上,有可能在点1的Y轴上 ,不管如何,遍历出的这个点肯定在点1的X轴或Y轴上,否则与点1就不是一条直线了,假设我们遍历点1直线上的点现在取到了该点为点E, 问题就变成点1与点E是否直线可联通, 点E与点2是否一次拐角可联通,点E与点2是否一次拐角可联通可以直接用情况二的算法. 当满足一个直线,一个拐角 and (且) 情况下即为联通,否则继续遍历寻找点E,直到满足条件.
坎坷的实现过程
再来说说实现的过程
selenium+PIL 获取网页flash图像
网页flash游戏来自:
http://www.4399.com/flash/24238_2.htm
这种方案是因为哥用的Ubuntu,应用商店几乎没有好玩点的连连看,H5, flash肯定是首选,那通过PC玩游戏更是首选.
参考:
利用selenium实现验证码获取并验证 https://zhuanlan.zhihu.com/p/25171554
selenium+python实现1688网站验证码图片的截取 https://blog.csdn.net/zwq912318834/article/details/78605486
selenium获取位置,通过PIL保存截图,分析相同图像,在通过click事件触发自动完成连连看, OK,上代码:
# 前提 程序启动时 对浏览器窗口大小 设置 ''' https://zhuanlan.zhihu.com/p/25171554 https://blog.csdn.net/zwq912318834/article/details/78605486 ''' from selenium import webdriver import time from PIL import Image chromeOpitons = webdriver.ChromeOptions() prefs = { "profile.managed_default_content_settings.images": 1, "profile.content_settings.plugin_whitelist.adobe-flash-player": 1, "profile.content_settings.exceptions.plugins.*,*.per_resource.adobe-flash-player": 1, } chromeOpitons.add_experimental_option('prefs', prefs) time.sleep(5) # 打开 driver = webdriver.Chrome() url = "http://www.4399.com/flash/24238_2.htm" driver.set_window_size(1200, 800) driver.get(url) time.sleep(15) # 获取截图 driver.get_screenshot_as_file('screenshot.png') # 获取指定元素位置 element = driver.find_element_by_id('swfdiv') left = int(element.location['x']) top = int(element.location['y']) print(left, top) print(element.size['width'], element.size['height']) right = int(element.location['x'] + element.size['width']) bottom = int(element.location['y'] + element.size['height']) # 通过Image处理图像 im = Image.open('screenshot.png') im = im.crop((left, top, right, bottom)) im.save('flash_game.png')
问题来了,chrome浏览器支持flash,但是当webdriver 控制浏览器的时候,flash死活不能启用! Firefox也是一样!
后来当我写这个blog的时候又search 查原因,尝试了下, 居然可以了!不过已经用本地player实现了, 有时间了在写这个webdriver 控制浏览器截图+自动click
说一下当时为什么flash在浏览器中可以启用, 在webdriver控制中就不能使用, 一行代码解决,自行体会
sudo apt-get install pepperflashplugin-nonfree
当时放弃了selenium 控制浏览器的方法后,就把flash.swf下载下来,通过 gnash flash播放器打开swf文件,PIL截图,切图,对比图片,pyautogui模拟鼠标点击.......的思路完成自动连连看.思路有了,就一步一步去实现.
PIL截图 切图
查找相似的图片就是把每个小方块都切割出来进行对比,PIL和openCV都可以做到,这里只提PIL.
PIL截图很容易Image.save 方法就是保存操作,切图使用Image.crop方法,crop有四个元组的参数表示为坐标 (left, upper, right, lower), right-left 与lower-upper 计算的大小就是图片的像素大小.
例如下面这张图片,切成4X4的小图片 (源图片的大小为 840X1280)
from PIL import Image img = Image.open('resource.jpg') # 固定图片的大小为 840X1280 region = (210, 0, 420, 320) # rop((x0,y0,x1,y1)) 图片裁切 for i in range(0, 4): # width长 for j in range(0, 4): # height高 r = (i * 210, j * 320, 210 + 210 * i, 320 + 320 * j) # 裁切图片 cropImg = img.crop(r) # 保存裁切后的图片 cropImg.save('crop' + str(i) + str(j) + '.jpg') print(r)
切割后就生成了12个小图片,以下是12个小图片拼接的效果
其实,在这个程序中,也可以不用保存小方块, 直接将切好的图片对象放在list中,用的时候直接从内存读取. 在程序中生成是便于理解.ok,知道了PIL如何截图和切图,我们现在模拟打开flash播放器,截取屏幕中的游戏区域与连连看的小方块区域,并生成小方块.
在这之前,我们要知道flash的大小,游戏区域与屏幕距离大小, 连连看的小方块区域与flash播放器的高度与左侧空间区域大小,每个小方块像素大小.
上代码:
import pyscreenshot as ImageGrab import os import time from PIL import Image os.popen('gnash a.swf -j750 -k450 -X100 -Y100') print('打开gnash swf 完成') time.sleep(5) im = ImageGrab.grab([100, 100, 850, 580]) # X1,Y1,X2,Y2 (X2-X1=750,Y2-Y1=450) # 通过截图工具查看为750*480 im.save("1.png") # 切片 # 通过Image处理图像 im = Image.open('1.png') left = 80 top = 145 im = im.crop((left, top, 560 + left, 320 + top)) im.save('2.png') # 贴片成小方块 img = Image.open('2.png') for y in range(0, 8): # height高 for x in range(0, 14): # width长 r = (x * 40, y * 40, 40 + 40 * x, 40 + 40 * y) cropImg = img.crop(r) x_play = x + 1 y_play = abs(8 - y) cropImg.save('./openpic_o/x' + str(x_play) + "y" + str(y_play) + '.png') cropImg = cropImg.crop((5, 5, 35, 35)) # 生成的40*40 左右上下裁剪5px cropImg.save('./openpic/x' + str(x_play) + "y" + str(y_play) + '.png') time.sleep(0.01) print(r)
打印的内容:
打开gnash swf 完成 (0, 0, 40, 40) (40, 0, 80, 40) (80, 0, 120, 40) (120, 0, 160, 40) (160, 0, 200, 40) (200, 0, 240, 40) (240, 0, 280, 40) (280, 0, 320, 40) (320, 0, 360, 40) (360, 0, 400, 40) (400, 0, 440, 40) (440, 0, 480, 40) (480, 0, 520, 40) (520, 0, 560, 40) (0, 40, 40, 80) (40, 40, 80, 80) (80, 40, 120, 80) (120, 40, 160, 80) (160, 40, 200, 80) (200, 40, 240, 80) (240, 40, 280, 80) (280, 40, 320, 80) (320, 40, 360, 80) (360, 40, 400, 80) (400, 40, 440, 80) (440, 40, 480, 80) (480, 40, 520, 80) (520, 40, 560, 80) (0, 80, 40, 120) (40, 80, 80, 120) (80, 80, 120, 120) (120, 80, 160, 120) (160, 80, 200, 120) (200, 80, 240, 120) (240, 80, 280, 120) (280, 80, 320, 120) (320, 80, 360, 120) (360, 80, 400, 120) (400, 80, 440, 120) (440, 80, 480, 120) (480, 80, 520, 120) (520, 80, 560, 120) (0, 120, 40, 160) (40, 120, 80, 160) (80, 120, 120, 160) (120, 120, 160, 160) (160, 120, 200, 160) (200, 120, 240, 160) (240, 120, 280, 160) (280, 120, 320, 160) (320, 120, 360, 160) (360, 120, 400, 160) (400, 120, 440, 160) (440, 120, 480, 160) (480, 120, 520, 160) (520, 120, 560, 160) (0, 160, 40, 200) (40, 160, 80, 200) (80, 160, 120, 200) (120, 160, 160, 200) (160, 160, 200, 200) (200, 160, 240, 200) (240, 160, 280, 200) (280, 160, 320, 200) (320, 160, 360, 200) (360, 160, 400, 200) (400, 160, 440, 200) (440, 160, 480, 200) (480, 160, 520, 200) (520, 160, 560, 200) (0, 200, 40, 240) (40, 200, 80, 240) (80, 200, 120, 240) (120, 200, 160, 240) (160, 200, 200, 240) (200, 200, 240, 240) (240, 200, 280, 240) (280, 200, 320, 240) (320, 200, 360, 240) (360, 200, 400, 240) (400, 200, 440, 240) (440, 200, 480, 240) (480, 200, 520, 240) (520, 200, 560, 240) (0, 240, 40, 280) (40, 240, 80, 280) (80, 240, 120, 280) (120, 240, 160, 280) (160, 240, 200, 280) (200, 240, 240, 280) (240, 240, 280, 280) (280, 240, 320, 280) (320, 240, 360, 280) (360, 240, 400, 280) (400, 240, 440, 280) (440, 240, 480, 280) (480, 240, 520, 280) (520, 240, 560, 280) (0, 280, 40, 320) (40, 280, 80, 320) (80, 280, 120, 320) (120, 280, 160, 320) (160, 280, 200, 320) (200, 280, 240, 320) (240, 280, 280, 320) (280, 280, 320, 320) (320, 280, 360, 320) (360, 280, 400, 320) (400, 280, 440, 320) (440, 280, 480, 320) (480, 280, 520, 320) (520, 280, 560, 320)
再看同级目录下有两张图片,1.png与2.png
在openpic_o目录,就可以看到裁剪后的小方块,方块大小就是原小方块大小 40*40像素的.
在openpic目录, 看到的是在小方块的基础之上,又裁剪了一次,把小方块的上 下 左 右 各边框都裁剪了5个像素,为什么要这样操作呢? 因为40*40px的图片即使flash引用的相同图片,但因分辨率问题,PIL裁剪后也有可能出现毛边,为了排除这个干扰才又裁剪了一次.如下图
关于坐标
有没有发现上面代码中有这么两行代码
x_play = x + 1
y_play = abs(8 - y)
为什么是这样呢?我们先来看下图, 下图是我们平时用的平面二位直角坐标,是从中心点(0,0)开始, 横向左为负数,横向右为正数,纵轴下为负数,纵轴上为正数,但是电脑上的坐标都是左上角(0,0)为坐标系,为了便于理解,我们就把Y轴进倒置,因为高度有8个小方块,所以就是8-y的绝对值.至于x轴,因为从左到右也是依次增大,so,X轴不变.
但为什么又是 x_play = x + 1 先看生成的小方块,小方块的名称是从 x1y1, x1y2,x1y3....x2y1...... 也就是从1开始,如果(1,1)就是起始的坐标点的话,四个边框的点坐标就只能直线连接或从里面消除点后再通过里面点进行迂回连接,这样便一开始就出问题了,因为四个边框的点是可以通过两个拐角连接的,换句话说,四个边框的外围还有一圈空的点,这些空的点坐标是可以联通的,所以才能构成边框上的点联通.也就有了(0,1),(1,0),(0,2),(0,3)...等坐标了,这里的x+1,也就是直接初试从(1,1)开始,给空的坐标点做预留.看下图.
我们定义link_ponts 为可联通的点坐标
link_points = [] # 可联通点坐标 for x in range(0, 14 + 2): # 网格个数+2 可以认为网格小方块的外围有一圈空的方块 for y in range(0, 8 + 2): # 网格个数+2 load_name = "x" + str(x) + "y" + str(y) if not os.path.exists("./openpic/" + load_name + '.png'): link_points.append(load_name) print(link_points)
打印link_points
['x0y0', 'x0y1', 'x0y2', 'x0y3', 'x0y4', 'x0y5', 'x0y6', 'x0y7', 'x0y8', 'x0y9', 'x1y0', 'x1y9', 'x2y0', 'x2y9', 'x3y0', 'x3y9', 'x4y0', 'x4y9', 'x5y0', 'x5y9', 'x6y0', 'x6y9', 'x7y0', 'x7y9', 'x8y0', 'x8y9', 'x9y0', 'x9y9', 'x10y0', 'x10y9', 'x11y0', 'x11y9', 'x12y0', 'x12y9', 'x13y0', 'x13y9', 'x14y0', 'x14y9', 'x15y0', 'x15y1', 'x15y2', 'x15y3', 'x15y4', 'x15y5', 'x15y6', 'x15y7', 'x15y8', 'x15y9']
相似图片分组
这一步骤, 就要提到昨天写的blog
Python OpenCV 图像相识度对比
图片的相似度不管知乎大神用的是
cv2.subtract
还是
numpy.subtract
我这里处理的小方块都有问题,很简单的(x1y2.png),(x2y4.png), (x2y6.png)识别就有问题.说x1y2.png与x2y6.png不相等,我就笑了.
(x1y2.png) (x2y4.png) (x2y6.png)
后来我就用了openCV图像的相似度对比,哈希算法也行,灰度RGB通道直方图算法匹配也行, 但是感觉通道直方图计算出来的值更准确, 只能说没有更好的,只有更适合的.
但是有个问题,通道直方图计算匹配的时候贼慢贼慢的,我的电脑计算的时候执行了8秒多.
def has_group_list(list_group, m2): ret = False if list_group: for klist in list_group: if m2 in klist: ret = True break return ret # 通过得到每个通道的直方图来计算相似度 def classify_hist_with_split(image1, image2, size=(256, 256)): # 将图像resize后,分离为三个通道,再计算每个通道的相似值 image1 = cv2.resize(image1, size) image2 = cv2.resize(image2, size) sub_image1 = cv2.split(image1) sub_image2 = cv2.split(image2) sub_data = 0 for im1, im2 in zip(sub_image1, sub_image2): sub_data += calculate(im1, im2) sub_data = sub_data / 3 return sub_data # 计算单通道的直方图的相似值 def calculate(image1, image2): hist1 = cv2.calcHist([image1], [0], None, [256], [0.0, 255.0]) hist2 = cv2.calcHist([image2], [0], None, [256], [0.0, 255.0]) # 计算直方图的重合度 degree = 0 for i in range(len(hist1)): if hist1[i] != hist2[i]: degree = degree + (1 - abs(hist1[i] - hist2[i]) / max(hist1[i], hist2[i])) else: degree = degree + 1 degree = degree / len(hist1) return degree list_group = [] def get_list_group(): tmpgroup = [] tmpIJ = '' for i in range(1, 15): for j in range(1, 9): # ij 坐标1 m1 = 'x' + str(i) + 'y' + str(j) for n in range(1, 15): for m in range(1, 9): # nm 坐标2 m2 = 'x' + str(n) + 'y' + str(m) img1 = cv2.imread('openpic/' + m1 + ".png") img2 = cv2.imread('openpic/' + m2 + ".png") hn = classify_hist_with_split(img1, img2) if hn > 0.80: if tmpIJ != m1: if tmpgroup: list_group.append(tmpgroup) tmpgroup = [] print(tmpIJ) print(list_group) tmpIJ = m1 else: if not has_group_list(list_group, m2): if m1 not in tmpgroup: tmpgroup.append(m1) tmpgroup.append(m2) get_list_group() print(list_group)
打印list_group
[['x1y1', 'x1y3', 'x5y6', 'x8y4', 'x13y8', 'x14y8'], ['x1y2', 'x1y8', 'x2y4', 'x3y8', 'x6y8', 'x7y2'], ['x1y4', 'x11y1'], ['x1y5', 'x4y7', 'x6y6', 'x11y3', 'x11y5', 'x12y8'], ['x1y6', 'x3y2', 'x4y8', 'x8y1', 'x9y7', 'x10y4', 'x11y4', 'x14y3'], ['x1y7', 'x2y1', 'x3y3', 'x4y3', 'x6y4', 'x8y5'], ['x2y2', 'x2y3', 'x7y8', 'x12y2', 'x13y1', 'x14y5'], ['x2y5', 'x6y3', 'x6y5', 'x8y8'], ['x2y6', 'x6y2'], ['x2y7', 'x5y5'], ['x2y8', 'x3y6', 'x12y5', 'x14y4'], ['x3y1', 'x5y1', 'x9y1', 'x10y3', 'x13y2', 'x13y5'], ['x3y4', 'x7y3', 'x7y4', 'x10y2', 'x11y8', 'x12y6', 'x13y4', 'x13y7', 'x14y2', 'x14y6'], ['x3y5', 'x4y4', 'x7y6', 'x9y8'], ['x3y7', 'x14y1'], ['x4y1', 'x9y2', 'x10y8', 'x11y2'], ['x4y2', 'x4y6', 'x12y3', 'x12y4'], ['x4y5', 'x5y7', 'x6y1', 'x9y5'], ['x5y2', 'x13y6'], ['x5y3', 'x7y1'], ['x5y4', 'x8y2'], ['x5y8', 'x9y3', 'x10y5', 'x11y7', 'x12y7', 'x13y3'], ['x6y7', 'x9y6'], ['x7y5', 'x8y3', 'x9y4', 'x10y7'], ['x7y7', 'x11y6'], ['x8y6', 'x12y1'], ['x8y7', 'x10y1'], ['x10y6', 'x14y7']]
问我为什么视频中没有等待8秒时间, 笑笑笑.....
看代码:
""" list_group 写入文件 """ fileObject = open('data_list_group.txt', 'w') ret = json.dump(link_points,fileObject) fileObject.close() """ 读取 list_group """ f= open('data_list_group.txt','r') list_group=[] for i in f.readlines(): list_group.append(i.strip('\n')) print(list_group)
模拟鼠标点击
现在算法有了, 小方块坐标有了, 可联通的link_points列表有了,相似图片分组list_group有了,就差模拟鼠标点击了,pyautogui登场
PyAutoGUI 简介
画个方形我也会:
import pyautogui for i in range(3): pyautogui.moveTo(300, 300, duration=0.25) pyautogui.moveTo(400, 300, duration=0.25) pyautogui.moveTo(400, 400, duration=0.25) pyautogui.moveTo(300, 400, duration=0.25)
OK! 例子在手, 说走就走!
完整代码
config.py
# -*- coding:utf-8 -*- """常量配置文件""" PLAYER = 'gnash' # player SWF_PATH = 'play.swf' GAME_Width = 750 # flash 实际 750*450 GAME_HEIGHT = 450 PLAYER_JK = '-j' + str(GAME_Width) + ' -k' + str(GAME_HEIGHT) # jk 窗口宽高,参考 gnash -h PLAYER_X = 100 # XY 窗口 距离(0.0)坐标的位置长度与高度 PLAYER_Y = 100 # XY 窗口 距离(0.0)坐标的位置长度与高度 GRID_SIZE = 40 # 网格大小 GRID_X_NUM = 14 # 网格X轴个数 GRID_Y_NUM = 8 # 网格Y轴个数 X11_HEIGHT = 30 # Ubuntu X11 窗体 title栏高度 PLAY_BTN = (315, 380) # player flash 开始游戏按钮坐标 CENTER_LEFT = 80 # 中心网格左侧距离 CENTER_TOP = 145 # 中心网格顶部高度距离 GRID_DIR = "img/" # 网格小方块目录 GRID_MIC_DIR = 'img_micro/' # 裁剪后小方块保存目录 GRID_ZOOM = 5 # 裁剪小方块 上 下 左 右 5px
play.swf
回到本页顶部,自己手动玩哈.
screenshot.py
# -*- coding:utf-8 -*- import pyscreenshot as ImageGrab from PIL import Image import time, os from config import * class screenshot_class: """ 截取 player 窗体图片在init中 """ def __init__(self): im = ImageGrab.grab([PLAYER_X, PLAYER_Y, PLAYER_X + GAME_Width, PLAYER_Y + GAME_HEIGHT + X11_HEIGHT]) # X1,Y1,X2,Y2 (X2-X1=750,Y2-Y1=450+30) im.save("1.png") print("截取player图片完成") time.sleep(1) im = Image.open('1.png') im = im.crop((CENTER_LEFT, CENTER_TOP, GRID_X_NUM * GRID_SIZE + CENTER_LEFT, GRID_Y_NUM * GRID_SIZE + CENTER_TOP)) # 80,145,40*14+80,40*8+155 im.save('2.png') print("裁剪截图处理完成") def save_grid(self): """ 保存小方块 """ print("cut cut cut .......") if not os.path.exists(GRID_DIR): os.mkdir(GRID_DIR) # 小方块存放目录 if not os.path.exists(GRID_MIC_DIR): os.mkdir(GRID_MIC_DIR) # 裁剪后的小方块存放目录 img = Image.open('2.png') for y in range(0, GRID_Y_NUM): # height高 for x in range(0, GRID_X_NUM): # width长 r = (x * GRID_SIZE, y * GRID_SIZE, GRID_SIZE + GRID_SIZE * x, GRID_SIZE + GRID_SIZE * y) crop_img = img.crop(r) x_play = x + 1 y_play = abs(GRID_Y_NUM - y) # 原坐标是从左上开始计算Y轴,将其转换为从(0,0) 开始 crop_img.save(GRID_DIR + "x" + str(x_play) + "y" + str(y_play) + '.png') crop_img = crop_img.crop((GRID_ZOOM, GRID_ZOOM, GRID_SIZE - GRID_ZOOM, GRID_SIZE - GRID_ZOOM)) # 生成的40*40 左右上下裁剪5px (5, 5, 35, 35) crop_img.save(GRID_MIC_DIR + "x" + str(x_play) + "y" + str(y_play) + '.png') time.sleep(1) print("cut 小方块完成") if __name__ == '__main__': print("from img") screenshot_class()
autolinlink.py
# -*- coding:utf-8 -*- import pyautogui from itertools import combinations import cv2 import os, time from config import * import screenshot class autolinlink: """autolinlink""" @staticmethod def open_swf(): """打开player""" os.popen(PLAYER + " " + SWF_PATH + " " + PLAYER_JK + " " + "-X" + str(PLAYER_X) + " -Y" + str(PLAYER_Y)) print("打开player完成") @staticmethod def play_btn(): """play btn 开始按钮""" pyautogui.moveTo(PLAYER_X + PLAY_BTN[0], PLAYER_Y + PLAY_BTN[1], duration=3.6) # 移动鼠标至play位置 pyautogui.click() print("已进入游戏") def screen_shot(self): time.sleep(0.2) # 休眠0.2s 等待background后的网格小方块加载 screenshot.screenshot_class() screenshot.screenshot_class.save_grid(self) def get_points(self): """ 获取 可联通点 坐标 :return: list_points """ print("获取可联通点坐标") link_points = [] # 可联通点坐标 for x in range(0, GRID_X_NUM + 2): # 网格个数+2 可以认为网格小方块的外围有一圈空的方块 for y in range(0, GRID_Y_NUM + 2): # 网格个数+2 load_name = "x" + str(x) + "y" + str(y) if not os.path.exists(GRID_MIC_DIR + load_name + '.png'): link_points.append(load_name) return link_points def get_group(self): """ 获取 相似图片group :return: list_group """ print("计算图片相似度,大概持续8秒左右.....") list_group = [] # f = open('data_list_group.txt') # list_group = json.load(f) # return list_group tmp_group = [] # 每个相同小方块的group分组 tmpIJ = '' # 每个相同坐标分组中,第一个坐标1,之后循环与该点匹配判断是否在同一个分组 for i in range(1, GRID_X_NUM + 1): # GRID_X_NUM+1 [1-14]区间 for j in range(1, GRID_Y_NUM + 1): # ij 坐标1 m1 = 'x' + str(i) + 'y' + str(j) for n in range(1, GRID_X_NUM + 1): for m in range(1, GRID_Y_NUM + 1): # nm 坐标2 m2 = 'x' + str(n) + 'y' + str(m) img1 = cv2.imread("./" + GRID_MIC_DIR + m1 + ".png") img2 = cv2.imread(GRID_MIC_DIR + m2 + ".png") hn = self.classify_hist_with_split(img1, img2) if hn > 0.80: # 大于0.8认为小方块相同 if tmpIJ != m1: if tmp_group: list_group.append(tmp_group) tmp_group = [] tmpIJ = m1 else: if not self.has_group_list(list_group, m2): if m1 not in tmp_group: tmp_group.append(m1) tmp_group.append(m2) return list_group def classify_hist_with_split(self, image1, image2, size=(256, 256)): """通过得到每个通道的直方图来计算相似度""" # 将图像resize后,分离为三个通道,再计算每个通道的相似值 image1 = cv2.resize(image1, size) image2 = cv2.resize(image2, size) sub_image1 = cv2.split(image1) sub_image2 = cv2.split(image2) sub_data = 0 for im1, im2 in zip(sub_image1, sub_image2): sub_data += self.calculate(im1, im2) sub_data = sub_data / 3 return sub_data def calculate(self, image1, image2): """计算单通道的直方图的相似值""" hist1 = cv2.calcHist([image1], [0], None, [256], [0.0, 255.0]) hist2 = cv2.calcHist([image2], [0], None, [256], [0.0, 255.0]) # 计算直方图的重合度 degree = 0 for i in range(len(hist1)): if hist1[i] != hist2[i]: degree = degree + (1 - abs(hist1[i] - hist2[i]) / max(hist1[i], hist2[i])) else: degree = degree + 1 degree = degree / len(hist1) return degree def has_group_list(self, list_group, p): """判断当前点坐标在不在group中""" ret = False if list_group: for klist in list_group: if p in klist: ret = True break return ret def run(self, list_group, link_points, tmp_points_list=[]): """执行操作""" total = (GRID_X_NUM + 2) * (GRID_Y_NUM + 2) sum_link_points = len(link_points) haslink = False if link_points != total: for k in list_group: # k=(x2y1,x2y4,x12y12,x90y90) for i in combinations(k, 2): # permutations 排列重复组合,combinations 组合不重复 if i[0] not in link_points and i[1] not in link_points: # 排除 当前k () 已在link_points中 llk = self.linlink(i, link_points) if llk: tmp_points_list.append(i[0]) tmp_points_list.append(i[1]) if i == ('x5y4', 'x8y2'): time.sleep(1) # flash "加时"魔法干扰,临时sleep 1s time.sleep(0.5) self.click(i[0], i[1]) haslink = True break if sum_link_points != total and haslink == False: print('剩余点无法消除') elif sum_link_points == total: print('完成') else: print('再来一次') list_group, link_points, tmp_points_list = self.update_group(list_group, link_points, tmp_points_list) return self.run(list_group, link_points, tmp_points_list) def update_group(self, list_group, link_points, tmp_points_list): link_points += tmp_points_list # 删除list_group 已连接数据 for i in list_group: for t in tmp_points_list: if t in i: i.remove(t) tmp_points_list = [] return list_group, link_points, tmp_points_list def linlink(self, xy, link_points): ret = False # 判断点是否能联通 x1 = xy[0].split('y')[0] y1 = 'y' + xy[0].split('y')[1] x2 = xy[1].split('y')[0] y2 = 'y' + xy[1].split('y')[1] result = self.lineCase(xy, link_points, x1, y1, x2, y2) if result: print(xy, '直线连接可联通') ret = True else: result = self.onceCorner(xy, link_points, x1, y1, x2, y2) # 一次拐角 if result: print(xy, '一次拐角可联通') ret = True else: result = self.doubleCorner(xy, link_points, x1, y1, x2, y2) # 两个拐角 if result: print(xy, '两次拐角可联通') ret = True else: # print(xy, '两次拐角貌似不可联通') pass return ret # 两次拐角 def doubleCorner(self, xy, link_points, x1, y1, x2, y2): ret = False first_match = 1 for px in range(GRID_X_NUM + 2): # (GRID_X_NUM + 2) * (GRID_Y_NUM + 2) for py in range(GRID_Y_NUM + 2): tmpx = 'x' + str(px) tmpy = 'y' + str(py) # 以第一个点坐标寻找拐点, if (tmpx == x1 and tmpy != y1) or (tmpy == y1 and tmpx != x1): # 第一个点坐标四个方向但除了自己 # 任意拐点,(px,py)与第一个点坐标为直线情况,与第二个点为一次拐角情况 lineC = self.lineCase(xy, link_points, x1, y1, tmpx, tmpy, corner=True) onceC = self.onceCorner(xy, link_points, tmpx, tmpy, x2, y2) if lineC and onceC and first_match == 1: first_match += 1 ret = True break return ret # 一次拐角 def onceCorner(self, xy, link_points, x1, y1, x2, y2): ret = False C = (x2, y1) # 用 第一个的Y,第二个X # C,D 为中间(p,Tmp) 的过渡点 D = (x1, y2) # 用 第一个X,第二个Y # C点分别与第一个点 与第二点 联通 ClineCaseY = self.lineCase(xy, link_points, x1, y1, C[0], C[1], True) # 即 (x1,y1,x2,y1) ClineCaseX = self.lineCase(xy, link_points, x2, y2, C[0], C[1], True) # 即 (x2,y2,x2,y1) if ClineCaseX and ClineCaseY: # C点与第一个点的X轴联通,与第二个点的Y轴联通 ret = True DlineCaseX = self.lineCase(xy, link_points, x1, y1, D[0], D[1], True) # 即 (x1,y1,x1,y2) DlineCaseY = self.lineCase(xy, link_points, x2, y2, D[0], D[1], True) # 即 (x2,y2,x1,y2) if DlineCaseX and DlineCaseY: # D点与第一个点的X轴联通,与第二个点的Y轴联通 ret = True return ret def lineCase(self, xy, link_points, x1, y1, x2, y2, corner=False): ret = False if x1 == x2: # x纵轴 相同 xmin = min(int(y1[1:]), int(y2[1:])) # 字符串不能进行max min比较 int(max(x1,x2)[1:]) # x1=9 x2=13 xmax = max(int(y1[1:]), int(y2[1:])) point_num = xmax - xmin - 1 if point_num == 0 and corner == False: ret = True elif point_num == 0 and corner: # 相连 and 来自拐角 if x2 + y2 in link_points: # 判断后(xy,tmp) tmp中间点 点是否为空 ret = True else: point_num_able = 0 for i in range(xmin + 1, xmax): if x1 + "y" + str(i) in link_points: point_num_able += 1 if point_num == point_num_able and point_num > 0 and ((x2 + y2 in link_points and corner) or corner == False): # 可联通点的个数等于同轴points点的个数 ret = True if y1 == y2: ymin = min(int(x1[1:]), int(x2[1:])) # 字符串不能进行max min比较 int(max(x1,x2)[1:]) # x1=9 x2=13 ymax = max(int(x1[1:]), int(x2[1:])) point_num = ymax - ymin - 1 if point_num == 0 and corner == False: ret = True elif point_num == 0 and corner: if x2 + y2 in link_points: ret = True else: point_num_able = 0 for i in range(ymin + 1, ymax): if "x" + str(i) + y1 in link_points: point_num_able += 1 if (point_num == point_num_able) and point_num > 0 and (( x2 + y2 in link_points and corner) or corner == False): # 可联通点的个数等于同轴points点的个数 # x2+y2 in link_points and corner作为拐角的点也必须为空 ret = True return ret def click(self, p1, p2): """模拟鼠标点击""" # ('x4y8', 'x8y1') # 设置x(0,0),y(0,0)坐标 init_pint = (PLAYER_X + CENTER_LEFT - GRID_SIZE + 20, CENTER_TOP + X11_HEIGHT + PLAYER_Y + ((GRID_Y_NUM) * GRID_SIZE) - 20) # x=80-40 , y= 145+30+9*40 x+20 与y-20可以让鼠标移动到小方块的中心 # x轴 相加, Y轴 相减 pX1 = init_pint[0] + int(p1.split('y')[0][1:]) * GRID_SIZE pY1 = init_pint[1] - int(p1.split('y')[1]) * GRID_SIZE pX2 = init_pint[0] + int(p2.split('y')[0][1:]) * GRID_SIZE pY2 = init_pint[1] - int(p2.split('y')[1]) * GRID_SIZE pyautogui.moveTo(pX1, pY1, duration=0.05) pyautogui.click() pyautogui.moveTo(pX2, pY2, duration=0.05) pyautogui.click() if __name__ == '__main__': app = autolinlink() app.open_swf() app.play_btn() app.screen_shot() link_points = app.get_points() list_group = app.get_group() if link_points and list_group: app.run(list_group, link_points)
源文件:
http://u.163.com/Bhfyp9nm 提取码: LkYikiOz
https://files.cnblogs.com/files/dcb3688/autolinlink.tar.gz