研究背景
做这个研究的起源是一周多以前在B站上看到一个视频,是编程模拟研究扫雷的一些数据,上面提到Win7扫雷高级可以达到36%的胜率。后来看了一下代码,发现这里面只用到了一些简单的推理,逻辑不是很完善。我想我写了这么多年代码了,如果写一个逻辑更加完善的AI能不能统计出扫雷真正的极限胜率呢。
当时上网查了一下,基本都认为xp规则下高级极限胜率是38~39%,win7规则下49+%
周末的时候理清了一下思路,花一天多时间实现了一下(400+行的C++,跟我以前做ACM的时候最长的代码差不多长),模拟自测了250000盘,胜率可以达到40.07%,win7规则下胜率可以达到52.98% 后来用系统扫雷和Arbiter做了上千盘的实测,结果都在40%以上,基本印证了模拟结果的正确性。当然这个AI还有很多参数可以调整,离真正的极限胜率还有些距离,但应该是目前世界上最好的结果了。
算法细节1. 基本的推理
扫雷里面最基本的推理就是数雷。
如果一个格子周边没有打开的格子数 = 这个格子上的数字,那么它周边没有打开的格子全部是雷。如果一个格子周边已知的雷数 = 这个格子上的数字,那么它周边没有打开的格子全部不是雷。利用这两条规则可以在大部分情况下找出新的安全格。
2. 计算每一格有雷的概率
2.1 总体思想
假设一共有
2.2 分块枚举
如果
这样我们可以对每一个联通块中的未知格进行枚举,大大减少了枚举量。模拟测试中,99.99+%以上的游戏都没有出现单个联通块合法方案数超过一千万的情况。
2.3 考虑剩余雷数,计算精确概率
需要注意的是,因为有剩余雷数的限制,联通块内部的概率其实是不准确的,例如
因此我们计算每格概率的时候需要考虑剩余雷数的限制。
在枚举过程中,对每个联通块我们可以统计出:
对每个格子
那么我们依次考虑每个格子的胜率。除开格子本身所在的联通块不看,考虑其它所有联通块(假设一共有
复杂度显然还可以优化,但这部分在整个算法中占比不大。
假设当前剩下
2.4 基于概率的贪心算法
这一步后可以使用贪心算法:如果没有确定无雷的格子,那么点击概率最小的格子,概率相同时先点角,角开完之后点附近5*5的地图里打开格子数最多的格子。
到这一步胜率可以达到39%了,但其实贪心的规则是可以调整的,我这里没深入尝试了,很可能存在一些贪心方法可以大幅度提升胜率。
3. 计算点击每一格的获胜概率
3.1 有雷的概率不代表胜率
我们看一个经典的例子
在一些情况下,有雷概率低的格子胜率还不如有雷概率高的格子,因此我们需要用更严格的方法计算胜率
3.2 局面表示
我们考虑用一个列表 表示每个未知格上最终的数字(或者是雷),那么每种可能的情况称为一个解。一个局面可以表示为若干个解的集合,代表这个局面的所有可能解。例如我们用-1代表雷,那么之前的图片中
考虑一个局面
局面的转移关系是拓扑的,满足无后效性,因此可以用状态压缩的动态规划,
3.3 状态转移
最后的难点是3.2中,已知当前局面
对于一个局面,假设我们点开格子
实现这一步之后,就可以根据3.2的方程计算每一格的严格胜率了。
总结
这个算法理论上是可以严格计算出扫雷胜率的,但是复杂度过于爆炸了,实际上我只能在最后状态不多的时候开启算法3的胜率计算,但也足够将胜率提升到40.07%了。虽然这应该是目前最好的结果,但大家可以看到其中很多步骤调整的空间是很大的,稍微改改可能就有不小的提升。
开源代码地址
https://github.com/ztxz16/Mine
有兴趣的同学可以随意使用,顺手给个star就更好啦。