注: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))
' ===============================沖棋延伸結束==============================
那么,這集就結束了。
本集代碼:
下一集預告:
靜態搜索。而靜態搜索之后,將會是置換表。置換表的發布可能要久一些。因為這幾天時間比較少,要實施好爸爸計划,還有一些其他的事情需要處理。
全部文章和源碼整理完成,以后更新也會在下面地址:
