Without that constraint there is something to be said for treating all piece types uniformly. The array unitStep[piece][boardStep] is probably best implemented as an array of pointers than as an array of arrays, so that multiple pieces can refer to the same table, and then using captFlag[piece] & captMask[boardStep] takes the same number of instructions as unitStep[piece][boardStep] != 0. And all pieces can share the same unitStep[boardStep] array.
gaard wrote: ↑Mon Nov 22, 2021 5:00 am
Aside from make/check test/unmake, or copy/make/check test, what are the fastest move_gives_check routines for mailbox? This is easy enough to compute after the move, but I'm having trouble writing a method to do it beforehand. The most straightforward way I see is copying the board state (say) uint8_t mailbox[16 * 12], making the moves, and then using that, without incremental updates. Castling is easy enough to account for, so are the simple cases when from and to are not pseudo attacks, but all else looks ugly and inefficient.
If you are intending to use a check test for the extension/reduction then you can live with simple checks. Also, at higher depths, you can store that info in the TT, and use it in the next deepening.
In KingSlayer I do store the location of the checking piece in the TT (and different invalid square numbers for not-in-check and double check). This info is used by the move generator to speed up testing whether a move blocks the check or captures the checker: the direction of the to-square from the King must be the same as that of the checker from the king, while the distance of the to-square to the king must be <= that of checker and king. The quantities for the checker are taken from the unitStep[] and distance[] tables before the move generation.
hgm wrote: ↑Mon Nov 22, 2021 9:58 am
It only makes sense to test this without first making the move when you want to know it for the purpose of move sorting. If you would play the move anyway and the consequences of it delivering check only occur after that, it is easier to do the test after the making, as the first thing in any new node. And if you don't want to play the non-checks at all it is better to selectively generate the checks than to generate all moves and test them.
For mailbox check tests one typically uses a (16x15) table indexed by board step (= square-number difference) to detect alignment, which contains a set of flags indicating which piece types can potentially check along such a 'vector'. You can test the to-square of a move for the required alignment without making the move, and in most cases you would be done then, because there is no alignment. If there is, and the piece is a pawn or knight, you are also done, because it will alway be a check as pawns and knights cannot be blocked ('contact check'). And for a king it would be an illegal move, as checks are reciprocal. Only for sliders the path to the king could be blocked. This can be tested by scanning the board along the alignment ray; a second 16x15 table gives you the unit step belonging to that alignment. (This can be done before the move, because a potential slider checker could not have come from this ray, as it would have been already che king then.) I usually start at the king, because most of the game the king is safely tucked away, and the ray scan almost immediately bumps into a pawn. If the ray scan does not hit the to-square before it hits a piece, the moved slider will not deliver check.
That leaves discovered checks. You can 'bulk test' for those: if the from-square of the proposed move is aligned (which it usually isn't, so that you are immediately done with a no-discovered-check verdict) you can do the ray scan king to from-square to see if these 'connect'. And when it does (not often...), continue the ray scan in the same direction. If it hits a slider of the stm that matches the alignment, moves from that from-square can deliver a discovered check. If the moving piece is a pawn or king it can keep the ray blocked, however, so you would have to test for the to-square not having the same alignment or unit step. If a slider would be able to move along a ray that could be a discovered check, it can capture the king by itself, and the question of whether it has other moves that deliver check is probably no longer important. So you might as well do this test for staying on the ray for every type of moving piece.
I see no reason for making board copies.
The board copy was for updating the board in a const member function. I'm paranoid about modifying the board state outside of make_move/make_null after introducing so many bugs that way, so I maybe went to far with const correctness. I ended up doing it almost exactly like you describe here.
Sven wrote: ↑Tue Nov 23, 2021 4:30 pm
To determine whether a move gives check should never modify any part of the board state so const correctness is not an issue.
It's not necessary but it makes the code much simpler in cases like en passant discovered check for example.
In my experience maintaining X tables is always slower than just literally computing it if your computation is fast enought.
maintaining a table must be done twice on make/unmake. If your movegen is not a 100% valid movegen you might even do unnecessary work 4x...
So best is not to maintain anything at all and literally just have insanely fast lookup speed. (A maintained lookup is also a lookup. So it maybe the same speed to look it up directly.)
For example a lookup for all king squares would not need to be maintained because you could look it up in the first place.
Pawns dont need lookups. They need two branchless shifts and you are done. With mailbox they need two ifs which is also faster than a table.
hgm wrote: ↑Tue Nov 23, 2021 9:29 pmif(captFlags[piece] & captMask[step])
The argument was that you have to maintain captFlags and captMask in every move, and that's where the actual work is hidden.
No! Where does that idea come from? These are static tables, initialized at engine startup.
captMask[] is a table that specifies which move types (orthogonal, diagonal, knight, ...) can make a given step on an empty board, and captFlags[piece] a set of flags that specify which move types a given piece can make (e.g. Queen = all orthogonal and diagonal moves). This data only depends on the game rules.