确定两个国际象棋位置是否相等

时间:2010-10-08 08:37:21

标签: algorithm chess

我正在为一个国际象棋变体引擎调试我的换位表,其中可以放置各个部分(即最初不在板上)。我需要知道我经常遇到关键的碰撞。我正在保存每个表索引中的片段列表以及通常的哈希数据。我确定两个位置是否相等的简单解决方案是转换失败,因为我正在线性地比较两个位置。

请不要建议我应该以电路板为中心而不是以电脑为中心存储。由于可放置和捕获的碎片的独特性,我必须存储碎片清单。这些状态中的碎片就像它们占据了重叠且无位置的位置。 请查看件存储方式的说明

// [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

以下是非优化通用算法;内循环类似于另一个general problem,但增加了约束,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万个项目。从此以后,我正在寻找比复制和排序列表更快的东西。

7 个答案:

答案 0 :(得分:8)

对计算机国际象棋进行了大量研究,为位置创建独特哈希的方法是一个众所周知的问题,几乎每个国际象棋引擎都使用通用解决方案。

你需要做的是使用Zobrist Hashing创建一个独特的(不是真正独特的,但我们稍后会看到为什么这在实践中不是一个问题)关键每个不同的位置。 Algorithm applied to chess is explained here

当你启动程序时,你创建了我们称之为zobrist键的东西。这些是每个片/方对的64位随机整数。在C中你会得到一个像这样的二维数组:

unsigned long long zobKeys[NUMBER_OF_PIECES][NUMBER_OF_SQUARES];

每个密钥都使用一个好的随机数生成器初始化(警告:随gcc或VC ++提供的随机数生成器不够好,使用Mersenne Twister的实现。)

当电路板为空时你随意将它的散列键设置为0,然后当你在电路板上添加一块时,在A1上说一个Rook,你也可以通过对A1上的一个小车的zobrist键进行异或来更新散列键。董事会的哈希键。像这样(在C中):

boardHash = boardHash ^ zobKeys[ROOK][A1];

如果你以后从这个方块中移除了车,你需要反转你刚刚做的事情,因为可以通过再次回应来反转XOR,你可以在删除它时再次使用相同的命令:

boardHash = boardHash ^ zobKeys[ROOK][A1];

如果你移动一块,说A1上的车就行至B1,你需要做两次XOR,一次取消A1上的车,另一辆在B2上添加车。

boardHash = boardHash ^ zobKeys[ROOK][A1] ^ boardHash ^ zobKeys[ROOK][B1];

这样每次修改电路板时都会修改散列。它非常有效。您还可以通过xoring与电路板上所有部件对应的zobKeys来计算每次scatch的哈希值。你还需要对可以采用的pawn的位置进行异或,以及双方的炼制能力的状态。您可以通过为每个可能的值创建zobris键来以相同的方式执行此操作。

这个algotitm并不保证每个位置都有一个独特的哈希,但是,如果你使用一个好的伪随机数发生器,发生碰撞的几率非常低,即使你让你的引擎在你的整个生命中发挥作用,几乎没有发生碰撞的可能性。

编辑:我只是红色,你正试图为一个有棋盘的棋子的变种实现这个。 Zobrist散列仍然是适合您的解决方案。您必须找到一种方法将这些信息合并到散列中。例如,您可以为板上部分提供一些键:

unsigned long long offTheBoardZobKeys[NUMBER_OF_PIECE][MAXIMUM_NUMBER_OF_ON_PIECE_TYPE];

如果你有2个棋子离开棋盘并将其中一个棋子放在a2上,你将需要进行2次操作:

// remove one pawn from the off-the-board
boardHash = boardHash ^ offTheBoardZobKeys[WHITE_PAWN][numberOfWhitePawsOffTheBoard];

// Put a pawn on a2
boardHash = boardHash ^ zobKeys[WHITE_PAWN][A2];

答案 1 :(得分:6)

为什么不在数据库中保留与棋盘布局相对应的64字节字符串?每种类型的作品,包括“无作品”代表一个字母(两种颜色的不同大写,即黑色的ABC,白色的abc)。董事会比较归结为简单的字符串比较。

一般来说,从棋盘角度来看,而不是片段视角,将摆脱你的换位问题!

答案 2 :(得分:4)

“不建议我应该以电路板为中心而不是以电脑为中心”。

你如此专注于不这样做,你错过了明显的解决方案。 比较特定于电路板。要比较两个职位列表L1L2,请将L1的所有元素放在(临时)主板上。然后,对于L2的每个元素,检查它是否存在于临时板上。如果板上没有L2元素(因此在L1中),则返回不等。

如果在移除L2的所有元素后,电路板上仍有剩余部分,则L1必须包含L2中不存在的元素且列表相同。 <{1}}和L1仅在临时董事会之后为空时才相等。

优化是首先检查L2L1的长度。这不仅会很快发现许多差异,而且还消除了从baord中删除L2的elemet和最后的“空板”检查的需要。只需要了解L2L1的真实超集的情况。如果L2L1具有相同的尺寸,并且L2L2的子集,那么L1L1必须相等。< / p>

答案 3 :(得分:3)

你主要反对以电路板方式存放国家是你有一袋无位置的作品。为什么不保持一块板+一块片?这符合您的要求,它的优势在于它是您所在州的规范代表。因此,您不需要排序,您可以在内部使用此表示形式,也可以在需要比较时转换为它:

Piece-type in A1
... 63 more squares
Number of white pawns off-board
Number of black pawns off-board
... other piece types

答案 4 :(得分:1)

从作品的角度来看,你可以这样做:

for each color:
    for each piece type:
        start new list for board A
        for each piece of this piece type on board A
            add piece position to the list
        start new list for board B
        for each piece of this piece type on board B
            add piece position to the list
        order both lists and compare them

优化可以有不同的方式。您的优势是:一旦发现差异,您就完成了!

例如,您可以通过总结所有部分的所有索引(对于两个板)进行快速和脏检查。总和应该相等。如果没有,那就有区别了。

如果总和相等,您可以快速比较独特作品(国王和王后)的位置。然后你可以写出(在某些复杂的if语句中)成对的片段的比较。然后你要做的就是使用上述方法比较棋子。

答案 5 :(得分:1)

还有第三个选项(我真的希望在一个问题上发布3个答案就可以了,stackoverflow-wise;)):

始终按索引顺序保留相同类型的片段,即列表中的第一个pawn应始终具有最低索引。如果发生打破此操作的移动,只需翻转列表中的棋子位置即可。使用不会看到差异,pawn是pawn。

现在比较位置时,您可以确定没有换位问题,您可以使用建议的for循环。

答案 6 :(得分:1)

鉴于您选择的游戏状态表示,您必须以某种方式对黑色棋子的指数,白色棋子的指数等进行排序。如果您在创建新游戏状态的过程中没有这样做,则必须在比较时进行。因为您只需要对最多8个元素进行排序,所以这可以非常快速地完成。

有几种方法可以代表您的游戏状态:

  • 将每种类型的作品表示为位字段。前64位表示该板坐标上有一个这种类型;然后有 n 位的“可放置”和 n 位的“死”插槽,必须从一侧填充( n 是这种类型的件数。)。

  • 为每种类型的作品提供唯一的ID,例如:白色棋子可能是0x01。游戏状态包括64个棋子(棋盘)和两个“可放置”和“死”棋子的有序列表。在插入和删除时,可以非常有效地维护这些列表的顺序。

这两种选择不存在换位问题。

无论如何,当你第一次开始工作时,我的印象是你正在摆弄微优化。