五子棋AI算法第七篇-Zobrist

關於本博客

這個博客不是把五子棋算法研究透徹之後再寫的,而是一邊研究算法一邊寫代碼,同時一邊寫博客,所以有些博文的順序不太對,比如 Zobrist 其實應該放在算殺之前就講的。不過這並沒有大的影響,總體上的順序是OK的。

另外,這一系列博客講的五子棋代碼其實是一個開源的項目,源碼地址: https://github.com/lihongxun945/gobang

由於是邊寫代碼邊寫博客,所以博客中的代碼不是最新的,甚至是有bug的,所以源碼請儘量參考上述開源項目中的代碼。比如之前講極大極小值搜索改爲負極大值的時候,對玩家的評分就出現了一個重要bug,在後序的提交中修正了這個bug。

Zobrist

Zobrist 是一個快速Hash算法,非常適合用在各種棋類遊戲中(事實上也是在各種棋類遊戲中有大量應用)。

我們前面講了負極大值搜索和算殺,其實很多時候會有重複的搜索,比如這種:

[7,7],[8,7],[7,6],[7,9]

其實它和下面這種的走法只是順序不同 ,最終走出來的局面是一樣的:

[7,6],[7,9],[7,7],[8,7]

那麼如果我們搜索中碰到了上面兩種情況,我們會對兩種情況都進行一次打分,而其實有了第一次的打分,完全可以緩存起來,第二次就不用打分直接使用緩存數據了。除了這種情況,其實以前的搜索結果也可以存下來,可以用在啓發式搜索中。

那麼現在的問題就是,我們應該怎麼表示一種局面呢?顯然需要通過一種哈希算法,而且這個算法不能太慢,不然可能反而會降低搜索速度。而 Zobrist 就是一種滿足我們需求的快速數組哈希算法。關於Zobrist算法請參考 https://en.wikipedia.org/wiki/Ben_Zobrist

Zobrist 效率非常高,每下一步棋,只需要進行一次 異或 操作,相對於對每一步棋的打分來說,這一次異或操作帶來的性能消耗可以忽略不計。Zobrist具體實現如下:

  • 初始化一個兩個 Zobrist[M][M] 的二維數組,其中M是五子棋的棋盤寬度。當然也可以是 Zobrist[M*M] 的一維數組。設置兩個是爲了一個表示黑棋,一個表示白旗。
  • 上述數組的每一個都填上一個隨機數,至少保證是32位的長度(即32bit),最好是64位。初始鍵值也設置一個隨機數。
  • 每下一步棋,則用當前鍵值異或Zobrist數組裏對應位置的隨機數,得到的結果即爲新的鍵值。如果是刪除棋子(悔棋),則再異或一次即可。

對應的JS代碼如下:

var Zobrist = function(size) {
  this.size = size || 15;
}

Zobrist.prototype.init = function() {
  this.com = [];
  this.hum = [];
  for(var i=0;i<this.size*this.size;i++) {
    this.com.push(this._rand());
    this.hum.push(this._rand());
  }

  this.code = this._rand();
}

Zobrist.prototype._rand = function() {
  return Math.floor(Math.random() * 1000000000);  //再多一位就溢出了。。
}

Zobrist.prototype.go = function(x, y, role) {
  var index = this.size * x + y;
  this.code ^= (role == R.com ? this.com[index] : this.hum[index]);
  return this.code;
}

源碼在 zobrist.js 文件裏。

注意每次走棋都要進行一次zobrist操作。千萬不要自行設計哈希函數,除非你能保證你的哈希函數比一次64位整數的異或操作更簡單,並且同時證明衝突的概率很低。Zobrist數組中的隨機數的 質量 很重要,不過我用JS內置的 Math.random() 生成的隨機數暫時沒有發現問題,如果這個隨機度不夠高,可以考慮換用一些更好的隨機函數。

有了這個快速hash算法,我們就可以通過一個64位的整數來表示一個棋局。至於該存哪些信息,該怎麼使用,下一篇再講。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章