如何找到魔术位板?

时间:2015-06-06 08:16:02

标签: c bit-manipulation chess

const int BitTable[64] = {
  63, 30, 3, 32, 25, 41, 22, 33, 15, 50, 42, 13, 11, 53, 19, 34, 61, 29, 2,
  51, 21, 43, 45, 10, 18, 47, 1, 54, 9, 57, 0, 35, 62, 31, 40, 4, 49, 5, 52,
  26, 60, 6, 23, 44, 46, 27, 56, 16, 7, 39, 48, 24, 59, 14, 12, 55, 38, 28,
  58, 20, 37, 17, 36, 8
};

int pop_1st_bit(uint64 *bb) {
  uint64 b = *bb ^ (*bb - 1);
  unsigned int fold = (unsigned) ((b & 0xffffffff) ^ (b >> 32));
  *bb &= (*bb - 1);
  return BitTable[(fold * 0x783a9b23) >> 26];
}

uint64 index_to_uint64(int index, int bits, uint64 m) {
  int i, j;
  uint64 result = 0ULL;
  for(i = 0; i < bits; i++) {
    j = pop_1st_bit(&m);
    if(index & (1 << i)) result |= (1ULL << j);
  }
  return result;
}

来自Chess Programming Wiki:
https://www.chessprogramming.org/Looking_for_Magics

这是查找magic numbers的一些代码的一部分。

参数uint64 mbitboard,表示车辆或主教移动的可能被阻挡的方块。 e4广场上车的示例:

0 0 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 
0 0 0 0 1 0 0 0 
0 0 0 0 1 0 0 0 
0 1 1 1 0 1 1 0 
0 0 0 0 1 0 0 0 
0 0 0 0 1 0 0 0 
0 0 0 0 0 0 0 0 

边缘正方形为零,因为它们总是阻塞,减少所需的位数显然是有帮助的。

/* Bitboard, LSB to MSB, a1 through h8:
 * 56 - - - - - - 63
 *  - - - - - - - -
 *  - - - - - - - -
 *  - - - - - - - -
 *  - - - - - - - -
 *  - - - - - - - -
 *  - - - - - - - -
 *  0 - - - - - - 7
 */

因此,在上面的示例中,index_to_uint64采用索引(0到2 ^位),以及位板(10)和位板中设置的位数。

然后pops_1st_bit为每个位数,然后是另一个少量的代码。 pops_1st_bit将位板与自身减去一个XOR(为什么?)。然后它用一个完整的32位对它进行AND运算,在这里的某个地方,我的大脑耗尽RAM。不知何故,涉及神奇的十六进制数0x783a9b23(是Lost的数字序列?)。并且有一个从0-63(BitTable[64])随机排序的数字的荒谬神秘数组。

3 个答案:

答案 0 :(得分:6)

好吧,我知道了。

首先,一些术语:

阻止程序掩码:包含所有可以阻止某个部分的方块的位板,对于给定的部件类型和该部件所在的方块。它排除了终止边缘正方形,因为它们总是阻塞。

拦截板:包含占用方块的位板。它只有正方形,也在阻挡掩码中。

移动板:一个包含所有正方形的位板,一块可以移动到,给定一个块类型,一个方块和一块阻挡板。如果棋子可以在那里移动,它包括终止边缘正方形。

e4广场上的车辆示例,e2,e5,e7,b4和c4上有一些随机的部分。

 The blocker mask        A blocker board         The move board
 0 0 0 0 0 0 0 0         0 0 0 0 0 0 0 0         0 0 0 0 0 0 0 0 
 0 0 0 0 1 0 0 0         0 0 0 0 1 0 0 0         0 0 0 0 0 0 0 0 
 0 0 0 0 1 0 0 0         0 0 0 0 0 0 0 0         0 0 0 0 0 0 0 0 
 0 0 0 0 1 0 0 0         0 0 0 0 1 0 0 0         0 0 0 0 1 0 0 0 
 0 1 1 1 0 1 1 0         0 1 1 0 0 0 0 0         0 0 1 1 0 1 1 1 
 0 0 0 0 1 0 0 0         0 0 0 0 0 0 0 0         0 0 0 0 1 0 0 0 
 0 0 0 0 1 0 0 0         0 0 0 0 1 0 0 0         0 0 0 0 1 0 0 0 
 0 0 0 0 0 0 0 0         0 0 0 0 0 0 0 0         0 0 0 0 0 0 0 0 

