Storing player turn in Zobrist hash
我目前正在中国跳棋极小极大算法中实现转置表。在中国跳棋中,没有棋子被捕获,棋盘在功能上是 81 个空格。玩家轮流在棋盘上移动棋子。
该过程的一部分涉及为棋盘状态创建哈希。到目前为止,我已经有了一个可以为每个棋盘状态创建(希望)唯一哈希的有效方法:
1 2 3 4 5 6 | 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 函数)中逐步执行此操作:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 函数的可逆性。
我现在遇到的问题是哈希函数不存储轮到谁了。也就是说,你可以有两个相同的游戏板,但它们会在 minimax 算法中返回不同的值,因为一个试图最大化分数,另一个试图最小化它。
基本上,我的问题是:如何将玩家的回合存储在增量生成的散列函数中,同时保持完美反转它的能力(最好是便宜)?假设玩家的轮数是整数而不是布尔值,因为游戏最终会有 6 名玩家而不是 2 名玩家。
您可以使用填充伪随机值的
1 2 3 4 5 | 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 |
这类似于片段位置增量更新,适用于
附带说明...
Zobrist 散列通常是通过扫描棋子的位置来完成的,不包括空方格。空方格的位置未明确散列。
所以你可以使用一个更小(对缓存更友好)的数组。类似于:
1 2 3 4 5 | std::uint64_t randoms[81][2]; // two players for (unsigned x(0); x < 81; ++x) if (board[x]) myHash ^= randoms[x][board[x]]; |
对于它的价值,您可以将转弯状态存储为哈希开头的位...
1 2 3 | inline bool GetTurn(int hash){ return (bool)(hash & 1); } |
并且让数组中的 Zobrist 哈希键的最低有效位都为 0,例如。