Move Generation
1. King
1.1 Basics
As described from the previous page, we can automate the generation of bitboards through calculating all squares around it with values: square << (-9, -8, -7, -1, +1, +7, +8, +9)
But this goes wrong with the king on the edge of the board. Case 1:
King attacks warp / wrap around to the other side. How do we fix this? A simple check to see if the king is on the (bitboard) A file, then remove / stop moves generated on the H file (and vice versa)
Another check should be made to ensure the calculation does not go out of bounds. Simply perform a check something like (square + direction) < 64 && (square + direction) >= 0.
1.2 Attacks
How would we figure out attacks? We can utilize our custom bitwise operations to calculate legal moves from static pieces using pieceLegalMoves & ~ourPieces. Here is an example:
But what about the opponents bishop on b8? After capturing the knight on d6, we put ourselves in check.
We need to see if the move from the king is in check after we move it. To do this, I applied the easist method: Copy the board position and make the move. If the king is in check, the move is illegal, revert the board position back to its original state:
The image above is the correct solution to solving king attacks. For Pseudo-legal move generation which my Chess Engine uses, moves follow piece rules but may leave the king in check, thus needs extra validation after making the move.
There is a slightly faster way to do this via Attack and Defend Maps and pinned pieces map. By keeping an incremental track of where each piece is, what squares it attacks, and pinned pieces, we can reduce move generation time by a lot. For instance, if our rook is absolutely pinned to our king, we cannot move it, so why bother calculating moves when we know they will all put the king in check, thus being illegal.
1.3 Castling
Coming soon...
2. Pawns
1.1 Basics
Pawns, which may look fairly insignificant, actually are quite complex. Let's sequentially go through what moves they can make in bullet points:
- Pawns cannot go on rank 1 or 8 (either is a promotion / illegal)
- Starting rank, pawns can move 1 or 2 squares forward (double move)
- Pawns can capture either left or right diagonally in front of them
- When a pawn reaches the 2nd to last rank, it can promote to a Knight, Bishop, Rook, or Queen
- After doing a double move on their first turn, the square behind them becomes the "en passant" square for one turn only, where an opposing pawn can capture one square behind them IF it is directly beside it.
We need two separate precomputed tables otherwise black pawns would be promoting where white pawns are. Using the same principles as before for captures (this time forward diagonal ones), we can get our precomputed pawn moves (however double pawn pushes & en passants are calculated manually, as for pawn move generation they would be mostly redundant since they happen only once)
1.2 Examples
Validation & Test Positions
1. Perft
Heard of it before? It stands for "Performance test", and chess engine developers LOVE it. To put simply, when you designed your move generator, how would you know it actually works on a larger scale? That's where perft comes in.
1.1 Iterative deepening & bottlenecks
In my own engine, I created a function with parameter depth. Assuming I called perft(6), it would first generate all moves, copy the board state, and make each (legal) move, and call perft(depth - 1) and revert the board position. By doing this, it would go through every single move in the current position.
Depth 6 for the starting position is fine (for me) as the time is fairly short. When going to depth perft(7), the number of moves grow so exponential that the time taken is 26 times more than perft(6) completion time!! Later on, I will show you how your chess engine can cut down on searches through pruning techniques and position evaluation.
2. Test Positions & Results
Having found Position Perft Results, I incorporated most of those positions from the Chess Programming Main Page into my engine with their final results (nodes calculated) for testing. The page has varied test positions with useful information and variation in each position.
Some of the positions here have even highlighted bugs found in old chess engines, which the starting position was sometimes not enough for a valid, reliable perft. The positions tested captures, castling, promotions, checks, discovered checks, double checks, and checkmates.
Running my Chess Engine, here are the correct results for the following chess positions:
Performance test results for rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1:
| a2a3 | 4463267 | a2a4 | 5363555 | b2b3 | 5310358 | b2b4 | 5293555 | c2c3 | 5417640 |
| c2c4 | 5866666 | d2d3 | 8073082 | d2d4 | 8879566 | e2e3 | 9726018 | e2e4 | 9771632 |
| f2f3 | 4404141 | f2f4 | 4890429 | g2g3 | 5346260 | g2g4 | 5239875 | h2h3 | 4463070 |
| h2h4 | 5385554 | b1a3 | 4856835 | b1c3 | 5708064 | g1f3 | 5723523 | g1h3 | 4877234 |
| Depth | 6 | Total Nodes | 119060324 | Time | 1329 ms | ||||
Performance test results for r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -
| d5d6 | 3835265 | d5e6 | 4727437 | a2a3 | 4627439 | a2a4 | 4387586 | b2b3 | 3768824 |
| g2g3 | 3472039 | g2g4 | 3338154 | g2h3 | 3819456 | e5d7 | 4404043 | e5f7 | 4164923 |
| e5c6 | 4083458 | e5g6 | 3949417 | e5c4 | 3494887 | e5g4 | 3415992 | e5d3 | 3288812 |
| c3b5 | 4317482 | c3a4 | 4628497 | c3b1 | 3996171 | c3d1 | 3995761 | d2h6 | 3967365 |
| d2g5 | 4370915 | d2f4 | 3941257 | d2e3 | 4407041 | d2c1 | 3793390 | e2a6 | 3553501 |
| e2b5 | 4032348 | e2c4 | 4182989 | e2d3 | 4066966 | e2d1 | 3074219 | e2f1 | 4095479 |
| a1b1 | 3827454 | a1c1 | 3814203 | a1d1 | 3568344 | h1f1 | 3685756 | h1g1 | 3989454 |
| f3f6 | 3975992 | f3f5 | 5271134 | f3h5 | 4743335 | f3f4 | 4327936 | f3g4 | 4514010 |
| f3d3 | 3949570 | f3e3 | 4477772 | f3g3 | 4669768 | f3h3 | 5067173 | e1g1 | 4119629 |
| e1c1 | 3551583 | e1d1 | 3559113 | e1f1 | 3377351 | ||||
| Depth | 5 | Total Nodes | 193690690 | Time | 2140 ms | ||||
Performance test results for 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -:
| e2e3 | 745505 | e2e4 | 597519 | g2g3 | 271220 | g2g4 | 892781 | b4a4 | 745667 |
| b4c4 | 1027199 | b4d4 | 957108 | b4e4 | 860971 | b4f4 | 174919 | b4b3 | 941129 |
| b4b2 | 818501 | b4b1 | 1160678 | a5a6 | 968724 | a5a4 | 868162 | ||
| Depth | 6 | Total Nodes | 11030083 | Time | 156 ms | ||||
Performance test results for r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1:
| c4c5 | 2145218 | d2d4 | 2816009 | f3d4 | 2928923 | b4c5 | 2027632 | f1f2 | 2703427 |
| g1h1 | 3212083 | ||||||||
| Depth | 5 | Total Nodes | 15833292 | Time | 204 ms | ||||
Performance test results for r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1:
| d7d5 | 2816009 | c5c4 | 2145218 | f6d5 | 2928923 | b5c4 | 2027632 | f8f7 | 2703427 |
| g8h8 | 3212083 | ||||||||
| Depth | 5 | Total Nodes | 15833292 | Time | 203 ms | ||||
Performance test results for rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8:
| d7c8q | 2106366 | d7c8r | 1628284 | d7c8b | 2712122 | d7c8n | 2522065 | a2a3 | 1936679 |
| a2a4 | 2101105 | b2b3 | 1904400 | b2b4 | 1990854 | c2c3 | 2090166 | g2g3 | 1779903 |
| g2g4 | 1830854 | h2h3 | 1926608 | h2h4 | 2015932 | e2d4 | 2353001 | e2f4 | 2274063 |
| e2c3 | 2465011 | e2g3 | 2302529 | e2g1 | 2032466 | b1a3 | 1777712 | b1c3 | 2191455 |
| b1d2 | 1450852 | c4f7 | 1817665 | c4a6 | 1644109 | c4e6 | 2079471 | c4b5 | 1832566 |
| c4d5 | 1934045 | c4b3 | 1686064 | c4d3 | 1672391 | c1h6 | 1748274 | c1g5 | 2042591 |
| c1f4 | 2380374 | c1e3 | 2484771 | c1d2 | 1894062 | h1f1 | 1911413 | h1g1 | 1781002 |
| d1d6 | 2229266 | d1d5 | 2742654 | d1d4 | 2920749 | d1d3 | 2792521 | d1d2 | 2109913 |
| e1g1 | 1979843 | e1d2 | 1043007 | e1f2 | 1667505 | e1f1 | 2154511 | ||
| Depth | 5 | Total Nodes | 89941194 | Time | 1093 ms | ||||
Performance test results for r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10:
| a3a4 | 3878463 | d3d4 | 4380579 | b2b3 | 3419939 | b2b4 | 3711088 | g2g3 | 3878092 |
| h2h3 | 4197369 | h2h4 | 3743675 | c3b5 | 3665630 | c3d5 | 3699930 | c3a4 | 3477406 |
| c3a2 | 3313599 | c3b1 | 2773444 | c3d1 | 2638406 | f3e5 | 5187884 | f3d4 | 4645393 |
| f3h4 | 3800952 | f3d2 | 3640148 | f3e1 | 2861872 | g5f6 | 3337594 | g5h6 | 3822473 |
| g5f4 | 4338859 | g5h4 | 3278233 | g5e3 | 4005372 | g5d2 | 3717908 | g5c1 | 3146201 |
| c4f7 | 322511 | c4a6 | 3526986 | c4e6 | 3933073 | c4b5 | 3445927 | c4d5 | 3466775 |
| c4b3 | 3453971 | c4a2 | 3449599 | a1a2 | 3183971 | a1b1 | 3453891 | a1c1 | 3303169 |
| a1d1 | 3169502 | a1e1 | 2889902 | f1b1 | 3488438 | f1c1 | 3629003 | f1d1 | 3788187 |
| f1e1 | 3778886 | e2e3 | 4096975 | e2d2 | 3941010 | e2d1 | 3324203 | e2e1 | 3476443 |
| g1h1 | 4392620 | ||||||||
| Depth | 5 | Total Nodes | 164075551 | Time | 1844 ms | ||||