有些注意事项:

  • 对于给定的方形和棋子类型(白嘴鸦或主教),阻挡面具总是相同的。
  • 拦截板包括友善和敌人的碎片,它是阻挡面具的一个子集。
  • 生成的移动板可能包含捕捉您自己棋子的移动,但之后很容易移除这些移动:moveboard &= ~friendly_pieces)

幻数方法的目标是快速查找给定拦截板的预先计算的移动板。否则你每次都必须(慢慢地)计算移动板。这仅适用于滑块,即车和主教。女王只是车和主教的组合。

可以找到每个方块和幻灯片的幻数。片型组合。为此,您必须为每个方块/组合计算每个可能的阻挡板变体。这就是有问题的代码正在做的事情。 它如何这样做对我来说仍然有点神秘,但是that also seems to be the case for the apparent original author, Matt Taylor。 (感谢@Pradhan的链接)

所以我所做的就是重新实现生成所有可能阻塞板变体的代码。它采用了不同的技术,虽然速度稍慢,但阅读和理解起来要容易得多。它稍微慢一点的事实不是问题,因为这段代码不是速度关键。该程序只需在程序启动时执行一次,双核i5只需几微秒。

/* Generate a unique blocker board, given an index (0..2^bits) and the blocker mask 
 * for the piece/square. Each index will give a unique blocker board. */
static uint64_t gen_blockerboard (int index, uint64_t blockermask) 
{
    /* Start with a blockerboard identical to the mask. */
    uint64_t blockerboard = blockermask;

    /* Loop through the blockermask to find the indices of all set bits. */
    int8_t bitindex = 0;
    for (int8_t i=0; i<64; i++) {
        /* Check if the i'th bit is set in the mask (and thus a potential blocker). */
        if ( blockermask & (1ULL<<i) ) {
            /* Clear the i'th bit in the blockerboard if it's clear in the index at bitindex. */
            if ( !(index & (1<<bitindex)) ) {
                blockerboard &= ~(1ULL<<i); //Clear the bit.
            }
            /* Increment the bit index in the 0-4096 index, so each bit in index will correspond 
             * to each set bit in blockermask. */
            bitindex++;
        }
    }
    return blockerboard;
}

要使用它,请执行以下操作:

int bits = count_bits( RookBlockermask[square] );
/* Generate all (2^bits) blocker boards. */ 
for (int i=0; i < (1<<bits); i++) {
    RookBlockerboard[square][i] = gen_blockerboard( i, RookBlockermask[square] );
}

工作原理:有2 ^位阻塞板,其中bits是阻塞掩码中1的编号,它们是唯一的相关位。此外,0到2 ^位的每个整数具有1&s的唯一序列和长度为bits的0&#。因此,此函数仅将给定整数中的每个位与阻塞掩码中的相关位对应,并相应地将其关闭/打开以生成唯一的阻塞板。

它并不聪明或快速,但它的可读性。

答案 1 :(得分:4)

好吧,我将尝试逐步完成此事。

bitboard:
0 0 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 
0 0 0 0 1 0 0 0 
0 0 0 0 1 0 0 0 
0 1 1 1 0 1 1 0 
0 0 0 0 1 0 0 0 
0 0 0 0 1 0 0 0 
0 0 0 0 0 0 0 0 
dec: 4521262379438080
hex: 0x1010106e101000
bin: 0000 0000 0001 0000 0001 0000 0001 0000 0110 1110 0001 0000 0001 0000 0000 0000

7只是0到2 ^ 10之间随机选择的数字,10是以m为单位设置的位数。 m可以用四种方式表示:

pop_1st_bit(&m);

继续前进。这将被称为10次。它有一个返回值,它修改m。

uint64 b = m^(m-1);

