五子棋AI循序渐进【4】接近人类的思考方式——迭代加深、棋盘剪裁、空步剪裁、冲棋延伸

注:PCGO函数扔未修正添子方法。请自行修改。

一、迭代加深

1、什么是迭代加深

所谓迭代加深,就是让alpha-beta剪裁运行深度1,然后运行深度1,2,然后运行深度1,2,3。

2、为什么进行迭代加深

这样做的好处就是可以在搜索完一次之后,得到排序的依据——历史表,然后下次搜索时,历史表会给出一个大约的方向——下哪一步更好,也就是更容易产生截断了。

3、这样做增加了多少开销

实际上,单纯考虑迭代加深,它可能会多运行一点时间,但是考虑到历史表,也差不多抵消这些时间了。更何况,还有置换表(我一直不太喜欢这个名字)。

4、如何实现迭代加深

想法很简单,实际上代码也很简单,只需要实现一个循环,for i = 1 to n,i是要扫描的深度,在这个循环中调用alpha-beta剪裁即可。代码就这么几行:

 

     ' ===============================迭代加深===============================
     ' 迭代加深搜索过程
     Function SearchMain()  As  Integer
         Dim i, t, vl  As  Integer
         ' 初始化
        pos.ClearnHistoryTable()  '  清空历史表
        mvsCompare.ms = pos.nHistoryTable
        t = My.Computer.Clock.TickCount  '  初始化定时器
        pos.nDistance =  0    ' 初始步数
        winplayr =  2         ' 胜利者
         ' 迭代加深过程
         For i =  1  To LIMIT_DEPTH_SearchFull -  1
            Debug.Print( " 正在迭代: " & i)
            vl = SearchFull(-MATE_VALUE, MATE_VALUE, i)
             ' 搜索到杀棋,就终止搜索
             If vl > WIN_VALUE  Then   ' 计算机胜利
                winplayr =  1
                 Exit  For
             End  If
             If vl < -WIN_VALUE  Then  ' 玩家胜利
                winplayr =  0
                 Exit  For
             End  If
             ' 超过一秒,就终止搜索
             If My.Computer.Clock.TickCount - t >  1000  Then
                 Exit  For
             End  If
         Next
        Debug.Print( " 迭代加深: " & i)
         Return pos.mvResult
     End Function
     ' ==============================================================================

 

整个循环中,先清空历史表(但是我感觉不应该同时置换表,当然这是后话);然后调用alpha-beta的递归函数就可以了。其他的判断都很容易理解。

 

 

二、棋盘剪裁

1、为什么剪裁棋盘

这可以大量剪裁掉不合理招法,在五子棋中,我们很容易遇见,如果下的子远每个离黑子和白子,那一定是臭棋。那么远离程度是多少呢,可能是4,因为要成5,所以最远只能离当前子4格,但是真的是这样吗?让我们大胆的猜测:其实第四格也是臭棋!为什么呢,中间有3个格的话,远离程度也太大了,需要在该方向上再连续下3子才能连起来。而我们,有多大机会能这样做呢?即使我们想,可我们成功的机会有多大呢?我想微乎其微——五子棋双方的缠绕(指互相冲堵,当然好棋是冲并且堵着……)是非常严重的,所以,我们的结论是3而不是4。

2、如何实现

我的代码中是先遍历棋盘,找到黑子或白子,然后把他们周围的非子点记录下来。当然这存在一个重复记录的问题,所以我是记录在一个bitarray里面,它的操作速度非常快,然后再次遍历它取出合理点。当然,另一种想法也许更好——遍历所有点,并记录每个三格以内有子的空点。但遗憾的是我最初意识到的是第一种做法。

 

这个代码在上一个示例里面已经在使用了。这里不再罗列。

 

三、空步剪裁

1、什么是空步剪裁

就是轮到自己不下子却不下,让对方接着下。

转载请注明出处:http://www.cnblogs.com/zcsor/

2、为什么进行空步剪裁

