以前寫過一個帶AI的象棋程式,但是那時候上大二,寫的代碼奇亂無比,然後最近就抽空重寫了一個,這次寫的時候嫌在寫介面太麻煩,就直接按照UCCI協議寫個引擎,然後直接網上找個介面,簡單、粗暴、有效,目前我的程式在開中局的時候3秒內可以達到7-8層,殘局的時候5秒可以到9-10層,美中不足的是評估函數實在太粗暴了。
我是用c#寫的,而且本人貌似只會c#。。期間無數次的參考象棋百科全書網,網址http://www.xqbase.com,有興趣的可以去看下,很不錯的一個網站。
一個引擎說白了就是一個能根據輸入局面等相關資訊,給出相應反饋資訊的控制台程式。
引擎接受介面傳來的訊息【控制台的輸入】,根據這個訊息處理引擎內部需要做的事情,然後以控制台輸出的方式作出反饋。我只知道介面會截獲引擎的標準輸入輸出資料流,然後和引擎互動資訊,具體怎麼實現的,沒有去研究。。
一個引擎大概可以分以下幾大步:
1.監聽輸入
2.解析指令
3.運行相應的計算
4.輸出相應反饋
在我自己的程式中,大概分為下面幾部分:
1.監聽輸入
2.解析指令
3.執行計算
4.輸出反饋
5.記錄日誌
首先需要解析介面發來的UCCI指令,這就需要一個解析指令的類,在我的程式中是UCCIHelper類;然後我們知道了介面讓我們幹什麼,但是我們還沒幹,就需要根據指令和指令參數作出相應的動作。在我的程式中只支援以下幾種指令:
1.ucci:
這個指令是在引擎剛被介面啟動之後傳給引擎的,屬於引導期指令,他告訴引擎現在採用的協議是UCCI協議,引擎在這個時候要初始化引擎的一些資料設定一些參數等,最後反饋引擎名稱,版本,著作權等資訊,最後反饋 ucciok,以這個反饋來告訴介面準備工作就緒,此後引擎將處於空閑狀態【引擎剛啟動的時候處於引導狀態】。
2.position fen <fen> moves <...>
這個指令告訴引擎一個局面和在這個局面之後又走了哪幾步,引擎就需要根據這個資訊設定引擎內部表示棋盤的數組值,以及當前走子方,記錄走過的局面,等等,
此後引擎不做任何反饋,等待別的指令,引擎依然處於空閑狀態。
3.go [ponder][depth][time] []movestogo]
這個指令告訴引擎開始計算,引擎將根據這個指令的參數設定數值,開始計算,此後引擎進入思考狀態,在計算完成後,或者逾時後,引擎輸出bestmove xx ponder xx作為反饋,然後返回空閑狀態。
例如:go depth 7,引擎將以當前設定的棋盤數值和相關資料開始深度為7的計算,當計算完成後,反饋 bestmove b2e2 ponder b9c7,以此反饋告訴介面當前局面最佳走法是中炮,並且猜測對手可能跳馬。
4.ponderhit
這個指令告訴引擎,猜測的對手走法命中,此指令通常是引擎處於後台思考狀態中,收到這個指令後,引擎自己的時鐘開啟。
5.stop
這個指令要求引擎立即停止計算,反饋最佳走法。
我的程式就只支援這五種指令,但已經和介面互動的很好了【對我來說,嘿嘿】
這些都是和介面的互動等等就不說了,主要記錄下核心部分的設計和遇到的問題:
1.資料表示
資料表示,無非就是棋子和棋盤,在我的程式中棋子用的是byte 1到7來表示七種不同的類型棋子,棋盤用的是一個大小為256的一維數組。
2.產生走法
這一部分也是整個引擎的相當大的一部分,要設計出好的走法產生函數還是很值得研究的,因為這一部分是程式運行時佔用時間最多的。首先,我們需要找到棋盤上一個自己的棋子,然後根據他的走法規則,產生他能走的所有走法,最後把棋盤上所有自己的棋子能走的走法匯總到一個集合中,然後返回這個集合供其他部分使用。
3.搜尋
搜尋就是你一步我一步輪流走,把可能的情況都逐個推演。
4.評估局面
搜尋的目的就是逐一推演可能的情況,但是都推演了一遍,怎麼比較哪一種對自己比較好,就需要對局面做評估,給一個局面的好壞打一個分數。
核心就這麼四部分,就目前來說,所有的博弈軟體都逃脫不了這種架構,但是就每一步而言每一種博弈軟體都不盡相同。
在我的程式中,產生走法用到了一些數組,來避免不必要的迴圈,但是車和炮的走法不知道怎麼可以避免迴圈,搜尋用的是α-β剪枝,這個演算法對待搜尋的節點順序相當敏感,正規的軟體中都有N多種排序,而在我的程式中只有兩種排序,第一種是先搜尋吃子的走法,按吃最有價值的子排序,剩下的不吃子走法按曆史表排序,各種各樣的排序策略被稱為啟發,評估那一部分直接照搬的開源的象棋引擎 ElephantEye 的子力價值表作為評估函數的核心,在評估這一部分比較專業一點還要使用靜態搜尋來克服水平線效應,但是我的程式搜尋速度比較慢,吃不消靜態搜尋帶來的資源開銷,就沒有用,有一個能加快搜尋的方法就是空步向前裁剪,這個大概能提高2-3層,但是在殘局和有些特殊局面的情況下,空步向前裁剪會出大問題,比如那些不走棋要比走任何一步都好的局面,例如困憋,在我的程式中,也沒有扛得住能提高2-3層這個誘惑使用了這個技術,但是他的負面效應我僅僅做了在殘局的時候不允許使用空步向前裁剪策略這一預防方法,比如開局的時候,紅方中炮,我的程式執黑,他在不使用空步向前裁剪的時候,3-9層的搜尋結果都是跳正馬,但是使用了空步向前裁剪之後3-7層是跳正馬,從第八層開始,他就認為跳邊馬比跳正馬要好,這是不合理的,具體原因沒有找到,還有一個問題就是對於同一個局面奇數層和偶數層搜尋返回的分值經常是一個正一個負,數值也不同,按道理來說,對於同一個局面,奇數層和偶數層不應該差到變符號的地步,頂多就是數值不同而已。
現在把源碼地址貼下,裡面的注釋甚至要多於這篇文章,有興趣的可以下載下來看看,有共同興趣的可以加我qq好友,嘿嘿。
原始碼地址:
http://files.cnblogs.com/lipf/EvilGeniusChessEngine.rar
轉載請註明出處