win7扫雷_最强扫雷AI算法详解+源码分享

1b20a23d9b8b7b62b4d4dd89beaae511.png

研究背景
做这个研究的起源是一周多以前在B站上看到一个视频,是编程模拟研究扫雷的一些数据,上面提到Win7扫雷高级可以达到36%的胜率。后来看了一下代码,发现这里面只用到了一些简单的推理,逻辑不是很完善。我想我写了这么多年代码了,如果写一个逻辑更加完善的AI能不能统计出扫雷真正的极限胜率呢。
当时上网查了一下,基本都认为xp规则下高级极限胜率是38~39%,win7规则下49+%
周末的时候理清了一下思路,花一天多时间实现了一下(400+行的C++,跟我以前做ACM的时候最长的代码差不多长),模拟自测了250000盘,胜率可以达到40.07%,win7规则下胜率可以达到52.98% 后来用系统扫雷和Arbiter做了上千盘的实测,结果都在40%以上,基本印证了模拟结果的正确性。当然这个AI还有很多参数可以调整,离真正的极限胜率还有些距离,但应该是目前世界上最好的结果了。

算法细节1. 基本的推理
扫雷里面最基本的推理就是数雷。
如果一个格子周边没有打开的格子数 = 这个格子上的数字,那么它周边没有打开的格子全部是雷。如果一个格子周边已知的雷数 = 这个格子上的数字,那么它周边没有打开的格子全部不是雷。利用这两条规则可以在大部分情况下找出新的安全格。

2eae18af2b082f6f94282747174ef1af.png
例如上图,标旗的一格必定有雷,下面两格必定没有雷。

2. 计算每一格有雷的概率
2.1 总体思想

假设一共有

个格子没有打开,那么存在至多
种可能的解,假设去除与已知格子矛盾的解后一共有
(显然
)种可能的解。我们可以统计每一个格子在多少种解中是有雷的,然后除以
就得到了这一格是雷的概率。(概率为0一定不是雷,可以打开)

2.2 分块枚举
如果

足够小,那么直接枚举所有情况判断就可以了,但
比较大的时候就需要分块处理了。如果一个未知格在一个数字格周围的九宫格内,可以把这两个格子连一条边代表它们存在互相制约,连边完成后整个地图被分割成若干个联通块(有的空白格单独构成一个联通块)。不同联通块之间不存在互相制约。

8d6738ae5dfa9c8b1dfc787ed37b5601.png
例如上图中,被分成了四个连通块,可以分别处理

这样我们可以对每一个联通块中的未知格进行枚举,大大减少了枚举量。模拟测试中,99.99+%以上的游戏都没有出现单个联通块合法方案数超过一千万的情况。

2.3 考虑剩余雷数,计算精确概率
需要注意的是,因为有剩余雷数的限制,联通块内部的概率其实是不准确的,例如

0068ce7c92339b501a9bee6c3cd4e1fe.png
仅考虑这个联通块,一共有5种可能的解,其中3种解最左边的格子有雷,概率为60%。但假设整个地图只剩下两个雷,那么大概率这个连通块中只有一个雷,所以最左边的格子有雷的概率很小

因此我们计算每格概率的时候需要考虑剩余雷数的限制。
在枚举过程中,对每个联通块我们可以统计出:

代表这个联通块的未知格中一共有
颗雷的方案数。

对每个格子
可以统计出:

代表当格子所在的联通块中一共有
颗雷时,多少种方案中
这个格子是雷。

那么我们依次考虑每个格子的胜率。除开格子本身所在的联通块不看,考虑其它所有联通块(假设一共有
个连通块),我们可以计算出计算
代表前
个连通块共
个雷的方案数,这里是一个典型的背包问题,转移方程


复杂度显然还可以优化,但这部分在整个算法中占比不大。
假设当前剩下
个雷,枚举当前格子所在联通块的雷数
,有
种可行方案,其中
种方案中当前格有雷,对这两个值分别求和,就可以得到当前格有雷的精确概率。

2.4 基于概率的贪心算法
这一步后可以使用贪心算法:如果没有确定无雷的格子,那么点击概率最小的格子,概率相同时先点角,角开完之后点附近5*5的地图里打开格子数最多的格子。
到这一步胜率可以达到39%了,但其实贪心的规则是可以调整的,我这里没深入尝试了,很可能存在一些贪心方法可以大幅度提升胜率。

3. 计算点击每一格的获胜概率
3.1 有雷的概率不代表胜率

我们看一个经典的例子

142f147fab63259aebedf79d5f67253e.png
假设只剩一个雷。图中三格有雷的概率都是三分之一,但显然点击中间格获胜概率是三分之一,点击另外两格获胜概率是三分之二。

在一些情况下,有雷概率低的格子胜率还不如有雷概率高的格子,因此我们需要用更严格的方法计算胜率

3.2 局面表示
我们考虑用一个列表 表示每个未知格上最终的数字(或者是雷),那么每种可能的情况称为一个解。一个局面可以表示为若干个解的集合,代表这个局面的所有可能解。例如我们用-1代表雷,那么之前的图片中

142f147fab63259aebedf79d5f67253e.png
假设剩一个雷,从左到右描述每个格子,有三个可能的解:[-1, 3, 1], [4, -1, 2], [3, 3, -1]

考虑一个局面

,它有
个未知的格子,假设我们点击第
个格子,我们可以计算出点击这个格子之后所有可能的后续局面,假设一共
种可能,第
种可能是以
的概率转移到局面

局面的转移关系是拓扑的,满足无后效性,因此可以用状态压缩的动态规划,
表示局面为
时的胜率,显然有转移方程

3.3 状态转移
最后的难点是3.2中,已知当前局面

,如何计算

对于一个局面,假设我们点开格子
,那么那些
格是雷的解就不用考虑了(直接失败),对于其它解构成的集合,我们考虑剩下的每一个格子,如果一个格子在所有解中都不是雷,那么这个格子是安全的,可以点开。这时候,如果两个解这一格的数字不同,它们就必定不会同属一个局面了。那么我们把解集按这格拆分成若干子集,并对每一个子集递归处理,这样就得到了所有后续的局面。转移到局面
的概率为

实现这一步之后,就可以根据3.2的方程计算每一格的严格胜率了。

总结
这个算法理论上是可以严格计算出扫雷胜率的,但是复杂度过于爆炸了,实际上我只能在最后状态不多的时候开启算法3的胜率计算,但也足够将胜率提升到40.07%了。虽然这应该是目前最好的结果,但大家可以看到其中很多步骤调整的空间是很大的,稍微改改可能就有不小的提升。

开源代码地址

https://github.com/ztxz16/Mine

有兴趣的同学可以随意使用,顺手给个star就更好啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值