華容道遊戲(中)

此爲《算法的樂趣》讀書筆記,我用javascript(ES6)從新實現算法。javascript

華容道遊戲看似簡單,但求解須要設計的數據結構比較複雜,還牽涉到棋類遊戲的棋局判斷,因此整個過程仍是挺費勁的。我儘可能用面向對象的思想來進行封裝,整個過程將分紅幾個部分記錄下來,今天是第二部分,棋局處理Zobrist算法原理及實現。

Zobrist算法原理

Zobrist哈希算法是一種適用於棋類遊戲的棋局編碼方式,經過創建一個特殊的轉換表,對棋盤上每個位置的全部可能狀態賦予一個毫不重複的隨機編碼,經過對不一樣位置上的隨機編碼進行異或計算,實如今極低衝突率的前提下將複雜的棋局編碼爲一個整數類型哈希值的功能。java

Zobrist哈希算法步驟:

  • 識別出棋局的最小單位(格子或交叉點),肯定每一個最小單位上的全部可能的狀態數。以華容道的棋局爲例,最小單位就是20個小格子,每一個格子有五種狀態,分別是空狀態、被橫長方形佔據、被堅長方形佔據、被小方格佔據和被大方格佔據。
  • 爲每一個單位上的全部狀態都分配一個隨機的編碼值。棋類遊戲通常須要「行數×列數×狀態數」個狀態,以華容道爲例,須要爲5×4×5=100個狀態分配編碼值。
  • 對指定的棋局,對每一個單位上的狀態用對應的編碼值(隨機數)作異或運算,最後獲得一個哈希值。

Zobrist哈希算法優勢:

  • 衝突機率小,只要隨機編碼值的範圍夠大,棋局哈希衝突的機率很是小,實際應用中基本上不考慮衝突的狀況。
  • 棋局發生變化時,沒必要對整個棋局從新計算哈希值,只須要計算髮生變化的那些最小單元的狀態變化便可。

Zobrist算法實現

編碼表定義

編碼表定義爲一個三維數組。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]
    }
}

計算棋局Zobrist哈希值

對棋盤的格子逐個處理,根據棋盤格子的武將信息獲取武將的類型,從而獲取該類型對應的編碼值,用此編碼值參與哈希值進行異或運算。算法

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;
}

取鏡像Zobrist哈希值

棋盤狀態左右鏡像問題:兩個棋局雖然武將的位置不同,可是若是忽略武將的名字信息,單純從形狀上看是左右對稱的鏡像結構。對於華容道遊戲來講,這種左右鏡像的狀況對於滑動棋子尋求結果的影響是同樣的。

鏡像即左右對稱,進行一個座標變換便可獲得。數組

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

小結

儘可能使用面向對象的思想來解決問題,讓數據和操做綁定在一塊兒,努力使代碼容易看懂。測試

相關文章
相關標籤/搜索