博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[Python] 用python做一个游戏辅助脚本,完整思路
阅读量:4677 次
发布时间:2019-06-09

本文共 16750 字,大约阅读时间需要 55 分钟。

一、说明

  简述:本文将以4399小游戏《宠物连连看经典版2》作为测试案例,通过识别小图标,模拟鼠标点击,快速完成配对。对于有兴趣学习游戏脚本的同学有一定的帮助。

  运行环境:Win10/Python3.5。

  主要模块:win32gui(识别窗口、窗口置顶等操作)、PIL(屏幕截图)、numpy(创建矩阵)、operator(比较值)、pymouse(模拟鼠标点击)。

  注意点:

      1、如果安装pymouse不成功或者运行报错,可以考虑先通过whl 安装pyHook、然后再通过pip安装pyuserinput。

      2、如果报错 [ImportError: No module named 'windows' ],可以修改__init__.py相应的行 为 windows => pymouse.windows。

 

  本文主要参考:https://baijiahao.baidu.com/s?id=1618385402903335091&wfr=spider&for=pc。

    

 

二、开发前景(随便唠叨一哈,可跳过)

  游戏辅助脚本在当前环境也算是比较流行了,对于经常玩游戏人来说,适当的游戏辅助还是很有帮助的,让计算机做一些繁琐乏味的操作。当然还有更加高大上的其他操作,这里就不赘述了。对于游戏辅助脚本,能想到基本有以下两种:一是读取游戏在内存中的数据,理想的话可以做到更改游戏一些基本属性,原理和很多的外挂或破解游戏类似;二是模拟用户用户行为,模拟鼠标点击、键盘操作等。当然,由于本人从未涉及游戏辅助脚本这一领域,出于个人兴趣,学习研究一下,本文例子则是第二种,主要还是模拟用户行为,让程序代替用户操作。

 

三、开发流程

  先看看程序运行图吧:

  

 

  浏览器打开游戏窗口(单个一个窗口),游戏界面如下图所示,游戏主要界面截图需要两个坐标(左上角坐标和右下角坐标)来确定,原点一般是屏幕左上角,不确定坐标点值的同学,可以全屏截图,用编辑图片软件查看坐标值。获取窗口句柄,这里就是浏览器标题栏的标题了(右键-查看源代码-title,加上软件名)比如:“宠物连连看经典2,宠物连连看经典版2小游戏,4399小游戏 www.4399.com - Google Chrome“。获取窗口句柄就可以开始了。

  总体开发思路:截取游戏主图 ---> 分割成小图 ---> 对比每个小图,对比图片相识度,编号存入矩阵 ---> 对矩阵进行可连计算 ---> 模拟点击。

  

 

  3.1、获取窗口句柄,把窗口置顶

    python可以使用win32gui模块调用Windows API实现对窗口的操作,使用FindWindow()方法可以获取窗口的句柄(handle),需要传入两个参数,第一个为父窗口句柄(这里填0即可),第二个参数是窗口的名称(标签title - Google Chrome)。获取句柄之后然后通过SetForegroundWindows() 设置窗口在前面,这里传入游戏窗口的举报即可,代码如下:

1 import win32gui 2  3 class GameAssist: 4  5     def __init__(self, wdname): 6         """初始化""" 7  8         # 取得窗口句柄 9         self.hwnd = win32gui.FindWindow(0, wdname)10         if not self.hwnd:11             print("窗口找不到,请确认窗口句柄名称:【%s】" % wdname )12             exit()13         # 窗口显示最前面14         win32gui.SetForegroundWindow(self.hwnd)15 16 17 if __name__ == "__main__":18     # wdname 为连连看窗口的名称,必须写完整19     wdname = u'宠物连连看经典版2,宠物连连看经典版2小游戏,4399小游戏 www.4399.com - Google Chrome'20 21     demo = GameAssist(wdname)22     demo.start()

     

  3.2、截取游戏界面,分割图标,图片比较

    这里需要花费一些时间来校验程序,如果截取的图片不好,则会影响后续操作,所以比较主要的是确认游戏左上角和右下角这两个坐标值,以及每个小图标的宽高。如下图所示,先截取整个游戏界面图,然后分割小图标,接着对每个图标进行比较,然后以编号代替图标存入矩阵(这里的编号矩阵和游戏图不一致,原理一样)。

    

    

    根据初始化设定的左上角和右下角两个坐标,使用ImageGrab.grab()方法进行截图,传入一个元组即可,然后对这个大图进行分割,切割成一个个小图标存入到images_list数组中。   

