天天看點

QT五子棋詳解之九:置換表,zobrist哈希,生成32位和64位随機數

QT五子棋詳解之九:置換表,zobrist哈希,生成32位和64位随機數

我們很容易明白,在alpha-beta剪枝算法中,會遇到重複的棋局。如下面的兩個圖的走法是一樣的,不必要将兩個局面的得分都計算一次。

QT五子棋詳解之九:置換表,zobrist哈希,生成32位和64位随機數
QT五子棋詳解之九:置換表,zobrist哈希,生成32位和64位随機數

是以需要将棋盤的資訊儲存起來以備後續使用,這就是置換表。在棋類遊戲中經常使用的就是zobrist哈希。

zobrist哈希并不是一種最好的雜湊演算法,但是确實最高效的。有時候我們必須在最好與效率之間做出選擇。

zobrist是一個人的名字,源自他1970的年的一片論文

QT五子棋詳解之九:置換表,zobrist哈希,生成32位和64位随機數

試想一下你如何有效的存儲一個棋盤的資訊呢?zobrist提供了一種辦法:棋盤的一個唯一的辨別就是棋盤的所有狀态的異或值。

棋盤的每一個狀态實際就對應着一個随機數。在五子棋中,我們忽視空白,将每一個位置的狀态标示為黑棋或白棋兩種狀态。

第一步:是需要初始化每一個狀态的随機數。設定兩個數組。對于随機數的産生使用了Twister算法,開源項目:www.agner.org/random

其實就是用别人寫好的,調用一下函數,不要想得那麼複雜。rand函數隻能生成五位随機數,不推薦使用。

int WriteZobrist[15][15];      
int BlackZobrist[15][15];      
void  Game::InitZobrist()      
{      
int seed = (int)time(0);            // random seed      
// choose one of the random number generators:      
CRandomMersenne RanGen(seed);       // make instance of random number generator      
for(int i= 0;i<15;i++)      
{      
for(int j = 0;j<15;j++)      
{      
WriteZobrist[i][j]=RanGen.IRandom(0,INT_MAX);      
     BlackZobrist[i][j]=RanGen.IRandom(0,INT_MAX);      
}      
}      
}      

接下來對于這個棋局的哈希值,是多少呢?

黑棋在(6,6),(8,8)點

白棋在(6,8),(8,6)點

hashcode =WriteZobrist[6][8]^WriteZobrist[8][6]^BlackZobrist[6][6]^BlackZobrist[8][8]

QT五子棋詳解之九:置換表,zobrist哈希,生成32位和64位随機數

就這麼簡單啊兄弟。由于異或的特性,是以,在五子棋中,下一個子後,隻需要用hashcode異或一次就可以啦。

接下來就是考慮一下hash沖突,由于hash沖突問題常常需要加上一個64位的校驗位。來保證這個棋局是哈希表中的棋局,減少沖突。

longlong WZobrist[15][15];      
longlong BZobrist[15][15];      

第二、一定要注意,棋局轉到hashcode隻是第一步,第二步是要把hashcode轉換為位址存到hash表中。這兩步都可能會發生沖突。把hashcode轉換為位址存到hash表中簡單的辦法就是除表的大小取餘數。是以,哈希表越大沖突就越小。

第三、一定要存儲深度,我們來看一段zobrist在1970年的描述:

這段話就說明了深度是重要的,因為在博弈算法中,更深的分數意味着更穩健,是以,在第3層的局面可以作為di5層局面的參考,因為第3層搜尋得更深。這段話同時說明了在剪枝中如何使用。

QT五子棋詳解之九:置換表,zobrist哈希,生成32位和64位随機數

第四、在剪枝中還需要考慮一些額外的極大值極小值問題。着實有點繁瑣。是以必須要存儲極大值極小值資訊。

一段僞代碼:

QT五子棋詳解之九:置換表,zobrist哈希,生成32位和64位随機數

最後要說的是,置換表用在五子棋上沒多大的效率提升,一般是用在象棋等遊戲上。