python第六周项目华容道_华容道游戏(中)

此为《算法的乐趣》读书笔记,我用javascript(ES6)重新实现算法。

华容道游戏看似简单,但求解需要设计的数据结构比较复杂,还牵涉到棋类游戏的棋局判断,所以整个过程还是挺费劲的。我尽量用面向对象的思想来进行封装,整个过程将分成几个部分记录下来,今天是第二部分,棋局处理Zobrist算法原理及实现。

Zobrist算法原理

Zobrist哈希算法是一种适用于棋类游戏的棋局编码方式,通过建立一个特殊的转换表,对棋盘上每一个位置的所有可能状态赋予一个绝不重复的随机编码,通过对不同位置上的随机编码进行异或计算,实现在极低冲突率的前提下将复杂的棋局编码为一个整数类型哈希值的功能。

Zobrist哈希算法步骤:

识别出棋局的最小单位(格子或交叉点),确定每个最小单位上的所有可能的状态数。以华容道的棋局为例,最小单位就是20个小格子,每个格子有五种状态,分别是空状态、被横长方形占据、被坚长方形占据、被小方格占据和被大方格占据。

为每个单位上的所有状态都分配一个随机的编码值。棋类游戏一般需要“行数×列数×状态数”个状态,以华容道为例,需要为5×4×5=100个状态分配编码值。

对指定的棋局,对每个单位上的状态用对应的编码值(随机数)做异或运算,最后得到一个哈希值。

Zobrist哈希算法优点:

冲突概率小,只要随机编码值的范围够大,棋局哈希冲突的概率非常小,实际应用中基本上不考虑冲突的情况。

棋局发生变化时,不必对整个棋局重新计算哈希值,只需要计算发生变化的那些最小单元的状态变化即可。

Zobrist算法实现

编码表定义

编码表定义为一个三维数组。

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即华容道解法。

https://gitee.com/zhoutk/test

小结

尽量使用面向对象的思想来解决问题,让数据和操作绑定在一起,努力使代码容易看懂。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数字华容道游戏是一个经典的益智游戏,可以用Python来实现。以下是一个简单的实现: ```python import random # 初始化数字华容道 def init_board(): board = [i for i in range(1, 16)] board.append(0) random.shuffle(board) return board # 打印数字华容道 def print_board(board): for i in range(4): for j in range(4): print(board[i * 4 + j], end='\t') print() # 判断数字华容道是否已经完成 def check_win(board): for i in range(15): if board[i] != i+1: return False return True # 移动数字 def move(board, direction): zero_index = board.index(0) # 找到0的位置 if direction == 'left': if zero_index % 4 != 0: board[zero_index], board[zero_index-1] = board[zero_index-1], board[zero_index] elif direction == 'right': if zero_index % 4 != 3: board[zero_index], board[zero_index+1] = board[zero_index+1], board[zero_index] elif direction == 'up': if zero_index >= 4: board[zero_index], board[zero_index-4] = board[zero_index-4], board[zero_index] elif direction == 'down': if zero_index <= 11: board[zero_index], board[zero_index+4] = board[zero_index+4], board[zero_index] # 游戏循环 def main(): board = init_board() print_board(board) while True: direction = input('请输入移动方向(left/right/up/down):') move(board, direction) print_board(board) if check_win(board): print('恭喜你,游戏胜利!') break if __name__ == '__main__': main() ``` 在这个实现,`init_board`函数用来初始化数字华容道,`print_board`函数用来打印数字华容道,`check_win`函数用来判断数字华容道是否已经完成,`move`函数用来移动数字,`main`函数用来控制游戏循环。运行后,你可以输入`left`、`right`、`up`或`down`来移动数字,直到完成游戏
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值