【Python】如何用pyth做游戏脚本(太简单了吧)
文章目录
- 前言
- 一、开发前景
- 二、开发流程
- 3.1、获取窗口句柄,把窗口置顶
- 3. 2、截取游戏界面,分割图标,图片比较
- 二、程序核心-图标连接算法(路径寻找)
- 四、开发总结
- 五、源码
- 总结
前言
简述:本文将以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() 设置窗口在前面,这里传入游戏窗口的举报即可,代码如下:
3. 2、截取游戏界面,分割图标,图片比较
这里需要花费一些时间来校验程序,如果截取的图片不好,则会影响后续操作,所以比较主要的是确认游戏左上角和右下角这两个坐标值,以及每个小图标的宽高。如下图所示,先截取整个游戏界面图,然后分割小图标,接着对每个图标进行比较,然后以编号代替图标存入矩阵(这里的编号矩阵和游戏图不一致,原理一样)。
根据初始化设定的左上角和右下角两个坐标,使用ImageGrab.grab()方法进行截图,传入一个元组即可,然后对这个大图进行分割,切割成一个个小图标存入到images_list数组中。
通过上面代码切割的小图标,转成数字矩阵,如果图标已经存入image_type_list则返回这个索引,如果不存在,则在追加进去,然后当前长度就是这个新加入图标的编号,代码如下所示:
上面的getIndex就是对比图片,判断图标是否出现过(是否已存在image_type_list中,没出现则追加进去),这里使用汉明距离判断两个图片的相识度,设置阀值10,当小于阀值则认为是同一个图片,具体代码如下:
二、程序核心-图标连接算法(路径寻找)
这里仅对算法代码进行简单分析,如果对程序不好理解,可以留言,后续可以图文分析。
通过上面的开发流程,基本获取如下这样的矩阵,只要比较两个编号相同的值进行可连路径寻找,如果找到即进行模拟点击操作。这里简单介绍下游戏规则: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())可以连接的坐标集合,最后再对集合进行遍历比较是否存在可连的,如果存在则表示传入的两个坐标是可以连接的。
四、开发总结
学习这样一个游戏辅助脚本,对于个人培养编程兴趣也是有很多帮助的,在工作之余不失为一个好的消遣方式,以后会多向这些方向研究学习。本案例仅仅是截图、比较图片和模拟鼠标点击,我觉得还可以更加强大,而且还不局限于游戏这样一个领域,相信大家应该见过自动发QQ消息的软件吧,我觉得这完全可以做。还有很多模拟操作可以实现:鼠标滚轮,左右键、键盘输入等。
五、源码
# -*- coding:utf-8 -*-import win32guiimport timefrom PIL import ImageGrab, Imageimport numpy as npimport operatorfrom pymouse import PyMouseclass GameAssist:def __init__(self, wdname):"""初始化"""# 取得窗口句柄self.hwnd = win32gui.FindWindow(0, wdname)if not self.hwnd:print("窗口找不到,请确认窗口句柄名称:【%s】" % wdname )exit()# 窗口显示最前面win32gui.SetForegroundWindow(self.hwnd)# 小图标编号矩阵self.im2num_arr = []# 主截图的左上角坐标和右下角坐标self.scree_left_and_right_point = (299, 251, 768, 564)# 小图标宽高self.im_width = 39# PyMouse对象,鼠标点击self.mouse = PyMouse()def screenshot(self):"""屏幕截图"""# 1、用grab函数截图,参数为左上角和右下角左标# image = ImageGrab.grab((417, 257, 885, 569))image = ImageGrab.grab(self.scree_left_and_right_point)# 2、分切小图# exit()image_list = {}offset = self.im_width # 39# 8行12列for x in range(8):image_list[x] = {}for y in range(12):# print("show",x, y)# exit()top = x * offsetleft = y * offsetright = (y + 1) * offsetbottom = (x + 1) * offset# 用crop函数切割成小图标,参数为图标的左上角和右下角左边im = image.crop((left, top, right, bottom))# 将切割好的图标存入对应的位置image_list[x][y] = imreturn image_listdef image2num(self, image_list):"""将图标矩阵转换成数字矩阵"""# 1、创建全零矩阵和空的一维数组arr = np.zeros((10, 14), dtype=np.int32) # 以数字代替图片image_type_list = []# 2、识别出不同的图片,将图片矩阵转换成数字矩阵for i in range(len(image_list)):for j in range(len(image_list[0])):im = image_list[i][j]# 验证当前图标是否已存入index = self.getIndex(im, image_type_list)# 不存在image_type_listif index < 0:image_type_list.append(im)arr[i + 1][j + 1] = len(image_type_list)else:arr[i + 1][j + 1] = index + 1print("图标数:", len(image_type_list))self.im2num_arr = arrreturn arr# 检查数组中是否有图标,如果有则返回索引下表def getIndex(self,im, im_list):for i in range(len(im_list)):if self.isMatch(im, im_list[i]):return ireturn -1# 汉明距离判断两个图标是否一样def isMatch(self, im1, im2):# 缩小图标,转成灰度image1 = im1.resize((20, 20), Image.ANTIALIAS).convert("L")image2 = im2.resize((20, 20), Image.ANTIALIAS).convert("L")# 将灰度图标转成01串,即系二进制数据pixels1 = list(image1.getdata())pixels2 = list(image2.getdata())avg1 = sum(pixels1) / len(pixels1)avg2 = sum(pixels2) / len(pixels2)hash1 = "".join(map(lambda p: "1" if p > avg1 else "0", pixels1))hash2 = "".join(map(lambda p: "1" if p > avg2 else "0", pixels2))# 统计两个01串不同数字的个数match = sum(map(operator.ne, hash1, hash2))# 阀值设为10return match < 10# 判断矩阵是否全为0def isAllZero(self, arr):for i in range(1, 9):for j in range(1, 13):if arr[i][j] != 0:return Falsereturn True# 是否为同行或同列且可连def isReachable(self, x1, y1, x2, y2):# 1、先判断值是否相同if self.im2num_arr[x1][y1] != self.im2num_arr[x2][y2]:return False# 1、分别获取两个坐标同行或同列可连的坐标数组list1 = self.getDirectConnectList(x1, y1)list2 = self.getDirectConnectList(x2, y2)
# print(x1, y1, list1)# print(x2, y2, list2)# exit()# 2、比较坐标数组中是否可连for x1, y1 in list1:for x2, y2 in list2:if self.isDirectConnect(x1, y1, x2, y2):return Truereturn False# 获取同行或同列可连的坐标数组def getDirectConnectList(self, x, y):plist = []for px in range(0, 10):for py in range(0, 14):# 获取同行或同列且为0的坐标if self.im2num_arr[px][py] == 0 and self.isDirectConnect(x, y, px, py):plist.append([px, py])return plist# 是否为同行或同列且可连def isDirectConnect(self, x1, y1, x2, y2):# 1、位置完全相同if x1 == x2 and y1 == y2:return False# 2、行列都不同的if x1 != x2 and y1 != y2:return False# 3、同行if x1 == x2 and self.isRowConnect(x1, y1, y2):return True# 4、同列if y1 == y2 and self.isColConnect(y1, x1, x2):return Truereturn False# 判断同行是否可连def isRowConnect(self, x, y1, y2):minY = min(y1, y2)maxY = max(y1, y2)# 相邻直接可连if maxY - minY == 1:return True# 判断两个坐标之间是否全为0for y0 in range(minY + 1, maxY):if self.im2num_arr[x][y0] != 0:return Falsereturn True# 判断同列是否可连def isColConnect(self, y, x1, x2):minX = min(x1, x2)maxX = max(x1, x2)# 相邻直接可连if maxX - minX == 1:return True# 判断两个坐标之间是否全为0for x0 in range(minX + 1, maxX):if self.im2num_arr[x0][y] != 0:return Falsereturn True# 点击事件并设置数组为0def clickAndSetZero(self, x1, y1, x2, y2):# print("click", x1, y1, x2, y2)# (299, 251, 768, 564)# 原理:左上角图标中点 + 偏移量p1_x = int(self.scree_left_and_right_point[0] + (y1 - 1)*self.im_width + (self.im_width / 2))p1_y = int(self.scree_left_and_right_point[1] + (x1 - 1)*self.im_width + (self.im_width / 2))p2_x = int(self.scree_left_and_right_point[0] + (y2 - 1)*self.im_width + (self.im_width / 2))p2_y = int(self.scree_left_and_right_point[1] + (x2 - 1)*self.im_width + (self.im_width / 2))time.sleep(0.2)self.mouse.click(p1_x, p1_y)time.sleep(0.2)self.mouse.click(p2_x, p2_y)# 设置矩阵值为0self.im2num_arr[x1][y1] = 0self.im2num_arr[x2][y2] = 0print("消除:(%d, %d) (%d, %d)" % (x1, y1, x2, y2))# exit()# 程序入口、控制中心def start(self):# 1、先截取游戏区域大图,然后分切每个小图image_list = self.screenshot()# 2、识别小图标,收集编号self.image2num(image_list)print(self.im2num_arr)# 3、遍历查找可以相连的坐标while not self.isAllZero(self.im2num_arr):for x1 in range(1, 9):for y1 in range(1, 13):if self.im2num_arr[x1][y1] == 0:continuefor x2 in range(1, 9):for y2 in range(1, 13):# 跳过为0 或者同一个if self.im2num_arr[x2][y2] == 0 or (x1 == x2 and y1 == y2):continueif self.isReachable(x1, y1, x2, y2):self.clickAndSetZero(x1, y1, x2, y2)if __name__ == "__main__":# wdname 为连连看窗口的名称,必须写完整wdname = u'宠物连连看经典版2,宠物连连看经典版2小游戏,4399小游戏 www.4399.com - Google Chrome'demo = GameAssist(wdname)demo.start()
GameAssist.py
总结
希望大家继续关注徐浪大讲堂!
相关文章:

【Python】如何用pyth做游戏脚本(太简单了吧)
文章目录 前言一、开发前景二、开发流程3.1、获取窗口句柄,把窗口置顶3. 2、截取游戏界面,分割图标,图片比较 二、程序核心-图标连接算法(路径寻找)四、开发总结五、源码总结 前言 简述:本文将以4399小游戏…...

【Linux】磁盘与文件系统
目录 一、磁盘的物理结构 二、磁盘逻辑抽象 三、文件系统 1、Super Block 2、Group Descriptor Table 3、inode Table 4、Data Blocks 5、inode Bitmap 6、Block Bitmap 四、Linux下文件系统 1、inode与文件名 2、文件的增删查改 2.1、查看文件内容 2.2、删除文件…...

Transformer中的注意力机制及代码
文章目录 1、简介2、原理2.1 什么是注意力机制2.2 注意力机制在NLP中解决了什么问题2.3 注意力机制公式解读2.4 注意力机制计算过程 3、单头注意力机制与多头注意力机制4、代码4.1 代码14.2 代码2 1、简介 最近在学习transformer,首先学习了多头注意力机制…...

ChatGPT在连续追问下对多线程和双重检查锁模式的理解--已经超越中级程序员
一、问: private static final Map<Method, GZHttpClientResultModel> CACHE_RESULT_MODEL new ConcurrentHashMap<>();public void abc(Method method){cacheResultMode(method);GZHttpClientResultModel model CACHE_RESULT_MODEL.get(method);}pr…...

每天一道大厂SQL题【Day22】华泰证券真题实战(四)
每天一道大厂SQL题【Day22】华泰证券真题实战(四) 大家好,我是Maynor。相信大家和我一样,都有一个大厂梦,作为一名资深大数据选手,深知SQL重要性,接下来我准备用100天时间,基于大数据岗面试中的经典SQL题&…...

【智能电网】智能电网中针对DOS和FDIA的弹性分布式EMA(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

IDEA 创建微服务项目实例
🎈 作者:Linux猿 🎈 简介:CSDN博客专家🏆,华为云享专家🏆,Linux、C/C++、云计算、物联网、面试、刷题、算法尽管咨询我,关注我,有问题私聊! 🎈 关注专栏:C/C++面试通关【精讲】 优质好文持续更新中……🚀🚀🚀 🎈 欢迎小伙伴们点赞👍、收藏⭐、留…...

注册苹果开发者账号的方法
在2020年以前,注册苹果开发者账号后,就可以生成证书。 但2020年后,因为注册苹果开发者账号需要使用Apple Developer app注册开发者账号,所以需要缴费才能创建ios证书了。 所以新政策出来后,注册苹果开发者账号&#…...

OpenCV2 计算机视觉应用编程秘籍:1~5
原文:OpenCV2 Computer Vision Application Programming Cookbook 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 计算机视觉 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 当别人说你没有底线…...

Domino自带的JSON校验工具
大家好,才是真的好。 JSON数据在Notes/Domino已经变得非常重要。从Domino 10开始,在LotusScript语言中就加入了对JSON数据处理功能。在管理中,我们知道,从Domino 12版本开始就支持Domino自动化配置,也是使用JSON数据作…...

CentOS(linux)使用Docker安装nacos
1. 拉取nacos镜像 docker pull nacos/nacos-server:2.0.3 2. 创建所需文件夹(以安装在home目录下为例) 1) 创建conf文件夹 mkdir -p /home/nacos/conf a. 新增文件application.properties(或者不增加该文件,会使用默认的) 文件内容如下: # spring server.servlet.contextP…...

无线测温在线监测系统工作原理与产品选型
摘要:本文首先介绍了无线测温在线监测系统的基本工作原理以及软硬件组成,重点介绍了在线监测的无线测温技术特点。在此研究基础上,探讨了无线测温在线监测系统在实际工作场景中的应用案例,证明了其在温度检测方面的重要应用价值。…...

Nginx rewrite ——重写跳转
Nginx常见模块 http http块是Nginx服务器配置中的重要部分,代理、缓存和日志定义等绝大多数的功能和第三方模块的配置都可以放在这模块中。作用包括:文件引入、MIME-Type定义、日志自定义、是否使用sendfile传输文件、连接超时时间、单连接请求数上限等…...

【华为OD机试真题 C++】1038 - 全量和已占用字符集 | 机试题+算法思路+考点+代码解析
文章目录 一、题目🔸题目描述🔸输入输出🔸样例1二、代码参考作者:KJ.JK🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🍂个人博客首页: KJ.JK 💖系列专栏:华为OD机试真题(C++) 一、题目 🔸题目描述 所谓水仙花数,是指一个n位的正整数…...

网络中的网关和物联网的网关区别 局域网 路由器 交换机 服务器
网关:是个概念。连接两种不同的网络。例如局域网要与外部通信,需要经过网关。 设备和设备之间的通信,转换协议需要网关 路由器里有功能是对网关这个概念的实现。 所以网关它可以是路由器,交换机或者是PC。 路由器有网关功能&a…...

2023 年嵌入式世界的3 大趋势分析
目录 大家好,本文讲解了嵌入式发展的3个大趋势,分享给大家。 趋势#1 – Visual Studio Code Integration 趋势#2 –支持“现代”软件流程 趋势 #3 – 在设计中利用 AI 和 ML 结论 大家好,本文讲解了嵌入式发展的3个大趋势,分享…...

基于 DolphinDB 机器学习的出租车行程时间预测
DolphinDB 集高性能时序数据库与全面的分析功能为一体,可用于海量结构化数据的存储、查询、分析、实时计算等,在工业物联网场景中应用广泛。本文以纽约出租车行程时间预测为例,介绍如何使用 DolphinDB 训练机器学习模型,并进行实时…...

Python调用最小二乘法
文章目录 numpy实现scipy封装速度对比 所谓线性最小二乘法,可以理解为是解方程的延续,区别在于,当未知量远小于方程数的时候,将得到一个无解的问题。最小二乘法的实质,是保证误差最小的情况下对未知数进行赋值。 最小…...

15.数据表格.上
本节课我们来开始了解 Layui 的内置模块:table 数据表格。 一.基本使用 1. table 模块,通过异步加载数据来渲染表格来展现数据内容; <table id"table"></table> layui.use([table], () > { const table …...

在poetry虚拟环境下打包exe
本博客介绍了在poetry虚拟环境下打包exe的流程,包含两个部分 打包的基本流程打包过程中遇到的问题 打包的基本流程 copy打包工具到本地,(share:\公用共享\芯片部\乔羽\img_generate\系统部提供的打包exe工具) 用poetry搭建虚拟环境 在打包…...

【Unity VR开发】结合VRTK4.0:高亮与标签
语录: 信仰到底是什么呢,就是纵身一跃,就是我们跟神之间一个永远的约定,是舍弃日的去开始新的生活;信仰就是从今以后,再也不要放开你的手。 前言: Interactable Highlighter :当我们的手柄触碰…...

有了MySQL,为什么还要有NoSQL
🏆今日学习目标: 🍀MySQL和NoSQL的区别 ✅创作者:林在闪闪发光 ⏰预计时间:30分钟 🎉个人主页:林在闪闪发光的个人主页 🍁林在闪闪发光的个人社区,欢迎你的加入: 林在闪闪…...

找PPT模板就上这5个网站~
分享几个可以永久免费下载PPT模板、素材的网站,上万个模板随便下载,赶紧收藏起来~ 1、菜鸟图库 https://www.sucai999.com/search/ppt/0_0_0_1.html?vNTYxMjky 网站素材非常全面,主要以设计类素材为主,办公类素材也很多&#x…...

Ae:摄像机选项
摄像机选项 Camera Options 快捷键:AA 摄像机选项 Camera Options与“摄像机设置”中的参数大同小异且同步变化,额外增加了一些与镜头模糊和散景光斑形状有关的摄像机属性。 请参阅: 《Ae:摄像机设置》 在合成设置中,选…...

嵌入式日志库ulog的使用和解析
嵌入式日志信息保存调试(ulog) 获取 项目地址:https://github.com/rdpoor/ulog uLog 为嵌入式微控制器或任何资源有限的系统提供结构化的日志记录机制。它继承了流行的 Log4c 和 Log4j 平台背后的一些概念,但开销更低。 使用方…...

自阿里P8爆出内部1031道java面试题后,在Boss直聘狂拿千份Offer
开始之前我问大家几个问题,看大家是如何思考的: 1.程序员一定要去一线城市漂泊吗?在自己家乡如何拿到一份满意的薪水? 2.程序员被裁员、找不到工作,代表什么? 3.程序员一定要进一线大厂吗?你…...

Java最新面试题100道,包含答案示例(41-50题)
非常抱歉,我理解有误。以下是第41至45题的Java面试题和答案: 请问Java中有哪些常用的集合类型? 答:Java中有多种常用的集合类型,包括List、Set、Map等。其中,List和Set分别代表一组元素的序列和一组无序不…...

C++之深入解析野指针和悬空指针
一、野指针 ① 什么是野指针? 野指针指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为NULL避免,而只能通过养成良好的编程习惯来尽力减少,对野指针进行操作很容易造成程序错误…...

YOLOv7+单目测距(python)
YOLOv7单目测距(python) 1. 相关配置2. 测距原理3. 相机标定3.1:标定方法13.2:标定方法2 4. 相机测距4.1 测距添加4.2 主代码 5. 实验效果 相关链接 1. YOLOV5 单目测距(python) 2. YOLOV5 单目跟踪&…...

SYSU程设c++(第九周)函数对象、友元函数、友元类
函数对象: 如果一个类定义了operator()运算符函数,则可以使用该类的对象名为函数名调用这个函数. 函数对象是一个对象,但调用形式和普通函数调用一样,因此取名叫函数对象 (注意operator()先有个括号,接着才是括号(参数…...