五子棋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))
'===============================冲棋延伸结束==============================
那么,这集就结束了。
本集代码:
下一集预告:
静态搜索。而静态搜索之后,将会是置换表。置换表的发布可能要久一些。因为这几天时间比较少,要实施好爸爸计划,还有一些其他的事情需要处理。
全部文章和源码整理完成,以后更新也会在下面地址: