My move-gen is pseudo legal which means that each move is technically possible to be played but some are creating a position where the moving side is in check afterwards. To detect those moves I just play each move first and then see if the king is attacked and if it is I'll just skip calling Evaluate on the resulting position. And I'm also not counting the position as a node visited even though a lot time has already been spent on it.
So half-jokingly I thought if I would count them too my nodes per second would appear much higher! You know... for bragging rights. But this gave me an idea... wouldn't it be fair to count the moves if I then also played them normally? What would happen? In Evaluation - regardless of the depth - captures would be generated for each position (Qsearch) and for an illegal position one capture is going to be targeting the King. The move ordering would pick this move to be played first. Now in the resulting position a king would be missing which is trivial to check by just logical-anding together two bitboards and if there's no bit remaining (KINGS & WHITE == 0) the side has no king. In that case I was returning the checkmate score and the whole branch would fail beta for the side capturing the king. And... everything still works!
It's a lot of extra work to play out these captures. But only after an illegal move was made, in all other cases it's saving the effort of doing the legality check. I could have imagined this to be winning bargain.
But alas, it wasn't.
Then I thought about a small optimization: Why even play the king capture? It's cheap to know that the move is targeting a king without playing it. So I was just returning the appropriate checkmate-score immediately. Now the tradeoff is, that I save the legality check on *all* moves but on some moves (that are illegal) you waste time: you play it, pass it on to the next Eval() step where you have to generate all the captures, look once at the movelist (pick the best move) and only then you realize that this position was in fact the result of an illegal move.
Code: Select all
Searching IterativeSearch(7)
Searched 300 positions with IterativeSearch(7)
3868798K nodes visited. Took 303,228 seconds!
12758K NPS.
Best move found in 255 / 300 positions!
Searching IterativeSearchNext(7)
Searched 300 positions with IterativeSearchNext(7)
6053496K nodes visited. Took 366,23 seconds!
16529K NPS.
Best move found in 254 / 300 positions!
Maybe for other engines it could turn out differently? I just thought it was interesting enough to share it here.