序言
本人平时无聊时总会玩玩扫雷游戏,某天一如既往的被炸死后,突然想写一个自动扫雷的程序来玩玩,经过几天的思考和CODING,算是完成了一个初版,下面就来简单介绍一下扫雷的基础和程序的流程。
命名
先规定一下本文中使用的基本名词
(1)扫雷中的方格称为<块>
(2)有数字的块称为<数字块>
(3)没有点击也没有做标记的块称为<未知块>
(4)已经确定是雷的称为<雷>
(5)<块>周围相邻的所有块的集合称为<8-邻域>
(6)将<8-邻域>扩大一个单位,并去除四个角落,称为<20-邻域>

<8-邻域>和<20-邻域>示意图
<8-邻域>:红色区域除去a
<20-邻域>:黄色区域除去a
扫雷基础
在扫雷游戏刚开始,第一步貌似点哪里被炸的几率都一样,但其实并非如此,我们以WINDOWS-XP的扫雷(简称<XP扫雷>)为例,<XP扫雷>的第一步一定不会是雷,有兴趣的童鞋可以用DEBUG工具分析或者查看源代码。<XP扫雷>的运行机制是当用户第一次点击时,生成雷区,如果用户点击的<块>正好是雷,则重新生成雷区。因此第一步不用担心。
既然第一步不会被炸,那么随便点哪里都一样吗?其实也不是,因为我们希望第一步能点开一片区域(也就是被点击的<块>的<8-邻域>没有雷),这样我们就可以从区域的边界来推算下一步该如何选择。
好了,那么问题就变成这样了:<XP扫雷>刚开始时,哪些<块>在点击后,<8-邻域>一个雷都没有的几率最大?相信大家凭直觉就能知道是雷区的角落和边界,实际上也正是如此,通过计算也能得出角落是最优选择,其次是边界。
判断方法1
假如第一步已经点开了一片区域,那么下面我们就要推算哪些是雷,哪些不是雷了。
先说最简单的判断方法,也就是<数字块>的<8-邻域>中的<未知块>的数量等于<数字块>的数字,此时,所有的<未知块>都是<雷>。下面的图就是一个典型例子。

判断方法2
这是一个稍微复杂一点的方法,涉及到的区域是<20-邻域>。看到下图中,黄色方框中的<3>的<8-邻域>中有2个<未知块>,但是只有1个<雷>,因此无法判断。再看红色方框中的<1>,它的<8-邻域>中有3个<未知块>,1个<雷>,也无法判断。但是将这两个区域结合起来,我们将<1>的<8-邻域>的所有<未知块>称为<未知集合1>,将<3>的<8-邻域><未知块>称为<未知集合2>,我们发现<未知集合1>包含了<未知集合2>,因此可以果断得出结论,<未知集合1>和<未知集合2>的未重叠部分,一定没有<雷>。
将这个方法归纳一下:
设<未知集合1>有n1个<未知块>,b1个<雷>
<未知集合2>有n2个<未知块>,b2个<雷>
且<未知集合1>包含<未知集合2>,可以得出n1>n2
那么<未知集合1>除去<未知集合2>的剩余部分有n1-n2个<未知块>,有b1-b2个<雷>
此时可以考察b1-b2,如果b1-b2 = 0,则说明n1-n2个<未知块>全都不是<雷>,如果 b1-b2 = n1-n2,则说明n1-n2个<未知块>全都是<雷>。
其实这个方法还可以继续推广,<未知集合1>包含<未知集合2>~<未知集合n>,并且<未知集合2>~<未知集合n>互相不想交,则也可以适用上面的判断。

更高级的判断方法
本人比较愚笨,只会用这两种方法,穷途末路以后就全拼RP..........
如果各位有什么其它更高级的方法,希望能告诉我。程序实现
程序分为几个部分
(1)雷区的获取
本程序是直接通过分析图像来获得雷区信息的,步骤如下:
l 通过win32函数找到扫雷游戏的窗口坐标和窗口大小
l 对扫雷窗口截图,转换为彩色图和灰度图两个版本
l 通过灰度图分析雷区的方格信息,方法是找到每行每列像素的灰度边界,找出边界之间的距离,并列出距离的直方图,通过直方图找到方格的大小和数量,并可以找到雷区相对于窗口的相对坐标
l 获取每个方格的状态则是采用彩色图,简单的将方格像素直接和预存的常量对比,状态包含<未知>,<0>,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<旗帜>,<?><雷>
(2)雷区判断
l 使用上文提到的两种方法遍历雷区,获取整个雷区的<概率图>,概率是从0~1,0代表一定不是雷,1代表一定是雷,除了0和1以外的概率并不是非常准确,因为只是采用了上文提到的两种方法来计算。
l 调用win32函数控制鼠标,将所有概率是0的方格点开,概率是1的标记旗帜(也可以不标记,因为标记旗帜对程序来说没有任何作用)
l 重复上述步骤,直到完成扫雷或者无法继续
下图是一次自动解<扫雷高级>的结果,可以看到并没有扫完,程序无法继续了,实际上只通过上面说到的两种方法,只能完成<扫雷初级>和大部分<扫雷中级>,<扫雷高级>的完成率是很低的,最后只能使用概率来拼RP。

屏幕最上面的图表是概率图,概率0是白色,概率1是红色,概率越大颜色越深














