华容道游戏(中)

此为《算法的乐趣》读书笔记,我用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

小结

尽可能使用面向对象的思想来解决问题,让数据和操做绑定在一块儿,努力使代码容易看懂。测试

相关文章
相关标签/搜索