在pop_1st_bit中,m由bb引用。为清晰起见,我将其更改为m。

m  : 0000 0000 0001 0000 0001 0000 0001 0000 0110 1110 0001 0000 0001 0000 0000 0000 
m-1: 0000 0000 0001 0000 0001 0000 0001 0000 0110 1110 0001 0000 0000 1111 1111 1111
b  : 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1111 1111 1111

m-1部分获取设置的最低有效位并将其翻转以及它下面的所有位。在XOR之后,所有这些更改的位现在都设置为1,而所有更高的位都设置为0.

unsigned int fold = (unsigned) ((b & 0xffffffff) ^ (b >> 32));

下一步:

(b & 0xffffffff)

(b & 0xffffffff) b: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1111 1111 1111 &: 0000 0000 0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111 =: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1111 1111 1111 部分ANDs b具有低32位设置位。所以这基本上清除了b的上半部分中的任何位。

... ^ (b >> 32)

>> :0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ^ :0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1111 1111 1111 uint fold = 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 1111 1111 1111 部分将b的上半部分移动到下半部分,然后用前一操作的结果对其进行异或。因此它基本上将b的上半部分与b的下半部分进行异或。在这种情况下,这没有任何效果,因为b的上半部分开始是空的。

m &= (m - 1);
m  : 0000 0000 0001 0000 0001 0000 0001 0000 0110 1110 0001 0000 0001 0000 0000 0000 
m-1: 0000 0000 0001 0000 0001 0000 0001 0000 0110 1110 0001 0000 0000 1111 1111 1111
&  : 0000 0000 0001 0000 0001 0000 0001 0000 0110 1110 0001 0000 0000 0000 0000 0000 

即使在b的上半部分设置了位,我也不明白&#34;折叠&#34;的意思。

无论如何,继续前进。下一行实际上通过取消设置最低位来修改m。这有点道理。

fold

下一部分将return BitTable[(fold * 0x783a9b23) >> 26]; 乘以一些十六进制数(素数?),右移产品26,并将其用作BitTable的索引,BitTable是我们神秘的随机排序数字0-63的数组。此时我怀疑作者可能正在编写伪随机数生成器。

j = pop_1st_bit(&m);
if(index & (1 << i)) result |= (1ULL << j);

pop_1st_bit结束。这些都完成了10次(最初在m中设置的每个位一次)。对pop_1st_bit的10个调用中的每一个都返回0-63的数字。

i

在上面的两行中,index是我们当前的位,0-9。因此,如果select * from item where iditem in (select iditem from item order by iditem desc limit 10) order by random() limit 5 数字(最初作为index_to_uint64的参数传递的7)具有第i位设置,则在结果中设置第j个位,其中j为0-63从pop_1st_bit返回值。

那就是它!我还是很困惑:(

答案 2 :(得分:3)

在youtube上观看国际象棋引擎上的视频系列时,我遇到了与paulwal222完全相同的问题。似乎涉及一些高水平的数学。解释这一困难主题背景的最佳链接是https://chessprogramming.wikispaces.com/Matt+Taylorhttps://chessprogramming.wikispaces.com/BitScan。似乎Matt Taylor在2003年的一个google.group(https://groups.google.com/forum/#!topic/comp.lang.asm.x86/3pVGzQGb1ys)(也是由pradhan发现)提出了一些现在叫做Matt Taylor的折叠技巧,一个32位友好的实现来找到比特索引LS1B(https://en.wikipedia.org/wiki/Find_first_set)。泰勒的折叠技巧显然是改编自1997年设计的De Bruijn(https://en.wikipedia.org/wiki/Nicolaas_Govert_de_Bruijn)位扫描,根据MartinLäuter的Donald Knuth通过最小完美散列(https://en.wikipedia.org/wiki/Perfect_hash_function)来确定LS1B指数。 BitTable(63,30,...)和PopBit中的折叠(0x783a9b23)的数字可能是与Matt Taylor的32位折叠技巧相关的所谓魔术数字(唯一?)。这种折叠技巧似乎非常快,因为很多引擎都复制了这种方法(f.i Stockfish)。