1.简介:本文将以4399小游戏《 宠物连连看经典版2 》为测试用例,通过识别小图标,模拟鼠标点击,快速完成配对。对于有兴趣学习游戏脚本的同学很有帮助。运行环境:Win10/Python3.5。主要模块:win32gui(识别窗口、置顶窗口等)、PIL(截屏)、numpy(创建矩阵)、operator(比较值)、pymouse(模拟鼠标点击)。注意事项:1、如果pymouse安装不成功或者运行报错,可以考虑先通过whl安装pyHook,再通过pip安装pyuserinput。2.如果报错[ImportError:Nomodulenamed'windows'],可以修改__init__.pywindows=>pymouse.windows对应的行为。2、发展前景(闲聊,可以略过)游戏辅助脚本在当前环境下比较流行。对于经常玩游戏的人来说,适当的游戏辅助还是很有帮助的,让电脑做一些繁琐的工作繁琐的操作。当然还有其他更高级的操作,这里不再赘述。至于游戏辅助脚本,基本上可以想到的有两种:一种是读取内存中游戏的数据,理想情况下可以改变游戏的一些基本属性。原理和很多外挂或者破解的游戏类似;另一个是模拟用户userBehaviors,模拟鼠标点击,键盘操作等。当然,由于本人没有涉足过游戏辅助脚本领域,出于个人兴趣,我会学习研究。本文的例子是第二种,主要是模拟用户行为,让程序代替用户操作。3、开发过程浏览器打开游戏窗口(单窗口),游戏界面如下图所示。游戏主界面截图需要两个坐标(左上角坐标和右下角坐标)来确定。原点一般是屏幕的左上角。确定坐标点值的同学可以全屏截图,使用图片编辑软件查看坐标值。获取窗口句柄,这里是浏览器标题栏的标题(右键-查看源代码-标题,添加软件名称)例如:《宠物连连看经典2,宠物连连看经典2小游戏,4399小游戏www.4399.com-谷歌浏览器”。获取窗口句柄,您就可以开始了。总体开发思路:截取游戏主图--->分成小图--->比较每张小图,比较图片的熟悉程度,将数字存入矩阵--->执行在矩阵上连续计算-??-->模拟点击。3.1.获取窗口句柄并将窗口放在最上面。Python可以使用win32gui模块调用WindowsAPI实现对窗口的操作。使用FindWindow()方法获取窗口句柄(handle)。需要传入两个参数,第一个是父窗口。handle(这里可以填0),第二个参数是窗口的名称(labeltitle-GoogleChrome)。获取句柄后,通过SetForegroundWindows()将窗口设置到最前面。这里可以传入游戏窗口的报告。代码如下:3.2.截取游戏界面,拆分图标,对比图片。验证程序需要一些时间。如果抓拍的图片不好的话,会影响后续的操作,所以更重要的是确认游戏的左上角和右下角两个坐标值,还有宽度和每个小图标的高度。如下图,先截取整个游戏界面图,然后划分小图标,然后比较每个图标,然后将数字存入矩阵而不是图标(这里的数字矩阵与游戏图不一致,原理是一样的)。根据初始化设置的左上角和右下角两个坐标,使用ImageGrab.grab()方法进行截图,传入一个元组,然后将大图分割成小图标存储在images_list数组中。将上面代码切割出来的小图标转换成数字矩阵。如果图标已存储在image_type_list中,则返回索引。如果它不存在,它将被添加。那么当前长度就是新增图标的个数。代码如下:上面的getIndex是比较图片,判断图标是否出现过(image_type_list中是否存在,没有出现则添加)。这里使用汉明距离来判断两张图片相识程度,阈值设置为10,当小于阈值时,则认为是同一张图片,具体代码如下:4.程序核心-图标连接算法(寻路)这里只对算法代码进行简单分析,对程序有不懂的可以留言,后续可以图文分析。通过以上开发过程,基本得到如下矩阵。只需比较两个相同编号的值,搜索可连接的路径,如果找到,则进行模拟点击操作。这里简单介绍一下游戏规则:8行12列的游戏图标区域,外围的0其实就是找路的时候可以通过。例如,坐标(1,1)可以与(1,10)、(7,1)和(7,2)连接起来。算法思想:寻找路径首先是找到一组横纵坐标可以直接相连的坐标,比如坐标集合p1(1,1)有[(0,1),(1,0)],另外一个坐标p2的可连通集(1,10)为[(0,10)],然后比较p1和p2的可连通坐标集,如果也有可连通坐标在集合中,表示p1和p2是连通的,显然,(0,1)和(0,10)在同一直线上并且可以连通,也就是说p1和p2之间存在连通路径。代码如下:简单分析一下代码实现过程:在isReachable()中传入两个需要比较的坐标值,然后获取横向和纵向可以连接的坐标集(isRowConnect(),isColConnect分别对两个点进行()),最后遍历集合比较是否有可连通的,如果有则说明传入的两个坐标是可以连通的。5.开发总结学习这样的游戏辅助脚本,对于个人培养编程兴趣也是很有帮助的。这是下班后消遣的好方法。以后我会在这些方向上多学习多学习。本案例只是截图、对比图片和模拟鼠标点击。我认为它可以更强大,而且不仅仅局限于游戏领域。相信大家应该都见过自动发送QQ消息的软件吧。我认为这是可以做到的。可以实现的模拟操作有很多:鼠标滚轮、左右键、键盘输入等。6.附件-源码注:源码仅供学习1#-*-coding:utf-8-*-23importwin32gui4importtime5fromPILimportImageGrab,Image6importnumpyasnp7importoperator8frompymouseimportPyMouseimportPyMouse91011classGameAssist:1213def__init__(self,wdname):14"""initialization16#self."1getwindowhandlehwnd=win32gui.FindWindow(0,wdname)18ifnotself.hwnd:19print("找不到窗口,请确认窗口句柄名称:【%s】"%wdname)20exit()2122#显示窗口23win32gui.SetForegroundWindow(self.hwnd)2425#小图标编号矩阵26self.im2num_arr=[]2728#主截图左上角和右下角坐标29self.scree_left_and_right_point=(299,251,768,564)30#小图标宽高31self.im_width=393233#PyMouse对象,鼠标点击34self.mouse=PyMouse()3536defscreenshot(self):37"""Screenshot"""3839#1,使用grab功能进行截图,参数为左上角和右下角左标记40#image=ImageGrab.grab((417,257,885,569))41image=ImageGrab.grab(self.scree_left_and_right_point)4243#2,拆分小图44#exit()45image_list={}46offset=self.im_width#394748#8行和12列49forxinrange(8):50image_list[x]={}51foryinrange(12):52#print("show",x,y)53#exit()54top=x*offset55left=y*offset56right=(y+1)*offset57bottom=(x+1)*offset5859#使用裁剪功能裁剪成小图标,参数为图标的左上角和右下角60im=image.crop((left,top,right,bottom))61#将切好的图标保存到相应位置62image_list[x][y]=im6364returnimage_list6566defimage2num(self,image_list):67"""将图标矩阵转换成数字matrix"""6869#1、创建一个全零矩阵和一个空的一维数组70arr=np.zeros((10,14),dtype=np.int32)#用数字替换图片71image_type_list=[]7273#2.识别不同的图片,将矩阵转化为数字矩阵74foriinrange(len(image_list)):75forjinrange(len(image_list[0])):76im=image_list[i][j]7778#验证当前是否icon已存储79index=self.getIndex(im,image_type_list)8081#不存在image_type_list82ifindex<0:83image_type_list.append(im)84arr[i+1][j+1]=len(image_type_list)85else:86arr[i+1][j+1]=index+18788print("图标个数:",len(image_type_list))8990self.im2num_arr=arr91returnarr9293#检查数组中是否有图标,如果有则返回下面的索引表94defgetIndex(self,im,im_list):95foriinrange(len(im_list)):96ifself。isMatch(im,im_list[i]):97returni9899return-1100101#海明距离判断两个图标是否相同102defisMatch(self,im1,im2):103104#缩小图标变成灰度105image1=im1.resize((20,20),Image.ANTIALIAS).convert("L")106image2=im2.resize((20,20),Image.ANTIALIAS).convert("L")107108#转换灰度图标为01字符串,二进制数据109pixels1=list(image1.getdata())110pixels2=list(image2.getdata())111112avg1=sum(pixels1)/len(pixels1)113avg2=sum(pixels2)/len(pixels2)114hash1="".join(map(lambdap:"1"ifp>avg1else"0",pixels1))115hash2="".join(map(lambdap:"1"ifp>avg2else"0",pixels2))116117#statistics两个01串中不同数的个数118match=sum(map(operator.ne,hash1,hash2))119120#阈值设置为10121returnmatch<10122123#判断矩阵是否全0124defisAllZero(self,arr):125foriinrange(1,9):126forjinrange(1,13):127ifarr[i][j]!=0:128returnFalse129returnTrue130131#是否在同一行或同一列且可以连接132defisReachable(self,x1,y1,x2,y2):133#1。首先判断值是否相同134ifself.im2num_arr[x1][y1]!=self.im2num_arr[x2][y2]:135returnFalse136137#1。分别获取同一行或同一列可连接的两个坐标数组138list1=self.getDirectConnectList(x1,y1)139list2=self.getDirectConnectList(x2,y2)140#print(x1,y1,list1)141#打印(x2,y2,list2)142143#exit()144145#2。比较坐标数组是否可以连通146forx1,y1inlist1:147forx2,y2inlist2:148ifself.isDirectConnect(x1,y1,x2,y2):149returnTrue150returnFalse151152#得到同行或同列的连通坐标数组153defgetDirectConnectList(self,x,y):154155plist=[]156forpxinrange(0,10):157forpyinrange(0,14):158#获取同行或同列与0的坐标159ifself.im2num_arr[px][py]==0andself.isDirectConnect(x,y,px,py):160plist.append([px,py])161162returnplist163164#是否在同一行或同一列并且可以连接到165defisDirectConnect(self,x1,y1,x2,y2):166#1,位置完全一样167ifx1==x2andy1==y2:168returnFalse169170#2,行列不同171ifx1!=x2andy1!=y2:172returnFalse173174#3,peer175ifx1==x2andself.isRowConnect(x1,y1,y2):176returnTrue177178#4,同列179ify1==y2andself.isColConnect(y1,x1,x2):180returnTrue181182returnFalse183184#判断peer是否可以连接185defisRowconnect(self,x,y1,y2):186minminY=min(y1,y2)187maxmaxY=max(y1,y2)188189#相邻直连190ifmaxY-minY==1:191returnTrue192193#判断两个坐标是否都是0194fory0inrange(minY+1,maxY):195ifself.im2num_arr[x][y0]!=0:196returnFalse197returnTrue198199#判断同列是否可以连接200defisColconnect(self,y,x1,x2):201minminX=min(x1,x2)202maxmaxX=max(x1,x2)203204#相邻可直连205ifmaxX-minX==1:206returnTrue207208#判断两个坐标是否都是0209forx0inrange(minX+1,maxX):210ifself.im2num_arr[x0][y]!=0:211returnFalse212returnTrue213214#点击事件,设置数组为0215defclickAndSetZero(self,x1,y1,x2,y2):216#print("click",x1,y1,x2,y2)217218#(299,251,768,564)219#原理:左上角图标中点+偏移220p1_x=int(self.scree_left_and_right_point[0]+(y1-1)*self.im_width+(self.im_width/2))221p1_y=int(self.scree_left_and_right_point[1]+(x1-1)*self.im_width+(self.im_width/2))222223p2_x=int(self.scree_left_and_right_point[0]+(y2-1)*self.im_width+(self.im_width/2))224p2_y=int(self.scree_left_and_right_point[1]+(x2-1)*self.im_width+(self.im_width/2))225226time.sleep(0.2)227self。mouse.click(p1_x,p1_y)228time.sleep(0.2)229self.mouse.click(p2_x,p2_y)230231#设置矩阵值为0232self.im2num_arr[x1][y1]=0233self.im2num_arr[x2][y2]=0234235print("消除:(%d,%d)(%d,%d)"%(x1,y1,x2,y2))236#exit()237238#程序入口,控制中心239defs馅饼(自己):240241#1。先截取游戏区的大图,再截取每张小图242image_list=self.screenshot()243244#2。识别小图标,收集编号245self.image2num(image_list)246247print(self.im2num_arr)248249#3。遍历找到可以连接的坐标250whilenotself.isAllZero(self.im2num_arr):251forx1inrange(1,9):252fory1inrange(1,13):253ifself.im2num_arr[x1][y1]==0:254continue255256forx2inrange(1,9):257fory2inrange(1,13):258#skip是0还是一样259ifself.im2num_arr[x2][y2]==0or(x1==x2andy1==y2):260continue261ifself.isReachable(x1,y1,x2,y2):262self.clickAndSetZero(x1,y1,x2,y2)263264265if__name__=="__main__":266#wdname是连连看窗口的名字,必须写全267wdname=u'pet连连看经典版2,pet连连看经典版2小游戏,4399小游戏www.4399.com-GoogleChrome'268269demo=GameAssist(wdname)270demo.start()GameAssist.py