它基于这样一种想法:如果己方不下,那对方会下哪?这对于五子棋来讲,好处不仅仅是预测性的剪裁,更重要的是对方冲棋时,可以把冲棋点直接作为合理招法点:因为如果不去封堵,自己就要被杀死了!当然,在我的代码中,由于评价函数还不完善(这里主要指分数设置方面的不准确性),所以程序会去封堵对方的单个冲3或活2,这完全可以通过重新规划分值解决。

3、如何限制的空步剪裁

首先,什么时候进行空步剪裁。如果对方已经冲棋了(冲4,活3等)那么再让他走,无疑是不明智的。所以,只有对方不冲棋的时候,我们才进行空步剪裁。

其次,无限制的空步剪裁当然不是一个好办法。从预测的角度来看,一步貌似有点少,两步还可以,如果3步对方的冲2都成5了!所以,结论是2步。

4、如何实现

其实代码很简单,但是要建立在对alpha-beta剪裁已经充分理解的前提下。首先分析是否被冲棋,如果没有,那么空步剪裁;如果有,用冲棋点作为接下来迭代的”合理招法“。这就是思路。代码也就不复杂了,当然需要注意的是,空步剪裁对”深度“的影响:

 

         ' ====================================空步剪裁=====================================
         If pos.nDistance >  0  Then
             ' 1. 到达水平线
             If nDepth <=  0  Then  Return pos.Evaluate  '
             ' 1-1. 到达极限深度就返回局面评价
             If pos.nDistance = LIMIT_DEPTH_SearchFull  Then  Return pos.Evaluate()
             ' 1-2. 尝试空步裁剪(根节点的Beta值是"MATE_VALUE",所以不可能发生空步裁剪)
             If pos.Evaluate() = - 3000  Then           ' 被冲棋时,根据冲棋点返回值生成走法,而不是生成全部走法。
                 ' 遍历棋型信息,提取全部冲棋点。
                 For i =  0  To pos.Vectors.lnkinf.Count -  1
                     For j =  0  To pos.Vectors.lnkinf(i).cqpend
                        nGenMoves +=  1
                        mvs(nGenMoves) = pos.Vectors.lnkinf(i).cqp(j)
                     Next
                 Next
             Else                                     ' 未被冲棋时进行空步剪裁
                pos.NullMove()
                vl = -SearchFull(-vlBeta,  1 - vlBeta, nDepth - NULL_DEPTH -  1)
                pos.UnNullMove()
                 If (vl >= vlBeta)  Then
                     Return vl
                 End  If
             End  If
         End  If
         ' ==================================空步剪裁结束===================================

接下来我们讨论冲棋延伸

 

四、冲棋延伸

1、什么是冲棋延伸

在对方冲棋时,我们无限制的进行迭代,直至分析出最终解。

2、为什么进行冲棋延伸

当对冲棋时,往往会形成比较险的连冲棋,一步失误全盘皆输,所以我们需要分析到最终局面——确切知道到底走哪步棋才是明智的。当然,这非常耗时,但同时也为置换表提供了非常多宝贵的局面。

3、如何实现冲棋延伸

基于我们的想法,我们无限延长迭代,使之达到最终解。但实际上,还是做了一些限制,我们没有真正分析全部局面的时间。具体的限制就在上面的代码中,”达到极限深度就返回局面评价“。而真正的冲棋延伸,实现起来很简单:当被冲棋时,我们的迭代过程传入的参数不是n-1,而是n,这样深度不会减少到0,也就会继续分析下去:

 

             ' =================================冲棋延伸================================
             ' vl = -SearchFull(-vlBeta, -vlAlpha, nDepth - 1)
             ' =========以上为被替换代码==========
            vl = -SearchFull(-vlBeta, -vlAlpha,  IIf(pos.Evaluate = - 3000, nDepth, nDepth -  1))
             ' ===============================冲棋延伸结束==============================

 

 

那么,这集就结束了。

 

本集代码:

/Files/zcsor/清月连珠0.4.7z

 

 

下一集预告:

静态搜索。而静态搜索之后,将会是置换表。置换表的发布可能要久一些。因为这几天时间比较少,要实施好爸爸计划,还有一些其他的事情需要处理。

 

全部文章和源码整理完成,以后更新也会在下面地址:

http://www.vbdevelopers.org

http://www.softos.org

 

你可能感兴趣的:(五子棋)