此爲《算法的樂趣》讀書筆記,我用javascript(ES6)從新實現算法。javascript
華容道遊戲看似簡單,但求解須要設計的數據結構比較複雜,還牽涉到棋類遊戲的棋局判斷,因此整個過程仍是挺費勁的。我儘可能用面向對象的思想來進行封裝,整個過程將分紅幾個部分記錄下來,今天是第二部分,棋局處理Zobrist算法原理及實現。
Zobrist哈希算法是一種適用於棋類遊戲的棋局編碼方式,經過創建一個特殊的轉換表,對棋盤上每個位置的全部可能狀態賦予一個毫不重複的隨機編碼,經過對不一樣位置上的隨機編碼進行異或計算,實如今極低衝突率的前提下將複雜的棋局編碼爲一個整數類型哈希值的功能。java
編碼表定義爲一個三維數組。git
class Zobrist{ constructor(){ //三維表屬性 this.zobHash = [] for(let i = 0; i < HRD_GAME_ROW; i++) //初始化 { this.zobHash.push([]) for(let j = 0; j < HRD_GAME_COL; j++) { this.zobHash[i].push([]) for(let k = 0; k < MAX_WARRIOR_TYPE; k++) { do{ var tmp = Math.random() tmp = Math.floor(tmp * Math.pow(2,15)) //對16位隨機整數值 }while(!tmp) //跳過零值 this.zobHash[i][j].push(tmp) } } } } get(i,j,k){ //get接口 return this.zobHash[i][j][k] } }
對棋盤的格子逐個處理,根據棋盤格子的武將信息獲取武將的類型,從而獲取該類型對應的編碼值,用此編碼值參與哈希值進行異或運算。算法
function getZobristHash(zobHash, state) { let hash = 0; let heroes = state.heroes; for(let i = 1; i <= HRD_GAME_ROW; i++) { for(let j = 1; j <= HRD_GAME_COL; j++) { let index = state.board[i][j] - 1; //取得格子上武將序號 let type = (index >= 0 && index < heroes.length) ? heroes[index].type : 0; //數組索引值超出範圍,定爲零 hash ^= zobHash.get(i - 1,j - 1,type); //異或計算 // console.log(index+'--'+type+'--'+zobHash[i - 1][j - 1][type]+'<=>'+hash) } } return hash; }
棋盤狀態左右鏡像問題:兩個棋局雖然武將的位置不同,可是若是忽略武將的名字信息,單純從形狀上看是左右對稱的鏡像結構。對於華容道遊戲來講,這種左右鏡像的狀況對於滑動棋子尋求結果的影響是同樣的。
鏡像即左右對稱,進行一個座標變換便可獲得。數組
function getMirrorZobristHash(zobHash, state) { let hash = 0; let heroes = state.heroes; for(let i = 1; i <= HRD_GAME_ROW; i++) { for(let j = 1; j <= HRD_GAME_COL; j++) { let index = state.board[i][j] - 1; let type = (index >= 0 && index < heroes.length) ? heroes[index].type : 0; //(HRD_GAME_COL - 1) - (j - 1)) 座標變換 hash ^= zobHash.get(i - 1,HRD_GAME_COL - j,type); } } return hash; }
設計了三個棋局,測試目標:同一個棋局的Zobrist哈希與鏡像哈希相等,鏡像棋局的Zobrist哈希與其鏡像哈希相等。數據結構
var zobHash = new Zobrist() var gameState = new HrdGameState() var gameStateL = new HrdGameState() var gameStateR = new HrdGameState() var hs = [new Warrior(WARRIOR_TYPE.HT_VBAR,0,0), new Warrior(WARRIOR_TYPE.HT_BOX,1,0), new Warrior(WARRIOR_TYPE.HT_VBAR,3,0), new Warrior(WARRIOR_TYPE.HT_VBAR,0,2), new Warrior(WARRIOR_TYPE.HT_HBAR,1,2), new Warrior(WARRIOR_TYPE.HT_VBAR,3,2), new Warrior(WARRIOR_TYPE.HT_BLOCK,0,4), new Warrior(WARRIOR_TYPE.HT_BLOCK,1,3), new Warrior(WARRIOR_TYPE.HT_BLOCK,2,3), new Warrior(WARRIOR_TYPE.HT_BLOCK,3,4) ] var hsl = [ new Warrior(WARRIOR_TYPE.HT_BOX,0,0), new Warrior(WARRIOR_TYPE.HT_VBAR,2,0), new Warrior(WARRIOR_TYPE.HT_VBAR,3,0), new Warrior(WARRIOR_TYPE.HT_HBAR,0,2), new Warrior(WARRIOR_TYPE.HT_BLOCK,2,2), new Warrior(WARRIOR_TYPE.HT_VBAR,3,2), new Warrior(WARRIOR_TYPE.HT_VBAR,0,3), new Warrior(WARRIOR_TYPE.HT_BLOCK,1,3), new Warrior(WARRIOR_TYPE.HT_BLOCK,1,4), new Warrior(WARRIOR_TYPE.HT_BLOCK,3,4) ] var hsr = [ new Warrior(WARRIOR_TYPE.HT_VBAR,0,0), new Warrior(WARRIOR_TYPE.HT_VBAR,1,0), new Warrior(WARRIOR_TYPE.HT_BOX,2,0), new Warrior(WARRIOR_TYPE.HT_VBAR,0,2), new Warrior(WARRIOR_TYPE.HT_BLOCK,1,2), new Warrior(WARRIOR_TYPE.HT_HBAR,2,2), new Warrior(WARRIOR_TYPE.HT_BLOCK,2,3), new Warrior(WARRIOR_TYPE.HT_VBAR,3,3), new Warrior(WARRIOR_TYPE.HT_BLOCK,0,4), new Warrior(WARRIOR_TYPE.HT_BLOCK,2,4) ] gameState.initState(hs) gameStateL.initState(hsl) gameStateR.initState(hsr) console.dir(getZobristHash(zobHash,gameStateL) + '--' + getMirrorZobristHash(zobHash,gameStateR)) //兩值相等
代碼託管在開源中國,其中的hyd.js即華容道解法。dom
https://gitee.com/zhoutk/test
儘可能使用面向對象的思想來解決問題,讓數據和操做綁定在一塊兒,努力使代碼容易看懂。測試