python — Auto_QQ连连看
目录
1.前言
2.使用的扩展库
3.思路
4.具体实现
1) __main__
2) GetGameWindow()
3) getScreenImage()
4) getAllSquare()
5) getAllSquareTypes()
6) getAllSquareRecord()
7) autoRemove()
5.思考
1) 窗体大小可修改
2) 若空白块变成渐变色的问题 / 方块不完全相同 / 同种周围存在某些小特效导致pending结果为不同
6.总结
1.前言
继python跳一跳之后的下一份python练习....先上一个成果图 , 当然这里的东西仅供学习和参考 , 若用于其他途径后果自负
当然开始之前要先来一句...Python大法好~...Java下这个代码大概需要2000行左右,而python算上注释也才300行而已
2.使用的扩展库
python3.6 + opencv-python + pywin32 + PIL + numpy
若缺失库,这里给出安装命令
pip install opencv-python
pip install pypiwin32
pip install PIL
pip install numpy
若无法安装请自行百度
注意分辨率需要 1920 * 1080
3.思路
4.具体实现
1) __main__
if __name__ == '__main__':# 撒随机种子random.seed()# i. 定位游戏窗体game_pos = getGameWindow()# 定位到游戏窗体后等待一秒time.sleep(1)# ii. 获取屏幕截图screen_image = getScreenImage()# iii. 对截图切片,形成一张二维地图all_square_list = getAllSquare(screen_image,game_pos)# iv. 获取所有类型的图形,并编号types = getAllSquareTypes(all_square_list)# v. 讲获取的图片地图转换成数字矩阵result = np.transpose(getAllSquareRecord(all_square_list,types))# vi. 执行消除 , 并输出消除数量print('The total elimination amount is ' + str(autoRemove(result,game_pos)) )2) GetGameWindow()
# 获取窗体坐标位置 def getGameWindow():# 通过窗口标题名称定位游戏窗口window = win32gui.FindWindow(None,WINDOW_TITLE)# 若没有定位到游戏窗体# 则10s后重新定位,直到定位到游戏窗口while not window:print('Failed to locate the game window , reposition the game window after 10 seconds...')time.sleep(10)window = win32gui.FindWindow(None,WINDOW_TITLE)# 将游戏窗口置顶win32gui.SetForegroundWindow(window) # 获取窗口左上角的坐标pos = win32gui.GetWindowRect(window)print("Game windows at " + str(pos))return (pos[0],pos[1])3) getScreenImage()
# 获取屏幕截图 def getScreenImage():print('Shot screen...')# 获取屏幕截图 # 储存在根目录的 'screen.png'scim = ImageGrab.grab() scim.save('screen.png')# 用opencv读取屏幕截图 # 获取ndarrayreturn cv2.imread("screen.png")4) getAllSquare()
# 从截图中分辨图片 处理成地图 def getAllSquare(screen_image,game_pos):print('Processing pictures...')# 通过游戏窗体定位 加上偏移量获取游戏区域的左上角坐标game_x = game_pos[0] + MARGIN_LEFTgame_y = game_pos[1] + MARGIN_HEIGHT# 从游戏区域左上开始# 把图像按照具体大小切割成相同的小块# 切割标准是按照小块的横纵坐标all_square = []for x in range(0,H_NUM):for y in range(0,V_NUM):# ndarray的切片方法 : [纵坐标起始位置:纵坐标结束为止,横坐标起始位置:横坐标结束位置]square = screen_image[game_y + y * POINT_HEIGHT :game_y + (y+1) * POINT_HEIGHT,game_x + x * POINT_WIDTH:game_x + (x+1) * POINT_WIDTH]all_square.append(square)# 因为有些图片的边缘会造成干扰 所以统一把图片往内缩小一圈# 对所有的方块进行处理 去掉边缘一圈后返回finalresult = []for square in all_square:s = square[SUB_LT_Y:SUB_RB_Y, SUB_LT_X:SUB_RB_X]finalresult.append(s)return finalresult5) getAllSquareTypes()
# 判断列表中是否存在相同图形 # 存在返回进行判断图片所在的id # 否则返回-1 def isImageExist(img,img_list):i = 0for existed_img in img_list:# 两个图片进行比较 返回的是两个图片的标准差b = np.subtract(existed_img,img) # 若标准差全为0 即两张图片没有区别if not np.any(b): return ii = i + 1return -1# 获取所有的方块类型 def getAllSquareTypes(all_square):print("Init pictures types...")types = []# number列表用来记录每个id的出现次数number = []# 当前出现次数最多的方块 # 这里我们默认出现最多的方块应该是空白块nowid = 0;for square in all_square:nid = isImageExist(square,types)# 如果这个图像不存在则插入列表if nid == -1 :types.append(square)number.append(1);else:# 若这个图像存在则给计数器 + 1number[nid] = number[nid] + 1if (number[nid] > number[nowid]):nowid = nid# 更新EMPTY_ID # 即判断在当前这张图中的空白块idglobal EMPTY_IDEMPTY_ID = nowidprint('EMPTY_ID = ' + str(EMPTY_ID))return types6) getAllSquareRecord()
# 将二维图片矩阵转换为二维数字矩阵 # 注意因为在上面对截屏切片时是以列为优先切片的 # 所以生成的record二维矩阵每行存放的其实是游戏屏幕中每列的编号 # 换个说法就是record其实是游戏屏幕中心对称后的列表 def getAllSquareRecord(all_square_list,types):print("Change map...")record = [] line = [] for square in all_square_list: num = 0for type in types: res = cv2.subtract(square,type) if not np.any(res): line.append(num) break num += 1 # 每列的数量为V_NUM # 那么当当前的line列表中存在V_NUM个方块时我们认为本列处理完毕if len(line) == V_NUM: print(line); record.append(line)line = []return record7) autoRemove()
# 判断给出的两个图像能否消除 def canConnect(x1,y1,x2,y2,r):result = r[:]# 如果两个图像中有一个为0 直接返回Falseif result[x1][y1] == EMPTY_ID or result[x2][y2] == EMPTY_ID:return Falseif x1 == x2 and y1 == y2 :return Falseif result[x1][y1] != result[x2][y2]:return False# 判断横向连通if horizontalCheck(x1,y1,x2,y2,result):return True# 判断纵向连通if verticalCheck(x1,y1,x2,y2,result):return True# 判断一个拐点可连通if turnOnceCheck(x1,y1,x2,y2,result):return True# 判断两个拐点可连通if turnTwiceCheck(x1,y1,x2,y2,result):return True# 不可联通返回Falsereturn False# 判断横向联通 def horizontalCheck(x1,y1,x2,y2,result):if x1 == x2 and y1 == y2:return Falseif x1 != x2:return FalsestartY = min(y1, y2)endY = max(y1, y2)# 判断两个方块是否相邻if (endY - startY) == 1:return True# 判断两个方块通路上是否都是0,有一个不是,就说明不能联通,返回falsefor i in range(startY+1,endY):if result[x1][i] != EMPTY_ID:return Falsereturn True# 判断纵向联通 def verticalCheck(x1,y1,x2,y2,result):if x1 == x2 and y1 == y2:return Falseif y1 != y2:return FalsestartX = min(x1, x2)endX = max(x1, x2)# 判断两个方块是否相邻if (endX - startX) == 1:return True# 判断两方块儿通路上是否可连。for i in range(startX+1,endX):if result[i][y1] != EMPTY_ID:return Falsereturn True# 判断一个拐点可联通 def turnOnceCheck(x1,y1,x2,y2,result):if x1 == x2 or y1 == y2:return Falsecx = x1cy = y2dx = x2dy = y1# 拐点为空,从第一个点到拐点并且从拐点到第二个点可通,则整条路可通。if result[cx][cy] == EMPTY_ID:if horizontalCheck(x1, y1, cx, cy, result) and verticalCheck(cx, cy, x2, y2, result):return Trueif result[dx][dy] == EMPTY_ID:if verticalCheck(x1, y1, dx, dy, result) and horizontalCheck(dx, dy, x2, y2, result):return Truereturn False# 判断两个拐点可联通 def turnTwiceCheck(x1,y1,x2,y2,result):if x1 == x2 and y1 == y2:return False# 遍历整个数组找合适的拐点for i in range(0,len(result)):for j in range(0,len(result[1])):# 不为空不能作为拐点if result[i][j] != EMPTY_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):continueif turnOnceCheck(x1, y1, i, j, result) and (horizontalCheck(i, j, x2, y2, result) or verticalCheck(i, j, x2, y2, result)):return Trueif turnOnceCheck(i, j, x2, y2, result) and (horizontalCheck(x1, y1, i, j, result) or verticalCheck(x1, y1, i, j, result)):return Truereturn False# 自动消除 def autoRelease(result,game_x,game_y):# 遍历地图for i in range(0,len(result)):for j in range(0,len(result[0])):# 当前位置非空if result[i][j] != EMPTY_ID:# 再次遍历地图 寻找另一个满足条件的图片for m in range(0,len(result)):for n in range(0,len(result[0])):if result[m][n] != EMPTY_ID:# 若可以执行消除if canConnect(i,j,m,n,result):# 消除的两个位置设置为空result[i][j] = EMPTY_IDresult[m][n] = EMPTY_IDprint('Remove :'+ str(i+1) + ',' + str(j+1) + ' and ' + str(m+1) + ',' + str(n+1))# 计算当前两个位置的图片在游戏中应该存在的位置x1 = game_x + j * POINT_WIDTHy1 = game_y + i * POINT_HEIGHTx2 = game_x + n * POINT_WIDTHy2 = game_y + m * POINT_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(random.uniform(TIME_INTERVAL_MIN,TIME_INTERVAL_MAX))# 模拟鼠标点击第二个图片所在的位置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(random.uniform(TIME_INTERVAL_MIN,TIME_INTERVAL_MAX))#执行消除后返回Truereturn Truereturn Falsedef autoRemove(squares,game_pos):game_x = game_pos[0] + MARGIN_LEFTgame_y = game_pos[1] + MARGIN_HEIGHT# 重复执行消除直到不存在可消除的方块# 这里给出一个消除上限次数,防止代码某些地方出错对空白块进行pending后死循环# while True:for i in range(0,MAX_ROUND):if not autoRelease(squares,game_x,game_y):# 当不再有可消除的方块时结束 , 返回消除数量return i;
5.思考
首先这个代码肯定有许多的不足之处:
1) 窗体大小可修改
当然如果要解决这个问题也不难,定位窗体时会获得pos[4]四个参数,左上角和右下角的坐标,一般来说游戏界面是等比例放大的,所以只要计算具体的游戏区域占整个窗体的百分比即可
如果存在上面的问题,那么每个方块的大小也需要修改
修改方法也不是很麻烦...同样的首先获取游戏区域的像素大小,不管游戏如何放大,游戏区域内能放下的方格矩阵长宽是不会变的,比如QQ游戏就是11 * 19 , 所以每个方块的大小就用游戏区域的像素大小除一下就行了,边缘处理也可以简单的使用60%,至于为什么是60%....自己想吧
2) 若空白块变成渐变色的问题 / 方块不完全相同 / 同种周围存在某些小特效导致pending结果为不同
其实也不难,这里判断两个方块是否相同的方式是通过两个图像的标准差为0,那么如果存在渐变色,那么我们只要计算出一个误差上限即可,标准差在误差内,我们就认为它们是同一种方块,这个问题就跟上面的60%一样,因为我们这里一定要标准差为0才认为两个方块是一样的,所以pending方块不能有一点误差,所以需要减掉的边缘需要大一些,那么如果不强制要求100%相似度判断的话,那自然也不需要减掉那么多边缘,因为边缘减掉的越多,也会造成另一种误差
6.总结
显然这里给出的代码不包含config参数.....毕竟连config都一起给出来了那不是直接变成发布外挂了吗....
至于我的参数是怎么得到的....我打开画图用手描出来的参数我会乱说?
当然不管怎么说这里还是会给出github:https://github.com/jnxxhzz/auto_lianliankan
打包好的.exe可执行文件 : https://download.csdn.net/download/jnxxhzz/10571614
当然再次强调: Python大法好!!! 呸呸呸说错了
---- 此项目开源仅仅是为了交流学习,大肆流传可能会对其他公司的商业产品造成损失,所以请自觉遵守法律以及道德规范,切勿将其挪作他用,更不可用其获取商业利益!
总结
以上是生活随笔为你收集整理的python — Auto_QQ连连看的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 手机中CAD图纸发送到电脑上后打不开了怎
- 下一篇: python识别cad图纸_手把手教你广