五子棋AI算法第八篇-重構代碼

爲什麼需要重構

之前的代碼有很多鬆散的模塊組合在一起。在把 Zobrist 集成進去時,會發現全部需要走棋的操作其實都需要進行一次 Zobrist 異或操作。另外在邏輯上,其實很多模塊都是可以合併到同一個類的,所以這次把代碼進行了一次大的重構。所以如果發現博客說的一些模塊找不到了也是很正常的,因爲大部分模塊都被移到了 Board 類中。

這次重構主要的工作就是 把AI相關的代碼分成了四個模塊:

  1. Board ,所有和棋子相關的操作都在這裏,包括打分,判斷勝負,zobrist緩存,啓發函數等。
  2. negamax 搜索模塊
  3. checkmate 算殺模塊
  4. 外殼,配置,一些輔助方法等,包括 ai.js, role.js

集成 Zobrist

把所有走棋操作都放到 Board 類中之後,只需要在 board 中進行 zobrist 異或操作就可以。可以避免在搜索或者算殺中進行Zobrist 操作。

具體的做法是在如下三個方法中都進行 zobrist 更新

//下子
Board.prototype.put = function(p, role, record) {
this.board[p[0]][p[1]] = role;
this.zobrist.go(p[0], p[1], role);
this.updateScore(p);
if(record) this.steps.push(p);
}

//移除棋子
Board.prototype.remove = function(p) {
var r = this.board[p[0]][p[1]];
this.zobrist.go(p[0], p[1], r);
this.board[p[0]][p[1]] = R.empty;
this.updateScore(p);
}

//悔棋
Board.prototype.back = function() {
if(this.steps.length < 2) return;
var s = this.steps.pop();
this.zobrist.go(s[0], s[1], this.board[s[0]][s[1]]);
this.board[s[0]][s[1]] = R.empty;
this.updateScore(s);
var s = this.steps.pop();
this.zobrist.go(s[0], s[1], this.board[s[0]][s[1]]);
this.board[s[0]][s[1]] = R.empty;
this.updateScore(s);
}

這樣任何時候完成走棋之後,都可以通過 board.zobrist.code 來獲取當前的哈希值。然後在 Negamax 或者 checkmate 中就可以以這個code 爲當前棋局的唯一標示來存儲分數。

目前實現了一個最簡單的方式,就是把每個棋局的分數存下來,下次如果遇到相同的棋局就不用打分了,而是直接取上次存儲的分數。但是這裏存儲分數的時候要同時存儲升深度,因爲只有深度大於等於當前深度的分數纔是有效的。具體實現可以看 Board 類中的代碼。

這樣的實際效果其實並不理想,大約 10%的命中率,雖然10%也是很客觀的提升,但是其實對最終走棋速度影響並不大,也就是加不加這種方式的置換表並沒有能感覺到的速度提升。
實際上對置換表最好的應用,應該是用在啓發搜索中對待選節點進行篩選和排序,但是具體怎麼實現暫時還是沒有想好。

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