基于OpenCV实现的自动扫雷机

Expiredlove

发布日期: 2019-05-12 10:18:56 浏览量: 517
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

先说结果吧,初级和中级都可以在1s内完成游戏,高级经过多次测试,最快4秒,最慢6秒左右。

然后汇报一下功能,嘛,就自动扫雷呗,可以在游戏开始的时候自动识别总雷数,游戏的规模大小,然后没啥别的特点了。。

然后简单介绍一下原理,首先自动扫雷首先就要获得游戏的状态,就是什么位置上现在的示数是有还是无,有的话是多少,获取这个主要有两种方法,第一种是外挂法,就是读取扫雷这个进程的内存单元,扫描后可以通过多次试验知道内存状态的含义,然后就可以开始扫了,这种方法我看到有人是这么干的了,这种方法只要会读取/扫描内存,知道内存的含义,额,之后就毫无技术含量了,因为那些还没开的雷的状态你都知道了。。不管规模多大都可以直接秒杀。。然后第二种是我这里用的方法,获取窗口的图像,通过图像来获取游戏的相关信息,进行推理,然后控制鼠标到相应位置完成左键或者右键的点击,在一个大循环中不断地获取图片的状态然后就可以不断的推理,点击了,就趣味性而言,我觉得这种方法比较好玩儿~

然后下面就是详细讲一下我实现的方法:

准备工作,作为图像处理,首先要准备一下基本图像,比如不同数字打开后的图片神马的,唉,你知道我试验出数字8那个图像花了多少时间么??在做模板匹配的时候一般不会有人把模板存为jpeg吧。。

首先是如何获取游戏的窗口的图像,额,我以前的API实现过这个功能,只不过是把图像存到OPENCV的IplImage这种数据类型下而已~

好,接下来就是要获取雷数,我们知道游戏刚开始的时候左上角显示的就是这盘的总雷数,比如说下图中的40:

获取这个数字自然也是通过获取这一块的图像来获得啦,由于无论如何都是3位数,所以这个小窗口的大小是不会变的,实验获得这个窗口的位置,宽高什么的都不是难事儿(我这里是通过定位黑色边框找到的,就是下图中最外层的那一圈黑,不过我获取一次后就把位置当常量记下来了。。之后变成默认我知道这个位置。。)

看上图我们知道每个数字其实就是一个7SEG数码管,亮和暗很容易区分出来,所以我们只要扫描每一条的像素值,就可以知道这个位置是亮还是暗了,把数据存到一个3*7的矩阵中,之后直接译码即可。关于扫描,参见下图:

横着扫描时可以通过在1/4和3/4位置横扫两次就可以了,竖着也差不多,只要用上数学上的取模运算,可以把for的次数讲到最低。

然后获取游戏的规模,额,坑爹啊,我实现的方法很简单,就是用上面那个图像库中unknown.bmp去匹配,然后有多少个匹配的就可以知道游戏的规模了,然后写完发现,其实研究一下,就可以通过游戏窗口的大小直接获得游戏的规模了。。不过我那个方法可以在匹配的过程中记录下每个格子相对于游戏窗口的像素坐标,后面点击操作的时候可以直接用,(其实另外一个方法研究一下规律也可以。。)具体的这里就不详述了。。很简单的。。

接下来需要从图片中获取格子数字,我用了一个比较取巧的方法,我们可以看到每个数字都是由一种颜色写成的,而且每个数字颜色还不一样,所以我们可以在每个格子中间周围找一下背景颜色以外的颜色,如果有1-8中某个数字的颜色就可以知道那个格子是什么数字了。这个方法很快,也很好用,但是细细分析,会有一个不影响使用的缺点,就是数字7是黑色的,而一旦点中雷后,雷的显示也是黑色的,就会把雷判断为数字7,不过嘛,都点出雷了,说明game over了,判断也啥意义,而且我们只要判断游戏上部中间那张脸的样子就可以知道是否点雷了,如果不是,那这个确定就无所谓了。

如果真有代码的”洁癖”的话,可以用另一种方法来识别,每个格子是16*16 pix的,我们可以记录下每个数字中间那一行的16个点的颜色作为那个数字的”特征向量”,然后在匹配这个就行了,这时候数字7和黑色雷中间一行其实是不一样的,所以不会有这个问题。