1     def screenshot(self): 2         """屏幕截图""" 3  4         # 1、用grab函数截图,参数为左上角和右下角左标 5         # image = ImageGrab.grab((417, 257, 885, 569)) 6         image = ImageGrab.grab(self.scree_left_and_right_point) 7  8         # 2、分切小图 9         # exit()10         image_list = {}11         offset = self.im_width  # 3912 13         # 8行12列14         for x in range(8):15             image_list[x] = {}16             for y in range(12):17                 # print("show",x, y)18                 # exit()19                 top = x * offset20                 left = y * offset21                 right = (y + 1) * offset22                 bottom = (x + 1) * offset23 24                 # 用crop函数切割成小图标,参数为图标的左上角和右下角左边25                 im = image.crop((left, top, right, bottom))26                 # 将切割好的图标存入对应的位置27                 image_list[x][y] = im28 29         return image_list

    

    通过上面代码切割的小图标,转成数字矩阵,如果图标已经存入image_type_list则返回这个索引,如果不存在,则在追加进去,然后当前长度就是这个新加入图标的编号,代码如下所示:

1     def image2num(self, image_list): 2         """将图标矩阵转换成数字矩阵""" 3  4         # 1、创建全零矩阵和空的一维数组 5         arr = np.zeros((10, 14), dtype=np.int32)    # 以数字代替图片 6         image_type_list = [] 7  8         # 2、识别出不同的图片,将图片矩阵转换成数字矩阵 9         for i in range(len(image_list)):10             for j in range(len(image_list[0])):11                 im = image_list[i][j]12                 13                 # 验证当前图标是否已存入14                 index = self.getIndex(im, image_type_list)15 16                 # 不存在image_type_list17                 if index < 0:18                     image_type_list.append(im)19                     arr[i + 1][j + 1] = len(image_type_list)20                 else:21                     arr[i + 1][j + 1] = index + 122 23         print("图标数:", len(image_type_list))24 25         self.im2num_arr = arr26         return arr

 

    

    上面的getIndex就是对比图片,判断图标是否出现过(是否已存在image_type_list中,没出现则追加进去),这里使用汉明距离判断两个图片的相识度,设置阀值10,当小于阀值则认为是同一个图片,具体代码如下:  

1     # 检查数组中是否有图标,如果有则返回索引下表 2     def getIndex(self,im, im_list): 3         for i in range(len(im_list)): 4             if self.isMatch(im, im_list[i]): 5                 return i 6  7         return -1 8  9     # 汉明距离判断两个图标是否一样10     def isMatch(self, im1, im2):11 12         # 缩小图标,转成灰度13         image1 = im1.resize((20, 20), Image.ANTIALIAS).convert("L")14         image2 = im2.resize((20, 20), Image.ANTIALIAS).convert("L")15 16         # 将灰度图标转成01串,即系二进制数据17         pixels1 = list(image1.getdata())18         pixels2 = list(image2.getdata())19 20         avg1 = sum(pixels1) / len(pixels1)21         avg2 = sum(pixels2) / len(pixels2)22         hash1 = "".join(map(lambda p: "1" if p > avg1 else "0", pixels1))23         hash2 = "".join(map(lambda p: "1" if p > avg2 else "0", pixels2))24 25         # 统计两个01串不同数字的个数26         match = sum(map(operator.ne, hash1, hash2))27 28         # 阀值设为1029         return match < 10

     

 

四、程序核心-图标连接算法(路径寻找)

  这里仅对算法代码进行简单分析,如果对程序不好理解,可以留言,后续可以图文分析。

  通过上面的开发流程,基本获取如下这样的矩阵,只要比较两个编号相同的值进行可连路径寻找,如果找到即进行模拟点击操作。这里简单介绍下游戏规则:8行乘12列游戏图标区域,外围的0其实表示寻找路径的时候可以通过,例如坐标(1, 1)可以与(1,10)进行连接、(7, 1)和(7,2)进行连接。

1 arr = [ 2     [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], 3     [ 0,  1,  2,  3,  4,  5,  4,  6,  7,  2,  1,  1,  8,  0], 4     [ 0,  9,  3,  3, 10,  4,  7, 11,  7,  2,  3,  1,  6,  0], 5     [ 0,  6,  7,  4, 11,  5,  8,  1,  6,  5,  4,  2,  8,  0], 6     [ 0,  6,  2,  9,  6,  8,  9,  7, 12, 11,  3, 11, 11,  0], 7     [ 0,  5,  9,  8,  9,  2,  6, 11, 11,  3,  9,  2, 12,  0], 8     [ 0, 12,  5, 12,  5, 10,  5,  6,  5,  7, 12,  4,  3,  0], 9     [ 0,  1,  8, 10, 12,  9, 10,  4,  3,  7,  2,  1, 10,  0],10     [ 0,  1,  4, 10,  8, 12, 10, 10,  9, 12,  8,  7, 11,  0],11     [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0]12 ]

  

  算法的思路:路径的寻找首先是寻找一个坐标的横向竖向可以直接相连的坐标集合,比如坐标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())可以连接的坐标集合,最后再对集合进行遍历比较是否存在可连的,如果存在则表示传入的两个坐标是可以连接的。

