python扫雷代码_扫雷游戏的布雷算法、策略与优化(附Python代码)

eb9ebe3c9317b40b108e71a9bab977f3.png

1 布雷算法的应用

在扫雷游戏中,将雷均匀地分布在局面中依靠一种布雷算法。众所周知,在原始版本的Windows扫雷中,由于布雷算法的缺陷,使得其存在Board Cycle(局面循环)。而标准扫雷游戏中,要求每一个位置是否为雷都是独立随机事件,因此需要设计一种数学上可靠的布雷算法。

标准的扫雷游戏是在第一次左键弹起时开始布雷的,由于布雷的速度非常快(快于一帧),玩家一般不易感觉到游戏的停顿。但是在玩超大尺寸的扫雷时,仍然需要保证玩家感觉不到游戏的停顿,这就需要有一种快速可靠的布雷算法。

此外,在测算各局面的3BV的概率分布时,目前的主流做法仍然是通过大量的局面仿真。为得到精确可靠的3BV的概率分布,曾有玩家连续仿真超过1万亿局高级局面。在这个过程中,布雷算法的性能是否优越,极大地影响到仿真能否快速完成。

2 扫雷的布雷算法

本文介绍几种扫雷的布雷算法。为突出重点,所有算法不考虑起手不能点到雷的规则,并且所有布雷算法的输出均为一维数组,其长度为高×宽;在这个一维数组中,雷用数字-1表示,非雷用0表示。代码均采用Python编写,在代码中,Row为局面的高,默认为16;Column为局面的宽,默认为30;Num为总雷数。每个代码均执行10000次,并在普通pc上重复运行若干次,取最短用时为这种算法的用时。

2.1 算法一——朴素的算法

最朴素简单的算法就是,随机选一个位置,如果没有雷就摆一个雷,这个方法与现实中物理手段的布雷很像。具体步骤是这样的:

Step1:随机寻找一个位置

Step2:如果该位置是空的,那么则布雷

Step3:如果该位置是雷,那么返回step1

Step4:直到布雷数达到总雷数

代码如下:

y=[0]*Row*Column
Numi=0
while Numi < Num:
    while 1:
        idd = random.randint(0, Row*Column-1);
        if y[idd] != -1 :
           y[idd] = -1
           Numi+=1
           break

经测试,当雷数为99时,算法的运行时间为1.2754秒。但是这种算法在雷数较大时性能急剧下降,因为随机选择的位置大概率是雷。当雷数为479时,算法的运行时间长达33.0841秒。

2.2 算法二——性能恒定

算法一的性能受雷数影响非常大,算法二的性能则完全不受雷数的影响。这种算法有一点动态规划的思想,其基本思路是,依次访问一维局面数组的每一个位置,并按照采用某种方法计算出的概率,来决定该位置是否为雷。在这个过程中,需保证每一个格子为雷的综合概率正好是 总雷数/总面积

步骤为:

Step1:从起始位置开始依次访问数组的每个位置

Step2:当前位置布雷的概率为:未布的雷数/未访问的位置数

Step3:直到遍历完整个数组

代码如下:

y=[0]*Row*Column
mineLeft=Num
for point in range(0,Row*Column):
    if random.randint(1,Row*Column-point)<=mineLeft:
        y[point]=-1
        mineLeft-=1

经测试,当雷数为99时,算法的运行时间为5.0708秒。当雷数为479时,算法的运行时间为5.5179秒。即这种算法的性能基本不受雷数影响。

2.3 算法三——算法一的改进算法

算法三是对算法一的直接改进。既然算法一的性能的下降是由于随机选择的位置大概率是雷,那么我们只要在每次选择出布雷位置后,将这个位置从下次挑选范围内除去即可。

即维护两个列表:AllId和MineId,前者是所有不是雷的方格ID,后者是所有雷的ID。

代码如下:

y=[0]*(Row*Column)
AllId=list(range(0,Row*Column))
MineId=[]    
MineLaid=0
while MineLaid<Num:
    Id=random.randint(0,Row*Column-MineLaid-1)
    MineId.append(AllId[Id])
    AllId.pop(Id)
    MineLaid+=1
for i in MineId:
    y[i]=-1

经测试,当雷数为99时,算法的运行时间为1.4729秒。当雷数为240时,算法的运行时间为3.5417秒。当雷数为479时,算法的运行时间为6.8174秒。

2.4 算法四——洗牌算法

可以看到,算法一在雷数较少时比较快,而算法二在雷数较多时比较快。算法三虽然综合了这两种算法的优点,但似乎又不够优雅,尤其是维护两个列表的操作似乎比较繁琐。

那么有没有一种算法能同时超越以上三种算法呢?答案是肯定的。接下来介绍的就是大名鼎鼎的洗牌算法。

洗牌算法的步骤为:

Step1:初始化一维局面数组,使得前Row*Column-Num个位置均为0,后Num个位置均为-1

Step2:遍历数组从id= Row*Column-Num到id=Row*Column-1的位置

Step3:交换当前访问位置与在当前访问位置之前随机选取的位置的值

代码如下:

y=[0]*(Row*Column-Num)
y=y+[-1]*Num
for i in range(Row*Column-Num,Row*Column):
    idd=random.randint(0,i-1)
    y[idd],y[i]=y[i],y[idd]

经测试,当雷数为99时,算法的运行时间仅为1.1901秒。当雷数为479时,算法的运行时间仅为5.4258秒。可见洗牌算法在各方面均超越了前面三种算法。

2.5 算法五——改进的洗牌算法

在标准洗牌算法的基础上,我们很容易能做出一个重大的改进。

简单地说就是,当雷数少于一半时,算法不变;但当雷数多于一半时,我们把非雷看作雷,而把雷看作非雷。这样仅仅添加一个选择语句后,就兼顾了算法在雷数较多与较少时的性能。

代码如下:

if Num<Row*Column/2:
    y=[0]*(Row*Column-Num)
    y=y+[-1]*Num
    for i in range(Row*Column-Num,Row*Column):
        idd=random.randint(0,i-1)
        y[idd],y[i]=y[i],y[idd]
else:
    y=[-1]*Num
    y=y+[0]*(Row*Column-Num)
    for i in range(Num,Row*Column):
        idd=random.randint(0,i-1)
        y[idd],y[i]=y[i],y[idd]

经测试,当雷数为99时,算法的运行时间为1.2161秒。当雷数为240时,算法的运行时间为2.9356秒。当雷数为479时,算法的运行时间仅为0.0492秒。

2.6 算法六——极限

在算法五的基础上引入一些技巧,还以进一步提升算法的性能。

优化后的代码如下:

area=Row*Column    
if Num<area/2:
    y=[0]*(area-Num)
    y=y+[-1]*Num
    for i in range(area-Num,area):
        idd=random.randint(0,i-1)
        if y[idd]!=-1:
            y[idd]=-1
            y[i]=0
else:
    y=[-1]*Num
    y=y+[0]*(area-Num)
    for i in range(Num,area):
        idd=random.randint(0,i-1)
        if y[idd]!=0:
            y[idd]=0
            y[i]=-1

经测试,当雷数为99时,算法的运行时间为1.1559秒。当雷数为240时,算法的运行时间为2.7441秒。当雷数为479时,算法的运行时间为0.0481秒。

3总结

其实在大部分情况下,这六种方法都是可以满足普通扫雷游戏中的要求的,只有在计算3BV的概率分布时,对布雷算法的优化研究才有价值。

扫雷游戏的布雷算法介绍到此结束,本文旨在抛砖引玉。对扫雷有兴趣的朋友可继续研究,后续问题可以在后台留言!

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值