# Magic Bitboard Design Guide for a Zig Chess Engine ## Overview This document describes the implementation strategy for sliding-piece move generation using Magic Bitboards in Zig. The goal is: - Extremely fast rook/bishop/queen move generation - All move tables precomputed at startup or compile time - Efficient runtime lookups using: - Bit masks - Bitwise operations - Multiplication by magic numbers - Table indexing This document assumes: - Board representation uses 64-bit bitboards (`u64`) - One bitboard per piece type - Separate white and black occupancy bitboards - Additional metadata stored separately, such as castling and en passant References for follow-up: - Chess Programming Wiki, “Magic Bitboards”: https://www.chessprogramming.org/Magic_Bitboards - Chess Programming Wiki, “Sliding Piece Attacks”: https://www.chessprogramming.org/Sliding_Piece_Attacks - Chess Programming Wiki, “Bitboards”: https://www.chessprogramming.org/Bitboards ## 1. Board Representation Use one `u64` per piece type. Example: ```zig const Board = struct { white_pawns: u64, white_knights: u64, white_bishops: u64, white_rooks: u64, white_queens: u64, white_king: u64, black_pawns: u64, black_knights: u64, black_bishops: u64, black_rooks: u64, black_queens: u64, black_king: u64, white_occ: u64, black_occ: u64, all_occ: u64, }; ``` ## 2. Square Numbering Recommended: ```text A1 = 0 B1 = 1 ... H1 = 7 A2 = 8 ... H8 = 63 ``` This layout aligns naturally with white perspective, rank/file math, and bit shifting. ## 3. Sliding Piece Basics Magic bitboards are only needed for: - Rooks - Bishops - Queens Knights, kings, and pawns can use precomputed attack masks or direct bit operations. ## 4. Relevant Occupancy Masks Each rook/bishop square has a relevant occupancy mask. This mask contains only squares that can block movement. Board edges are excluded because edge blockers do not affect how far the slider can move; they create redundant occupancy states. Example: rook on D4. Relevant mask includes: ```text D5 D6 D7 D3 D2 C4 B4 E4 F4 G4 ``` Not included: ```text D8 D1 A4 H4 ``` ## 5. Runtime Occupancy Extraction At runtime: ```zig const blockers = board.all_occ & rook_masks[square]; ``` This isolates only blockers relevant to the rook. ## 6. Magic Bitboard Lookup Formula Core formula: ```zig index = (blockers * magic) >> shift; ``` Where: - `blockers` = masked occupancy - `magic` = precomputed magic number - `shift` = reduces result to table index size ## 7. Why Multiplication Works The multiplication mixes blocker bits into a pseudo-random pattern. A good magic number guarantees that every possible blocker configuration maps to a usable table index. In practice, collisions are allowed only when the colliding occupancies produce the same attack bitboard. ## 8. Why Shifting Is Needed Multiplication produces a 64-bit result. We only need enough bits to index the move table. Example: ```text 4096 occupancy states -> need 12 bits shift = 64 - 12 ``` Formula: ```zig index = (blockers *% magic) >> (64 - relevant_bits); ``` Use Zig wrapping multiplication (`*%`) for magic indexing. ## 9. Move Tables Each square has attack entries: ```zig rook_attacks[64][N] bishop_attacks[64][N] ``` Where `N` depends on occupancy combinations. Typical upper bounds: - Rook: up to 4096 entries per square - Bishop: up to 512 entries per square Each entry stores a `u64` attack bitboard. ## 10. Runtime Move Generation Rook: ```zig const blockers = board.all_occ & rook_masks[square]; const index = (blockers *% rook_magics[square]) >> rook_shifts[square]; const moves = rook_attacks[square][index]; ``` Bishop: ```zig const blockers = board.all_occ & bishop_masks[square]; const index = (blockers *% bishop_magics[square]) >> bishop_shifts[square]; const moves = bishop_attacks[square][index]; ``` Queen: ```zig const queen_moves = rook_moves | bishop_moves; ``` Use OR (`|`), not AND (`&`). ## 11. Friendly Piece Removal Sliding attacks include friendly occupied squares. Remove illegal destinations: ```zig const legal_moves = attacks & ~friendly_occ; ``` ## 12. Captures Captures naturally remain in the move set because sliding stops on enemy blockers and includes the enemy blocker square. To isolate captures: ```zig const captures = legal_moves & enemy_occ; ``` ## 13. Generating Relevant Occupancy Masks Rook rays: - North - South - East - West Bishop rays: - North-east - North-west - South-east - South-west For magic relevant occupancy masks, exclude edge squares. ## 14. Generating All Occupancy Variations If a mask has `n` relevant bits, then occupancy count is: ```text 2^n ``` Example: ```text 12 relevant bits -> 4096 occupancy states ``` Generate every occupancy subset. ## 15. Generating Attack Tables For each occupancy subset: 1. Simulate sliding movement. 2. Stop when a blocker is encountered. 3. Include the blocker square. 4. Save resulting move bitboard. ## 16. Magic Number Generation Magic generation is brute force. Algorithm for a square: 1. Generate all occupancies. 2. Generate all attacks. 3. Pick random `u64` candidate magic. 4. Test `(blockers *% magic) >> shift`. 5. Ensure every occupancy maps without harmful collision. 6. If collision is harmful, reject magic and try another. ## 17. Zig-Specific Caveats Use wrapping multiplication: ```zig const index = (blockers *% magic) >> shift; ``` Normal multiplication may trap in safety-checked modes if overflow occurs. ## 18. Zig Integer Types Use explicit types everywhere: ```zig u64 u32 usize ``` Avoid implicit casts. ## 19. Compile-Time Generation Zig can generate tables at compile time. Runtime generation is also useful while learning and debugging. Potential workflow: 1. Runtime utility searches for magics and prints/generated tables. 2. Generated constants are reviewed. 3. Final engine uses embedded constants and precomputed attack tables. ## 20. Memory Usage Approximate attack table RAM usage: | Table | Size | | --- | ---: | | Rook attacks | ~800 KB | | Bishop attacks | ~40 KB | | Total | <1 MB | ## 21. Suggested Development Order ### Phase 1 Implement: - square numbering - bitboard helpers - occupancy masks ### Phase 2 Implement: - rook ray generation - bishop ray generation ### Phase 3 Implement: - occupancy subset generation - attack generation ### Phase 4 Implement: - brute-force magic finder ### Phase 5 Implement: - runtime lookup system ### Phase 6 Validate against known positions/FENs. ## 22. Validation Tests Verify: - edge squares - center squares - empty board - fully blocked board - single blocker - captures - friendly blockers - queen union correctness ## 23. Final Runtime Formula Summary ```zig const blockers = board.all_occ & mask; const index = (blockers *% magic) >> shift; const attacks = attack_table[index]; const legal_moves = attacks & ~friendly_occ; ``` Queen: ```zig const queen_moves = rook_moves | bishop_moves; ```