ZirconiumX wrote: ↑Wed Aug 04, 2021 11:04 pm
I really like attack tables, but the question is how much information you want to store in them. Dorpsgek uses a piece-set per square of the board for 256 bytes in total; HGM's mailbox trials engine uses a piece-set per piece of the board, along with other information like distance to next piece in a direction; Rookie uses a 16-bit bit-set with one bit for an attack in each direction; Rebel/Pro Deo uses an 8-bit count of attacks to square.
In terms of information density, Rookie's approach is probably best, but I like being able to look up the squares of attacking pieces in a piece list.
Hi, sorry for the late reply after many weeks, i was so busy, i did just that, i did exactly the "Classical Approach" from chess programming wiki.
i made some early tests on the aprox. performance and the performance was
much worse than the "Square attacked by"
the main reason is the need for the "update" attack maps
I was doing something like in the make move function:
Code: Select all
ulong attacks_from,attacks_to,attacks_mask;
switch(moved_piece)
{
...
case bishop:
attacks_from = GetBishopAttacks(sq_from, total_occupancy);
attacks_to = GetBishopAttacks(sq_to, total_occupancy);
attacks_mask = attacks_from & ~attacks_to; // determinate the squares that needs to be removed
while(attacks_mask)
{
// remove each bit from the attacks_mask
attacks_table[getLS1BIndex(attacks_mask)] &= ~(1ul << square_from);
attacks_mask = RemoveLS1B(attacks_mask);
}
attacks_mask = ~attacks_from & attacks_to; // determinate the squares that needs to be added
while(attacks_mask)
{
// set each bit from the new wanted attacks
attacks_table[getLS1BIndex(attacks_mask)] |= (1ul << square_from);
attacks_mask = RemoveLS1B(attacks_mask);
}
break;
}
And i did the opposite for the UnMakeMove, the problem is that it is worse than my "SquareAttackedBy" Implementation:
Code: Select all
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong GetAttackersForSquare(byte _square, byte _player)
{
byte adversary = (byte)(_player ^ 1);
ulong op_pawns,op_knights,op_rooks,op_bishops,occ,op_queens;
occ = bitboards[_player] | bitboards[adversary];
op_queens = (bitboards[adversary] & bitboards[ChessPiece.Queen]);
(op_bishops,op_rooks) = (op_queens,op_queens);
op_bishops |= bitboards[adversary] & bitboards[ChessPiece.Bishop];
op_rooks |= bitboards[adversary] & bitboards[ChessPiece.Rook];
op_pawns = bitboards[adversary] & bitboards[ChessPiece.Pawn];
op_knights = bitboards[adversary] & bitboards[ChessPiece.Knight];
return (GetBishopMoves(_square, occ) & op_bishops) |
(GetRookMoves(_square, occ) & op_rooks) |
(GetPawnAttacks(_square, _player) & op_pawns) |
(GetKnightMoves(_square) & op_knights);
}
The perft in startposition with SquareAttackedBy at depth of 5 (4M of moves) was:
~580ms
with that attacks map:
~980ms !!
the most branchless and loop free implementation that i've found is the "square attacked by"
or anyone else have found another idea of keeping some sort of attack maps with a good performance? i am thinking to switch to a fully legal move generator to see if speed improves so i don't need to call unamakeMove each illegal move anymore...