1     # 是否为同行或同列且可连 2     def isReachable(self, x1, y1, x2, y2): 3         # 1、先判断值是否相同 4         if self.im2num_arr[x1][y1] != self.im2num_arr[x2][y2]: 5             return False 6  7         # 1、分别获取两个坐标同行或同列可连的坐标数组 8         list1 = self.getDirectConnectList(x1, y1) 9         list2 = self.getDirectConnectList(x2, y2)10         # print(x1, y1, list1)11         # print(x2, y2, list2)12 13         # exit()14 15         # 2、比较坐标数组中是否可连16         for x1, y1 in list1:17             for x2, y2 in list2:18                 if self.isDirectConnect(x1, y1, x2, y2):19                     return True20         return False21 22     # 获取同行或同列可连的坐标数组23     def getDirectConnectList(self, x, y):24 25         plist = []26         for px in range(0, 10):27             for py in range(0, 14):28                 # 获取同行或同列且为0的坐标29                 if self.im2num_arr[px][py] == 0 and self.isDirectConnect(x, y, px, py):30                     plist.append([px, py])31 32         return plist33 34     # 是否为同行或同列且可连35     def isDirectConnect(self, x1, y1, x2, y2):36         # 1、位置完全相同37         if x1 == x2 and y1 == y2:38             return False39 40         # 2、行列都不同的41         if x1 != x2 and y1 != y2:42             return False43 44         # 3、同行45         if x1 == x2 and self.isRowConnect(x1, y1, y2):46             return True47 48         # 4、同列49         if y1 == y2 and self.isColConnect(y1, x1, x2):50             return True51 52         return False53 54     # 判断同行是否可连55     def isRowConnect(self, x, y1, y2):56         minY = min(y1, y2)57         maxY = max(y1, y2)58 59         # 相邻直接可连60         if maxY - minY == 1:61             return True62 63         # 判断两个坐标之间是否全为064         for y0 in range(minY + 1, maxY):65             if self.im2num_arr[x][y0] != 0:66                 return False67         return True68 69     # 判断同列是否可连70     def isColConnect(self, y, x1, x2):71         minX = min(x1, x2)72         maxX = max(x1, x2)73 74         # 相邻直接可连75         if maxX - minX == 1:76             return True77 78         # 判断两个坐标之间是否全为079         for x0 in range(minX + 1, maxX):80             if self.im2num_arr[x0][y] != 0:81                 return False82         return True

  

 

五、开发总结

  学习这样一个游戏辅助脚本,对于个人培养编程兴趣也是有很多帮助的,在工作之余不失为一个好的消遣方式,以后会多向这些方向研究学习。本案例仅仅是截图、比较图片和模拟鼠标点击,我觉得还可以更加强大,而且还不局限于游戏这样一个领域,相信大家应该见过自动发QQ消息的软件吧,我觉得这完全可以做。还有很多模拟操作可以实现:鼠标滚轮,左右键、键盘输入等。

 

六、附件-源码

  注意:源码仅供学习,转发注明出处,谢谢!

