JavaScript中國象棋程序(8) - 進一步優化


“JavaScript中國象棋程序” 這一系列教程將帶你從頭使用JavaScript編寫一個中國象棋程序。這是教程的第8節。

程序的最終效果點擊這里查看

這一系列共有9個部分:

0、JavaScript中國象棋程序(0)- 前言

 

在這最后一節,我們的主要工作是使用開局庫、對根節點的搜索分離出來、以及引入PVS(Principal Variation Search)主要變例搜索。

8.1、開局庫

這一節我們引入book.js文件。該文件中定義了一個二維數組BOOK_DAT。這個數組就是開局庫,保存的數據格式如下:

[lock, mv, vl]

其中,lock = zobristLock >>> 1(無符號右移1位,高位補0)

mv是步驟

vl是權重(隨機選擇走法的幾率,僅當兩個相同的lock有不同的mv時,vl的值才有意義,這是為了實現走棋的隨機性)。

開局庫是按照lock排序的,因此可以用二分查找。找到一項以后,把它前后lock相同的所有項都取出,從中隨機選擇一個mv。

壓縮開局庫的容量,所有對稱的局面只用一項,所以當一個局面在BOOK_DAT中找不到時,還應該試一下它的對稱局面是否在BOOK_DAT中。

8.2、對根節點的特殊處理

現在由於開局庫的加入,開局程序的走法具有了一定的隨機性。但如果是開局庫中沒有的局面,程序的走法依然是固定不變的。我們在根節點處,對搜索得到的分值,做小范圍的浮動,以此實現走法的隨機性。

vlBest += Math.floor(Math.random() * RANDOMNESS) - Math.floor(Math.random() * RANDOMNESS);

其中,RANDOMNESS = 8。新的分值會在區間(vlBest - 8, vlBest + 8 )浮動。

我們把對根節點的搜索分離出來,這有以下好處:

1、更方便實現走法的隨機性

2、沒有必要嘗試Beta截斷(根節點處Beta正無窮);

3、省略了檢查重復局面、獲取置換表、空步裁剪等步驟。

8.3、PVS主要變例搜索

經過前面的工作,走法已經得到了很好的排序,好的走法會先被搜索。這是PVS的基礎。

 a                                                                                 圖b

假設第一個走法是最好的走法,沒有引發剪枝,A點的搜索區間為(0, 100),走法1得到估值30。由於30  > 0,所以A點的alpha變為30,以后的搜索區間變為(30, 100),所以B2點的搜索區間為(-100, -30)。

可以進一步大膽地考慮,假設第1個走法就是最好的走法,那么后面走法得到的估值不會落在區間(30, 100)。所以從A點的第2個走法開始,要做的就是驗證這種假設,搜索區間為(30, 31)。由於搜索區間很小,搜索速度會很快。返回值vl有3種情況。

1)、vl <= 30。說明走法不比第1個走法好,假設成立。

2)、vl >= 100。返回值比A點的原有搜索邊界beta還大,應該剪枝,假設成立。

3)、30 < vl < 100。走法比第1個走法好,假設不成立。

 3種情況時,走法不成立,應該對該走法重新以(30, 100)區間進行搜索。如果得到40,則該走法就是最好的走法,后續搜索又對該走法進行假設驗證,區間為(40, 41)。

8.4、長將判負策略

程序會調用repStatus函數,判斷局面是否出現重復,以及長將,這在第6節已經介紹。如果出現長將,會得到-BAN_VALUE(其中,BAN_VALUE = MATE_VALUE - 100),再根據殺棋步數做調整。但是由於長將判負並不是對某個單純局面的評分,而是跟路線有關的,把這個分值直接存入置換表就不太合適了。

我們的解決辦法就是:獲取置換表時把“利用長將判負策略搜索到的局面”過濾掉。如果某個局面分值在WIN_VALUE(MATE_VALUE - 200)和BAN_VALUE之間,那么這個局面就是“利用長將判負策略搜索到的局面”。如果是通過repStatus函數得到了和棋的分值,會同樣被過濾掉。

我們仍舊把“利用長將判負策略搜索到的局面”記錄到置換表,因為這些局面提供的最佳走法是有啟發價值的。反過來說,如果“利用長將判負策略搜索到的局面”沒有最佳走法,那么這種局面就沒有必要記錄到置換表了。

8.5、核心代碼說明

本節的代碼可以在 Github 下載,也可以直接clone

git clone -b step-8 https://github.com/Royhoo/write-a-chinesechess-program

Position中新增或修改的主要屬性和方法:

1)、bookMove()

獲取開局庫中的走法。

Search中新增或修改的主要屬性和方法:

1)、searchRoot(depth)

對根節點的搜索。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM