目录

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.思路

  1. 通过win32定位游戏窗体,并获取截图
  2. 从截图中定位需要pending的游戏区域
  3. 将图片切片成二维单个方块的Img地图,并将方块分类编号
  4. 将Img地图转换成二维编号矩阵
  5. 处理是否可以消除并模拟点击

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 finalresult

5)  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 types

6)  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 record

7)  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连连看相关推荐

  1. 【python】python制作 连连看 游戏脚本(一)

    [python]python制作 连连看 游戏脚本(一)_sunriver2000的博客-CSDN博客 [python]python制作 连连看 游戏脚本(二)_sunriver2000的博客-CSD ...

  2. python实现连连看辅助--图像识别延伸(百度AI)

    python实现连连看辅助–图像识别延伸(百度AI) 百度AI平台提供图片相似检索API接口,并有详细的API文档说明,可以更好的实现图片识别. from aip import AipImageSea ...

  3. 【python】python制作 连连看 游戏脚本(二)

    [python]python制作 连连看 游戏脚本(一)_sunriver2000的博客-CSDN博客 [python]python制作 连连看 游戏脚本(二)_sunriver2000的博客-CSD ...

  4. 【python】python制作 连连看 游戏脚本(三)

    [python]python制作 连连看 游戏脚本(一)_sunriver2000的博客-CSDN博客 [python]python制作 连连看 游戏脚本(二)_sunriver2000的博客-CSD ...

  5. c++连连看游戏_用Python玩连连看是什么效果?

    1.前言Python实现的qq连连看辅助, 仅用于学习, 请在练习模式下使用, 请不要拿去伤害玩家们...2.基本环境配置版本:Python3.6系统:Windows3.相关模块: 1import P ...

  6. 用Python玩连连看是什么效果?

    1.前言 Python实现的qq连连看辅助, 仅用于学习, 请在练习模式下使用, 请不要拿去伤害玩家们... 很多人学习python,不知道从何学起. 很多人学习python,掌握了基本语法过后,不知 ...

  7. Python制作连连看脚本工具,全程自动,不需要你动,简直太爽了

    最近女朋友在玩连连看,玩了一个星期了还没通关,真的是菜. 我实在是看不过去了,直接用python写了个脚本代码,一分钟一把游戏. 快是快,就是联网玩容易被骂,嘿嘿~ 直接上代码 模块导入 import ...

  8. 自动识图进行点击,用Python玩连连看是什么效果?

    1.前言 Python实现的qq连连看辅助, 仅用于学习, 请在练习模式下使用, 请不要拿去伤害玩家们- 2.基本环境配置 版本:Python3.6 系统:Windows 3.相关模块: 私信小编00 ...

  9. 用python实现连连看

    编程一直是在课余时间,放假时间自学,到现在为止也有半年了 这是我自己用python实现的连连看,也是第一个完成的游戏..虽然极其简陋. 思路呢,一开始是想要从一个点出发开始寻路,遇到数字就换一条路,直 ...

  10. 用Python 玩连连看 是什么效果?别霍霍别人了

    作者:Laziji 源自:https://laboo.top/2018/11/07/lianliankan/ 1.前言 Python 实现的qq连连看辅助, 仅用于学习, 请在练习模式下使用, 请不要 ...

最新文章

  1. 17、Kubernetes容器交付介绍
  2. linux命令用tar czvf .tar.gz好用的
  3. Hark的数据结构与算法练习之基数排序
  4. python的pandas包使用教程_「Python」pandas入门教程
  5. NGUI Example5 演示示例评论– lights and Refraction
  6. 狼道:强者的成人礼(第2版)
  7. HyperLedger Composer 测试 Fabric网络是否连通
  8. 第四次黄鹤楼之老照片
  9. C#实现简单小说阅读器
  10. 值得看三次的高干文_5本精彩的高干文推荐,本本是经典,值得刷三遍以上!...
  11. 艾艾贴Mysql主从同步
  12. 一行代码显示WiFi密码
  13. 厨房里的ERP(MRP)
  14. 共阳极数码管显示0~9_《显示器件应用分析精粹》之(3)数码管静态显示
  15. ubantu 黑屏_普罗菲斯触摸屏黑屏问题维修经验丰富
  16. 爸爸妈妈儿子女儿吃水果问题以及五个哲学家吃饭问题
  17. imagenet 1000分类
  18. R语言train函数调参(caret包)
  19. 《庄子》中说到,“一尺之棰,日取其半,万世不竭”。第一天有一根长度为 a(a\le 10^9)a(a≤10 9 ) 的木棍,从第二天开始,每天都要将这根木棍锯掉一半(每次除2向下取整)第几天变为1
  20. 从入门到放弃C语言-入门篇(2)

热门文章

  1. python dll注入 网络_dll注入
  2. 【转】斐讯K2刷华硕固件教程
  3. matlab仿真介绍,谈一谈|Matlab仿真项目简介
  4. 数字图像处理吴娱课后答案_(完整版)数字图像处理每章课后题参考答案
  5. SIP呼叫流程——现代交换原理实验四
  6. F2FS文件系统论文解读
  7. IT桔子沙龙之本地生活服务O2O探路者笔记整理
  8. metaRTC5.0实现webrtc的TURN支持
  9. AVOD阅读笔记(一):摘要+特征提取----Aggregate View Obeject Detection network
  10. 十天征服单片机百度云_51单片机 郭天祥十天学会单片机教学视频