10 урок - нахождение индекса младшего значащего бита и возвращение его координаты в шахматной нотации
program bbc;
{$APPTYPE CONSOLE}
uses
SysUtils,Windows,TypInfo;
type
U64 = UInt64;
TBoard_squares = (
a8, b8, c8, d8, e8, f8, g8, h8,
a7, b7, c7, d7, e7, f7, g7, h7,
a6, b6, c6, d6, e6, f6, g6, h6,
a5, b5, c5, d5, e5, f5, g5, h5,
a4, b4, c4, d4, e4, f4, g4, h4,
a3, b3, c3, d3, e3, f3, g3, h3,
a2, b2, c2, d2, e2, f2, g2, h2,
a1, b1, c1, d1, e1, f1, g1, h1
);
TSide = (white, black);
//Первый индекс обозначает номер строки в матрице, второй индекс – номер столбца
TPawn_attacks=array[TSide,TBoard_squares] of U64;
TChip_attacks=array[TBoard_squares] of U64;
var
bitboard:U64=0;
block:U64=0;
// pawn attacks table [side][square]
pawn_attacks:TPawn_attacks;
// knight attacks table [square]
knight_attacks:TChip_attacks;
// king attacks table [square]
king_attacks:TChip_attacks;
square:TBoard_squares;
const
{/*
not A file
8 0 1 1 1 1 1 1 1
7 0 1 1 1 1 1 1 1
6 0 1 1 1 1 1 1 1
5 0 1 1 1 1 1 1 1
4 0 1 1 1 1 1 1 1
3 0 1 1 1 1 1 1 1
2 0 1 1 1 1 1 1 1
1 0 1 1 1 1 1 1 1
a b c d e f g h
not H file
8 1 1 1 1 1 1 1 0
7 1 1 1 1 1 1 1 0
6 1 1 1 1 1 1 1 0
5 1 1 1 1 1 1 1 0
4 1 1 1 1 1 1 1 0
3 1 1 1 1 1 1 1 0
2 1 1 1 1 1 1 1 0
1 1 1 1 1 1 1 1 0
a b c d e f g h
not HG file
8 1 1 1 1 1 1 0 0
7 1 1 1 1 1 1 0 0
6 1 1 1 1 1 1 0 0
5 1 1 1 1 1 1 0 0
4 1 1 1 1 1 1 0 0
3 1 1 1 1 1 1 0 0
2 1 1 1 1 1 1 0 0
1 1 1 1 1 1 1 0 0
a b c d e f g h
not AB file
8 0 0 1 1 1 1 1 1
7 0 0 1 1 1 1 1 1
6 0 0 1 1 1 1 1 1
5 0 0 1 1 1 1 1 1
4 0 0 1 1 1 1 1 1
3 0 0 1 1 1 1 1 1
2 0 0 1 1 1 1 1 1
1 0 0 1 1 1 1 1 1
a b c d e f g h
*/}
// not A file constant
not_a_file:U64 = $FEFEFEFEFEFEFEFE;//18374403900871474942;
// not H file constant
not_h_file:U64 = $7F7F7F7F7F7F7F7F;//9187201950435737471;
// not HG file constant
not_hg_file:U64 =$3F3F3F3F3F3F3F3F;//4557430888798830399;
// not AB file constant
not_ab_file:U64 =$FCFCFCFCFCFCFCFC;//18229723555195321596;
square_to_coordinates: array [0..63] of string = (
'a8', 'b8', 'c8', 'd8', 'e8', 'f8', 'g8', 'h8',
'a7', 'b7', 'c7', 'd7', 'e7', 'f7', 'g7', 'h7',
'a6', 'b6', 'c6', 'd6', 'e6', 'f6', 'g6', 'h6',
'a5', 'b5', 'c5', 'd5', 'e5', 'f5', 'g5', 'h5',
'a4', 'b4', 'c4', 'd4', 'e4', 'f4', 'g4', 'h4',
'a3', 'b3', 'c3', 'd3', 'e3', 'f3', 'g3', 'h3',
'a2', 'b2', 'c2', 'd2', 'e2', 'f2', 'g2', 'h2',
'a1', 'b1', 'c1', 'd1', 'e1', 'f1', 'g1', 'h1'
);
{/**********************************\
==================================
Bit manipulations
==================================
\**********************************/}
// set/get/pop
function get_bit(bb:U64;const sq:integer):integer;
begin
get_bit:=(bb shr sq) and 1;
end;
procedure set_bit(var bb:U64;const sq:TBoard_squares);
begin
bb:=bb or ($8000000000000000 shr (63-Ord(sq)));
end;
procedure pop_bit(var bb:U64;const sq:TBoard_squares);
begin
if get_bit(bb,Ord(sq))>0 then bb:=bb xor ($8000000000000000 shr (63-Ord(sq)));
end;
// count bits within a bitboard (Brian Kernighan's way)
function count_bits(bb:U64):integer;
var
count:integer;
begin
// bit counter
count := 0;
// consecutively reset least significant 1st bit
while bb>0
do begin
// increment count
Inc(count);
// reset least significant 1st bit
bb := bb and (bb - 1);
end;
count_bits:=count;
end;
// get least significant 1st bit index
function get_ls1b_index(bb:U64):integer;
begin
// make sure bitboard is not 0
if bb<>0 then
// count trailing bits before LS1B
result:=count_bits((bb and (-bb))-1)
// return illegal index
else result:=-1;
end;
{/**********************************\
==================================
Input & Output
==================================
\**********************************/}
// print bitboard
procedure print_bitboard(bb:U64);
var
rank_,file_,sq:integer;
begin
Writeln('');
// loop over board ranks
for rank_ := 0 to 7 do begin
// loop over board files
for file_ := 0 to 7 do begin
// convert file & rank into square index
sq:=rank_*8 + file_;
//// print ranks
if file_ = 0 then begin
Write(8 - rank_);Write(' ');
end;
Write(' ');
// print bit state (either 1 or 0)
Write(get_bit(bb,sq));
end;
// print new line every rank
Writeln('');
end;
// print new line
Writeln('');
// print board files
Writeln(' a b c d e f g h');
Writeln('');
// print bitboard as unsigned decimal number
Write('bitboard = ',bb);
Writeln('');
end;
{/**********************************\
==================================
Attacks
==================================
\**********************************/}
// generate pawn attacks
function mask_pawn_attacks(side:TSide;square:TBoard_squares):U64;
var
attacks,bitboard:U64;
begin
// result attacks bitboard
attacks := 0;
// piece bitboard
bitboard := 0;
// set piece on board
set_bit(bitboard, square);
// white pawns
if (side=white) then begin
if ((bitboard shr 7) and not_a_file)>0 then attacks:=attacks or (bitboard shr 7);
if ((bitboard shr 9) and not_h_file)>0 then attacks:=attacks or (bitboard shr 9);
end
// black pawns
else begin
if ((bitboard shl 7) and not_h_file)>0 then attacks:=attacks or (bitboard shl 7);
if ((bitboard shl 9) and not_a_file)>0 then attacks:=attacks or (bitboard shl 9);
end;
// return attack map
mask_pawn_attacks:=attacks;
end;
// generate knight attacks
function mask_knight_attacks(square:TBoard_squares):U64;
var
attacks,bitboard:U64;
begin
// result attacks bitboard
attacks := 0;
// piece bitboard
bitboard := 0;
// set piece on board
set_bit(bitboard, square);
// generate knight attacks
if ((bitboard shr 17) and not_h_file)>0 then attacks:=attacks or (bitboard shr 17);
if ((bitboard shr 15) and not_a_file)>0 then attacks:=attacks or (bitboard shr 15);
if ((bitboard shr 10) and not_hg_file)>0 then attacks:=attacks or (bitboard shr 10);
if ((bitboard shr 6) and not_ab_file)>0 then attacks:=attacks or (bitboard shr 6);
if ((bitboard shl 17) and not_a_file)>0 then attacks:=attacks or (bitboard shl 17);
if ((bitboard shl 15) and not_h_file)>0 then attacks:=attacks or (bitboard shl 15);
if ((bitboard shl 10) and not_ab_file)>0 then attacks:=attacks or (bitboard shl 10);
if ((bitboard shl 6) and not_hg_file)>0 then attacks:=attacks or (bitboard shl 6);
// return attack map
mask_knight_attacks:=attacks;
end;
// generate king attacks
function mask_king_attacks(square:TBoard_squares):U64;
var
attacks,bitboard:U64;
begin
// result attacks bitboard
attacks := 0;
// piece bitboard
bitboard := 0;
// set piece on board
set_bit(bitboard, square);
// generate king attacks
if (bitboard shr 8)>0 then attacks:=attacks or (bitboard shr 8);
if ((bitboard shr 9) and not_h_file)>0 then attacks:=attacks or (bitboard shr 9);
if ((bitboard shr 1) and not_h_file)>0 then attacks:=attacks or (bitboard shr 1);
if ((bitboard shr 7) and not_a_file)>0 then attacks:=attacks or (bitboard shr 7);
if (bitboard shl 8)>0 then attacks:=attacks or (bitboard shl 8);
if ((bitboard shl 9) and not_a_file)>0 then attacks:=attacks or (bitboard shl 9);
if ((bitboard shl 1) and not_a_file)>0 then attacks:=attacks or (bitboard shl 1);
if ((bitboard shl 7) and not_h_file)>0 then attacks:=attacks or (bitboard shl 7);
// return attack map
mask_king_attacks:=attacks;
end;
//урок 6-masking relevant bishop occupancy bits to form a key for MAGIC BITBOARDS
// mask bishop attacks
function mask_bishop_attacks(square:TBoard_squares):U64;
var
attacks,n:U64;
r,f,tr,tf:integer;
begin
n:=1;
// result attacks bitboard
attacks := 0;
// init target rank & files
tr := Ord(square) div 8;//целочисленное деление
tf := Ord(square) mod 8;//остаток от деления
// mask relevant bishop occupancy bits
f:=tf+1;
for r:=tr+1 to 6 do begin
if f>6 then break;
attacks := attacks or (n shl (r*8+f));
Inc(f);
end;
f:=tf+1;
for r:=tr-1 downto 1 do begin
if f>6 then break;
attacks := attacks or (n shl (r*8+f));
Inc(f);
end;
f:=tf-1;
for r:=tr+1 to 6 do begin
if f<1 then break;
attacks := attacks or (n shl (r*8+f));
Dec(f);
end;
f:=tf-1;
for r:=tr-1 downto 1 do begin
if f<1 then break;
attacks := attacks or (n shl (r*8+f));
Dec(f);
end;
// return attack map
mask_bishop_attacks:=attacks;
end;
//урок 7-masking relevant ROOK OCCUPANCY BITS to form a key for MAGIC BITBOARDS
// mask rook attacks
function mask_rook_attacks(square:TBoard_squares):U64;
var
attacks,n:U64;
r,f,tr,tf:integer;
begin
n:=1;
// result attacks bitboard
attacks := 0;
// init target rank & files
tr := Ord(square) div 8;//целочисленное деление
tf := Ord(square) mod 8;//остаток от деления
// mask relevant bishop occupancy bits
for r:=tr+1 to 6 do attacks := attacks or (n shl (r*8+tf));
for r:=tr-1 downto 1 do attacks := attacks or (n shl (r*8+tf));
for f:=tf+1 to 6 do attacks := attacks or (n shl (tr*8+f));
for f:=tf-1 downto 1 do attacks := attacks or (n shl (tr*8+f));
// return attack map
mask_rook_attacks:=attacks;
end;
//урок 8-generating SLIDER PIECES ATTACKS on the fly for MAGIC BITBOARD purposes
// generate bishop attacks on the fly
function bishop_attacks_on_the_fly(square:TBoard_squares;blk:U64):U64;
var
attacks,n:U64;
r,f,tr,tf:integer;
begin
n:=1;
// result attacks bitboard
attacks := 0;
// init target rank & files
tr := Ord(square) div 8;//целочисленное деление
tf := Ord(square) mod 8;//остаток от деления
// generate bishop atacks
f:=tf+1;
for r:=tr+1 to 7 do begin
if f>7 then break;
attacks := attacks or (n shl (r*8+f));
if ((n shl (r*8+f)) and blk)>0 then break;
Inc(f);
end;
f:=tf+1;
for r:=tr-1 downto 0 do begin
if f>7 then break;
attacks := attacks or (n shl (r*8+f));
if ((n shl (r*8+f)) and blk)>0 then break;
Inc(f);
end;
f:=tf-1;
for r:=tr+1 to 7 do begin
if f<0 then break;
attacks := attacks or (n shl (r*8+f));
if ((n shl (r*8+f)) and blk)>0 then break;
Dec(f);
end;
f:=tf-1;
for r:=tr-1 downto 0 do begin
if f<0 then break;
attacks := attacks or (n shl (r*8+f));
if ((n shl (r*8+f)) and blk)>0 then break;
Dec(f);
end;
// return attack map
bishop_attacks_on_the_fly:=attacks;
end;
// generate rook attacks on the fly
function rook_attacks_on_the_fly(square:TBoard_squares;blk:U64):U64;
var
attacks,n:U64;
r,f,tr,tf:integer;
begin
n:=1;
// result attacks bitboard
attacks := 0;
// init target rank & files
tr := Ord(square) div 8;//целочисленное деление
tf := Ord(square) mod 8;//остаток от деления
// generate rook attacks
for r:=tr+1 to 7 do begin
attacks := attacks or (n shl (r*8+tf));
if ((n shl (r*8+tf)) and blk)>0 then break;
end;
for r:=tr-1 downto 0 do begin
attacks := attacks or (n shl (r*8+tf));
if ((n shl (r*8+tf)) and blk)>0 then break;
end;
for f:=tf+1 to 7 do begin
attacks := attacks or (n shl (tr*8+f));
if ((n shl (tr*8+f)) and blk)>0 then break;
end;
for f:=tf-1 downto 0 do begin
attacks := attacks or (n shl (tr*8+f));
if ((n shl (tr*8+f)) and blk)>0 then break;
end;
// return attack map
rook_attacks_on_the_fly:=attacks;
end;
// init leaper pieces attacks
procedure init_leapers_attacks();
var
square:TBoard_squares;
begin
// loop over 64 board squares
for square := a8 to h1 do begin
//урок 3-generating pre-calculated PAWN ATTACK tables
// init pawn attacks
pawn_attacks[white][square] := mask_pawn_attacks(white,square);
pawn_attacks[black][square] := mask_pawn_attacks(black,square);
//урок 4-generating pre-calculated KNIGHT ATTACK table
// init knight attacks
knight_attacks[square] := mask_knight_attacks(square);
//урок 5-generating pre-calculated KING ATTACK tables
// init king attacks
king_attacks[square] := mask_king_attacks(square);
end;
end;
{/**********************************\
==================================
Main driver
==================================
\**********************************/}
begin
//Переключение окна консоли на кодовую страницу CP1251 (Win-1251).
//Если после переключения русские буквы показываются неверно,
//следует открыть системное меню консольного окна - щелчком мыши в левом
//верхнем углу окна консоли. И выбрать:
//Свойства - закладка "Шрифт" - выбрать шрифт: "Lucida Console".
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
// ==================================
init_leapers_attacks();
block:=4512464976740352;
print_bitboard(block);
square:=TBoard_squares(get_ls1b_index(block));
Write('get_ls1b_index:',get_ls1b_index(block));
Writeln(' coordinate:',square_to_coordinates[Ord(square)]);
set_bit(bitboard,square);
print_bitboard(bitboard);
Writeln('Нажмите <ENTER>, чтобы выйти ...');
Readln;
end.
Разобрался с 11 уроком - Bitboard CHESS ENGINE in C: populating OCCUPANCY sets to multiply them later by MAGIC NUMBERS.
Пока это все подготовка к реализации Magic Bitboards
В этом уроке описывается функция нызываемая - начальными занятиями (set_occupancy).
По сути это комбинация всех различных расположений фигур на линии атаки скользящей фигуры (ладьи, слона).
8/8/8/8/8/8/8/R7
Например при ладье на a1 и ее атаках по линиям A2-A7 и B1-G1 (края доски не учитываются), теоретически эти поля могут быть заняты другими фигурами в различных комбинациях. По сути мы имеем дело с 6 клетками по вертикали и 6 по горизонали.
6 клеток это число с 6 включенными битами, соответствует числу 63.
Правило умножения (основная формула комбинаторики)
Общее число N способов, которыми можно выбрать по одному элементу из каждой группы и расставить их в определенном порядке (то есть получить упорядоченную совокупность a,b,c,d), равно:
N=n1*n2*n3* ... nk
T.e. в нашел случае при ладье на a1
N=64*64=4096 различных комбинаций занятости этих линий.
В двоичном виде это число выглядит так
По сути пробегая в цикле от нуля до 4095 мы получим все эти различные комбинации перестановок
block:=mask_rook_attacks(a1);
print_bitboard(block);
for i:=0 to 4095 do begin
block:=mask_rook_attacks(a1);
bitboard:=set_occupancy(i,count_bits(block),block);
print_bitboard(bitboard);
Readln;
end;
Writeln('Нажмите <ENTER>, чтобы выйти ...');
Readln;
// make sure occupancy is on board
if (index and (1 shl count))>0 then
occupancy:=occupancy or (n shl integer(square_));
Index -это как раз число перестановок (в конкретном случае - 4096)
Т.е. пробигая по вариантам занятости for i:=0 to 4095 при условии - i and (1 shl count) мы находим установленный бит и и получаем конкретную занятость для i.
Короче вот реализация на Делфи
// set occupancies
//перебираем по очереди биты в attack_mask
function set_occupancy(index,bits_in_mask:integer;var attack_mask:U64):U64;
var
occupancy,n:U64;
count:integer;
square_:TBoard_squares;
begin
n:=1;
// occupancy map
occupancy:=0;
// loop over the range of bits within attack mask
for count:=0 to bits_in_mask-1 do begin
// get LS1B index of attacks mask
square_:=TBoard_squares(get_ls1b_index(attack_mask));
// pop LS1B in attack map
pop_bit(attack_mask,square_);
// make sure occupancy is on board
if (index and (n shl count))>0 then
occupancy:=occupancy or (n shl integer(square_));
end;
set_occupancy:=occupancy;
end;
Writeln('bishop_relevant_bits');
for square := a8 to h1 do begin
if (((integer(square) mod 8)=0) and (square > a8)) then Writeln('');
Write(count_bits(mask_bishop_attacks(square))); Write(' ');
end;
Writeln('');
Writeln('rook_relevant_bits');
for square := a8 to h1 do begin
if (((integer(square) mod 8)=0) and (square > a8)) then Writeln('');
Write(count_bits(mask_rook_attacks(square))); Write(' ');
end;
Writeln('');
Writeln('Нажмите <ENTER>, чтобы выйти ...');
Readln;
Уроки 13 и 14
13.Bitboard CHESS ENGINE in C: implementing pseudo RANDOM NUMBER generator using XORSHIFT32 algorithm
14.Bitboard CHESS ENGINE in C: generating MAGIC NUMBER candidates
Макс рассказывает как сгенерировать числа - кандитаты в магические числа.
По сути - это псевдослучайные числа.
И самое интересное, далее он расскажет, как их использовать в качестве магических чисел.
{/**********************************\
==================================
Random numbers
==================================
\**********************************/}
// generate 32-bit pseudo legal numbers
function get_random_U32_number():cardinal;
begin
// XOR shift algorithm
stateU32:=stateU32 xor (stateU32 shl 13);
stateU32:=stateU32 xor (stateU32 shr 17);
stateU32:=stateU32 xor (stateU32 shl 5);
get_random_U32_number:=stateU32;
end;
// generate 64-bit pseudo legal numbers
function get_random_U64_number():U64;
var
// define 4 random numbers
n1,n2,n3,n4:U64;
begin
n1:=0;n2:=0;n3:=0; n4:=0;
// init random numbers slicing 16 bits from MS1B side
n1:=n1 or (get_random_U32_number() and $FFFF);
n2:=n2 or (get_random_U32_number() and $FFFF);
n3:=n3 or (get_random_U32_number() and $FFFF);
n4:=n4 or (get_random_U32_number() and $FFFF);
// return random number
get_random_U64_number:=n1 or (n2 shl 16) or (n3 shl 32) or (n4 shl 48);
end;
// generate magic number candidate
function generate_magic_number():U64;
begin
result:=get_random_U64_number() and get_random_U64_number() and get_random_U64_number();
end;
Хотя эти числа всегда одни и те же, но раз это работает, то делаем так же.
Урок 15.Bitboard CHESS ENGINE in C: generating MAGIC NUMBERS via brute force trial and error method.
Разобрался с этим уроком - генерация магических чисел.
Попробую рассказать свое понимание этого.
Итак, какие исходные данные есть у нас.
1. Позиция дальнобойной фигуры (например ладья на a1)
TBoard_squares = (
a8, b8, c8, d8, e8, f8, g8, h8,
a7, b7, c7, d7, e7, f7, g7, h7,
a6, b6, c6, d6, e6, f6, g6, h6,
a5, b5, c5, d5, e5, f5, g5, h5,
a4, b4, c4, d4, e4, f4, g4, h4,
a3, b3, c3, d3, e3, f3, g3, h3,
a2, b2, c2, d2, e2, f2, g2, h2,
a1, b1, c1, d1, e1, f1, g1, h1
);
2. Количество атакуемых клеток из этой позиции при отсутствии блокирующих фигур (края доски не учитываются)
(ладья на a1 атакует 6 клеток по горизонтали, 6 по вертикали: всего 12)
// rook relevant occupancy bit count for every square on board
rook_relevant_bits: array [0..63] of integer =(
12, 11, 11, 11, 11, 11, 11, 12,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
12, 11, 11, 11, 11, 11, 11, 12
);
Производные от первоначальных данных
1.битбоард атаки (ладьи) этих 12 клеток
attack_mask:=mask_bishop_attacks(square)
2. Количество перестановок блокирующих фигур на атакуемых ладьей полях
occupancy_indicies:integer;
- 64*64
- 2 в степени 12
- 1 << 12
occupancy_indicies:=1 shl relevant_bits;
3.Комбинации занятости блокирующих фигур
occupancies:array[0..4095] of U64;
4.Самое важное это атаки с учетом блокирующих фигур
// init attack tables
attacks:array[0..4095] of U64;
Вроде бы все прекрасно - эти атаки нам и нужны в идеале.
Но есть одно большое НО.
Чтобы найти их в таблице array[0..4095], придется долго и упорно ее перелапачивать.
Умные люди решили проблему так -
Придумать такую хэш функция, чтобы с помощью простого ключа быстро находить нужные атаки в таблице.
Ключ, это и есть - магическое число с простыми манипуляциями с ним.
Это как подойти к реальной двери, глянуть на скважину замка, достать отмычку, туда сюда ее изогнуть на глаз и пробовать открыть замок.
Этот урок Макса, как раз и есть, описание способа, гнуть отмычку туда сюда , чтобы получить ключ.
Способ такой.
1.Создается массив
// init used attacks
used_attacks:array[0..4095] of U64;
Инициализируется 0.
FillChar(used_attacks, 4096*SizeOf(UInt64), n); - делфи
memset(used_attacks, 0ULL, sizeof(used_attacks)); - СИ
2.U64 magic_number - генерируем 64 битные случайные числа
3.Подставляем ключ в хэш функцию
magic_index := integer((occupancies[index] * magic_number) shr (64 - relevant_bits)); - делфи
int magic_index = (int)((occupancies[index] * magic_number) >> (64 - relevant_bits)); - Си
Пробуем полученный ключ - если уже был другой ключ, то это коллизия ( ключ выбрасываем)
/**********************************\
==================================
Didactic
BITBOARD CHESS ENGINE
by
Code Monkey King
==================================
\**********************************/
// system headers
#include <stdio.h>
#include <string.h>
// define bitboard data type
#define U64 unsigned long long
// board squares
enum {
a8, b8, c8, d8, e8, f8, g8, h8,
a7, b7, c7, d7, e7, f7, g7, h7,
a6, b6, c6, d6, e6, f6, g6, h6,
a5, b5, c5, d5, e5, f5, g5, h5,
a4, b4, c4, d4, e4, f4, g4, h4,
a3, b3, c3, d3, e3, f3, g3, h3,
a2, b2, c2, d2, e2, f2, g2, h2,
a1, b1, c1, d1, e1, f1, g1, h1
};
// sides to move (colors)
enum { white, black };
// bishop and rook
enum { rook, bishop };
// convert squares to coordinates
const char *square_to_coordinates[] = {
"a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8",
"a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7",
"a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6",
"a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5",
"a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4",
"a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3",
"a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2",
"a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1",
};
/**********************************\
==================================
Random numbers
==================================
\**********************************/
// pseudo random number state
unsigned int random_state = 1804289383;
// generate 32-bit pseudo legal numbers
unsigned int get_random_U32_number()
{
// get current state
unsigned int number = random_state;
// XOR shift algorithm
number ^= number << 13;
number ^= number >> 17;
number ^= number << 5;
// update random number state
random_state = number;
// return random number
return number;
}
// generate 64-bit pseudo legal numbers
U64 get_random_U64_number()
{
// define 4 random numbers
U64 n1, n2, n3, n4;
// init random numbers slicing 16 bits from MS1B side
n1 = (U64)(get_random_U32_number()) & 0xFFFF;
n2 = (U64)(get_random_U32_number()) & 0xFFFF;
n3 = (U64)(get_random_U32_number()) & 0xFFFF;
n4 = (U64)(get_random_U32_number()) & 0xFFFF;
// return random number
return n1 | (n2 << 16) | (n3 << 32) | (n4 << 48);
}
// generate magic number candidate
U64 generate_magic_number()
{
return get_random_U64_number() & get_random_U64_number() & get_random_U64_number();
}
/**********************************\
==================================
Bit manipulations
==================================
\**********************************/
// set/get/pop bit macros
#define set_bit(bitboard, square) (bitboard |= (1ULL << square))
#define get_bit(bitboard, square) (bitboard & (1ULL << square))
#define pop_bit(bitboard, square) (get_bit(bitboard, square) ? bitboard ^= (1ULL << square) : 0)
// count bits within a bitboard (Brian Kernighan's way)
static inline int count_bits(U64 bitboard)
{
// bit counter
int count = 0;
// consecutively reset least significant 1st bit
while (bitboard)
{
// increment count
count++;
// reset least significant 1st bit
bitboard &= bitboard - 1;
}
// return bit count
return count;
}
// get least significant 1st bit index
static inline int get_ls1b_index(U64 bitboard)
{
// make sure bitboard is not 0
if (bitboard)
{
// count trailing bits before LS1B
return count_bits((bitboard & -bitboard) - 1);
}
//otherwise
else
// return illegal index
return -1;
}
/**********************************\
==================================
Input & Output
==================================
\**********************************/
// print bitboard
void print_bitboard(U64 bitboard)
{
printf("\n");
// loop over board ranks
for (int rank = 0; rank < 8; rank++)
{
// loop over board files
for (int file = 0; file < 8; file++)
{
// convert file & rank into square index
int square = rank * 8 + file;
// print ranks
if (!file)
printf(" %d ", 8 - rank);
// print bit state (either 1 or 0)
printf(" %d", get_bit(bitboard, square) ? 1 : 0);
}
// print new line every rank
printf("\n");
}
// print board files
printf("\n a b c d e f g h\n\n");
// print bitboard as unsigned decimal number
printf(" Bitboard: %llud\n\n", bitboard);
}
/**********************************\
==================================
Attacks
==================================
\**********************************/
/*
not A file
8 0 1 1 1 1 1 1 1
7 0 1 1 1 1 1 1 1
6 0 1 1 1 1 1 1 1
5 0 1 1 1 1 1 1 1
4 0 1 1 1 1 1 1 1
3 0 1 1 1 1 1 1 1
2 0 1 1 1 1 1 1 1
1 0 1 1 1 1 1 1 1
a b c d e f g h
not H file
8 1 1 1 1 1 1 1 0
7 1 1 1 1 1 1 1 0
6 1 1 1 1 1 1 1 0
5 1 1 1 1 1 1 1 0
4 1 1 1 1 1 1 1 0
3 1 1 1 1 1 1 1 0
2 1 1 1 1 1 1 1 0
1 1 1 1 1 1 1 1 0
a b c d e f g h
not HG file
8 1 1 1 1 1 1 0 0
7 1 1 1 1 1 1 0 0
6 1 1 1 1 1 1 0 0
5 1 1 1 1 1 1 0 0
4 1 1 1 1 1 1 0 0
3 1 1 1 1 1 1 0 0
2 1 1 1 1 1 1 0 0
1 1 1 1 1 1 1 0 0
a b c d e f g h
not AB file
8 0 0 1 1 1 1 1 1
7 0 0 1 1 1 1 1 1
6 0 0 1 1 1 1 1 1
5 0 0 1 1 1 1 1 1
4 0 0 1 1 1 1 1 1
3 0 0 1 1 1 1 1 1
2 0 0 1 1 1 1 1 1
1 0 0 1 1 1 1 1 1
a b c d e f g h
*/
// not A file constant
const U64 not_a_file = 18374403900871474942ULL;
// not H file constant
const U64 not_h_file = 9187201950435737471ULL;
// not HG file constant
const U64 not_hg_file = 4557430888798830399ULL;
// not AB file constant
const U64 not_ab_file = 18229723555195321596ULL;
// bishop relevant occupancy bit count for every square on board
const int bishop_relevant_bits[64] = {
6, 5, 5, 5, 5, 5, 5, 6,
5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 7, 7, 7, 7, 5, 5,
5, 5, 7, 9, 9, 7, 5, 5,
5, 5, 7, 9, 9, 7, 5, 5,
5, 5, 7, 7, 7, 7, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5,
6, 5, 5, 5, 5, 5, 5, 6
};
// rook relevant occupancy bit count for every square on board
const int rook_relevant_bits[64] = {
12, 11, 11, 11, 11, 11, 11, 12,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
12, 11, 11, 11, 11, 11, 11, 12
};
// rook magic numbers
U64 rook_magic_numbers[64] = {
0x8a80104000800020ULL,
0x140002000100040ULL,
0x2801880a0017001ULL,
0x100081001000420ULL,
0x200020010080420ULL,
0x3001c0002010008ULL,
0x8480008002000100ULL,
0x2080088004402900ULL,
0x800098204000ULL,
0x2024401000200040ULL,
0x100802000801000ULL,
0x120800800801000ULL,
0x208808088000400ULL,
0x2802200800400ULL,
0x2200800100020080ULL,
0x801000060821100ULL,
0x80044006422000ULL,
0x100808020004000ULL,
0x12108a0010204200ULL,
0x140848010000802ULL,
0x481828014002800ULL,
0x8094004002004100ULL,
0x4010040010010802ULL,
0x20008806104ULL,
0x100400080208000ULL,
0x2040002120081000ULL,
0x21200680100081ULL,
0x20100080080080ULL,
0x2000a00200410ULL,
0x20080800400ULL,
0x80088400100102ULL,
0x80004600042881ULL,
0x4040008040800020ULL,
0x440003000200801ULL,
0x4200011004500ULL,
0x188020010100100ULL,
0x14800401802800ULL,
0x2080040080800200ULL,
0x124080204001001ULL,
0x200046502000484ULL,
0x480400080088020ULL,
0x1000422010034000ULL,
0x30200100110040ULL,
0x100021010009ULL,
0x2002080100110004ULL,
0x202008004008002ULL,
0x20020004010100ULL,
0x2048440040820001ULL,
0x101002200408200ULL,
0x40802000401080ULL,
0x4008142004410100ULL,
0x2060820c0120200ULL,
0x1001004080100ULL,
0x20c020080040080ULL,
0x2935610830022400ULL,
0x44440041009200ULL,
0x280001040802101ULL,
0x2100190040002085ULL,
0x80c0084100102001ULL,
0x4024081001000421ULL,
0x20030a0244872ULL,
0x12001008414402ULL,
0x2006104900a0804ULL,
0x1004081002402ULL
};
// bishop magic numbers
U64 bishop_magic_numbers[64] = {
0x40040844404084ULL,
0x2004208a004208ULL,
0x10190041080202ULL,
0x108060845042010ULL,
0x581104180800210ULL,
0x2112080446200010ULL,
0x1080820820060210ULL,
0x3c0808410220200ULL,
0x4050404440404ULL,
0x21001420088ULL,
0x24d0080801082102ULL,
0x1020a0a020400ULL,
0x40308200402ULL,
0x4011002100800ULL,
0x401484104104005ULL,
0x801010402020200ULL,
0x400210c3880100ULL,
0x404022024108200ULL,
0x810018200204102ULL,
0x4002801a02003ULL,
0x85040820080400ULL,
0x810102c808880400ULL,
0xe900410884800ULL,
0x8002020480840102ULL,
0x220200865090201ULL,
0x2010100a02021202ULL,
0x152048408022401ULL,
0x20080002081110ULL,
0x4001001021004000ULL,
0x800040400a011002ULL,
0xe4004081011002ULL,
0x1c004001012080ULL,
0x8004200962a00220ULL,
0x8422100208500202ULL,
0x2000402200300c08ULL,
0x8646020080080080ULL,
0x80020a0200100808ULL,
0x2010004880111000ULL,
0x623000a080011400ULL,
0x42008c0340209202ULL,
0x209188240001000ULL,
0x400408a884001800ULL,
0x110400a6080400ULL,
0x1840060a44020800ULL,
0x90080104000041ULL,
0x201011000808101ULL,
0x1a2208080504f080ULL,
0x8012020600211212ULL,
0x500861011240000ULL,
0x180806108200800ULL,
0x4000020e01040044ULL,
0x300000261044000aULL,
0x802241102020002ULL,
0x20906061210001ULL,
0x5a84841004010310ULL,
0x4010801011c04ULL,
0xa010109502200ULL,
0x4a02012000ULL,
0x500201010098b028ULL,
0x8040002811040900ULL,
0x28000010020204ULL,
0x6000020202d0240ULL,
0x8918844842082200ULL,
0x4010011029020020ULL
};
// pawn attacks table [side][square]
U64 pawn_attacks[2][64];
// knight attacks table [square]
U64 knight_attacks[64];
// king attacks table [square]
U64 king_attacks[64];
// generate pawn attacks
U64 mask_pawn_attacks(int side, int square)
{
// result attacks bitboard
U64 attacks = 0ULL;
// piece bitboard
U64 bitboard = 0ULL;
// set piece on board
set_bit(bitboard, square);
// white pawns
if (!side)
{
// generate pawn attacks
if ((bitboard >> 7) & not_a_file) attacks |= (bitboard >> 7);
if ((bitboard >> 9) & not_h_file) attacks |= (bitboard >> 9);
}
// black pawns
else
{
// generate pawn attacks
if ((bitboard << 7) & not_h_file) attacks |= (bitboard << 7);
if ((bitboard << 9) & not_a_file) attacks |= (bitboard << 9);
}
// return attack map
return attacks;
}
// generate knight attacks
U64 mask_knight_attacks(int square)
{
// result attacks bitboard
U64 attacks = 0ULL;
// piece bitboard
U64 bitboard = 0ULL;
// set piece on board
set_bit(bitboard, square);
// generate knight attacks
if ((bitboard >> 17) & not_h_file) attacks |= (bitboard >> 17);
if ((bitboard >> 15) & not_a_file) attacks |= (bitboard >> 15);
if ((bitboard >> 10) & not_hg_file) attacks |= (bitboard >> 10);
if ((bitboard >> 6) & not_ab_file) attacks |= (bitboard >> 6);
if ((bitboard << 17) & not_a_file) attacks |= (bitboard << 17);
if ((bitboard << 15) & not_h_file) attacks |= (bitboard << 15);
if ((bitboard << 10) & not_ab_file) attacks |= (bitboard << 10);
if ((bitboard << 6) & not_hg_file) attacks |= (bitboard << 6);
// return attack map
return attacks;
}
// generate king attacks
U64 mask_king_attacks(int square)
{
// result attacks bitboard
U64 attacks = 0ULL;
// piece bitboard
U64 bitboard = 0ULL;
// set piece on board
set_bit(bitboard, square);
// generate king attacks
if (bitboard >> 8) attacks |= (bitboard >> 8);
if ((bitboard >> 9) & not_h_file) attacks |= (bitboard >> 9);
if ((bitboard >> 7) & not_a_file) attacks |= (bitboard >> 7);
if ((bitboard >> 1) & not_h_file) attacks |= (bitboard >> 1);
if (bitboard << 8) attacks |= (bitboard << 8);
if ((bitboard << 9) & not_a_file) attacks |= (bitboard << 9);
if ((bitboard << 7) & not_h_file) attacks |= (bitboard << 7);
if ((bitboard << 1) & not_a_file) attacks |= (bitboard << 1);
// return attack map
return attacks;
}
// mask bishop attacks
U64 mask_bishop_attacks(int square)
{
// result attacks bitboard
U64 attacks = 0ULL;
// init ranks & files
int r, f;
// init target rank & files
int tr = square / 8;
int tf = square % 8;
// mask relevant bishop occupancy bits
for (r = tr + 1, f = tf + 1; r <= 6 && f <= 6; r++, f++) attacks |= (1ULL << (r * 8 + f));
for (r = tr - 1, f = tf + 1; r >= 1 && f <= 6; r--, f++) attacks |= (1ULL << (r * 8 + f));
for (r = tr + 1, f = tf - 1; r <= 6 && f >= 1; r++, f--) attacks |= (1ULL << (r * 8 + f));
for (r = tr - 1, f = tf - 1; r >= 1 && f >= 1; r--, f--) attacks |= (1ULL << (r * 8 + f));
// return attack map
return attacks;
}
// mask rook attacks
U64 mask_rook_attacks(int square)
{
// result attacks bitboard
U64 attacks = 0ULL;
// init ranks & files
int r, f;
// init target rank & files
int tr = square / 8;
int tf = square % 8;
// mask relevant rook occupancy bits
for (r = tr + 1; r <= 6; r++) attacks |= (1ULL << (r * 8 + tf));
for (r = tr - 1; r >= 1; r--) attacks |= (1ULL << (r * 8 + tf));
for (f = tf + 1; f <= 6; f++) attacks |= (1ULL << (tr * 8 + f));
for (f = tf - 1; f >= 1; f--) attacks |= (1ULL << (tr * 8 + f));
// return attack map
return attacks;
}
// generate bishop attacks on the fly
U64 bishop_attacks_on_the_fly(int square, U64 block)
{
// result attacks bitboard
U64 attacks = 0ULL;
// init ranks & files
int r, f;
// init target rank & files
int tr = square / 8;
int tf = square % 8;
// generate bishop atacks
for (r = tr + 1, f = tf + 1; r <= 7 && f <= 7; r++, f++)
{
attacks |= (1ULL << (r * 8 + f));
if ((1ULL << (r * 8 + f)) & block) break;
}
for (r = tr - 1, f = tf + 1; r >= 0 && f <= 7; r--, f++)
{
attacks |= (1ULL << (r * 8 + f));
if ((1ULL << (r * 8 + f)) & block) break;
}
for (r = tr + 1, f = tf - 1; r <= 7 && f >= 0; r++, f--)
{
attacks |= (1ULL << (r * 8 + f));
if ((1ULL << (r * 8 + f)) & block) break;
}
for (r = tr - 1, f = tf - 1; r >= 0 && f >= 0; r--, f--)
{
attacks |= (1ULL << (r * 8 + f));
if ((1ULL << (r * 8 + f)) & block) break;
}
// return attack map
return attacks;
}
// generate rook attacks on the fly
U64 rook_attacks_on_the_fly(int square, U64 block)
{
// result attacks bitboard
U64 attacks = 0ULL;
// init ranks & files
int r, f;
// init target rank & files
int tr = square / 8;
int tf = square % 8;
// generate rook attacks
for (r = tr + 1; r <= 7; r++)
{
attacks |= (1ULL << (r * 8 + tf));
if ((1ULL << (r * 8 + tf)) & block) break;
}
for (r = tr - 1; r >= 0; r--)
{
attacks |= (1ULL << (r * 8 + tf));
if ((1ULL << (r * 8 + tf)) & block) break;
}
for (f = tf + 1; f <= 7; f++)
{
attacks |= (1ULL << (tr * 8 + f));
if ((1ULL << (tr * 8 + f)) & block) break;
}
for (f = tf - 1; f >= 0; f--)
{
attacks |= (1ULL << (tr * 8 + f));
if ((1ULL << (tr * 8 + f)) & block) break;
}
// return attack map
return attacks;
}
// init leaper pieces attacks
void init_leapers_attacks()
{
// loop over 64 board squares
for (int square = 0; square < 64; square++)
{
// init pawn attacks
pawn_attacks[white][square] = mask_pawn_attacks(white, square);
pawn_attacks[black][square] = mask_pawn_attacks(black, square);
// init knight attacks
knight_attacks[square] = mask_knight_attacks(square);
// init king attacks
king_attacks[square] = mask_king_attacks(square);
}
}
// set occupancies
U64 set_occupancy(int index, int bits_in_mask, U64 attack_mask)
{
// occupancy map
U64 occupancy = 0ULL;
// loop over the range of bits within attack mask
for (int count = 0; count < bits_in_mask; count++)
{
// get LS1B index of attacks mask
int square = get_ls1b_index(attack_mask);
// pop LS1B in attack map
pop_bit(attack_mask, square);
// make sure occupancy is on board
if (index & (1 << count))
// populate occupancy map
occupancy |= (1ULL << square);
}
// return occupancy map
return occupancy;
}
/**********************************\
==================================
Magics
==================================
\**********************************/
// find appropriate magic number
U64 find_magic_number(int square, int relevant_bits, int bishop)
{
// init occupancies
U64 occupancies[4096];
// init attack tables
U64 attacks[4096];
// init used attacks
U64 used_attacks[4096];
// init attack mask for a current piece
U64 attack_mask = bishop ? mask_bishop_attacks(square) : mask_rook_attacks(square);
// init occupancy indicies
int occupancy_indicies = 1 << relevant_bits;
// loop over occupancy indicies
for (int index = 0; index < occupancy_indicies; index++)
{
// init occupancies
occupancies[index] = set_occupancy(index, relevant_bits, attack_mask);
// init attacks
attacks[index] = bishop ? bishop_attacks_on_the_fly(square, occupancies[index]) :
rook_attacks_on_the_fly(square, occupancies[index]);
}
// test magic numbers loop
for (int random_count = 0; random_count < 100000000; random_count++)
{
// generate magic number candidate
U64 magic_number = generate_magic_number();
// skip inappropriate magic numbers
if (count_bits((attack_mask * magic_number) & 0xFF00000000000000) < 6) continue;
// init used attacks
memset(used_attacks, 0ULL, sizeof(used_attacks));
// init index & fail flag
int index, fail;
// test magic index loop
for (index = 0, fail = 0; !fail && index < occupancy_indicies; index++)
{
// init magic index
int magic_index = (int)((occupancies[index] * magic_number) >> (64 - relevant_bits));
//printf("occupancies[%d] = %llud\n\n",index,occupancies[index]);
//printf("magic_index = %d\n\n", magic_index);
//printf("magic_number = %llud\n\n", magic_number);
//getchar();
// if magic index works
if (used_attacks[magic_index] == 0ULL)
// init used attacks
used_attacks[magic_index] = attacks[index];
// otherwise
else if (used_attacks[magic_index] != attacks[index])
// magic index doesn't work
fail = 1;
}
// if magic number works
if (!fail)
// return it
return magic_number;
}
// if magic number doesn't work
printf(" Magic number fails!\n");
return 0ULL;
}
// init magic numbers
void init_magic_numbers()
{
// loop over 64 board squares
for (int square = 0; square < 64; square++)
// init rook magic numbers
rook_magic_numbers[square] = find_magic_number(square, rook_relevant_bits[square], rook);
// loop over 64 board squares
for (int square = 0; square < 64; square++)
// init bishop magic numbers
bishop_magic_numbers[square] = find_magic_number(square, bishop_relevant_bits[square], bishop);
}
/**********************************\
==================================
Init all
==================================
\**********************************/
// init all variables
void init_all()
{
// init leaper pieces attacks
init_leapers_attacks();
// init magic numbers
init_magic_numbers();
}
/**********************************\
==================================
Main driver
==================================
\**********************************/
int main()
{
// init all
init_all();
return 0;
}
Делфи
program bbc;
{$APPTYPE CONSOLE}
uses
SysUtils,Windows,TypInfo;
type
U64 = UInt64;
TBoard_squares = (
a8, b8, c8, d8, e8, f8, g8, h8,
a7, b7, c7, d7, e7, f7, g7, h7,
a6, b6, c6, d6, e6, f6, g6, h6,
a5, b5, c5, d5, e5, f5, g5, h5,
a4, b4, c4, d4, e4, f4, g4, h4,
a3, b3, c3, d3, e3, f3, g3, h3,
a2, b2, c2, d2, e2, f2, g2, h2,
a1, b1, c1, d1, e1, f1, g1, h1
);
// sides to move (colors)
TSide = (white, black);
// bishop and rook
Tbishop = (rook, bishop);
//Первый индекс обозначает номер строки в матрице, второй индекс – номер столбца
TPawn_attacks=array[TSide,TBoard_squares] of U64;
TChip_attacks=array[TBoard_squares] of U64;
var
bitboard:U64=0;
block:U64=0;
// pawn attacks table [side][square]
pawn_attacks:TPawn_attacks;
// knight attacks table [square]
knight_attacks:TChip_attacks;
// king attacks table [square]
king_attacks:TChip_attacks;
square:TBoard_squares;
// pseudo random number state
//cardinal - беззнаковое целое
stateU32:cardinal = 1804289383;
const
{/*
not A file
8 0 1 1 1 1 1 1 1
7 0 1 1 1 1 1 1 1
6 0 1 1 1 1 1 1 1
5 0 1 1 1 1 1 1 1
4 0 1 1 1 1 1 1 1
3 0 1 1 1 1 1 1 1
2 0 1 1 1 1 1 1 1
1 0 1 1 1 1 1 1 1
a b c d e f g h
not H file
8 1 1 1 1 1 1 1 0
7 1 1 1 1 1 1 1 0
6 1 1 1 1 1 1 1 0
5 1 1 1 1 1 1 1 0
4 1 1 1 1 1 1 1 0
3 1 1 1 1 1 1 1 0
2 1 1 1 1 1 1 1 0
1 1 1 1 1 1 1 1 0
a b c d e f g h
not HG file
8 1 1 1 1 1 1 0 0
7 1 1 1 1 1 1 0 0
6 1 1 1 1 1 1 0 0
5 1 1 1 1 1 1 0 0
4 1 1 1 1 1 1 0 0
3 1 1 1 1 1 1 0 0
2 1 1 1 1 1 1 0 0
1 1 1 1 1 1 1 0 0
a b c d e f g h
not AB file
8 0 0 1 1 1 1 1 1
7 0 0 1 1 1 1 1 1
6 0 0 1 1 1 1 1 1
5 0 0 1 1 1 1 1 1
4 0 0 1 1 1 1 1 1
3 0 0 1 1 1 1 1 1
2 0 0 1 1 1 1 1 1
1 0 0 1 1 1 1 1 1
a b c d e f g h
*/}
// not A file constant
not_a_file:U64 = $FEFEFEFEFEFEFEFE;//18374403900871474942;
// not H file constant
not_h_file:U64 = $7F7F7F7F7F7F7F7F;//9187201950435737471;
// not HG file constant
not_hg_file:U64 =$3F3F3F3F3F3F3F3F;//4557430888798830399;
// not AB file constant
not_ab_file:U64 =$FCFCFCFCFCFCFCFC;//18229723555195321596;
square_to_coordinates: array [0..63] of string = (
'a8', 'b8', 'c8', 'd8', 'e8', 'f8', 'g8', 'h8',
'a7', 'b7', 'c7', 'd7', 'e7', 'f7', 'g7', 'h7',
'a6', 'b6', 'c6', 'd6', 'e6', 'f6', 'g6', 'h6',
'a5', 'b5', 'c5', 'd5', 'e5', 'f5', 'g5', 'h5',
'a4', 'b4', 'c4', 'd4', 'e4', 'f4', 'g4', 'h4',
'a3', 'b3', 'c3', 'd3', 'e3', 'f3', 'g3', 'h3',
'a2', 'b2', 'c2', 'd2', 'e2', 'f2', 'g2', 'h2',
'a1', 'b1', 'c1', 'd1', 'e1', 'f1', 'g1', 'h1'
);
// bishop relevant occupancy bit count for every square on board
bishop_relevant_bits: array [0..63] of integer =(
6, 5, 5, 5, 5, 5, 5, 6,
5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 7, 7, 7, 7, 5, 5,
5, 5, 7, 9, 9, 7, 5, 5,
5, 5, 7, 9, 9, 7, 5, 5,
5, 5, 7, 7, 7, 7, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5,
6, 5, 5, 5, 5, 5, 5, 6
);
// rook relevant occupancy bit count for every square on board
rook_relevant_bits: array [0..63] of integer =(
12, 11, 11, 11, 11, 11, 11, 12,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
11, 10, 10, 10, 10, 10, 10, 11,
12, 11, 11, 11, 11, 11, 11, 12
);
{/**********************************\
==================================
Random numbers
==================================
\**********************************/}
// generate 32-bit pseudo legal numbers
function get_random_U32_number():cardinal;
begin
// XOR shift algorithm
stateU32:=stateU32 xor (stateU32 shl 13);
stateU32:=stateU32 xor (stateU32 shr 17);
stateU32:=stateU32 xor (stateU32 shl 5);
get_random_U32_number:=stateU32;
end;
// generate 64-bit pseudo legal numbers
function get_random_U64_number():U64;
var
// define 4 random numbers
n1,n2,n3,n4:U64;
begin
n1:=0;n2:=0;n3:=0; n4:=0;
// init random numbers slicing 16 bits from MS1B side
n1:=(n1 or (get_random_U32_number())) and $FFFF;
n2:=(n2 or (get_random_U32_number())) and $FFFF;
n3:=(n3 or (get_random_U32_number())) and $FFFF;
n4:=(n4 or (get_random_U32_number())) and $FFFF;
// return random number
get_random_U64_number:=n1 or (n2 shl 16) or (n3 shl 32) or (n4 shl 48);
end;
// generate magic number candidate
function generate_magic_number():U64;
begin
result:=get_random_U64_number() and get_random_U64_number() and get_random_U64_number();
end;
{/**********************************\
==================================
Bit manipulations
==================================
\**********************************/}
// set/get/pop
function get_bit(bb:U64;const sq:integer):integer;
begin
get_bit:=(bb shr sq) and 1;
end;
procedure set_bit(var bb:U64;const sq:TBoard_squares);
begin
bb:=bb or ($8000000000000000 shr (63-Ord(sq)));
end;
procedure pop_bit(var bb:U64;const sq:TBoard_squares);
begin
if get_bit(bb,Ord(sq))>0 then bb:=bb xor ($8000000000000000 shr (63-Ord(sq)));
end;
// count bits within a bitboard (Brian Kernighan's way)
function count_bits(bb:U64):integer;
var
count:integer;
begin
// bit counter
count := 0;
// consecutively reset least significant 1st bit
while bb>0
do begin
// increment count
Inc(count);
// reset least significant 1st bit
bb := bb and (bb - 1);
end;
count_bits:=count;
end;
// get least significant 1st bit index
function get_ls1b_index(bb:U64):integer;
begin
// make sure bitboard is not 0
if bb<>0 then
// count trailing bits before LS1B
result:=count_bits((bb and (-bb))-1)
// return illegal index
else result:=-1;
end;
{/**********************************\
==================================
Input & Output
==================================
\**********************************/}
// print bitboard
procedure print_bitboard(bb:U64);
var
rank_,file_,sq:integer;
begin
Writeln('');
// loop over board ranks
for rank_ := 0 to 7 do begin
// loop over board files
for file_ := 0 to 7 do begin
// convert file & rank into square index
sq:=rank_*8 + file_;
//// print ranks
if file_ = 0 then begin
Write(8 - rank_);Write(' ');
end;
Write(' ');
// print bit state (either 1 or 0)
Write(get_bit(bb,sq));
end;
// print new line every rank
Writeln('');
end;
// print new line
Writeln('');
// print board files
Writeln(' a b c d e f g h');
Writeln('');
// print bitboard as unsigned decimal number
Write('bitboard = ',bb);
Writeln('');
end;
{/**********************************\
==================================
Attacks
==================================
\**********************************/}
// generate pawn attacks
function mask_pawn_attacks(side:TSide;square:TBoard_squares):U64;
var
attacks,bitboard:U64;
begin
// result attacks bitboard
attacks := 0;
// piece bitboard
bitboard := 0;
// set piece on board
set_bit(bitboard, square);
// white pawns
if (side=white) then begin
if ((bitboard shr 7) and not_a_file)>0 then attacks:=attacks or (bitboard shr 7);
if ((bitboard shr 9) and not_h_file)>0 then attacks:=attacks or (bitboard shr 9);
end
// black pawns
else begin
if ((bitboard shl 7) and not_h_file)>0 then attacks:=attacks or (bitboard shl 7);
if ((bitboard shl 9) and not_a_file)>0 then attacks:=attacks or (bitboard shl 9);
end;
// return attack map
mask_pawn_attacks:=attacks;
end;
// generate knight attacks
function mask_knight_attacks(square:TBoard_squares):U64;
var
attacks,bitboard:U64;
begin
// result attacks bitboard
attacks := 0;
// piece bitboard
bitboard := 0;
// set piece on board
set_bit(bitboard, square);
// generate knight attacks
if ((bitboard shr 17) and not_h_file)>0 then attacks:=attacks or (bitboard shr 17);
if ((bitboard shr 15) and not_a_file)>0 then attacks:=attacks or (bitboard shr 15);
if ((bitboard shr 10) and not_hg_file)>0 then attacks:=attacks or (bitboard shr 10);
if ((bitboard shr 6) and not_ab_file)>0 then attacks:=attacks or (bitboard shr 6);
if ((bitboard shl 17) and not_a_file)>0 then attacks:=attacks or (bitboard shl 17);
if ((bitboard shl 15) and not_h_file)>0 then attacks:=attacks or (bitboard shl 15);
if ((bitboard shl 10) and not_ab_file)>0 then attacks:=attacks or (bitboard shl 10);
if ((bitboard shl 6) and not_hg_file)>0 then attacks:=attacks or (bitboard shl 6);
// return attack map
mask_knight_attacks:=attacks;
end;
// generate king attacks
function mask_king_attacks(square:TBoard_squares):U64;
var
attacks,bitboard:U64;
begin
// result attacks bitboard
attacks := 0;
// piece bitboard
bitboard := 0;
// set piece on board
set_bit(bitboard, square);
// generate king attacks
if (bitboard shr 8)>0 then attacks:=attacks or (bitboard shr 8);
if ((bitboard shr 9) and not_h_file)>0 then attacks:=attacks or (bitboard shr 9);
if ((bitboard shr 1) and not_h_file)>0 then attacks:=attacks or (bitboard shr 1);
if ((bitboard shr 7) and not_a_file)>0 then attacks:=attacks or (bitboard shr 7);
if (bitboard shl 8)>0 then attacks:=attacks or (bitboard shl 8);
if ((bitboard shl 9) and not_a_file)>0 then attacks:=attacks or (bitboard shl 9);
if ((bitboard shl 1) and not_a_file)>0 then attacks:=attacks or (bitboard shl 1);
if ((bitboard shl 7) and not_h_file)>0 then attacks:=attacks or (bitboard shl 7);
// return attack map
mask_king_attacks:=attacks;
end;
//урок 6-masking relevant bishop occupancy bits to form a key for MAGIC BITBOARDS
// mask bishop attacks
function mask_bishop_attacks(square:TBoard_squares):U64;
var
attacks,n:U64;
r,f,tr,tf:integer;
begin
n:=1;
// result attacks bitboard
attacks := 0;
// init target rank & files
tr := Ord(square) div 8;//целочисленное деление
tf := Ord(square) mod 8;//остаток от деления
// mask relevant bishop occupancy bits
f:=tf+1;
for r:=tr+1 to 6 do begin
if f>6 then break;
attacks := attacks or (n shl (r*8+f));
Inc(f);
end;
f:=tf+1;
for r:=tr-1 downto 1 do begin
if f>6 then break;
attacks := attacks or (n shl (r*8+f));
Inc(f);
end;
f:=tf-1;
for r:=tr+1 to 6 do begin
if f<1 then break;
attacks := attacks or (n shl (r*8+f));
Dec(f);
end;
f:=tf-1;
for r:=tr-1 downto 1 do begin
if f<1 then break;
attacks := attacks or (n shl (r*8+f));
Dec(f);
end;
// return attack map
mask_bishop_attacks:=attacks;
end;
//урок 7-masking relevant ROOK OCCUPANCY BITS to form a key for MAGIC BITBOARDS
// mask rook attacks
function mask_rook_attacks(square:TBoard_squares):U64;
var
attacks,n:U64;
r,f,tr,tf:integer;
begin
n:=1;
// result attacks bitboard
attacks := 0;
// init target rank & files
tr := Ord(square) div 8;//целочисленное деление
tf := Ord(square) mod 8;//остаток от деления
// mask relevant bishop occupancy bits
for r:=tr+1 to 6 do attacks := attacks or (n shl (r*8+tf));
for r:=tr-1 downto 1 do attacks := attacks or (n shl (r*8+tf));
for f:=tf+1 to 6 do attacks := attacks or (n shl (tr*8+f));
for f:=tf-1 downto 1 do attacks := attacks or (n shl (tr*8+f));
// return attack map
mask_rook_attacks:=attacks;
end;
//урок 8-generating SLIDER PIECES ATTACKS on the fly for MAGIC BITBOARD purposes
// generate bishop attacks on the fly
function bishop_attacks_on_the_fly(square:TBoard_squares;blk:U64):U64;
var
attacks,n:U64;
r,f,tr,tf:integer;
begin
n:=1;
// result attacks bitboard
attacks := 0;
// init target rank & files
tr := Ord(square) div 8;//целочисленное деление
tf := Ord(square) mod 8;//остаток от деления
// generate bishop atacks
f:=tf+1;
for r:=tr+1 to 7 do begin
if f>7 then break;
attacks := attacks or (n shl (r*8+f));
if ((n shl (r*8+f)) and blk)>0 then break;
Inc(f);
end;
f:=tf+1;
for r:=tr-1 downto 0 do begin
if f>7 then break;
attacks := attacks or (n shl (r*8+f));
if ((n shl (r*8+f)) and blk)>0 then break;
Inc(f);
end;
f:=tf-1;
for r:=tr+1 to 7 do begin
if f<0 then break;
attacks := attacks or (n shl (r*8+f));
if ((n shl (r*8+f)) and blk)>0 then break;
Dec(f);
end;
f:=tf-1;
for r:=tr-1 downto 0 do begin
if f<0 then break;
attacks := attacks or (n shl (r*8+f));
if ((n shl (r*8+f)) and blk)>0 then break;
Dec(f);
end;
// return attack map
bishop_attacks_on_the_fly:=attacks;
end;
// generate rook attacks on the fly
function rook_attacks_on_the_fly(square:TBoard_squares;blk:U64):U64;
var
attacks,n:U64;
r,f,tr,tf:integer;
begin
n:=1;
// result attacks bitboard
attacks := 0;
// init target rank & files
tr := Ord(square) div 8;//целочисленное деление
tf := Ord(square) mod 8;//остаток от деления
// generate rook attacks
for r:=tr+1 to 7 do begin
attacks := attacks or (n shl (r*8+tf));
if ((n shl (r*8+tf)) and blk)>0 then break;
end;
for r:=tr-1 downto 0 do begin
attacks := attacks or (n shl (r*8+tf));
if ((n shl (r*8+tf)) and blk)>0 then break;
end;
for f:=tf+1 to 7 do begin
attacks := attacks or (n shl (tr*8+f));
if ((n shl (tr*8+f)) and blk)>0 then break;
end;
for f:=tf-1 downto 0 do begin
attacks := attacks or (n shl (tr*8+f));
if ((n shl (tr*8+f)) and blk)>0 then break;
end;
// return attack map
rook_attacks_on_the_fly:=attacks;
end;
// init leaper pieces attacks
procedure init_leapers_attacks();
var
square:TBoard_squares;
begin
// loop over 64 board squares
for square := a8 to h1 do begin
//урок 3-generating pre-calculated PAWN ATTACK tables
// init pawn attacks
pawn_attacks[white][square] := mask_pawn_attacks(white,square);
pawn_attacks[black][square] := mask_pawn_attacks(black,square);
//урок 4-generating pre-calculated KNIGHT ATTACK table
// init knight attacks
knight_attacks[square] := mask_knight_attacks(square);
//урок 5-generating pre-calculated KING ATTACK tables
// init king attacks
king_attacks[square] := mask_king_attacks(square);
end;
end;
// set occupancies
//перебираем по очереди биты в attack_mask
//испр. ошибка - было -var attack_mask:U64, стало-attack_mask:U64
function set_occupancy(index,bits_in_mask:integer;attack_mask:U64):U64;
var
occupancy,n:U64;
count:integer;
square_:TBoard_squares;
begin
n:=1;
// occupancy map
occupancy:=0;
// loop over the range of bits within attack mask
for count:=0 to bits_in_mask-1 do begin
// get LS1B index of attacks mask
square_:=TBoard_squares(get_ls1b_index(attack_mask));
// pop LS1B in attack map
pop_bit(attack_mask,square_);
// make sure occupancy is on board
if (index and (n shl count))>0 then
occupancy:=occupancy or (n shl integer(square_));
end;
set_occupancy:=occupancy;
end;
{/**********************************\
==================================
Magics
function rook_attacks_on_the_fly(square:TBoard_squares;blk:U64):U64;
==================================
\**********************************/}
// find appropriate magic number
function find_magic_number(square:TBoard_squares;relevant_bits:integer;bishop_flag:Tbishop):U64;
//function find_magic_number():U64;
var
// init occupancies
occupancies:array[0..4095] of U64;
// init attack tables
attacks:array[0..4095] of U64;
// init used attacks
used_attacks:array[0..4095] of U64;
n:Byte;
// init attack mask for a current piece
attack_mask:U64;
// init occupancy indicies
occupancy_indicies:integer;
magic_index:integer;
index,fail,random_count:integer;
magic_number:U64;
//memset
//https://metanit.com/c/tutorial/8.3.php?ysclid=ldc20xz850854644891
begin
attack_mask:=0;
// init attack mask for a current piece
if bishop_flag=bishop then attack_mask:=mask_bishop_attacks(square)
else attack_mask:=mask_rook_attacks(square);
// init occupancy indicies напр. ладья на a1 - rook_relevant_bits: array [ ] = 12
// 1 shl 12 = 4096
occupancy_indicies:=1 shl relevant_bits;
// loop over occupancy indicies
for index:=0 to occupancy_indicies-1 do begin
// init occupancies
occupancies[index] := set_occupancy(index,relevant_bits,attack_mask);
// init attacks
if bishop_flag=bishop then attacks[index]:= bishop_attacks_on_the_fly(square, occupancies[index])
else attacks[index]:= rook_attacks_on_the_fly(square, occupancies[index]);
end;
// test magic numbers loop
n:=0;
for random_count:= 0 to 100000000 do begin
// generate magic number candidate
magic_number := generate_magic_number();
// skip inappropriate magic numbers
if (count_bits((attack_mask * magic_number) and $FF00000000000000) < 6) then continue;
//http://www.delphibasics.ru/FillChar.php
// procedure FillChar ( var Buffer; FillCount : Integer; FillValue : Byte ) ;
{Процедура FillChar заполняет раздел памяти Buffer
тем же самым байтом или символом FillValue FillCount раз.
Это используется, преимущественно, для инициализирования массивов чисел.}
// Теперь заполняем массив used_attacks значением 0
// UInt64 - 8 байт
FillChar(used_attacks, 4096*SizeOf(UInt64), n);
fail := 0;
for index:=0 to occupancy_indicies-1 do begin
// init magic index
magic_index := integer((occupancies[index] * magic_number) shr (64 - relevant_bits));
{Writeln('occupancies[',index,'] = ',occupancies[index]);
Writeln('magic_index = ',magic_index);
Writeln('magic_number = ',magic_number);
Readln;}
// if magic index works
if (used_attacks[magic_index] = 0) then
// init used attacks
used_attacks[magic_index] := attacks[index]
// otherwise
else if (used_attacks[magic_index] <> attacks[index]) then begin
// magic index doesn't work
fail := 1;break;
end;
end;
// if magic number works
if (fail=0) then begin
// return it
result:=magic_number;
exit;
end;
end;
// if magic number doesn't work
Writeln(' Magic number fails! ');
result:=0;
end;
// init magic numbers
procedure init_magic_numbers();
var
tem64:U64;
temp32:integer;
begin
// loop over 64 board squares
for square := a8 to h1 do begin
tem64:=find_magic_number(square,rook_relevant_bits[integer(square)],rook);
Write('0x');Write(Format('%x',[tem64]));Write('ULL');
Writeln('');
//Write(tem64);
//Readln;
end;
Writeln('====================');
for square := a8 to h1 do begin
tem64:=find_magic_number(square,bishop_relevant_bits[integer(square)],bishop);
Write('0x');Write(Format('%x',[tem64]));Write('ULL');
Writeln('');
//Write(tem64);
//Readln;
end;
end;
{/**********************************\
==================================
Main driver
==================================
\**********************************/}
begin
//Переключение окна консоли на кодовую страницу CP1251 (Win-1251).
//Если после переключения русские буквы показываются неверно,
//следует открыть системное меню консольного окна - щелчком мыши в левом
//верхнем углу окна консоли. И выбрать:
//Свойства - закладка "Шрифт" - выбрать шрифт: "Lucida Console".
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
// ==================================
init_leapers_attacks();
init_magic_numbers();
Writeln('Нажмите <ENTER>, чтобы выйти ...');
Readln;
end.
{print_bitboard(get_random_U32_number());
print_bitboard(get_random_U32_number());
print_bitboard(get_random_U64_number());
print_bitboard(generate_magic_number());}
{Writeln('bishop_relevant_bits');
for square := a8 to h1 do begin
if (integer(square) mod 8)=0 then Writeln('');
Write(count_bits(mask_bishop_attacks(square))); Write(' ');
end;
Writeln('');
Writeln('rook_relevant_bits');
for square := a8 to h1 do begin
if (integer(square) mod 8)=0 then Writeln('');
Write(count_bits(mask_rook_attacks(square))); Write(' ');
end;
Writeln('');}
{ block:=4512464976740352;
print_bitboard(block);
square:=TBoard_squares(get_ls1b_index(block));
Write('get_ls1b_index:',integer(square));
Writeln(' coordinate:',square_to_coordinates[integer(square)]);
set_bit(bitboard,square);
print_bitboard(bitboard);}
//ZobristKey[i][j][z]=rd.nextLong()^(rd.nextLong()<<15)^(rd.nextLong()<<30)^(rd.nextLong()<<45)^(rd.nextLong()<<60);
//<< shl
Магические числа, полученные с помощью кода адаптированного на Делфи
Посмотрел 16 урок - Bitboard CHESS ENGINE in C: initializing SLIDER PIECES attack tables using PLAIN MAGIC BITBOARDS
К сожалению Макс перестал объснять, а просто пишет код.
Я тоже одновременно с ним пишу.
И да, в этом есть какая то магия, точно есть)))
// init slider piece's attack tables
procedure init_sliders_attacks(bishop_flag:Tbishop);
var
// init current mask
attack_mask:U64;
// init current occupancy variation
occupancy:U64;
// init relevant occupancy bit count
relevant_bits_count:integer;
// init occupancy indicies
occupancy_indicies:integer;
magic_index:integer;
index:integer;
begin
// loop over 64 board squares
for square := a8 to h1 do begin
// init bishop & rook masks
bishop_masks[square] := mask_bishop_attacks(square);
rook_masks[square] := mask_rook_attacks(square);
// init current mask
if bishop_flag=bishop then attack_mask:=bishop_masks[square]
else attack_mask:=rook_masks[square];
// init relevant occupancy bit count
relevant_bits_count := count_bits(attack_mask);
// init occupancy indicies
occupancy_indicies := 1 shl relevant_bits_count;
// loop over occupancy indicies
for index:=0 to occupancy_indicies-1 do begin
// bishop
if bishop_flag=bishop then begin
// init current occupancy variation
occupancy := set_occupancy(index, relevant_bits_count, attack_mask);
// init magic index
magic_index := integer((occupancy * bishop_magic_numbers[square]) shr (64 - bishop_relevant_bits[square]));
// init bishop attacks
bishop_attacks[square][magic_index] := bishop_attacks_on_the_fly(square, occupancy);
end
// rook
else begin
// init current occupancy variation
occupancy := set_occupancy(index, relevant_bits_count, attack_mask);
// init magic index
magic_index := integer((occupancy * rook_magic_numbers[square]) shr (64 - rook_relevant_bits[square]));
// init bishop attacks
rook_attacks[square][magic_index] := rook_attacks_on_the_fly(square, occupancy);
end;
end;
end;
end;
// get bishop attacks
function get_bishop_attacks(square:TBoard_squares;occupancy:U64):U64;
begin
occupancy:=occupancy and bishop_masks[square];
occupancy:=occupancy * bishop_magic_numbers[square];
occupancy:=occupancy shr (64 - bishop_relevant_bits[square]);
// return bishop attacks
result:=bishop_attacks[square][occupancy];
end;
// get rook attacks
function get_rook_attacks(square:TBoard_squares;occupancy:U64):U64;
begin
occupancy:=occupancy and rook_masks[square];
occupancy:=occupancy * rook_magic_numbers[square];
occupancy:=occupancy shr (64 - rook_relevant_bits[square]);
// return bishop attacks
result:=rook_attacks[square][occupancy];
end;
{/**********************************\
==================================
Init all
==================================
\**********************************/}
// init all variables
procedure init_all();
begin
// init leaper pieces attacks
init_leapers_attacks();
// init slider pieces attacks
init_sliders_attacks(bishop);
init_sliders_attacks(rook);
// init magic numbers
//init_magic_numbers();
end;
{/**********************************\
==================================
Main driver
==================================
\**********************************/}
begin
//Переключение окна консоли на кодовую страницу CP1251 (Win-1251).
//Если после переключения русские буквы показываются неверно,
//следует открыть системное меню консольного окна - щелчком мыши в левом
//верхнем углу окна консоли. И выбрать:
//Свойства - закладка "Шрифт" - выбрать шрифт: "Lucida Console".
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
// ==================================
// init all
init_all();
// set blocker pieces on board
set_bit(block, c5);
set_bit(block, f2);
set_bit(block, g7);
set_bit(block, b2);
set_bit(block, g5);
set_bit(block, e2);
set_bit(block, e7);
print_bitboard(block);
// print rook attacks
print_bitboard(get_rook_attacks(e5, block));
// print bishop attacks
print_bitboard(get_bishop_attacks(d4, block));
Writeln('Нажмите <ENTER>, чтобы выйти ...');
Readln;
end.
Надо бы докапаться до сути и понять как это работает.
Протестировал магические квадраты на задаче неприкосновенный король.
Конечно это достаточно условный тест с некоторыми допущениями.
Время складывается из работы основной тестируемой функции и условно постоянной величины, складывающейся из функции прорисовки, оценки позиции и т.д.
Но примем допущения, что эта величина одинакова для всех.
Еще в магических квадратах пришлось просчитывать дополнительно ходы черного короля с учетом шахов.
В общем результат при depth=8
Если есть числа, которые работают, то зачем искать другие другие числа.
Уроки
17.Bitboard CHESS ENGINE in C: defining BITBOARDS, OCCUPANCIES and helper variables
18.Bitboard CHESS ENGINE in C: printing CHESS BOARD position and game state variables
19.Bitboard CHESS ENGINE in C: parsing FEN string to initialize BITBOARDS, OCUUPANCIES & board state
20.Bitboard CHESS ENGINE in C: getting QUEEN ATTACKS by looking up bishop & rook attack tables
Пока, что каких-то особых секретов в этих уроках нет.
Но необходимо двигаться в ногу с Максом, иначе дальше двигаться будет невозможно.
Итак вкратце:
Атаки ферзя - объединяем атаки слона и ладьи
// get queen attacks
function get_queen_attacks(square:TBoard_squares;occupancy:U64):U64;
begin
result:=get_bishop_attacks(square,occupancy) or get_rook_attacks(square,occupancy);
end;
Позиция и вспомогательные переменные
uses
SysUtils,Windows,TypInfo;
type
U64 = UInt64;
TBoard_squares = (
a8, b8, c8, d8, e8, f8, g8, h8,
a7, b7, c7, d7, e7, f7, g7, h7,
a6, b6, c6, d6, e6, f6, g6, h6,
a5, b5, c5, d5, e5, f5, g5, h5,
a4, b4, c4, d4, e4, f4, g4, h4,
a3, b3, c3, d3, e3, f3, g3, h3,
a2, b2, c2, d2, e2, f2, g2, h2,
a1, b1, c1, d1, e1, f1, g1, h1
);
// sides to move (colors)
TSide = (white, black, both);
// bishop and rook
Tbishop = (rook, bishop);
//Pawn
TPawn_attacks=array[TSide,TBoard_squares] of U64;
//прыгающие фигуры
TChip_attacks=array[TBoard_squares] of U64;
// encode pieces
Tencode_pieces=( P_, N_, B_, R_, Q_, K_, p, n, b, r, q, k);
{// castling rights binary encoding
рокеровка
/*
bin dec
0001 1 white king can castle to the king side
0010 2 white king can castle to the queen side
0100 4 black king can castle to the king side
1000 8 black king can castle to the queen side
examples
1111 both sides an castle both directions
1001 black king => queen side
white king => king side
*/}
Tcastling = (wk = 1, wq = 2, bk = 4, bq = 8 );
var
{/**********************************\
==================================
Chess board
==================================
\**********************************/
/*
WHITE PIECES
Pawns Knights Bishops
8 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0
7 0 0 0 0 0 0 0 0 7 0 0 0 0 0 0 0 0 7 0 0 0 0 0 0 0 0
6 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0
2 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 1 0 1 0 0 1 0 0 1 0 0
a b c d e f g h a b c d e f g h a b c d e f g h
Rooks Queens King
8 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0
7 0 0 0 0 0 0 0 0 7 0 0 0 0 0 0 0 0 7 0 0 0 0 0 0 0 0
6 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0
1 1 0 0 0 0 0 0 1 1 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0
a b c d e f g h a b c d e f g h a b c d e f g h
BLACK PIECES
Pawns Knights Bishops
8 0 0 0 0 0 0 0 0 8 0 1 0 0 0 0 1 0 8 0 0 1 0 0 1 0 0
7 1 1 1 1 1 1 1 1 7 0 0 0 0 0 0 0 0 7 0 0 0 0 0 0 0 0
6 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0
a b c d e f g h a b c d e f g h a b c d e f g h
Rooks Queens King
8 1 0 0 0 0 0 0 1 8 0 0 0 1 0 0 0 0 8 0 0 0 0 1 0 0 0
7 0 0 0 0 0 0 0 0 7 0 0 0 0 0 0 0 0 7 0 0 0 0 0 0 0 0
6 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0
a b c d e f g h a b c d e f g h a b c d e f g h
}
// piece bitboards
bitboards_12:array[Tencode_pieces] of U64;
{
OCCUPANCIES
White occupancy Black occupancy All occupancies
8 0 0 0 0 0 0 0 0 8 1 1 1 1 1 1 1 1 8 1 1 1 1 1 1 1 1
7 0 0 0 0 0 0 0 0 7 1 1 1 1 1 1 1 1 7 1 1 1 1 1 1 1 1
6 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0
2 1 1 1 1 1 1 1 1 2 0 0 0 0 0 0 0 0 2 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1
}
// occupancy bitboards
occupancies_3:array[TSide] of U64;
// side to move
side:TSide = both;
// enpassant square
enpassantInt:integer=-1;
enpassant:TBoard_squares=a8;
// castling rights
castle:integer=0;
// ASCII pieces
ascii_pieces:array[0..11] of char = 'PNBRQKpnbrqk';
//===========================================
// piece bitboards
bitboards_12:array[Tencode_pieces] of U64; - битборды по типу и цвету для фигур
// occupancy bitboards
occupancies_3:array[TSide] of U64; - битбоарды белых, черных и всех фигур
// side to move
side:TSide = both; - белые, черные или все
// castling rights
castle:integer=0; - флаг рокеровки
bin dec
0001 1 white king can castle to the king side
0010 2 white king can castle to the queen side
0100 4 black king can castle to the king side
1000 8 black king can castle to the queen side
// enpassant square - флаг и поле (взятие на проходе)
enpassantInt:integer=-1;
enpassant:TBoard_squares=a8;//заведомо ложное поле
отладочная информация и визуализация (вывод досок в консоль, парсинг fen строки
// ASCII pieces
ascii_pieces:array[0..11] of char = 'PNBRQKpnbrqk';
{/**********************************\
==================================
Input & Output
==================================
\**********************************/}
// print bitboard
procedure print_bitboard(bb:U64);
var
rank_,file_,sq:integer;
begin
Writeln('');
// loop over board ranks
for rank_ := 0 to 7 do begin
// loop over board files
for file_ := 0 to 7 do begin
// convert file & rank into square index
sq:=rank_*8 + file_;
//// print ranks
if file_ = 0 then begin
Write(8 - rank_);Write(' ');
end;
Write(' ');
// print bit state (either 1 or 0)
Write(get_bit(bb,sq));
end;
// print new line every rank
Writeln('');
end;
// print new line
Writeln('');
// print board files
Writeln(' a b c d e f g h');
Writeln('');
// print bitboard as unsigned decimal number
Write('bitboard = ',bb);
Writeln('');
end;
// print board
procedure print_board();
var
rank_,file_,sq:integer;
bb_piece:Tencode_pieces;
flag:boolean;
begin
Writeln('');
// loop over board ranks
for rank_ := 0 to 7 do begin
// loop over board files
for file_ := 0 to 7 do begin
// convert file & rank into square index
sq:=rank_*8 + file_;
//// print ranks
if file_ = 0 then begin
Write(8 - rank_);Write(' ');
end;
Write(' ');
flag:=true;
// loop over all piece bitboards
for bb_piece := P_ to k do begin
if get_bit(bitboards_12[bb_piece],sq)>0 then begin
Write(ascii_pieces[integer(bb_piece)]);
flag:=false;
end;
end;
if flag then Write('.');
end;
// print new line every rank
Writeln('');
end;
// print new line
Writeln('');
// print board files
Writeln(' a b c d e f g h');
Writeln('');
Write('side = ');
//Write(GetEnumName(TypeInfo(TSide), ord(TSide(integer(side)))));
//TSide = (white, black, both);
if side=white then Write('white');
if side=black then Write('black');
Writeln('');
Write('castle = ');
if (castle and integer(Tcastling(wk)))>0 then Write('K') else Write('-');
if (castle and integer(Tcastling(wq)))>0 then Write('Q') else Write('-');
if (castle and integer(Tcastling(bk)))>0 then Write('k') else Write('-');
if (castle and integer(Tcastling(bq)))>0 then Write('q') else Write('-');
Writeln('');
Write('enpassant = ');
if enpassantInt=-1 then Write('no') else begin
//enpassant:=TBoard_squares(enpassantInt);
Write(GetEnumName(TypeInfo(TBoard_squares), ord(enpassantInt)));
end;
Writeln('');
end;
// parse FEN string
function parse_fen(sfen:String):boolean;
var
// i,j - индекс массива
// len_fen - длина строки fen
// n64 - счетчик шахматных клеток (64 клетки)
// num - позиция символа в strN или strF
i,j,len_fen,n64,num:integer;
//строки для цифр из fen строки - strN, фигур - strF, единичного символа - s01
strN,strF,s01,strA:string;
bt:Byte;
bb_piece:Tencode_pieces;
begin
//если fen строка пустая, то ошибка fen строки
result:=false;
if sfen='' then exit;
// reset board position (bitboards)
bt:=0;
FillChar(bitboards_12, 12*SizeOf(UInt64), bt);
// reset occupancies (bitboards)
FillChar(occupancies_3, 3*SizeOf(UInt64), bt);
// reset game state variables
// side to move
side := black;
// enpassant square
enpassantInt:=-1;
enpassant:=a8;//заведомо ложное поле
// castling rights
castle:=0;
// инициализация строк для парсинга fen строки
strN:='12345678';
strF:='PNBRQKpnbrqk';
strA:='abcdefgh';
//определяем длину fen строки
len_fen:=length(sfen);
//сбрасываем счетчик
n64:=-1;
//перебираем символы fen строки
for i := 1 to len_fen do begin
//берем один символ fen строки на позиции i
//возвращает подстроку строки sfen начинающуюся с индекса i длиной 1 символ
s01:=copy(sfen,i,1);
//возвр. позицию символа s01 в строке strN, если нет символа, то 0
//сначала проверяем s01 - это цифра?
num:=pos(s01,strN);
//continue прерывает только выполнение текущей итерации,
//текущего выполнения тела цикла
//и передает управление на следующую итерацию.
if s01='/' then continue;
//если пробел заканчиваем парсинг
if s01=' ' then begin
num:=pos(' ',sfen);
sfen:=copy(sfen,num+1,len_fen-num);
for j := 1 to length(sfen) do begin
s01:=copy(sfen,j,1);
if s01=' ' then continue;
// side to move
if s01='w' then begin side := white; continue;end;
case Ord(sfen[j]) of
Ord('K'):begin castle:=castle or integer(wk);end;
Ord('Q'):begin castle:=castle or integer(wq);end;
Ord('k'):begin castle:=castle or integer(bk);end;
Ord('q'):begin castle:=castle or integer(bq);end;
end;
if (((sfen[j] >= 'a') and (sfen[j] <= 'h')) and ((sfen[j+1] = '3') or (sfen[j+1] = '6'))) then begin
enpassantInt:=pos(copy(sfen,j,1),strA)-1+8*(8-(pos(copy(sfen,j+1,1),strN)));
enpassant:=TBoard_squares(enpassantInt);
end;
// Writeln(sfen[j]);
end;
break;
end;
//увеличиваем счетчик шахматных клеток n64
// если num=0, значит символ s01 фигура
if num=0 then begin
Inc(n64);
// иначе цифра
end else begin
n64:=n64+num;
end;
//если клеток больше 64(0..63), то ошибка fen строки
if n64>63 then exit;
//проверяем s01 - это символ фигуры?
num:=pos(s01,strF);
//если num - это не допустимый символ, то ошибка fen строки
if ((num=0) and (pos(s01,strN)=0))then exit;
//заносим фигуры в массивы
if num>0 then begin
//Tencode_pieces=( P_, N_, B_, R_, Q_, K_, p, n, b, r, q, k);
set_bit(bitboards_12[Tencode_pieces(num-1)],TBoard_squares(n64));
end;
end;//for
//если клеток не равно 64(0..63), то ошибка fen строки
if n64<>63 then exit;
//если королей не равно 1, то ошибка fen строки
if count_bits(bitboards_12[K_])<>1 then exit;
if count_bits(bitboards_12[k])<>1 then exit;
// loop over white pieces bitboardsoop over all piece bitboards
for bb_piece := P_ to K_ do
// populate white occupancy bitboard
occupancies_3[white] := occupancies_3[white] or bitboards_12[bb_piece];
// loop over black pieces bitboardsoop over all piece bitboards
for bb_piece := p to k do
// populate black occupancy bitboard
occupancies_3[black] := occupancies_3[black] or bitboards_12[bb_piece];
// init all occupancies
occupancies_3[both] := occupancies_3[both] or occupancies_3[white] or occupancies_3[black];
//парсинг завершен успешно
//print_board();
result:=true;
end;
Парсинг я взял свой с некоторыми правками.
Делать как у Макса не обязательно, все же Си отличается от делфи.
Урок 21.Bitboard CHESS ENGINE in C: implementing routine to find out whether SQUARE IS ATTACKED
Процедура для определения того, подвергается ли поле доски атаке.
На СИ конечно это все короче, но как есть
// is square current given attacked by the current given side
function is_square_attacked(square:TBoard_squares;sd:TSide):integer;
begin
// attacked by white pawns
if ((sd = white) and ((pawn_attacks[black][square] and bitboards_12[P_])>0)) then
begin
result:=1;exit;
end;
// attacked by black pawns
if ((sd = black) and ((pawn_attacks[white][square] and bitboards_12[p])>0)) then
begin
result:=1;exit;
end;
// attacked by knights
if ((sd = white) and ((knight_attacks[square] and bitboards_12[N_])>0)) then
begin
result:=1;exit;
end;
if ((sd = black) and ((knight_attacks[square] and bitboards_12[n])>0)) then
begin
result:=1;exit;
end;
// attacked by bishops
if ((sd = white) and ((get_bishop_attacks(square, occupancies_3[both]) and bitboards_12[B_])>0)) then
begin
result:=1;exit;
end;
if ((sd = black) and ((get_bishop_attacks(square, occupancies_3[both]) and bitboards_12[b])>0)) then
begin
result:=1;exit;
end;
// attacked by rooks
if ((sd = white) and ((get_rook_attacks(square, occupancies_3[both]) and bitboards_12[R_])>0)) then
begin
result:=1;exit;
end;
if ((sd = black) and ((get_rook_attacks(square, occupancies_3[both]) and bitboards_12[r])>0)) then
begin
result:=1;exit;
end;
// attacked by queens
if ((sd = white) and ((get_queen_attacks(square, occupancies_3[both]) and bitboards_12[Q_])>0)) then
begin
result:=1;exit;
end;
if ((sd = black) and ((get_queen_attacks(square, occupancies_3[both]) and bitboards_12[q])>0)) then
begin
result:=1;exit;
end;
// attacked by kings
if ((sd = white) and ((king_attacks[square] and bitboards_12[K_])>0)) then
begin
result:=1;exit;
end;
if ((sd = black) and ((king_attacks[square] and bitboards_12[k])>0)) then
begin
result:=1;exit;
end;
result:=0;
end;
// print attacked squares
procedure print_attacked_squares(sd:TSide);
var
rank_,file_,sq:integer;
begin
Writeln('');
// loop over board ranks
for rank_ := 0 to 7 do begin
// loop over board files
for file_ := 0 to 7 do begin
// convert file & rank into square index
sq:=rank_*8 + file_;
//// print ranks
if file_ = 0 then begin
Write(8 - rank_);Write(' ');
end;
Write(' ');
// check whether current square is attacked or not
Write(is_square_attacked(TBoard_squares(sq),sd));
end;
// print new line every rank
Writeln('');
end;
// print new line
Writeln('');
// print board files
Writeln(' a b c d e f g h');
Writeln('');
end;
if parse_fen('r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1 ')=false then Writeln('Ошибка FEN строки ...');
print_board();
print_attacked_squares(white);
Writeln('Нажмите <ENTER>, чтобы выйти ...');
Readln;
Вроде функция работает правильно.
Алгоритм такой:
Все ходы в шахматах (за исключением ходов пешек и рокировках) обладают очень интересным свойством.
Если фигура атакует из клетки A в клетку B, то если ее переместить в клетку В она всегда будет атаковать клетку А.
А из этого следует, что поставив на клетку В, которую мы хотим проверить, например слона он будет бить вражеского слона на клетке А тогда и только тогда, когда слон А бьет клетку В.
Аналогичная логика работает со всеми фигурами
Хотел обобщить шаги для создания шахматного движка на основе битбоард.
Итак.
1. Атаки прыгающих фигур (король,конь,пешка)
2. Атаки скользящих фигур (слон,ладья,ферзь)
3. Представление данных о позиции в памяти компа.
4. Интерфейс (взаимодействие с человеком) для наглядного представления работы движка.
5. Функции
- подсчет битов в числе
- нахождение первого установленного бита в числе
- проверка установлен ли бит в любой позиции числа
- сброс любого бита в числе
- нахождение для каждого поля доски всех атак на это поле со стороны фигур, принадлежащих белым или черным
Далее Макс, ну и я))) преступим к реализации генератора ходов, т.е. будем учить движок ходить по шахматным правилам.
На самом деле, это очень даже интересно.
А пока видео от Макса, как он проводил тесты скорости.
22.Bitboard CHESS ENGINE in C: writing GENERATE MOVES function skeleton (скелет GENERATE MOVES)
23.Bitboard CHESS ENGINE in C: generating QUIET PAWN moves (тихие ходы пешек)
24.Bitboard CHESS ENGINE in C: generating PAWN CAPTURE moves (ходы взятие пешек)
25.Bitboard CHESS ENGINE in C: generating CASTLING MOVES (рокеровка)
26.Bitboard CHESS ENGINE in C: generating SLIDER & LEAPER piece MOVES by attack tables lookup
(генерация ходов скользящих и прыгающих фигур с помощью таблиц атак)
Итак генератор ходов, если быть точным, то псевдо ходов, так сказал Макс в одном из комментариев.
Потому что король может пойти под шах, и этот момент будет отслеживаться в функции - сделать ход
Warning: Spoiler![ Click to expand ][ Click to hide ]
// generate all moves
procedure generate_moves();
var
piece :Tencode_pieces;
// define current piece's bitboard copy & it's attacks
attacks,bitboard,occupancies,enpassant_attacks,n1:U64;
// define source & target squares
source_square, target_square:TBoard_squares;
source_squareInt, target_squareInt,target_enpassantInt :integer;
begin
n1:=1;
// loop over all the bitboards
for piece := P_ to k do begin
// init piece bitboard copy
bitboard := bitboards_12[piece];
// generate white pawns & white king castling moves
if (side = white) then begin
Warning: Spoiler![ Click to expand ][ Click to hide ]
//
piece = P_
// pick up white pawn bitboards index
if (piece = P_) then begin
// loop over white pawns within white pawn bitboard
while bitboard>0 do begin
// init source square
source_square := TBoard_squares(get_ls1b_index(bitboard));
source_squareInt:=integer(source_square);
// init target square
target_square :=TBoard_squares(source_squareInt - 8);
target_squareInt:=integer(target_square);
// generate quite pawn moves
if((not(target_square < a8)) and (get_bit(occupancies_3[both], target_squareInt)=0)) then begin
// pawn promotion
if (source_square >= a7) and (source_square <= h7) then begin
Writeln('pawn promotion: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'q');
Writeln('pawn promotion: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'r');
Writeln('pawn promotion: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'b');
Writeln('pawn promotion: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'n');
end else begin
// one square ahead pawn move
Writeln('pawn push: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt]);
// two squares ahead pawn move
//if ((source_square >= a2 && source_square <= h2) && !get_bit(occupancies[both], target_square - 8))
if ((source_square >= a2) and (source_square <= h2) and (get_bit(occupancies_3[both], target_squareInt-8)=0) ) then
Writeln('double pawn push: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt - 8]);
end;//if
end;//if
// init pawn attacks bitboard
attacks := pawn_attacks[side][source_square] and occupancies_3[black];
// generate pawn captures
while attacks>0 do begin
// init target square
target_square := TBoard_squares(get_ls1b_index(attacks));
target_squareInt:=integer(target_square);
// pawn promotion
if (source_square >= a7) and (source_square <= h7) then begin
Writeln('pawn promotion capture: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'q');
Writeln('pawn promotion capture: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'r');
Writeln('pawn promotion capture: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'b');
Writeln('pawn promotion capture: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'n');
end else begin
// one square ahead pawn move
Writeln('pawn capture: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt]);
end;
// pop ls1b of the pawn attacks
pop_bit(attacks, target_square);
end;//while attacks>0
// generate enpassant captures
if enpassantInt<>-1 then begin
// lookup pawn attacks and bitwise AND with enpassant square (bit)
enpassant_attacks := pawn_attacks[side][source_square] and (n1 shl enpassantInt);
if enpassant_attacks>0 then begin
// init enpassant capture target square
target_enpassantInt := get_ls1b_index(enpassant_attacks);
Writeln('pawn enpassant capture: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_enpassantInt]);
end;
end;//if enpassantInt<>-1
// pop ls1b from piece bitboard copy
pop_bit(bitboard, source_square);
end;//while bitboard>0
end;//if piece = P_
//
piece = P_
Warning: Spoiler![ Click to expand ][ Click to hide ]
//
piece = K_
// castling moves
if (piece = K_) then begin
// king side castling is available
if (castle and integer(Tcastling(wk)))>0 then begin
// make sure square between king and king's rook are empty
if (get_bit(occupancies_3[both], integer(f1))=0) and (get_bit(occupancies_3[both], integer(g1))=0) then begin
// make sure king and the f1 squares are not under attacks
if (is_square_attacked(e1, black)=0) and (is_square_attacked(f1, black)=0) then
Writeln('castling move: e1g1');
end;
end;
// queen side castling is available
if (castle and integer(Tcastling(wq)))>0 then begin
// make sure square between king and queen's rook are empty
if (get_bit(occupancies_3[both], integer(b1))=0) and (get_bit(occupancies_3[both], integer(c1))=0) and (get_bit(occupancies_3[both], integer(d1))=0) then begin
// make sure king and the f1 squares are not under attacks
if (is_square_attacked(e1, black)=0) and (is_square_attacked(d1, black)=0) then
Writeln('castling move: e1c1');
end;
end;
end;//if piece = K_
//
piece = K_
end//if side = white
else begin
Warning: Spoiler![ Click to expand ][ Click to hide ]
//
piece = p
// pick up black pawn bitboards index
if (piece = p) then begin
// loop over black pawns within black pawn bitboard
while bitboard>0 do begin
// init source square
source_square := TBoard_squares(get_ls1b_index(bitboard));
source_squareInt:=integer(source_square);
// init target square
target_square :=TBoard_squares(source_squareInt + 8);
target_squareInt:=integer(target_square);
//
// generate quite pawn moves
if((not(target_square > h1)) and (get_bit(occupancies_3[both], target_squareInt)=0)) then begin
// pawn promotion
if (source_square >= a2) and (source_square <= h2) then begin
Writeln('pawn promotion: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'q');
Writeln('pawn promotion: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'r');
Writeln('pawn promotion: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'b');
Writeln('pawn promotion: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'n');
end else begin
// one square ahead pawn move
Writeln('pawn push: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt]);
// two squares ahead pawn move
if ((source_square >= a7) and (source_square <= h7) and (get_bit(occupancies_3[both], target_squareInt+8)=0) ) then
Writeln('double pawn push: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt + 8]);
end;//if
end;//if
//
// init pawn attacks bitboard
attacks := pawn_attacks[side][source_square] and occupancies_3[white];
// generate pawn captures
while attacks>0 do begin
// init target square
target_square := TBoard_squares(get_ls1b_index(attacks));
target_squareInt:=integer(target_square);
// pawn promotion
if (source_square >= a2) and (source_square <= h2) then begin
Writeln('pawn promotion capture: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'q');
Writeln('pawn promotion capture: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'r');
Writeln('pawn promotion capture: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'b');
Writeln('pawn promotion capture: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt],'n');
end else begin
// one square ahead pawn move
Writeln('pawn capture: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt]);
end;
// pop ls1b of the pawn attacks
pop_bit(attacks, target_square);
end;//while attacks>0
//
// generate enpassant captures
if enpassantInt<>-1 then begin
// lookup pawn attacks and bitwise AND with enpassant square (bit)
enpassant_attacks := pawn_attacks[side][source_square] and (n1 shl enpassantInt);
if enpassant_attacks>0 then begin
// init enpassant capture target square
target_enpassantInt := get_ls1b_index(enpassant_attacks);
Writeln('pawn enpassant capture: ', square_to_coordinates[source_squareInt], square_to_coordinates[target_enpassantInt]);
end;
end;//if enpassantInt<>-1
//
// pop ls1b from piece bitboard copy
pop_bit(bitboard, source_square);
end;//while bitboard>0
end;//if piece = p
//
piece = p
Warning: Spoiler![ Click to expand ][ Click to hide ]
//
piece = k
// castling moves
if (piece = k) then begin
// king side castling is available
if (castle and integer(Tcastling(bk)))>0 then begin
// make sure square between king and king's rook are empty
if (get_bit(occupancies_3[both], integer(f8))=0) and (get_bit(occupancies_3[both], integer(g8))=0) then begin
// make sure king and the f1 squares are not under attacks
if (is_square_attacked(e8, white)=0) and (is_square_attacked(f8, white)=0) then
Writeln('castling move: e8g8');
end;
end;
// queen side castling is available
if (castle and integer(Tcastling(bq)))>0 then begin
// make sure square between king and queen's rook are empty
if (get_bit(occupancies_3[both], integer(b8))=0) and (get_bit(occupancies_3[both], integer(c8))=0) and (get_bit(occupancies_3[both], integer(d8))=0) then begin
// make sure king and the f1 squares are not under attacks
if (is_square_attacked(e8, white)=0) and (is_square_attacked(d8, white)=0) then
Writeln('castling move: e8c8');
end;
end;
end;//if piece = k
//
piece = k
end;//else - if (side = white)
//
Warning: Spoiler![ Click to expand ][ Click to hide ]
// genarate knight moves
if ((side = white) and (piece = N_)) or ((side = black) and (piece = n))then begin
// loop over source squares of piece bitboard copy
while bitboard>0 do begin
// init source square
source_square := TBoard_squares(get_ls1b_index(bitboard));
source_squareInt:=integer(source_square);
// init piece attacks in order to get set of target squares
attacks := knight_attacks[source_square] and (not(occupancies_3[side]));
while attacks>0 do begin
// init target square
target_square := TBoard_squares(get_ls1b_index(attacks));
target_squareInt:=integer(target_square);
// quite move
if get_bit(occupancies_3[both], target_squareInt)=0 then
Writeln('knight quiet move: ',square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt])
// capture move
else Writeln('knight capture: ',square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt]);
// pop ls1b of the pawn attacks
pop_bit(attacks, target_square);
end;
// pop ls1b from piece bitboard copy
pop_bit(bitboard, source_square);
end;
end;//knight
//
Warning: Spoiler![ Click to expand ][ Click to hide ]
// generate bishop moves
if ((side = white) and (piece = B_)) or ((side = black) and (piece = b))then begin
// loop over source squares of piece bitboard copy
while bitboard>0 do begin
// init source square
source_square := TBoard_squares(get_ls1b_index(bitboard));
source_squareInt:=integer(source_square);
// init piece attacks in order to get set of target squares
attacks := get_bishop_attacks(source_square, occupancies_3[both]) and (not(occupancies_3[side]));
while attacks>0 do begin
// init target square
target_square := TBoard_squares(get_ls1b_index(attacks));
target_squareInt:=integer(target_square);
// quite move
if get_bit(occupancies_3[both], target_squareInt)=0 then
Writeln('bishop quiet move: ',square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt])
// capture move
else Writeln('bishop capture: ',square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt]);
// pop ls1b of the pawn attacks
pop_bit(attacks, target_square);
end;
// pop ls1b from piece bitboard copy
pop_bit(bitboard, source_square);
end;
end;//bishop
//
Warning: Spoiler![ Click to expand ][ Click to hide ]
// generate rook moves
if ((side = white) and (piece = R_)) or ((side = black) and (piece = r))then begin
// loop over source squares of piece bitboard copy
while bitboard>0 do begin
// init source square
source_square := TBoard_squares(get_ls1b_index(bitboard));
source_squareInt:=integer(source_square);
// init piece attacks in order to get set of target squares
attacks := get_rook_attacks(source_square, occupancies_3[both]) and (not(occupancies_3[side]));
while attacks>0 do begin
// init target square
target_square := TBoard_squares(get_ls1b_index(attacks));
target_squareInt:=integer(target_square);
// quite move
if get_bit(occupancies_3[both], target_squareInt)=0 then
Writeln('rook quiet move: ',square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt])
// capture move
else Writeln('rook capture: ',square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt]);
// pop ls1b of the pawn attacks
pop_bit(attacks, target_square);
end;
// pop ls1b from piece bitboard copy
pop_bit(bitboard, source_square);
end;
end;//rook
//
Warning: Spoiler![ Click to expand ][ Click to hide ]
// generate queen moves
if ((side = white) and (piece = Q_)) or ((side = black) and (piece = q))then begin
// loop over source squares of piece bitboard copy
while bitboard>0 do begin
// init source square
source_square := TBoard_squares(get_ls1b_index(bitboard));
source_squareInt:=integer(source_square);
// init piece attacks in order to get set of target squares
attacks := get_queen_attacks(source_square, occupancies_3[both]) and (not(occupancies_3[side]));
while attacks>0 do begin
// init target square
target_square := TBoard_squares(get_ls1b_index(attacks));
target_squareInt:=integer(target_square);
// quite move
if get_bit(occupancies_3[both], target_squareInt)=0 then
Writeln('queen quiet move: ',square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt])
// capture move
else Writeln('queen capture: ',square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt]);
// pop ls1b of the pawn attacks
pop_bit(attacks, target_square);
end;
// pop ls1b from piece bitboard copy
pop_bit(bitboard, source_square);
end;
end;//queen
//
Warning: Spoiler![ Click to expand ][ Click to hide ]
// generate king moves
if ((side = white) and (piece = K_)) or ((side = black) and (piece = k))then begin
// loop over source squares of piece bitboard copy
while bitboard>0 do begin
// init source square
source_square := TBoard_squares(get_ls1b_index(bitboard));
source_squareInt:=integer(source_square);
// init piece attacks in order to get set of target squares
attacks := king_attacks[source_square] and (not(occupancies_3[side]));
while attacks>0 do begin
// init target square
target_square := TBoard_squares(get_ls1b_index(attacks));
target_squareInt:=integer(target_square);
// quite move
if get_bit(occupancies_3[both], target_squareInt)=0 then
Writeln('king quiet move: ',square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt])
// capture move
else Writeln('king capture: ',square_to_coordinates[source_squareInt], square_to_coordinates[target_squareInt]);
// pop ls1b of the pawn attacks
pop_bit(attacks, target_square);
end;
// pop ls1b from piece bitboard copy
pop_bit(bitboard, source_square);
end;
end;//king
end;//for
end;
r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1
Для этой позиции должно быть 48 ходов
Warning: Spoiler![ Click to expand ][ Click to hide ]
8 r . . . k . . r
7 p . p p q p b .
6 b n . . p n p .
5 . . . P N . . .
4 . p . . P . . .
3 . . N . . Q . p
2 P P P B B P P P
1 R . . . K . . R
Гдето я прочитал, что наибольшее количество легальных ходов в шахматной позиции составляет 218 ходов.
Но на вскидку не нашел эту позицию.
Какая она, эта позиция?
Мне оч даже любопытно.
Гдето я прочитал, что наибольшее количество легальных ходов в шахматной позиции составляет 218 ходов.
Но на вскидку не нашел эту позицию.
Какая она, эта позиция?
Мне оч даже любопытно.
Да что-то такое было... Но это дикость
Надо видимо пешек наставить на 7ю с кучей взятий и т.д.
0001 1 white king can castle to the king side
0010 2 white king can castle to the queen side
0100 4 black king can castle to the king side
1000 8 black king can castle to the queen side
examples
1111 both sides an castle both directions
1001 black king => queen side
white king => king side
Попробовал списки делфи для хранения, добавления и печати списков ходов.
Warning: Spoiler![ Click to expand ][ Click to hide ]
type
//указатель на структуру данных
moves = ^move_list;
//элемент односвязного списка
move_list = record
//ходы
move:array[0..255] of integer;
// move count
count:integer;
//указатель на следующий элемент
next_move:moves;
end;
var
add_move:moves;
Не уверен, что это лучший вариант.
Но вроде работает.
Warning: Spoiler![ Click to expand ][ Click to hide ]
1. Атаки прыгающих фигур (король,конь,пешка)
2. Атаки скользящих фигур (слон,ладья,ферзь)
3. Представление данных о позиции в памяти компа.
4. Интерфейс (взаимодействие с человеком) для наглядного представления работы движка.
5. Функции
- подсчет битов в числе
- нахождение первого установленного бита в числе
- проверка установлен ли бит в любой позиции числа
- сброс любого бита в числе
- нахождение для каждого поля доски всех атак на это поле со стороны фигур, принадлежащих белым или черным
6. Генератор ходов
7. Сохранять и восстанавливать состояние текущей позиции (или отменять ход) после применения функции сделать ход (MAKE MOVE)
Мне кажется, что реализация "отменить ход" - сложнее, чем сохранить позицию с флагами и затем восстановить.
Есть интереснаяфункция memcpy (СИ).
В делфи тоже она есть - Move
Я не знал об этом.
Операция "@" - это получение адреса объекта. Операция "^" - получение
объекта по его адресу (под объектом я понимаю тут всё, что может занимать
память, а не только то, что называют объектом в ООП). Теперь разберёмся,
что такое нетипизированный параметр. Это такой параметр, что при вызове
функции надо указать некий объект, а реально функции будет передан
адрес этого объекта. Таким образом, функция Move фактически получает два адреса,
хотя формально ей передаются два объекта, и она копирует байты из одного
адреса в другой. Следовательно, на входе нам нужно передать в неё объекты,
которые располагаются по тем адресам, которые нас интересуют
Move(Source,Dest[j],Count);
Move(Source^,Dest^[j],Count);
Както так
31.Bitboard CHESS ENGINE in C: preserving & restoring BOARD STATE aka COPY/MAKE approach
// preserve board state
procedure copy_board();
begin
Move(bitboards_12[P_],bitboards_12_copy[P_],96);
Move(occupancies_3[white],occupancies_3_copy[white],24);
Move((@add_move^.move[0])^,(@add_move_copy^.move[0])^,256*SizeOf(integer));
Move((@add_move^.count)^,(@add_move_copy^.count)^,SizeOf(integer));
side_copy:=side;
enpassantInt_copy:=enpassantInt;
enpassant_copy:=enpassant;
castle_copy:=castle;
end;
// restore board state
procedure take_back();
begin
Move(bitboards_12_copy[P_],bitboards_12[P_],96);
Move(occupancies_3_copy[white],occupancies_3[white],24);
Move((@add_move_copy^.move[0])^,(@add_move^.move[0])^,256*SizeOf(integer));
Move((@add_move_copy^.count)^,(@add_move^.count)^,SizeOf(integer));
side:=side_copy;
enpassantInt:=enpassantInt_copy;
enpassant:=enpassant_copy;
castle:=castle_copy;
end;
Я кстати не знаю почему Макс не сохраняет ходы с флагами, а только доски.
Дальше у него идут уроки по MAKE MOVE (сделать ход)
Есть интереснаяфункция memcpy (СИ).
В делфи тоже она есть - Move
Я не знал об этом.
Это не совсем одно и то же...
Move это C-аналог memmove, а не memcpy
Аналог memcpy это CopyMemory вроде (еще Copy есть, но это тоже немного другое)
Разница довольно тонкая, но в Дельфи надо отдельно разбираться с особенностями. Может там и без разницы, не знаю.
В С там все понятно.
Но в любом случае это очень низкоуровневые операции. Для Дельфи и объектов плохо рекомендовано.
Получить облом в виде порчи памяти или утечек оной очень легко.
Сразу задался вопросом, правильно или нет использовать функцию Move или реализовывать как то по другому.
Ответ на цитатуVladimirovich wrote:
Аналог memcpy это CopyMemory вроде
нашел.
При вызове CopyMemory Delphi вызывает не API-функцию с таким именем, а свою, исходный код которой можно найти в Windows.pas:
procedure CopyMemory(Destination: Pointer; Source: Pointer; Length: DWORD);
begin
Move(Source^, Destination^, Length);
end;
Еще провел тест
init_all();
if parse_fen('N7/8/2K5/8/8/8/8/7k w - - 0 1')=false then Writeln('Ошибка FEN строки ...');
New(add_move);New(add_move_copy);
generate_moves();
print_move_list();
copy_board();
print_move_list();
N7/8/2K5/8/8/8/8/7k w - - 0 1
Warning: Spoiler![ Click to expand ][ Click to hide ]
Исходную память не нарушил, хотя почему назвали Move, не понятно
Для меня важно, чтобы не вылезла какая нибудь трудноуловимая ошибка.
Но вроде все работает, как задумал Макс, только в реализации делфи.
Будем двигаться дальше вместе с Максом
Для меня важно, чтобы не вылезла какая нибудь трудноуловимая ошибка.
Это возникнет, если адреса или Count нарушить
Если нет, то все хорошо. Иначе будет больно
P.S. Главная разница в том, что изначальная memcpy в С вообще ничего не проверяет, в отличие от memmove.
Поэтому она быстрее.
Понятно, что поздние реализации много чего накрутили поверх, так что надо смотреть конкретно в каждом случае
32.Bitboard CHESS ENGINE in C: implementing MAKE MOVE function (moving pieces)
Скелет функции - MAKE MOVE
Мне нравится, что Макс дает инфу по чуть.
Не спешит сразу все.
Короче можно уже подвигать фигурами.
Warning: Spoiler![ Click to expand ][ Click to hide ]
Warning: Spoiler![ Click to expand ][ Click to hide ]
==================================
\**********************************/
}
// make move on chess board
function make_move(move:integer;move_flag:TMove_types):integer;
var
source_squareInt,target_squareInt,pieceInt,promotedInt:integer;
capture,double_push,enpass,castling:integer;
begin
// quite moves
if (move_flag = all_moves) then begin
// preserve board state
copy_board();
// parse move
source_squareInt:=get_source(move,get_move_source);
target_squareInt:=get_source(move,get_move_target);
pieceInt:=get_source(move,get_move_piece);
promotedInt:=get_source(move,get_move_promoted);
capture:=get_source(move,get_move_capture);
double_push:=get_source(move,get_move_doubl);
enpass:=get_source(move,get_move_enpassan);
castling:=get_source(move,get_move_castling);
// move piece
pop_bit(bitboards_12[Tencode_pieces(pieceInt)], TBoard_squares(source_squareInt));
set_bit(bitboards_12[Tencode_pieces(pieceInt)], TBoard_squares(target_squareInt));
// capture moves
end else begin
end;
end;
Warning: Spoiler![ Click to expand ][ Click to hide ]
==================================
\**********************************/}var
i:integer;
begin
//Переключение окна консоли на кодовую страницу CP1251 (Win-1251).
//Если после переключения русские буквы показываются неверно,
//следует открыть системное меню консольного окна - щелчком мыши в левом
//верхнем углу окна консоли. И выбрать:
//Свойства - закладка "Шрифт" - выбрать шрифт: "Lucida Console".
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
// SetConsoleOutputCP(CP_UTF8);
// ==================================
// init all
init_all();
if parse_fen('r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1 ')=false then Writeln('Ошибка FEN строки ...');
//выделение памяти для нового списка ходов
New(add_move);New(add_move_copy);
generate_moves();
for i := 0 to add_move^.count-1 do begin
// preserve board state
copy_board();
Write(i+1,':');
print_move(add_move^.move);
print_board();
Readln;
clrscr();
// make move
make_move(add_move^.move,all_moves);
Write(i+1,':');
Writeln('');
print_board();
Readln;
clrscr();
// take back
take_back();
end;
print_board();
Writeln('Нажмите <ENTER>, чтобы выйти ...');
Readln;
end.
33.Bitboard CHESS ENGINE in C: implementing MAKE MOVE function (handling captures)
(ходы взятия)
34.Bitboard CHESS ENGINE in C: implementing MAKE MOVE function (handling pawn promotions)
(превращение пешек)
35.Bitboard CHESS ENGINE in C: implementing MAKE MOVE function (handling enpassant moves)
(взятие на проходе)
36.Bitboard CHESS ENGINE in C: implementing MAKE MOVE function (handling double pawn pushes)
(установка битого поля)
37.Bitboard CHESS ENGINE in C: implementing MAKE MOVE function (handling castling moves)
(рокеровка)
38.Bitboard CHESS ENGINE in C: implementing MAKE MOVE function (updating castling rights)
(обновление флагов рокеровки)
39.Bitboard CHESS ENGINE in C: implementing MAKE MOVE function (updating occupancy bitboards)
(массивы занятости - белые, черные, все фигуры)
40.Bitboard CHESS ENGINE in C: implementing MAKE MOVE function (checking whether the king is in check)
(проверка на легальность хода - король под ударом или нет)
Узнал много нового.
Результат 40 уроков
Last Edit: 08 Фев 2023 06:52 by Vladimirovich. Reason: unused zip removed
// perft driver
static inline void perft_driver(int depth)
{
// reccursion escape condition
if (depth == 0)
{
// increment nodes count (count reached positions)
nodes++;
return;
}
// create move list instance
moves move_list[1];
// generate moves
generate_moves(move_list);
// loop over generated moves
for (int move_count = 0; move_count < move_list->count; move_count++)
{
// preserve board state
copy_board();
// make move
if (!make_move(move_list->moves[move_count], all_moves))
// skip to the next move
continue;
// call perft driver recursively
perft_driver(depth - 1);
// take back
take_back();
}
}
Т.е. могу сохранить одну позицию с флагами.
Когда больше одной позиции то позиции, точнее ходы теряются.
Пришлось делать заплатки в виде стека.
В итоге тест perft не прошел.
Причем только в одной тестовой позиции и на глубине больше 5
8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -
Warning: Spoiler![ Click to expand ][ Click to hide ]
В других позициях прошел
Warning: Spoiler![ Click to expand ][ Click to hide ]
Прогнал через профайлер
Warning: Spoiler![ Click to expand ][ Click to hide ]
Итак начал вырисовываться план.
1. Находим из предыдущей таблицы наименьшую разницу в ходах
- b4a4 - 745667
- b4a4 - 745500
Разница 167 ходов
2. Обрезаем программно все другие ходы
- if ((depth=6) and (j<>5))then continue; Делфи
- if ((depth==6) & (move_count != 5)) continue; Си
3. Делаем проверку (сначала depth=5)
- выводим на глубине 1 - ходы короля
Делфи
if ((depth = 1) and (Tencode_pieces(get_source(add_move^.move[j],get_move_piece))=K_)) then begin
old_nodes := nodes - cummulative_nodes;
cummulative_nodes:=nodes;
Writeln('move_test:',square_to_coordinates[integer(get_source(add_move^.move[j],get_move_source))],
square_to_coordinates[integer(get_source(add_move^.move[j],get_move_target))],
promoted_pieces[get_source(add_move^.move[j],get_move_promoted)],' nodes:',old_nodes);
end;//test