UCT演算法算是介紹完了,以後主要講實踐。
由於在物件導向的程式設計語言中,本人只會C++和Objective-C(以後也許會寫一些iOS開發相關的博文),出於效能的考慮,選擇C++是必然的。記得當初開題答辯時,一老師問道,既然考慮效能,為什麼不用C,而用C++?對於這種明知故問無厘頭的問題,我只能回答:機器碼效能更好。後來才知道陳志行老師的”手談“是用彙編寫的……
本篇還是談談棋盤的資料結構吧!(這裡就不談整個程式的架構了,因為本來就設計得不好)
通常而言圍棋棋盤是19X19大小的,但電腦圍棋就各種大小都有了(不會超過19路是肯定的,原因……)。所以程式要支援可變大小的的棋盤,能想到的有兩種方法:
1)通過一個變數表示棋盤大小,在程式運行時動態分配棋盤空間。
2)用C++的模板,將橫盤大小作為棋盤類的模板參數。
第1)種方法需要在程式運行時刻動態申請推空間,而第2)種方法則是在程式運行時由OS分配棧空間,效率上大優於第1)種。考慮到在程式中,棋盤大小不會頻繁變動,而效率因素是很重要的,因此採用第2)種方法!(我的程式Foolish Go中誤採用了第1)種方法……)
下面來討論棋盤資料的具體儲存方法:
棋盤是為了表明當前棋局狀態的所有資訊,考慮現實中的棋盤,有哪些資訊呢?
1)棋盤上每一個交叉點的狀態(黑子,白子,空點)。2)當前的落子方。3)劫位。
在橫盤類中同樣需要表示這3個資訊,需要仔細考慮的是,怎樣表示棋盤上每一個交叉點的狀態?
交叉點狀態應該是什麼類型呢?布爾?但是有3種狀態,布爾表示不過來~整型?
整型確實可以,也確實有程式用短整型表示交叉點狀態。但從節省棋盤記憶體空間的角度考慮,整型是不夠的!
沒錯,還是布爾!不過要兩個~
我的程式Foolish Go借鑒了GNU Go(沒記錯的話)的做法——用兩個棋盤——一個黑棋盤,一個白棋盤來表示棋局狀態,其實質是用兩個布值表示一個交叉點狀態。來計算一下棋盤的大小:9 * 9 * 4bit = 324bit,補8的話就是328bit。
什嗎?一個布爾值佔8bit?MS是的……但是可以自己寫代碼打包啊,讓數組中的每一個布爾值都佔1bit!而通過位元運算的讀寫操作是不會太費時的。
什嗎?懶得寫!嗯,我也懶得寫,但還是有辦法——C++的STL中有個叫bitset模板類的,數組長度作為模板參數……不用擔心,它是靜態分配記憶體空間的~(我的Foolish Go用了vector<bool>,雖說vector<bool>是vector模板對bool作了特化,也進行了打包,但它是動態分配空間的……)這樣做除了節省記憶體空間,另一個好處是減少深拷貝棋盤的時間複雜度。
這裡有一個問題,棋盤是二維的,而我們用一維的數組,這就需要一維的數組下標與二維數組間的轉化。用一個函數做這個工作顯然是必須的,但效能是個問題。用C語言的利器,宏函數?可以,但太難看了,C++標準不建議這麼做,而內嵌函式是個不錯的選擇,用來實現頻繁調用的短小功能。
棋盤的資料結構就討論到此了,打算下篇開始講“提子”——這是常規演算法部分的痛點,提子演算法設計得好壞將極大影響程式的效能。