接下来就是扫雷策略上的问题了,很简单,基本是个人都想得到的,就是:

  • 如果一个格子周围的确定的雷的数目等于这个格子的示数,那么这个格子周围所有还未确定的格子都是安全的

  • 如果一个格子周围所有不确定的数目等于格子的示数减去已经确定的雷的数目,那么剩余的不确定的格子一定全都是雷

然后如果这个策略不能确定任何未知格子的状态的话,就随机蒙一个。。

额,我一开始就是按照这个思路写的,但是实验结果就是,对于初级和中级的游戏,没啥压力,一下子就过关了,但是对于高级,99个雷,棋盘规模有较大,每次都会推无可推,然后随便蒙一个,然后就挂彩了。。我试了很多次,一次都没过关过。。然后我就开始研究一个复杂一点的推导,准本在上面那个初级策略无效的时候采用。

上面的初级策略的缺点就是只考虑每个格子自身周围的数字而不考虑周围的格子的周围,就是多个格子联立思考,比如说下图:

细细研究一下就知道这时候初级策略已经没有任何可以确定的未知格子了,但是看下图:

我们研究红框的那一部分,为了方便说明,对于红框内的部分我们建立新坐标,比如A就是(3,2)。我们看(2,3)那个点,数字2表明了在A,B,C三个点钟还有一个雷,我们再看看这个点上方(1,3)那个点,它表明B和C中有一个雷

A,B,C中共有一个雷,BC中也只有一个雷,那就是说明A一定不是雷!!

再比如说下图:

也是到了初级策略束手无策的时候,我们再看看红框中的部分,点(3,4)的那个数字3表明a,b,c中有两个是雷,而它旁边(3,5)那个点表明bc中只有一个是雷,那就是说,a一定是雷!!

为了把上面的推导一般化,我们先声明一下几个概念:

一是公共未知区域,表示(i,j)点周围未知区域中和(i’,j’)周围未知区域中重复的那一部分【(i,j)要和(i’,j’)相邻】,比如说上图中(3,4)和(3,5)公共未知区域就是b,c,因为b,c不仅是(3,4)的周围的未知区域,还是(3,5)周围的未知区域

二是点(i,j)关于点(i’,j’)的非公共未知区域【(i,j)要和(i’,j’)相邻】,就是说(i,j)周围的未知区域中不是(i’,j’)周围的未知区域的那一些。比如上图中(3,4)关于(3,5)的非公共未知区域就是a,因为a属于(3,4)的未知区域,但是不属于(3,5)

然后我们要使用上述高级策略的话,必须要确定地知道公共未知区域中雷的数目,这点要怎么保证呢,就是(i’,j’)的未知区域就是公共未知区域,这样我们就可以通过(i’,j’)的示数和周围已经开拓出来的雷的位置知道(i’,j’)周围还有几个雷,而这个雷的数目就是公共未知区域的雷的数目。有了上述概念,高级策略如下:

  • 如果点(i,j)剩余的地雷数等于(i,j)关于(i’,j’)非公共区域位置的数目加上(i,j)和(i’,j’)的公共未知区域的雷的数目,那么(i,j)关于点(i’,j’)的非公共未知区域则全是雷

  • 如果点(i,j)剩余的地雷数等于(i,j)关于(i’,j’)非公共区域位置的数目,那么(i,j)关于点(i’,j’)的非公共未知区域则全部安全

实验结果表明,用了高级策略后,高级模式下无压力,只要不是运气太衰,一般都可以过关。运气太衰有两种表现,分别在开头和结尾,开头就是说你一开始每次随机点的时候都只点出一格,没有出一片来,那这种情况下你只能继续随机点,点着点着雷就爆了。。

还有另外一种情况就是在结尾的时候,比如说下图。。

幸好我这是机器扫出来的,输了就输了,如果是人手一个一个扫,扫到最后出现这种情况,直接砸电脑!!(掀桌!!暴怒)不过还真别说,这种情况还挺常见的!!果然游戏生成布局的时候就很不科学。。

上传的附件 cloud_download 基于OpenCV实现的自动扫雷机.7z ( 2.01mb, 3次下载 )

发送私信

你最美丽的时光陪我度过那些年

5
文章数
5
评论数
最近文章
eject