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 m
是bitboard,表示车辆或主教移动的可能被阻挡的方块。 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]
)随机排序的数字的荒谬神秘数组。
答案 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+Taylor和https://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)。