存储玩家回合Zobrist哈希

Storing player turn in Zobrist hash

本文关键字:Zobrist 哈希 玩家 存储      更新时间:2023-10-16

我目前正在实现一个中文Checkers最小最大算法中的换位表。在中国跳棋中,没有棋子被捕获,棋盘在功能上有81个空格大。玩家轮流在棋盘上移动棋子。

该过程的一部分涉及为董事会状态创建哈希。到目前为止,我已经有了一个有效的方法,可以为每个板状态创建(希望)唯一的散列:

myHash = 0;
//randoms[81][3] is an array filled completely with pseudorandom values
for (int x = 0; x < 81; x++) {
myHash ^= randoms[x][board[x]]; 
//board[x] is the piece at space x (1=player1 piece, 2=player2 piece, 0=empty)
}

更重要的是,我在applyMove函数(和undoMove函数)中增量执行此操作:

applyMove(int to, int from) {
//Undo the 'from' piece in the hash
myHash ^= randoms[from][board[from]];
// Apply the move
std::swap(board[from], board[to]);
//Add the 'to' piece to the hash
myHash ^= randoms[to][board[to]];
// Update whose turn it is
swapTurn();
}

这是因为XOR函数的可逆性。

我现在遇到的问题是,哈希函数不存储轮到谁。也就是说,你可以有两个相同的游戏板,但它们在最小-最大算法中会返回不同的值,因为一个试图最大化分数,另一个试图最小化分数

基本上,我的问题是:我如何将玩家的回合存储在增量生成的哈希函数中,同时保持完美反转的能力(最好是成本低廉)?假设玩家的回合是一个整数而不是布尔值,因为游戏最终将有6名玩家而不是两名玩家。

您可以使用填充有伪随机值的turns[6]数组:

unsigned n = 6;  // number of players;
myHash ^= turns[active_player];           // 'undo' the old active player
active_player = (active_player + 1) % n;  // new active player's index
myHash ^= turns[active_player];           // 'add' new active player

这类似于工件位置增量更新,并且适用于n∈[2,6]。


作为旁注。。。

通常Zobrist哈希是通过扫描碎片的位置来完成的,排除空方块。空正方形的位置没有明确散列。

因此,您可以使用更小(更便于缓存)的阵列。类似于:

std::uint64_t randoms[81][2];  // two players
for (unsigned x(0); x < 81; ++x)
if (board[x])
myHash ^= randoms[x][board[x]]; 

值得一提的是,您可以将turn状态存储在哈希的开头。。。

inline bool GetTurn(int hash){
return (bool)(hash & 1);
}

并且数组中的Zobrist散列密钥的最低有效位都为0,例如[[0x2348dec2, 0x93857e34, ....] ...]