1 # -*- coding:utf-8 -*-  2   3 import win32gui  4 import time  5 from PIL import ImageGrab, Image  6 import numpy as np  7 import operator  8 from pymouse import PyMouse  9  10  11 class GameAssist: 12  13     def __init__(self, wdname): 14         """初始化""" 15  16         # 取得窗口句柄 17         self.hwnd = win32gui.FindWindow(0, wdname) 18         if not self.hwnd: 19             print("窗口找不到,请确认窗口句柄名称:【%s】" % wdname ) 20             exit() 21  22         # 窗口显示最前面 23         win32gui.SetForegroundWindow(self.hwnd) 24  25         # 小图标编号矩阵 26         self.im2num_arr = [] 27  28         # 主截图的左上角坐标和右下角坐标 29         self.scree_left_and_right_point = (299, 251, 768, 564) 30         # 小图标宽高 31         self.im_width = 39 32  33         # PyMouse对象,鼠标点击 34         self.mouse = PyMouse() 35  36     def screenshot(self): 37         """屏幕截图""" 38  39         # 1、用grab函数截图,参数为左上角和右下角左标 40         # image = ImageGrab.grab((417, 257, 885, 569)) 41         image = ImageGrab.grab(self.scree_left_and_right_point) 42  43         # 2、分切小图 44         # exit() 45         image_list = {} 46         offset = self.im_width  # 39 47  48         # 8行12列 49         for x in range(8): 50             image_list[x] = {} 51             for y in range(12): 52                 # print("show",x, y) 53                 # exit() 54                 top = x * offset 55                 left = y * offset 56                 right = (y + 1) * offset 57                 bottom = (x + 1) * offset 58  59                 # 用crop函数切割成小图标,参数为图标的左上角和右下角左边 60                 im = image.crop((left, top, right, bottom)) 61                 # 将切割好的图标存入对应的位置 62                 image_list[x][y] = im 63  64         return image_list 65  66     def image2num(self, image_list): 67         """将图标矩阵转换成数字矩阵""" 68  69         # 1、创建全零矩阵和空的一维数组 70         arr = np.zeros((10, 14), dtype=np.int32)    # 以数字代替图片 71         image_type_list = [] 72  73         # 2、识别出不同的图片,将图片矩阵转换成数字矩阵 74         for i in range(len(image_list)): 75             for j in range(len(image_list[0])): 76                 im = image_list[i][j] 77  78                 # 验证当前图标是否已存入 79                 index = self.getIndex(im, image_type_list) 80  81                 # 不存在image_type_list 82                 if index < 0: 83                     image_type_list.append(im) 84                     arr[i + 1][j + 1] = len(image_type_list) 85                 else: 86                     arr[i + 1][j + 1] = index + 1 87  88         print("图标数:", len(image_type_list)) 89  90         self.im2num_arr = arr 91         return arr 92  93     # 检查数组中是否有图标,如果有则返回索引下表 94     def getIndex(self,im, im_list): 95         for i in range(len(im_list)): 96             if self.isMatch(im, im_list[i]): 97                 return i 98  99         return -1100 101     # 汉明距离判断两个图标是否一样102     def isMatch(self, im1, im2):103 104         # 缩小图标,转成灰度105         image1 = im1.resize((20, 20), Image.ANTIALIAS).convert("L")106         image2 = im2.resize((20, 20), Image.ANTIALIAS).convert("L")107 108         # 将灰度图标转成01串,即系二进制数据109         pixels1 = list(image1.getdata())110         pixels2 = list(image2.getdata())111 112         avg1 = sum(pixels1) / len(pixels1)113         avg2 = sum(pixels2) / len(pixels2)114         hash1 = "".join(map(lambda p: "1" if p > avg1 else "0", pixels1))115         hash2 = "".join(map(lambda p: "1" if p > avg2 else "0", pixels2))116 117         # 统计两个01串不同数字的个数118         match = sum(map(operator.ne, hash1, hash2))119 120         # 阀值设为10121         return match < 10122 123     # 判断矩阵是否全为0124     def isAllZero(self, arr):125         for i in range(1, 9):126             for j in range(1, 13):127                 if arr[i][j] != 0:128                     return False129         return True130 131     # 是否为同行或同列且可连132     def isReachable(self, x1, y1, x2, y2):133         # 1、先判断值是否相同134         if self.im2num_arr[x1][y1] != self.im2num_arr[x2][y2]:135             return False136 137         # 1、分别获取两个坐标同行或同列可连的坐标数组138         list1 = self.getDirectConnectList(x1, y1)139         list2 = self.getDirectConnectList(x2, y2)140         # print(x1, y1, list1)141         # print(x2, y2, list2)142 143         # exit()144 145         # 2、比较坐标数组中是否可连146         for x1, y1 in list1:147             for x2, y2 in list2:148                 if self.isDirectConnect(x1, y1, x2, y2):149                     return True150         return False151 152     # 获取同行或同列可连的坐标数组153     def getDirectConnectList(self, x, y):154 155         plist = []156         for px in range(0, 10):157             for py in range(0, 14):158                 # 获取同行或同列且为0的坐标159                 if self.im2num_arr[px][py] == 0 and self.isDirectConnect(x, y, px, py):160                     plist.append([px, py])161 162         return plist163 164     # 是否为同行或同列且可连165     def isDirectConnect(self, x1, y1, x2, y2):166         # 1、位置完全相同167         if x1 == x2 and y1 == y2:168             return False169 170         # 2、行列都不同的171         if x1 != x2 and y1 != y2:172             return False173 174         # 3、同行175         if x1 == x2 and self.isRowConnect(x1, y1, y2):176             return True177 178         # 4、同列179         if y1 == y2 and self.isColConnect(y1, x1, x2):180             return True181 182         return False183 184     # 判断同行是否可连185     def isRowConnect(self, x, y1, y2):186         minY = min(y1, y2)187         maxY = max(y1, y2)188 189         # 相邻直接可连190         if maxY - minY == 1:191             return True192 193         # 判断两个坐标之间是否全为0194         for y0 in range(minY + 1, maxY):195             if self.im2num_arr[x][y0] != 0:196                 return False197         return True198 199     # 判断同列是否可连200     def isColConnect(self, y, x1, x2):201         minX = min(x1, x2)202         maxX = max(x1, x2)203 204         # 相邻直接可连205         if maxX - minX == 1:206             return True207 208         # 判断两个坐标之间是否全为0209         for x0 in range(minX + 1, maxX):210             if self.im2num_arr[x0][y] != 0:211                 return False212         return True213 214     # 点击事件并设置数组为0215     def clickAndSetZero(self, x1, y1, x2, y2):216         # print("click", x1, y1, x2, y2)217 218         # (299, 251, 768, 564)219         # 原理:左上角图标中点 + 偏移量220         p1_x = int(self.scree_left_and_right_point[0] + (y1 - 1)*self.im_width + (self.im_width / 2))221         p1_y = int(self.scree_left_and_right_point[1] + (x1 - 1)*self.im_width + (self.im_width / 2))222 223         p2_x = int(self.scree_left_and_right_point[0] + (y2 - 1)*self.im_width + (self.im_width / 2))224         p2_y = int(self.scree_left_and_right_point[1] + (x2 - 1)*self.im_width + (self.im_width / 2))225 226         time.sleep(0.2)227         self.mouse.click(p1_x, p1_y)228         time.sleep(0.2)229         self.mouse.click(p2_x, p2_y)230 231         # 设置矩阵值为0232         self.im2num_arr[x1][y1] = 0233         self.im2num_arr[x2][y2] = 0234 235         print("消除:(%d, %d) (%d, %d)" % (x1, y1, x2, y2))236         # exit()237 238     # 程序入口、控制中心239     def start(self):240 241         # 1、先截取游戏区域大图,然后分切每个小图242         image_list = self.screenshot()243 244         # 2、识别小图标,收集编号245         self.image2num(image_list)246 247         print(self.im2num_arr)248 249         # 3、遍历查找可以相连的坐标250         while not self.isAllZero(self.im2num_arr):251             for x1 in range(1, 9):252                 for y1 in range(1, 13):253                     if self.im2num_arr[x1][y1] == 0:254                         continue255 256                     for x2 in range(1, 9):257                         for y2 in range(1, 13):258                             # 跳过为0 或者同一个259                             if self.im2num_arr[x2][y2] == 0 or (x1 == x2 and y1 == y2):260                                 continue261                             if self.isReachable(x1, y1, x2, y2):262                                 self.clickAndSetZero(x1, y1, x2, y2)263 264 265 if __name__ == "__main__":266     # wdname 为连连看窗口的名称,必须写完整267     wdname = u'宠物连连看经典版2,宠物连连看经典版2小游戏,4399小游戏 www.4399.com - Google Chrome'268 269     demo = GameAssist(wdname)270     demo.start()
GameAssist.py

 

转载于:https://www.cnblogs.com/reader/p/10111777.html

你可能感兴趣的文章
Python3+Selenium3+webdriver学习笔记8(单选、复选框、弹窗处理)
查看>>
Java String.indexOf() 函数用法小结
查看>>
SSL 1105——【USACO 2.1】顺序的分数(递归+二分)
查看>>
微信 小程序组件 焦点切换
查看>>
github上传文件
查看>>
编译指定安装路径
查看>>
IntelliJ IDEA实时模板变量
查看>>
mysql 多条记录判断相加减进行计算
查看>>
MySQL导入MongoDB
查看>>
JAX-RPC
查看>>
node爬虫
查看>>
Android学习笔记--Menu菜单的使用
查看>>
【bzoj4550】小奇的博弈 博弈论+dp
查看>>
资源网址
查看>>
linux下,保存退出vim编辑器(转)
查看>>
【新手向】阿里云上ubuntu+flask+gunicorn+nginx服务器部署(二)项目部署
查看>>
NOIP模拟赛
查看>>
Django DEBUG=False
查看>>
把实体 转为json 数据格式---jackson 的详细用法.
查看>>
数据库管理软件的由来
查看>>