判断两个棋局是否相等
我目前正在调试国际象棋变体引擎的换位表,可以在其中放置棋子(即最初不在棋盘上)。我需要知道我发生按键碰撞的频率。我将片段列表与常用的哈希数据一起保存在每个表索引中。我确定两个位置是否相等的简单解决方案在换位上失败,因为我正在线性比较两个片段列表。
请不要建议我应该以板为中心而不是以件为中心进行存储。由于可放置和捕获的棋子具有独特的性质,我必须存储棋子列表。这些状态中的棋子就像占据了一个重叠且没有位置的位置。 请查看有关如何存储碎片的说明。
// [Piece List]
//
// Contents: The location of the pieces.
// Values 0-63 are board indexes; -2 is dead; -1 is placeable
// Structure: Black pieces are at indexes 0-15
// White pieces are at indexes 16-31
// Within each set of colors the pieces are arranged as following:
// 8 Pawns, 2 Knights, 2 Bishops, 2 Rooks, 1 Queen, 1 King
// Example: piece[15] = 6 means the black king is on board index 6
// piece[29] = -2 means the white rook is dead
char piece[32];
当棋子以不同的顺序移动,但最终结果是相同的棋盘位置时,就会发生换位。例如以下位置是相等的:
1) first rook on A1; second rook on D7
2) first rook on D7; second rook on A1
以下是非优化的通用算法;内部循环类似于另一个一般问题,但增加了限制,0-63 中的值只会发生一次(即每平方仅一件)。
for each color:
for each piece type:
are all pieces in the same position, disregarding transpositions?
以下比较由于换位而不起作用。我需要的是一种检测换位是否相等并且仅报告实际不同位置的方法。
bool operator==(const Position &b)
{
for (int i = 0; i < 32; i++)
if (piece[i] != b.piece[i])
return false;
return true;
}
性能/内存是一个考虑因素,因为桌子每轮都会有超过 100K 次点击(其中键相等),并且典型的桌子有 100 万个项目。从今以后,我正在寻找比复制和排序列表更快的东西。
I'm currently debugging my transposition table for a chess variant engine where pieces can be placed (ie. originally not on the board). I need to know how often I'm hitting key collisions. I'm saving the piece list in each table index, along with the usual hash data. My simple solution for determining if two positions are equal is failing on transpositions because I'm linearly comparing the two piece lists.
Please do not suggest that I should be storing by board-centric instead of piece-centric. I have to store the piece list because of the unique nature of placable and captured pieces. Pieces in those states are like they are occupying an overlapping and position-less location. Please look at the description of how pieces are stored.
// [Piece List]
//
// Contents: The location of the pieces.
// Values 0-63 are board indexes; -2 is dead; -1 is placeable
// Structure: Black pieces are at indexes 0-15
// White pieces are at indexes 16-31
// Within each set of colors the pieces are arranged as following:
// 8 Pawns, 2 Knights, 2 Bishops, 2 Rooks, 1 Queen, 1 King
// Example: piece[15] = 6 means the black king is on board index 6
// piece[29] = -2 means the white rook is dead
char piece[32];
A transposition happens when pieces are moved in a different order, but the end result is the same board position. For example the following positions are equal:
1) first rook on A1; second rook on D7
2) first rook on D7; second rook on A1
The following is a non-optimised general algorithm; and the inner loop is similar to another general problem, but with the added restraint that values in 0-63 will only happen once (ie. only one piece per square).
for each color:
for each piece type:
are all pieces in the same position, disregarding transpositions?
The following comparison does NOT work because of transpositions. What I need is a way to detect transpositions as equal and only report actually different positions.
bool operator==(const Position &b)
{
for (int i = 0; i < 32; i++)
if (piece[i] != b.piece[i])
return false;
return true;
}
Performance/memory is a consideration because the table gets over 100K hits (where keys are equal) per turn and a typical table has 1 million items. Henceforth, I'm looking for something faster than copying and sorting the lists.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
对计算机国际象棋进行了大量研究,为位置创建唯一哈希的方法是一个众所周知的问题,几乎每个国际象棋引擎都使用通用解决方案。
您需要做的是使用 Zobrist Hashing 创建一个独特的(不是真正独特的,但我们'稍后会看到为什么这在实践中不是问题)每个不同位置的关键。 此处解释了应用于国际象棋的算法。
当您启动程序时,您将创建我们所说的 zobrist 密钥。这些是每个棋子/方块对的 64 位随机整数。在 C 中,您将有一个像这样的二维数组:
每个密钥都使用一个好的随机数生成器进行初始化(警告:gcc 或 VC++ 提供的随机数生成器不够好,请使用 梅森旋转器)。
当棋盘为空时,您可以任意将其哈希键设置为 0,然后当您在棋盘上添加棋子(例如 A1 上的 Rook)时,您还可以通过将 A1 上的车的 zobrist 键与哈希键进行异或来更新哈希键董事会的。像这样(在 C 中):
如果您稍后从这个方块中删除车,您需要反转您刚刚所做的事情,因为异或可以通过再次应用来反转,所以当您删除该棋子时,您可以简单地再次使用相同的命令:
如果你移动一个棋子,假设A1上的车到了B1,你需要做两次异或,一次删除A1上的车,一次添加B2上的车。
这样,每次修改主板时,您也会修改哈希值。这是非常有效的。您还可以通过异或与棋盘上所有棋子相对应的 zobKey,每次从 scatch 计算哈希值。您还需要对可以被路过的棋子的位置和双方的掠夺能力状态进行异或。您可以用同样的方式为每个可能的值创建 zobris 键。
该算法不能保证每个位置都有唯一的散列,但是,如果您使用良好的伪随机数生成器,发生碰撞的几率非常低,即使您让引擎玩一辈子,也几乎没有机会曾经发生过的碰撞。
编辑:我只是红色,你正在尝试为具有棋盘外棋子的国际象棋变体实现此功能。 Zobrist 哈希仍然是适合您的解决方案。您必须找到一种方法将这些信息合并到哈希中。例如,您可以拥有一些用于棋盘外棋子的钥匙:
如果您有 2 个爪子离开棋盘并将其中一个棋子放在 a2 上,您将必须执行 2 次操作:
There is lot of research done on computer chess and the way to create unique hash for a position is a well know problem with an universal solution used by virtually every chess engine.
What you need to do is use Zobrist Hashing to create a unique (not really unique, but we'll see later why this is not a problem in practice) key for each different positions. The Algorithm applied to chess is explained here.
When you start your program you create what we call zobrist keys. These are 64 bits random integers for each piece/square pairs. In C you would have an 2 dimension array like this :
Each of this key are initialized with a good random number generator (Warning : the random number generator provided with gcc or VC++ are not good enough, use an implementation of the Mersenne Twister).
When the board is empty you arbitrarily set it's hash key to 0, then when you add a piece on the board, say a Rook on A1, you also update the hash key by XORing the zobrist key for a rook on A1 with the hash key of the board. Like this (in C) :
If you later remove the rook from this square you need to reverse what you just did, since a XOR can be reversed by applaying it again, you can simply use the same command again when you remove the piece :
If you move a piece, say the rook on A1 goest to B1, you need to do two XOR, one to remove the rook on A1 and one to add a rook on B2.
This way everytime you modify the board you also modify the hash. It is very efficient. You could also compute the hash from scatch each time by xoring the zobKeys corresponding to all pieces on the board. You will also need to XOR the position of the pawn that can be taken en passant and the status of the rooking capabilities of both side. You do it the same way, by creating zobris keys for each possible values.
This algotitm does not guaranty that each position has a unique hash, however, if you use a good pseudo random number generator, the odds of a collision occuring are so low that even if you let your engine play you whole life there is virtually no chances of a collision ever occuring.
edit: I just red that you are trying to implement this for a variant of chess that has off-board pieces. Zobrist hashing is still the right solution for you. You will have to find a way to incorporate theses information in the hash. You could for example have some keys for the off-the-board pieces :
If you have 2 paws off the board and put one of this pawn on a2, you will have to do 2 operations :
为什么不在数据库中保留一个与棋盘布局相对应的 64 字节字符串呢?每种类型的棋子,包括“无棋子”,都代表一个字母(两种颜色的大写字母不同,即 ABC 代表黑色,abc 代表白色)。板比较归结为简单的字符串比较。
一般来说,从棋盘的角度而不是棋子的角度进行比较,将摆脱你的换位问题!
Why not keep an 64 byte string in your database that corresponds to the chessboard layout? Every type of piece, including 'no piece' represents a letter (different caps for both colors, ie ABC for black, abc for white). Board comparison boils down to simple string comparison.
In general, comparing from the chessboard perspective, instead of the piece perspective, will get rid of your transpositions problem!
“不建议我应该以板为中心而不是以件为中心进行存储”。
你太专注于不这样做,以至于错过了显而易见的解决方案。 比较特定于主板的情况。要比较两个位置列表
L1
和L2
,请将L1
的所有元素放在(临时)板上。然后,对于L2
的每个元素,检查它是否存在于临时板上。如果 L2 的元素不存在于棋盘上(因此也不存在于L1
中),则返回不相等。如果移除
L2
中的所有元素后,棋盘上仍然剩下棋子,那么L1
一定有L2
中不存在的元素,并且列表是相等的。仅当临时板随后为空时,L1
和L2
才相等。一种优化是首先检查
L1
和L2
的长度。这不仅可以快速发现许多差异,还消除了从 baord 中删除L2
的元素以及最后的“空板”检查的需要。仅需要捕获L1
是L2
的真正超集的情况。如果L1
和L2
具有相同的大小,并且L2
是L1
的子集,则L1
和L2
必须相等。"do not suggest that I should be storing by board-centric instead of piece-centric".
You're so focused on not doing that, that you miss the obvious solution. Compare board-specific. To compare two position lists
L1
andL2
, place all elements ofL1
on a (temporary) board. Then, for each element ofL2
, check if it's present on the temporary board. If an element of L2 is not present on the board (and thus inL1
), return unequal.If after removing all elements of
L2
, there are still pieces left on the board, thenL1
must have had elements not present inL2
and the lists are equal.L1
andL2
are only equal when the temporary board is empty afterwards.An optimization is to check the lengths of
L1
andL2
first. Not only will this catch many discrepancies quickly, it also eliminates the need to remove the elemetns ofL2
from the baord and the "empty board" check at the end. That is only needed to catch the case whereL1
is a true superset ofL2
. IfL1
andL2
have the same size, andL2
is a subset ofL1
, thenL1
andL2
must be equal.你对存储棋盘状态的主要反对意见是你有一袋无位置的棋子。为什么不维护一个棋盘+一个棋子向量呢?这将满足您的要求,并且它的优点是它是您所在州的规范表示。因此,您不需要排序,您可以在内部使用此表示形式,也可以在需要比较时转换为它:
Your main objection to storing the states board-wise is that you have a bag of position-less pieces. Why not maintain a board + a vector of pieces? This would meet your requirements and it has the advantage that it is a canonical representation for your states. Hence you don't need the sorting, and you either use this representation internally or convert to it when you need to compare :
从整体角度来看,您可以这样做:
优化可以通过不同的方式进行。您的优势是:一旦您发现差异:您就完成了!
例如,您可以通过总结两块板的所有部分的所有索引来开始快速而肮脏的检查。总和应该相等。如果没有,那就有区别了。
如果总和相等,您可以快速比较独特棋子(国王和王后)的位置。然后你可以写出(在有些复杂的 if 语句中)成对的部分的比较。然后您所要做的就是使用上述方法比较棋子。
From the piece perspective you could do this:
Optimizations can come in different ways. Your advantage is: as soon as you notice a difference: your done!
You could for instance start with a quick and dirty check by summing up all the indexes for all pieces, for both boards. The sums should be equal. If not, there's a difference.
If the sums are equal, you could quickly compare the positions of the unique pieces (King and Queen). Then you could write out (in somewhat complicated if statements) the comparisons for the pieces that are in pairs. All you then have to do is compare the pawns using the above stated method.
第三种选择(我真的希望对一个问题发布 3 个答案是可以的,从 stackoverflow 的角度来看;)):
始终按索引顺序保留相同类型的棋子,即列表中的第一个 pawn 应该始终具有最低的值指数。如果发生的移动打破了这一点,只需翻转列表中的棋子位置即可。使用起来看不出区别,棋子就是棋子。
现在,在比较位置时,您可以确定不存在换位问题,并且可以仅使用建议的 for 循环。
And a third option (I really do hope posting 3 answers to one question is ok, stackoverflow-wise ;)):
Always keep your pieces of the same type in index-order, ie the first pawn in the list should always have the lowest index. If a move takes place that breaks this, just flip the pawns positions in the list. The use won't see the difference, a pawn is a pawn.
Now when comparing positions, you can be sure there's no transpositions problem and you can just use your proposed for-loop.
考虑到您选择的游戏状态表示,您必须以一种或另一种方式对黑色棋子的索引、白色棋子的索引等进行排序。如果你在创建新游戏状态的过程中不这样做,你就必须在比较时这样做。因为您最多只需要对 8 个元素进行排序,所以可以非常快地完成。
有几种替代方法可以表示您的游戏状态:
或者
这两种选择不会有换位问题。
不管怎样,我的印象是,当你应该首先让它发挥作用时,你却在摆弄微观优化。
Given your choice of game state representation, you have to sort the black pawns' indices, the white pawns' indices, etc., one way or the other. If you don't do it in the course of creating a new game state, you will have to do it upon comparison. Because you only need to sort a maximum of 8 elements, this can be done quite fast.
There are a few alternatives to represent your game states:
or
These two alternatives would not have a transposition problem.
Anyway, I have the impression that you are fiddling around with micro-optimizations when you should first get it to work.