From 951a8bbec6166366176f0b48b45c885f3b3df5e7 Mon Sep 17 00:00:00 2001 From: Moritz Eigenauer Date: Wed, 12 Nov 2025 17:01:12 +0100 Subject: [PATCH] Reinitialize repository and add working move generation for all pieces --- .gitignore | 7 + Cargo.toml | 7 + README.md | 2 + helper_scripts/generate_king_attacks.py | 60 ++ helper_scripts/generate_knight_attacks.py | 60 ++ helper_scripts/generate_pawn_attacks.py | 87 +++ helper_scripts/generate_premasks.py | 110 +++ helper_scripts/grafical_bitboard_generator.py | 197 ++++++ helper_scripts/hexlist_to_binlist.py | 205 ++++++ src/board.rs | 328 +++++++++ src/lib.rs | 4 + src/main.rs | 13 + src/move.rs | 174 +++++ src/movegen/mod.rs | 19 + src/movegen/non_sliders.rs | 87 +++ src/movegen/pawns.rs | 251 +++++++ src/movegen/sliders.rs | 132 ++++ src/movegen/tables.rs | 647 ++++++++++++++++++ src/square.rs | 141 ++++ tests/bishop_move_generation.rs | 85 +++ tests/board_to_fen_conversion.rs | 76 ++ tests/king_move_generation.rs | 145 ++++ tests/knight_move_generation.rs | 96 +++ tests/move_to_algebraic_conversion.rs | 92 +++ tests/pawn_move_generation.rs | 142 ++++ tests/perft.rs | 12 + tests/queen_move_generation.rs | 93 +++ tests/rook_move_generation.rs | 101 +++ 28 files changed, 3373 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 helper_scripts/generate_king_attacks.py create mode 100644 helper_scripts/generate_knight_attacks.py create mode 100644 helper_scripts/generate_pawn_attacks.py create mode 100644 helper_scripts/generate_premasks.py create mode 100644 helper_scripts/grafical_bitboard_generator.py create mode 100644 helper_scripts/hexlist_to_binlist.py create mode 100644 src/board.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/move.rs create mode 100644 src/movegen/mod.rs create mode 100644 src/movegen/non_sliders.rs create mode 100644 src/movegen/pawns.rs create mode 100644 src/movegen/sliders.rs create mode 100644 src/movegen/tables.rs create mode 100644 src/square.rs create mode 100644 tests/bishop_move_generation.rs create mode 100644 tests/board_to_fen_conversion.rs create mode 100644 tests/king_move_generation.rs create mode 100644 tests/knight_move_generation.rs create mode 100644 tests/move_to_algebraic_conversion.rs create mode 100644 tests/pawn_move_generation.rs create mode 100644 tests/perft.rs create mode 100644 tests/queen_move_generation.rs create mode 100644 tests/rook_move_generation.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c7295e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/target +/.idea +/src/replace.py +/scripts +/Cargo.lock +/src/benchmark/stockfish +uci_log.txt diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1961cf5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "chess_engine" +version = "0.2.0" +authors = ["Moritz Eigenauer "] +edition = "2024" + +[dependencies] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..376daf5 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# ChessEngine +My own UCI chess engine in Rust diff --git a/helper_scripts/generate_king_attacks.py b/helper_scripts/generate_king_attacks.py new file mode 100644 index 0000000..9131ec5 --- /dev/null +++ b/helper_scripts/generate_king_attacks.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +def generate_king_attacks(): + """ + Generates a list of 64 u64 bitboards, where each bitboard represents + the squares a king can attack from a given source square. + Uses Little-Endian File-Rank (LEFR) mapping: A1=0, H1=7, A2=8, ..., H8=63. + """ + all_attacks = [] + + # All 8 possible king move offsets (delta_file, delta_rank) + king_moves = [ + (0, 1), (0, -1), (1, 0), (-1, 0), # Orthogonal + (1, 1), (1, -1), (-1, 1), (-1, -1) # Diagonal + ] + + for sq in range(64): + attacks_bb = 0 + + # Calculate rank and file for the source square + rank = sq // 8 + file = sq % 8 + + for df, dr in king_moves: + target_rank = rank + dr + target_file = file + df + + # Check if the target square is on the board + if 0 <= target_rank <= 7 and 0 <= target_file <= 7: + # Convert target rank and file back to a square index + target_sq = target_rank * 8 + target_file + # Set the corresponding bit in the bitboard + attacks_bb |= (1 << target_sq) + + all_attacks.append(attacks_bb) + + return all_attacks + +def print_rust_array(attacks, const_name): + """ + Prints the list of attack bitboards as a Rust array. + """ + print(f"pub const {const_name}: [u64; 64] = [") + + for i, attacks_bb in enumerate(attacks): + # Format as 64-bit zero-padded binary string + raw_binary_string = f"{attacks_bb:064b}" + + # Insert underscores every 8 bits for readability + chunks = [raw_binary_string[i:i+8] for i in range(0, 64, 8)] + binary_string = f"0b{'_'.join(chunks)}" + + # Correctly index the string 'ABCDEFGH' + print(f" {binary_string}, // Square {i} ({'ABCDEFGH'[i%8]}{i//8 + 1})") + + print("];") + +if __name__ == "__main__": + attacks = generate_king_attacks() + print_rust_array(attacks, "KING_ATTACKS") \ No newline at end of file diff --git a/helper_scripts/generate_knight_attacks.py b/helper_scripts/generate_knight_attacks.py new file mode 100644 index 0000000..8aa083f --- /dev/null +++ b/helper_scripts/generate_knight_attacks.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +def generate_knight_attacks(): + """ + Generates a list of 64 u64 bitboards, where each bitboard represents + the squares a knight can attack from a given source square. + Uses Little-Endian File-Rank (LEFR) mapping: A1=0, H1=7, A2=8, ..., H8=63. + """ + all_attacks = [] + + # All 8 possible knight move offsets (delta_file, delta_rank) + knight_moves = [ + (1, 2), (1, -2), (-1, 2), (-1, -2), + (2, 1), (2, -1), (-2, 1), (-2, -1) + ] + + for sq in range(64): + attacks_bb = 0 + + # Calculate rank and file for the source square + rank = sq // 8 + file = sq % 8 + + for df, dr in knight_moves: + target_rank = rank + dr + target_file = file + df + + # Check if the target square is on the board + if 0 <= target_rank <= 7 and 0 <= target_file <= 7: + # Convert target rank and file back to a square index + target_sq = target_rank * 8 + target_file + # Set the corresponding bit in the bitboard + attacks_bb |= (1 << target_sq) + + all_attacks.append(attacks_bb) + + return all_attacks + +def print_rust_array(attacks): + """ + Prints the list of attack bitboards as a Rust array. + """ + print("pub const KNIGHT_ATTACKS: [u64; 64] = [") + + for i, attacks_bb in enumerate(attacks): + # Format as 64-bit zero-padded binary string + raw_binary_string = f"{attacks_bb:064b}" + + # Insert underscores every 8 bits for readability + chunks = [raw_binary_string[i:i+8] for i in range(0, 64, 8)] + binary_string = f"0b{'_'.join(chunks)}" + + # Correctly index the string 'ABCDEFGH' + print(f" {binary_string}, // Square {i} ({'ABCDEFGH'[i%8]}{i//8 + 1})") + + print("];") + +if __name__ == "__main__": + attacks = generate_knight_attacks() + print_rust_array(attacks) \ No newline at end of file diff --git a/helper_scripts/generate_pawn_attacks.py b/helper_scripts/generate_pawn_attacks.py new file mode 100644 index 0000000..849c528 --- /dev/null +++ b/helper_scripts/generate_pawn_attacks.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 + +def generate_pawn_attacks(): + """ + Generates a list of 64 u64 bitboards for each color (White, Black), + where each bitboard represents the squares a pawn can attack from + a given source square. + Uses Little-Endian File-Rank (LEFR) mapping: A1=0, H1=7, A2=8, ..., H8=63. + + Returns: + [[u64; 64]; 2] (represented as a list of two lists) + Index 0: White attacks + Index 1: Black attacks + """ + + # all_attacks[0] = White, all_attacks[1] = Black + all_attacks = [[], []] + + for sq in range(64): + rank = sq // 8 + file = sq % 8 + + white_attacks_bb = 0 + black_attacks_bb = 0 + + # --- White Attacks (Index 0) --- + # Pawns attack "up" the board (increasing square index) + # We only generate attacks if the pawn is not on the 8th rank + if rank < 7: + # Attack Up-Left (e.g., B2 -> A3) + # Not possible if pawn is on the A-file + if file > 0: + white_attacks_bb |= (1 << (sq + 7)) + + # Attack Up-Right (e.g., B2 -> C3) + # Not possible if pawn is on the H-file + if file < 7: + white_attacks_bb |= (1 << (sq + 9)) + + # --- Black Attacks (Index 1) --- + # Pawns attack "down" the board (decreasing square index) + # We only generate attacks if the pawn is not on the 1st rank + if rank > 0: + # Attack Down-Left (e.g., G7 -> F6) + # Not possible if pawn is on the A-file + if file > 0: + black_attacks_bb |= (1 << (sq - 9)) + + # Attack Down-Right (e.g., G7 -> H6) + # Not possible if pawn is on the H-file + if file < 7: + black_attacks_bb |= (1 << (sq - 7)) + + all_attacks[0].append(white_attacks_bb) + all_attacks[1].append(black_attacks_bb) + + return all_attacks + +def print_rust_array(attacks_by_color, const_name): + """ + Prints the list of attack bitboards as a nested Rust array. + """ + print(f"pub const {const_name}: [[u64; 64]; 2] = [") + + # --- Print White Attacks --- + print(" [ // Color 0: White") + for i, attacks_bb in enumerate(attacks_by_color[0]): + raw_binary_string = f"{attacks_bb:064b}" + chunks = [raw_binary_string[i:i+8] for i in range(0, 64, 8)] + binary_string = f"0b{'_'.join(chunks)}" + print(f" {binary_string}, // Square {i} ({'ABCDEFGH'[i%8]}{i//8 + 1})") + print(" ],") + + # --- Print Black Attacks --- + print(" [ // Color 1: Black") + for i, attacks_bb in enumerate(attacks_by_color[1]): + raw_binary_string = f"{attacks_bb:064b}" + chunks = [raw_binary_string[i:i+8] for i in range(0, 64, 8)] + binary_string = f"0b{'_'.join(chunks)}" + print(f" {binary_string}, // Square {i} ({'ABCDEFGH'[i%8]}{i//8 + 1})") + print(" ]") + + print("];") + +if __name__ == "__main__": + attacks = generate_pawn_attacks() + print_rust_array(attacks, "PAWN_ATTACKS") \ No newline at end of file diff --git a/helper_scripts/generate_premasks.py b/helper_scripts/generate_premasks.py new file mode 100644 index 0000000..48c78c5 --- /dev/null +++ b/helper_scripts/generate_premasks.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 + +def generate_premasks(): + """ + Generiert relevante Belegungsmasken (relevant occupancy masks) für + Türme (Rooks) und Läufer (Bishops) auf allen 64 Feldern. + + Diese Masken enthalten alle Felder zwischen der Figur und dem Rand, + ABER AUSSCHLIESSLICH der Randfelder selbst. + """ + all_rook_masks = [] + all_bishop_masks = [] + + # Richtungen für beide Figurentypen + rook_directions = [ + (1, 0), # Hoch + (-1, 0), # Runter + (0, 1), # Rechts + (0, -1) # Links + ] + bishop_directions = [ + (1, 1), # Hoch-Rechts + (1, -1), # Hoch-Links + (-1, 1), # Runter-Rechts + (-1, -1) # Runter-Links + ] + + for sq in range(64): + rook_mask_bb = 0 + bishop_mask_bb = 0 + + rank = sq // 8 + file = sq % 8 + + # --- 1. Turm (Rook) Masken-Generierung --- + # (Dies ist die korrigierte Logik) + for dr, df in rook_directions: + target_rank = rank + dr + target_file = file + df + + # Schleife, solange wir auf dem Brett sind (0-7) + while 0 <= target_rank <= 7 and 0 <= target_file <= 7: + is_relevant = False + + # Prüfen, ob das Feld *vor* dem Rand liegt. + if df != 0: # Horizontale Bewegung + if 1 <= target_file <= 6: # Files 'b' bis 'g' + is_relevant = True + elif dr != 0: # Vertikale Bewegung + if 1 <= target_rank <= 6: # Ranks 2 bis 7 + is_relevant = True + + if is_relevant: + target_sq = target_rank * 8 + target_file + rook_mask_bb |= (1 << target_sq) + + # Zum nächsten Feld in dieser Richtung + target_rank += dr + target_file += df + + all_rook_masks.append(rook_mask_bb) + + # --- 2. Läufer (Bishop) Masken-Generierung --- + # (Diese Logik war in deinem "Rook"-Skript und ist hier korrekt) + for dr, df in bishop_directions: + target_rank = rank + dr + target_file = file + df + + # Schleife, solange wir *von allen* Rändern entfernt sind (1-6) + while 1 <= target_rank <= 6 and 1 <= target_file <= 6: + target_sq = target_rank * 8 + target_file + bishop_mask_bb |= (1 << target_sq) + + # Zum nächsten Feld in dieser Richtung + target_rank += dr + target_file += df + + all_bishop_masks.append(bishop_mask_bb) + + # Gibt beide Listen als Tupel zurück + return all_rook_masks, all_bishop_masks + +def print_rust_array(attacks, const_name): + """ + Gibt die Liste der Bitboards als Rust-Array aus. + """ + print(f"pub const {const_name}: [u64; 64] = [") + + for i, attacks_bb in enumerate(attacks): + # Formatieren als 64-Bit binärer String mit Nullen + raw_binary_string = f"{attacks_bb:064b}" + + # Unterstriche alle 8 Bits zur Lesbarkeit einfügen + chunks = [raw_binary_string[j:j+8] for j in range(0, 64, 8)] + binary_string = f"0b{'_'.join(chunks)}" + + # Index 'ABCDEFGH' korrekt zuordnen + print(f" {binary_string}, // Square {i} ({'ABCDEFGH'[i%8]}{i//8 + 1})") + + print("];") + +if __name__ == "__main__": + # Entpackt das Tupel mit beiden Masken-Listen + rook_masks, bishop_masks = generate_premasks() + + print("--- ROOK MASKS ---") + print_rust_array(rook_masks, "ROOK_RELEVANT_OCCUPANCY") + + print("\n--- BISHOP MASKS ---") + print_rust_array(bishop_masks, "BISHOP_RELEVANT_OCCUPANCY") \ No newline at end of file diff --git a/helper_scripts/grafical_bitboard_generator.py b/helper_scripts/grafical_bitboard_generator.py new file mode 100644 index 0000000..c2b9d2c --- /dev/null +++ b/helper_scripts/grafical_bitboard_generator.py @@ -0,0 +1,197 @@ +import tkinter as tk + +class ChessBitboardApp: + def __init__(self, root): + self.root = root + self.root.title("Chess Bitboard Generator") + + # The bitboard, stored as a 64-bit integer + self.bitboard = 0 + + # A dictionary to keep track of squares and their marked state + # Key: (row, col), Value: { + # 'widget': tk.Frame, 'label_alg': tk.Label, 'label_idx': tk.Label, + # 'marked': bool, 'original_color': str + # } + self.squares = {} + + # --- Create the GUI --- + + # Frame for the chessboard + board_frame = tk.Frame(root) + board_frame.pack() + + # Create the 8x8 grid of squares + # We loop from row 7 (rank 8) down to 0 (rank 1) for visual layout + for r in range(7, -1, -1): # 7, 6, 5, ... 0 + for c in range(8): # 0, 1, 2, ... 7 + + # Determine the square's original color + is_light_square = (r + c) % 2 == 1 + original_color = "#F0D9B5" if is_light_square else "#B58863" + + # Create the square as a Frame + square = tk.Frame( + board_frame, + width=50, + height=50, + bg=original_color, + relief="sunken", + borderwidth=1 + ) + # Make frame *not* resize to labels + square.pack_propagate(False) # Use pack_propagate since we use place/pack inside + square.grid(row=7 - r, column=c) + + # --- Add labels to the square (Feature 2) --- + algebraic_not = f"{'abcdefgh'[c]}{r + 1}" + bit_index = r * 8 + c + + label_alg = tk.Label(square, text=algebraic_not, bg=original_color, font=("Arial", 8, "bold")) + label_alg.place(x=2, y=1) # Use place to position label + + label_idx = tk.Label(square, text=f"{bit_index}", bg=original_color, font=("Arial", 8)) + label_idx.place(relx=1.0, rely=1.0, anchor='se', x=-2, y=-1) # Use place for bottom-right + + # Bind the click event to all parts of the square + click_lambda = lambda event, row=r, col=c: self.on_square_click(event, row, col) + square.bind("", click_lambda) + label_alg.bind("", click_lambda) + label_idx.bind("", click_lambda) + + # Store the square's info + self.squares[(r, c)] = { + 'widget': square, + 'label_alg': label_alg, + 'label_idx': label_idx, + 'marked': False, + 'original_color': original_color + } + + # Frame for the bitboard display + info_frame = tk.Frame(root, pady=10) + info_frame.pack() + + # --- Make display labels copyable (Feature 1) --- + self.binary_var = tk.StringVar() + self.int_var = tk.StringVar() + + tk.Label(info_frame, text="Binary:").pack() + self.binary_label = tk.Entry( + info_frame, + textvariable=self.binary_var, + state="readonly", + font=("Courier", 10), + width=77 # 64 chars + 15 underscores + 'b' + ) + self.binary_label.pack() + + tk.Label(info_frame, text="Integer:").pack() + self.int_label = tk.Entry( + info_frame, + textvariable=self.int_var, + state="readonly", + font=("Courier", 12, "bold"), + width=22 + ) + self.int_label.pack() + + # --- Add Entry for pasting bitboard (Feature 3) --- + input_frame = tk.Frame(root, pady=5) + input_frame.pack() + + tk.Label(input_frame, text="Paste Bitboard (int) and Press Enter:").pack(side=tk.LEFT) + self.paste_entry = tk.Entry(input_frame, font=("Courier", 12), width=22) + self.paste_entry.pack(side=tk.LEFT, padx=5) + self.paste_entry.bind("", self.on_paste_bitboard) + + # Initialize display + self.update_display() + + def on_square_click(self, event, row, col): + """Handles the click event for a square.""" + square_info = self.squares[(row, col)] + + # Toggle the marked state + square_info['marked'] = not square_info['marked'] + + self.update_square_visuals(row, col) + + # Recalculate the bitboard and update the display + self.recalculate_bitboard() + self.update_display() + + def update_square_visuals(self, row, col): + """Updates a single square's color based on its 'marked' state.""" + square_info = self.squares[(row, col)] + + is_marked = square_info['marked'] + new_color = "#50C878" if is_marked else square_info['original_color'] + label_fg_color = "white" if is_marked else "black" # Make text white on green + + square_info['widget'].config(bg=new_color) + square_info['label_alg'].config(bg=new_color, fg=label_fg_color) + square_info['label_idx'].config(bg=new_color, fg=label_fg_color) + + def recalculate_bitboard(self): + """Recalculates the 64-bit integer from the marked squares.""" + self.bitboard = 0 + + for r in range(8): + for c in range(8): + if self.squares[(r, c)]['marked']: + bit_index = r * 8 + c + self.bitboard |= (1 << bit_index) + + def update_display(self): + """Updates the binary and integer labels.""" + + # Update the integer label + self.int_var.set(f"{self.bitboard}") + + # Update the binary label + binary_string = f"{self.bitboard:064b}" + formatted_binary = "b" + "_".join(binary_string[i:i+8] for i in range(0, 64, 4)) + self.binary_var.set(f"{formatted_binary}") + + def on_paste_bitboard(self, event): + """Handles the 'Enter' key press in the paste entry box.""" + try: + # Get text from entry and convert to integer + new_bitboard = int(self.paste_entry.get()) + if 0 <= new_bitboard <= (1 << 64) - 1: + self.bitboard = new_bitboard + # Update the board visuals from the new bitboard + self.update_board_from_bitboard() + # Update the display labels + self.update_display() + else: + # Handle out-of-range numbers + self.paste_entry.delete(0, tk.END) + self.paste_entry.insert(0, "Out of 64-bit range") + + except ValueError: + # Handle non-integer input + self.paste_entry.delete(0, tk.END) + self.paste_entry.insert(0, "Invalid Integer") + + def update_board_from_bitboard(self): + """Updates the visual state of all squares based on self.bitboard.""" + for r in range(8): + for c in range(8): + bit_index = r * 8 + c + square_info = self.squares[(r, c)] + + # Check if the bit at bit_index is set + if (self.bitboard >> bit_index) & 1: + square_info['marked'] = True + else: + square_info['marked'] = False + + # Update the square's color + self.update_square_visuals(r, c) + +if __name__ == "__main__": + root = tk.Tk() + app = ChessBitboardApp(root) + root.mainloop() \ No newline at end of file diff --git a/helper_scripts/hexlist_to_binlist.py b/helper_scripts/hexlist_to_binlist.py new file mode 100644 index 0000000..1e1a25b --- /dev/null +++ b/helper_scripts/hexlist_to_binlist.py @@ -0,0 +1,205 @@ +a = """ +[ + 0x8a80104000800020, + 0x140002000100040, + 0x2801880a0017001, + 0x100081001000420, + 0x200020010080420, + 0x3001c0002010008, + 0x8480008002000100, + 0x2080088004402900, + 0x800098204000, + 0x2024401000200040, + 0x100802000801000, + 0x120800800801000, + 0x208808088000400, + 0x2802200800400, + 0x2200800100020080, + 0x801000060821100, + 0x80044006422000, + 0x100808020004000, + 0x12108a0010204200, + 0x140848010000802, + 0x481828014002800, + 0x8094004002004100, + 0x4010040010010802, + 0x20008806104, + 0x100400080208000, + 0x2040002120081000, + 0x21200680100081, + 0x20100080080080, + 0x2000a00200410, + 0x20080800400, + 0x80088400100102, + 0x80004600042881, + 0x4040008040800020, + 0x440003000200801, + 0x4200011004500, + 0x188020010100100, + 0x14800401802800, + 0x2080040080800200, + 0x124080204001001, + 0x200046502000484, + 0x480400080088020, + 0x1000422010034000, + 0x30200100110040, + 0x100021010009, + 0x2002080100110004, + 0x202008004008002, + 0x20020004010100, + 0x2048440040820001, + 0x101002200408200, + 0x40802000401080, + 0x4008142004410100, + 0x2060820c0120200, + 0x1001004080100, + 0x20c020080040080, + 0x2935610830022400, + 0x44440041009200, + 0x280001040802101, + 0x2100190040002085, + 0x80c0084100102001, + 0x4024081001000421, + 0x20030a0244872, + 0x12001008414402, + 0x2006104900a0804, + 0x1004081002402, +] +""".replace("[", "").replace("]", "").replace(",", "").strip().split("\n") +a = [int(x, 16) for x in a] + +b = """ +[ + 0x8a80104000800020, + 0x140002000100040, + 0x2801880a0017001, + 0x100081001000420, + 0x200020010080420, + 0x3001c0002010008, + 0x8480008002000100, + 0x2080088004402900, + 0x800098204000, + 0x2024401000200040, + 0x100802000801000, + 0x120800800801000, + 0x208808088000400, + 0x2802200800400, + 0x2200800100020080, + 0x801000060821100, + 0x80044006422000, + 0x100808020004000, + 0x12108a0010204200, + 0x140848010000802, + 0x481828014002800, + 0x8094004002004100, + 0x4010040010010802, + 0x20008806104, + 0x100400080208000, + 0x2040002120081000, + 0x21200680100081, + 0x20100080080080, + 0x2000a00200410, + 0x20080800400, + 0x80088400100102, + 0x80004600042881, + 0x4040008040800020, + 0x440003000200801, + 0x4200011004500, + 0x188020010100100, + 0x14800401802800, + 0x2080040080800200, + 0x124080204001001, + 0x200046502000484, + 0x480400080088020, + 0x1000422010034000, + 0x30200100110040, + 0x100021010009, + 0x2002080100110004, + 0x202008004008002, + 0x20020004010100, + 0x2048440040820001, + 0x101002200408200, + 0x40802000401080, + 0x4008142004410100, + 0x2060820c0120200, + 0x1001004080100, + 0x20c020080040080, + 0x2935610830022400, + 0x44440041009200, + 0x280001040802101, + 0x2100190040002085, + 0x80c0084100102001, + 0x4024081001000421, + 0x20030a0244872, + 0x12001008414402, + 0x2006104900a0804, + 0x1004081002402, +] +""".replace("[", "").replace("]", "").replace(",", "").strip().split("\n") +b = [int(x, 16) for x in b] + +def format_rust_array(data_list, array_name="GeneratedArray"): + """ + Converts a list of integers/hex into a formatted Rust array + with binary representation and chess square comments. + """ + print(f"pub const {array_name}: [u64; {len(data_list)}] = [") + + files = "ABCDEFGH" + + for i, val in enumerate(data_list): + # 1. Convert to 64-bit binary string (MSB on left) + bin_str = f"{val:064b}" + + # 2. Insert underscores every 8 bits for readability + # Range 0 to 64 with step 8 + chunks = [bin_str[j:j+8] for j in range(0, 64, 8)] + formatted_bin = "_".join(chunks) + + # 3. Calculate Square and Algebraic Notation for the comment + # Assuming standard Little-Endian Rank-File mapping (A1=0, B1=1 ... H8=63) + file_idx = i % 8 + rank_idx = i // 8 + + if rank_idx < 8: + algebraic = f"{files[file_idx]}{rank_idx + 1}" + else: + algebraic = "N/A" # Handle lists larger than 64 items gracefully + + # 4. Print the formatted line + print(f" 0b{formatted_bin}, // Square {i} ({algebraic})") + + print("];") + +# --- OPTION 1: Convert your specific hex list --- +my_hex_list = [ + 0x8a80104000800020, + 0x140002000100040 +] + +# --- OPTION 2: Generate the actual King Attacks (to match your example) --- +def generate_king_attacks(): + king_moves = [] + for square in range(64): + attacks = 0 + file = square % 8 + rank = square // 8 + + # Iterate over all 8 neighbors + for d_file in [-1, 0, 1]: + for d_rank in [-1, 0, 1]: + if d_file == 0 and d_rank == 0: + continue + + target_file = file + d_file + target_rank = rank + d_rank + + if 0 <= target_file < 8 and 0 <= target_rank < 8: + target_square = target_rank * 8 + target_file + attacks |= (1 << target_square) + king_moves.append(attacks) + return king_moves + +if __name__ == "__main__": + format_rust_array(a, "MAGICS_ROOK") + format_rust_array(b, "MAGICS_BISHOP") \ No newline at end of file diff --git a/src/board.rs b/src/board.rs new file mode 100644 index 0000000..3099062 --- /dev/null +++ b/src/board.rs @@ -0,0 +1,328 @@ +use crate::square::Square; +use std::mem; +use crate::r#move::*; +use std::ops::Not; + +pub const CASTLING_WK: u8 = 1; +pub const CASTLING_WK_MASK: u64 = 96; // F1 G1 + +pub const CASTLING_WQ: u8 = 2; +pub const CASTLING_WQ_MASK: u64 = 14; // B1 C1 D1 + +pub const CASTLING_BK: u8 = 4; +pub const CASTLING_BK_MASK: u64 = 6917529027641081856; // F8 G8 + +pub const CASTLING_BQ: u8 = 8; +pub const CASTLING_BQ_MASK: u64 = 1008806316530991104; // B8 C8 D8 + + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum Color { + White = 0, + Black = 1, +} + +impl Not for Color { + type Output = Self; + + fn not(self) -> Self::Output { + match self { + Color::White => Color::Black, + Color::Black => Color::White, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum PieceType { + Pawn = 0, + Knight = 1, + Bishop = 2, + Rook = 3, + Queen = 4, + King = 5, +} + +pub struct Board { + pub side_to_move: Color, + + pub pieces: [[u64; 2]; 6], + + pub occupied: [u64; 2], + pub all_occupied: u64, + pub empty_squares: u64, + + pub castling_rights: u8, + pub en_passant_target: Option, + + pub halfmove_clock: u8, + pub fullmove_number: u16, +} + +impl Board { + /// Creates a new Board instance from a FEN string. + /// Assumes the FEN string is valid. + pub fn from_fen(fen: &str) -> Self { + let mut parts = fen.split_whitespace(); + + // Initialisiere das 2D-Array + let mut pieces = [[0u64; 2]; 6]; + let mut occupied = [0u64; 2]; + + // Part 1: Piece placement + let placement = parts.next().unwrap_or(""); + let mut rank = 7; + let mut file = 0; + + for c in placement.chars() { + if c.is_digit(10) { + file += c.to_digit(10).unwrap_or(0) as usize; + } else if c == '/' { + rank -= 1; + file = 0; + } else if c.is_alphabetic() { + let sq = (rank * 8 + file) as u8; + let mask = 1u64 << sq; + + if c.is_uppercase() { + let color_idx = Color::White as usize; + occupied[color_idx] |= mask; + match c { + 'P' => pieces[PieceType::Pawn as usize][color_idx] |= mask, + 'N' => pieces[PieceType::Knight as usize][color_idx] |= mask, + 'B' => pieces[PieceType::Bishop as usize][color_idx] |= mask, + 'R' => pieces[PieceType::Rook as usize][color_idx] |= mask, + 'Q' => pieces[PieceType::Queen as usize][color_idx] |= mask, + 'K' => pieces[PieceType::King as usize][color_idx] |= mask, + _ => {} + } + } else { + let color_idx = Color::Black as usize; + occupied[color_idx] |= mask; + match c { + 'p' => pieces[PieceType::Pawn as usize][color_idx] |= mask, + 'n' => pieces[PieceType::Knight as usize][color_idx] |= mask, + 'b' => pieces[PieceType::Bishop as usize][color_idx] |= mask, + 'r' => pieces[PieceType::Rook as usize][color_idx] |= mask, + 'q' => pieces[PieceType::Queen as usize][color_idx] |= mask, + 'k' => pieces[PieceType::King as usize][color_idx] |= mask, + _ => {} + } + } + file += 1; + } + } + + // Part 2: Active color + let side_to_move = match parts.next().unwrap_or("w") { + "b" => Color::Black, + _ => Color::White, + }; + + // Part 3: Castling rights + let mut castling_rights = 0u8; + if let Some(castle_str) = parts.next() { + if castle_str.contains('K') { castling_rights |= CASTLING_WK; } + if castle_str.contains('Q') { castling_rights |= CASTLING_WQ; } + if castle_str.contains('k') { castling_rights |= CASTLING_BK; } + if castle_str.contains('q') { castling_rights |= CASTLING_BQ; } + } + + // Part 4: En passant target + let en_passant_target = match parts.next().unwrap_or("-") { + "-" => None, + sq_str => { + let chars: Vec = sq_str.chars().collect(); + let file = (chars[0] as u8 - b'a') as u8; + let rank = (chars[1] as u8 - b'1') as u8; + let sq_index = rank * 8 + file; + // This is unsafe, but assumes the FEN is valid + Some(unsafe { mem::transmute::(sq_index) }) + } + }; + + // Part 5: Halfmove clock + let halfmove_clock = parts.next().unwrap_or("0").parse::().unwrap_or(0); + + // Part 6: Fullmove number + let fullmove_number = parts.next().unwrap_or("1").parse::().unwrap_or(1); + + let all_occupied = occupied[Color::White as usize] | occupied[Color::Black as usize]; + let empty_squares = !all_occupied; + + Board { + side_to_move, + pieces, // Geändertes Feld + occupied, + all_occupied, + empty_squares, + castling_rights, + en_passant_target, + halfmove_clock, + fullmove_number, + } + } + + /// Converts the current board state into a FEN string. + pub fn to_fen(&self) -> String { + let mut fen = String::with_capacity(90); + + // Part 1: Piece placement + let mut empty_count = 0; + for rank in (0..=7).rev() { + for file in 0..=7 { + let sq = (rank * 8 + file) as u8; + let mask = 1u64 << sq; + + if let Some(piece) = self.get_piece_at(mask) { + if empty_count > 0 { + fen.push((b'0' + empty_count) as char); + empty_count = 0; + } + fen.push(piece); + } else { + empty_count += 1; + } + } + if empty_count > 0 { + fen.push((b'0' + empty_count) as char); + empty_count = 0; + } + if rank > 0 { + fen.push('/'); + } + } + + // Part 2: Active color + fen.push(' '); + fen.push(if self.side_to_move == Color::White { 'w' } else { 'b' }); + + // Part 3: Castling rights + fen.push(' '); + let mut castle_str = String::new(); + if (self.castling_rights & CASTLING_WK) != 0 { castle_str.push('K'); } + if (self.castling_rights & CASTLING_WQ) != 0 { castle_str.push('Q'); } + if (self.castling_rights & CASTLING_BK) != 0 { castle_str.push('k'); } + if (self.castling_rights & CASTLING_BQ) != 0 { castle_str.push('q'); } + + if castle_str.is_empty() { + fen.push('-'); + } else { + fen.push_str(&castle_str); + } + + // Part 4: En passant target + fen.push(' '); + if let Some(sq) = self.en_passant_target { + let sq_index = sq as u8; + let file = (sq_index % 8) as u8; + let rank = (sq_index / 8) as u8; + fen.push((b'a' + file) as char); + fen.push((b'1' + rank) as char); + } else { + fen.push('-'); + } + + // Part 5: Halfmove clock + fen.push(' '); + fen.push_str(&self.halfmove_clock.to_string()); + + // Part 6: Fullmove number + fen.push(' '); + fen.push_str(&self.fullmove_number.to_string()); + + fen + } + + /// Helper function to find which piece (as a char) is on a given square mask. + fn get_piece_at(&self, sq_mask: u64) -> Option { + let white = Color::White as usize; + let black = Color::Black as usize; + + if (self.pieces[PieceType::Pawn as usize][white] & sq_mask) != 0 { return Some('P'); } + if (self.pieces[PieceType::Pawn as usize][black] & sq_mask) != 0 { return Some('p'); } + if (self.pieces[PieceType::Knight as usize][white] & sq_mask) != 0 { return Some('N'); } + if (self.pieces[PieceType::Knight as usize][black] & sq_mask) != 0 { return Some('n'); } + if (self.pieces[PieceType::Bishop as usize][white] & sq_mask) != 0 { return Some('B'); } + if (self.pieces[PieceType::Bishop as usize][black] & sq_mask) != 0 { return Some('b'); } + if (self.pieces[PieceType::Rook as usize][white] & sq_mask) != 0 { return Some('R'); } + if (self.pieces[PieceType::Rook as usize][black] & sq_mask) != 0 { return Some('r'); } + if (self.pieces[PieceType::Queen as usize][white] & sq_mask) != 0 { return Some('Q'); } + if (self.pieces[PieceType::Queen as usize][black] & sq_mask) != 0 { return Some('q'); } + if (self.pieces[PieceType::King as usize][white] & sq_mask) != 0 { return Some('K'); } + if (self.pieces[PieceType::King as usize][black] & sq_mask) != 0 { return Some('k'); } + + None + } + + /// Prints a single bitboard (u64) as an 8x8 grid for debugging. + fn print_bitboard(&self, name: &str, bitboard: u64) { + println!("--- {} ---", name); + println!(" a b c d e f g h"); + for rank in (0..=7).rev() { + print!("{} ", rank + 1); + for file in 0..=7 { + let sq_index = rank * 8 + file; + let mask = 1u64 << sq_index; + + if (bitboard & mask) != 0 { + print!("1 "); + } else { + print!(". "); + } + } + println!(); + } + println!("RAW VALUE: {}", bitboard); + println!(); + } + + /// Prints all internal bitboards for debugging purposes. + pub fn pretty_print_internals(&self) { + println!("\n========= BOARD INTERNAL BITBOARDS ========="); + + let white = Color::White as usize; + let black = Color::Black as usize; + + self.print_bitboard("White Pawns", self.pieces[PieceType::Pawn as usize][white]); + self.print_bitboard("Black Pawns", self.pieces[PieceType::Pawn as usize][black]); + + self.print_bitboard("White Knights", self.pieces[PieceType::Knight as usize][white]); + self.print_bitboard("Black Knights", self.pieces[PieceType::Knight as usize][black]); + + self.print_bitboard("White Bishops", self.pieces[PieceType::Bishop as usize][white]); + self.print_bitboard("Black Bishops", self.pieces[PieceType::Bishop as usize][black]); + + self.print_bitboard("White Rooks", self.pieces[PieceType::Rook as usize][white]); + self.print_bitboard("Black Rooks", self.pieces[PieceType::Rook as usize][black]); + + self.print_bitboard("White Queens", self.pieces[PieceType::Queen as usize][white]); + self.print_bitboard("Black Queens", self.pieces[PieceType::Queen as usize][black]); + + self.print_bitboard("White King", self.pieces[PieceType::King as usize][white]); + self.print_bitboard("Black King", self.pieces[PieceType::King as usize][black]); + + println!("--- Aggregate Bitboards ---"); + self.print_bitboard("All White Pieces", self.occupied[white]); + self.print_bitboard("All Black Pieces", self.occupied[black]); + self.print_bitboard("All Occupied", self.all_occupied); + self.print_bitboard("Empty Squares", self.empty_squares); + + println!("============================================\n"); + } + + pub fn make_move(&mut self, mv: Move) { + let from = mv.value()& MOVE_FROM_MASK; + let to = mv.value() & MOVE_TO_MASK; + let flag = mv.value() & MOVE_FLAG_MASK; + + // promo must come first because of double usage of the flag bits + if flag == MOVE_FLAG_NO_PROMO { + + } else { // + + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..19ad6c3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,4 @@ +pub mod board; +pub mod r#move; +pub mod square; +pub mod movegen; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9efd5f4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,13 @@ +use chess_engine::board::Board; +use chess_engine::movegen::generate_pseudo_legal_moves; +use chess_engine::r#move::*; + + +fn main() { + let board = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 1 2"); + let mut move_list = MoveList::new(); + + generate_pseudo_legal_moves(&board, &mut move_list); + + println!("Counted {} pseudo legal moves.", move_list.len()); +} diff --git a/src/move.rs b/src/move.rs new file mode 100644 index 0000000..6ea8e3b --- /dev/null +++ b/src/move.rs @@ -0,0 +1,174 @@ +use std::slice; +use std::fmt; +use crate::square::Square; +use crate::board::PieceType; + +// BIT 0 - 5: FROM SQUARE (0-63) +pub const MOVE_FROM_MASK: u16 = 0b0000_0000_0011_1111; + +// BIT 6 - 11: TO SQUARE (0-63) +pub const MOVE_TO_MASK: u16 = 0b0000_1111_1100_0000; + +// BIT 12 - 15: FLAGS (4 bits) +// 1. 0 no capture, 1 capture +pub const MOVE_FLAG_MASK: u16 = 0b1111_0000_0000_0000; + +pub const MOVE_FLAG_QUIET: u16 = 0b0000_0000_0000_0000; +pub const MOVE_FLAG_CAPTURE: u16 = 0b0001_0000_0000_0000; +pub const MOVE_FLAG_EN_PASSANT: u16 = 0b0010_0000_0000_0000; + +pub const MOVE_FLAG_WK_CASTLE: u16 = 0b0011_0000_0000_0000; +pub const MOVE_FLAG_WQ_CASTLE: u16 = 0b0100_0000_0000_0000; +pub const MOVE_FLAG_BK_CASTLE: u16 = 0b0101_0000_0000_0000; +pub const MOVE_FLAG_BQ_CASTLE: u16 = 0b0110_0000_0000_0000; +// 0111 is free + +// Promotion flags (use the 1xxx bits) +// We combine capture flag with promotion type +pub const MOVE_FLAG_PROMO: u16 = 0b1000_0000_0000_0000; +pub const MOVE_FLAG_NO_PROMO: u16 = 0b0000_0000_0000_0000; + +pub const MOVE_FLAG_PROMO_N: u16 = 0b1000_0000_0000_0000; +pub const MOVE_FLAG_PROMO_B: u16 = 0b1001_0000_0000_0000; +pub const MOVE_FLAG_PROMO_R: u16 = 0b1010_0000_0000_0000; +pub const MOVE_FLAG_PROMO_Q: u16 = 0b1011_0000_0000_0000; +pub const MOVE_FLAG_PROMO_CAP_N: u16 = 0b1100_0000_0000_0000; +pub const MOVE_FLAG_PROMO_CAP_B: u16 = 0b1101_0000_0000_0000; +pub const MOVE_FLAG_PROMO_CAP_R: u16 = 0b1110_0000_0000_0000; +pub const MOVE_FLAG_PROMO_CAP_Q: u16 = 0b1111_0000_0000_0000; + + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Move(u16); + +impl Move { + pub fn new(from: Square, to: Square, flags: u16) -> Move { + Move(flags | + ((to as u16) << 6 ) | + from as u16) + } + + pub fn value(&self) -> u16 { + self.0 + } + + #[inline(always)] + pub fn get_flags(&self) -> u16 { + self.0 & MOVE_FLAG_MASK + } + + + /// Converts a square index (0-63) to algebraic notation (e.g., 0 -> "a1", 63 -> "h8"). + fn square_val_to_alg(val: u16) -> String { + let file = (b'a' + (val % 8) as u8) as char; + let rank = (b'1' + (val / 8) as u8) as char; + format!("{}{}", file, rank) + } + + /// Converts the move to coordinate notation (e.g., "e2e4", "e7e8q", "e1g1"). + pub fn to_algebraic(&self) -> String { + let flags = self.get_flags(); + + // Handle castling first. In this new format, the "to" square is + // the *king's* destination square (g1/c1 or g8/c8). + // Your old implementation reading the file is still fine. + if (flags == MOVE_FLAG_WK_CASTLE) || (flags == MOVE_FLAG_BK_CASTLE) { + return "O-O".to_string(); + } + if (flags == MOVE_FLAG_WQ_CASTLE) || (flags == MOVE_FLAG_BQ_CASTLE) { + return "O-O-O".to_string(); + } + + let from_val = self.0 & MOVE_FROM_MASK; + let to_val = (self.0 & MOVE_TO_MASK) >> 6; + + let from_str = Self::square_val_to_alg(from_val); + let to_str = Self::square_val_to_alg(to_val); + + // Check if it's any promotion type (1xxx) + if (flags & 0b1000_0000_0000_0000) != 0 { + let promo_char = match flags { + MOVE_FLAG_PROMO_N | MOVE_FLAG_PROMO_CAP_N => 'n', + MOVE_FLAG_PROMO_B | MOVE_FLAG_PROMO_CAP_B => 'b', + MOVE_FLAG_PROMO_R | MOVE_FLAG_PROMO_CAP_R => 'r', + MOVE_FLAG_PROMO_Q | MOVE_FLAG_PROMO_CAP_Q => 'q', + _ => '?', // Should not happen + }; + format!("{}{}{}", from_str, to_str, promo_char) + } else { + // This covers Quiet, DoublePawn, Capture, EnPassant + format!("{}{}", from_str, to_str) + } + } +} + +// ... Rest des MoveList-Codes bleibt exakt gleich ... +// (MoveList, new, push, len, is_empty, iter, impl fmt::Display) +pub struct MoveList { + moves: [Move; 256], + count: usize, +} + +impl MoveList { + pub fn new() -> Self { + MoveList { + moves: [unsafe { std::mem::zeroed() }; 256], + count: 0, + } + } + + #[inline(always)] + pub fn push(&mut self, mv: Move) { + debug_assert!(self.count < 256, "Move list overflow!"); + + self.moves[self.count] = mv; + self.count += 1; + } + + #[inline(always)] + pub fn len(&self) -> usize { + self.count + } + + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.count == 0 + } + + #[inline(always)] + pub fn iter(&self) -> slice::Iter<'_, Move> { + self.moves[..self.count].iter() + } +} + +impl fmt::Display for MoveList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.iter().map(|mv| mv.to_algebraic()).collect::>().join(" ")) + } +} + + +pub struct UndoMove { + mv: Move, + captured_piece: Option, + old_en_passant_square: Option, + old_castling_rights: u8, + old_halfmove_clock: u8, +} + +impl UndoMove { + pub fn new(mv: Move, + captured_piece: Option, + old_en_passant_square: Option, + old_castling_rights: u8, + old_halfmove_clock: u8) -> Self { + Self { + mv, + captured_piece, + old_en_passant_square, + old_castling_rights, + old_halfmove_clock + } + + } +} \ No newline at end of file diff --git a/src/movegen/mod.rs b/src/movegen/mod.rs new file mode 100644 index 0000000..f3d2061 --- /dev/null +++ b/src/movegen/mod.rs @@ -0,0 +1,19 @@ +pub mod non_sliders; +pub mod sliders; +pub mod pawns; +pub mod tables; + +use crate::board::Board; +use crate::r#move::*; +use non_sliders::*; +use sliders::*; +use pawns::*; + +pub fn generate_pseudo_legal_moves(board: &Board, list: &mut MoveList) { + generate_pawn_moves(board, list); + generate_knight_moves(board, list); + generate_bishop_moves(board, list); + generate_rook_moves(board, list); + generate_queen_moves(board, list); + generate_king_moves(board, list); +} \ No newline at end of file diff --git a/src/movegen/non_sliders.rs b/src/movegen/non_sliders.rs new file mode 100644 index 0000000..b3dc0d4 --- /dev/null +++ b/src/movegen/non_sliders.rs @@ -0,0 +1,87 @@ +use crate::board::*; +use crate::r#move::*; +use crate::square::*; +use super::tables::{KING_ATTACKS, KNIGHT_ATTACKS}; + +pub fn generate_knight_moves(board: &Board, list: &mut MoveList) { + let enemy_occupied = board.occupied[!board.side_to_move as usize]; + let mut friendly_knights = board.pieces[PieceType::Knight as usize][board.side_to_move as usize]; + + while friendly_knights != 0 { + let square = SQUARES[friendly_knights.trailing_zeros() as usize]; + let mut attacks = KNIGHT_ATTACKS[square as usize] & !board.occupied[board.side_to_move as usize]; + + while attacks != 0 { + let attack = SQUARES[attacks.trailing_zeros() as usize]; + let attack_bb = 1u64 << attack as u64; + + let flags = if (enemy_occupied & attack_bb) != 0 { + MOVE_FLAG_CAPTURE + } else { + MOVE_FLAG_QUIET + }; + + let mv = Move::new(square, attack, flags); + list.push(mv); + attacks &= attacks - 1; + } + friendly_knights &= friendly_knights - 1; + } +} + +pub fn generate_king_moves(board: &Board, list: &mut MoveList) { + let enemy_occupied = board.occupied[!board.side_to_move as usize]; + let friendly_king = board.pieces[PieceType::King as usize][board.side_to_move as usize]; + + if friendly_king == 0 { + return; + } + + let square = SQUARES[friendly_king.trailing_zeros() as usize]; + + // 1. Generate standard king moves + let mut attacks = KING_ATTACKS[square as usize] & !board.occupied[board.side_to_move as usize]; + while attacks != 0 { + let attack = SQUARES[attacks.trailing_zeros() as usize]; + let attack_bb = 1u64 << attack as u64; + + let flags = if (enemy_occupied & attack_bb) != 0 { + MOVE_FLAG_CAPTURE + } else { + MOVE_FLAG_QUIET + }; + + let mv = Move::new(square, attack, flags); + list.push(mv); + attacks &= attacks - 1; + } + + // 2. Generate castling king moves + if board.side_to_move == Color::White { + // Kingside (OO) + if (board.castling_rights & CASTLING_WK) != 0 { + if (board.all_occupied & CASTLING_WK_MASK) == 0 { + list.push(Move::new(Square::E1, Square::G1, MOVE_FLAG_WK_CASTLE)); + } + } + // Queenside (OOO) + if (board.castling_rights & CASTLING_WQ) != 0 { + if (board.all_occupied & CASTLING_WQ_MASK) == 0 { + list.push(Move::new(Square::E1, Square::C1, MOVE_FLAG_WQ_CASTLE)); + } + } + } else { // Black + // Kingside (OO) + if (board.castling_rights & CASTLING_BK) != 0 { + if (board.all_occupied & CASTLING_BK_MASK) == 0 { + list.push(Move::new(Square::E8, Square::G8, MOVE_FLAG_BK_CASTLE)); + } + } + // Queenside (OOO) + if (board.castling_rights & CASTLING_BQ) != 0 { + if (board.all_occupied & CASTLING_BQ_MASK) == 0 { + list.push(Move::new(Square::E8, Square::C8, MOVE_FLAG_BQ_CASTLE)); + } + } + } +} diff --git a/src/movegen/pawns.rs b/src/movegen/pawns.rs new file mode 100644 index 0000000..0a87965 --- /dev/null +++ b/src/movegen/pawns.rs @@ -0,0 +1,251 @@ +use crate::board::*; +use crate::r#move::*; +use crate::square::*; + +pub const RANK1_MASK: u64 = 255; // A1 - H1 +pub const RANK2_MASK: u64 = 65280; // A2 - H2 +pub const RANK3_MASK: u64 = 16711680; // A3 - H3 +pub const RANK4_MASK: u64 = 4278190080; // A4 - H4 +pub const RANK5_MASK: u64 = 1095216660480; // A5 - H5 +pub const RANK6_MASK: u64 = 280375465082880; // A6 - H6 +pub const RANK7_MASK: u64 = 71776119061217280; // A7 - H7 +pub const RANK8_MASK: u64 = 18374686479671623680; // A8 - H8 + + +pub const PAWN_A_SIDE_CAPTURE_MASK_WITHE: u64 = 280371153272574; // B1 - H6 (omitted promotions) +pub const PAWN_H_SIDE_CAPTURE_MASK_WITHE: u64 = 140185576636287; // A1- G6 (omitted promotions) + +pub const PAWN_A_SIDE_CAPTURE_MASK_BLACK: u64 = 18374403900871409664; // B3 - H8 (omitted promotions) +pub const PAWN_H_SIDE_CAPTURE_MASK_BLACK: u64 = 9187201950435704832; // A3- G8 (omitted promotions) + + +pub const PAWN_A_SIDE_CAPTURE_PROMOTION_MASK_WITHE: u64 = 71494644084506624; // B7 - H7 +pub const PAWN_H_SIDE_CAPTURE_PROMOTION_MASK_WITHE: u64 = 35747322042253312; // A7- G7 + +pub const PAWN_A_SIDE_CAPTURE_PROMOTION_MASK_BLACK: u64 = 65024; // B2 - H2 +pub const PAWN_H_SIDE_CAPTURE_PROMOTION_MASK_BLACK: u64 = 32512; // A2 - G2 + + + +pub fn generate_pawn_moves(board: &Board, list: &mut MoveList) { + + // 1. Withe + if board.side_to_move == Color::White { + let friendly_pawns = board.pieces[PieceType::Pawn as usize][0]; + let opponent_occupied = board.occupied[1]; + + // 1.1 Single Push + let mut single_push_targets = ((friendly_pawns & !(RANK8_MASK | RANK7_MASK)) << 8) & board.empty_squares; + + while single_push_targets > 0 { + let to = SQUARES[single_push_targets.trailing_zeros() as usize]; + let from = to - 8; + list.push(Move::new(from, to, MOVE_FLAG_QUIET)); + single_push_targets &= single_push_targets - 1; + } + + // 1.2 Double Push + let base_rank_pawns = friendly_pawns & RANK2_MASK; + let rank3_unblocked = (base_rank_pawns << 8) & board.empty_squares; + let mut double_push_targets = (rank3_unblocked << 8) & board.empty_squares; + + while double_push_targets > 0 { + let to = SQUARES[double_push_targets.trailing_zeros() as usize]; + let from = to - 16; + list.push(Move::new(from, to, MOVE_FLAG_QUIET)); + double_push_targets &= double_push_targets - 1; + } + + // 1.3 Captures + // 1.3.1 A-Side Capture (omitted promotion captures) + let mut a_side_capture_targets = (friendly_pawns & PAWN_H_SIDE_CAPTURE_MASK_WITHE) << 7 & opponent_occupied; + + while a_side_capture_targets > 0 { + let to = SQUARES[a_side_capture_targets.trailing_zeros() as usize]; + let from = to - 7; + list.push(Move::new(from, to, MOVE_FLAG_CAPTURE)); + a_side_capture_targets &= a_side_capture_targets - 1; + } + + // 1.3.2 H-Side Capture (omitted promotion captures) + let mut h_side_capture_targets = (friendly_pawns & PAWN_A_SIDE_CAPTURE_MASK_WITHE) << 9 & opponent_occupied; + + while h_side_capture_targets > 0 { + let to = SQUARES[h_side_capture_targets.trailing_zeros() as usize]; + let from = to - 9; + list.push(Move::new(from, to, MOVE_FLAG_CAPTURE)); + h_side_capture_targets &= h_side_capture_targets - 1; + } + + // 1.4 Promotion + // 1.4.1 Pushing promotion + let mut promotion_targets = ((friendly_pawns & RANK7_MASK) << 8) & board.empty_squares; + + while promotion_targets > 0 { + let to = SQUARES[promotion_targets.trailing_zeros() as usize]; + let from = to - 8; + list.push(Move::new(from, to, MOVE_FLAG_PROMO_Q)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_R)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_B)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_N)); + promotion_targets &= promotion_targets - 1; + } + + // 1.4.2 Capturing Promotion + // 1.4.2.1 A-side capturing promotion + // CORRECTED: Use PAWN_A_SIDE_CAPTURE_PROMOTION_MASK_WITHE (excludes A-file) + let mut promotion_targets_a_side_capture = ((friendly_pawns & PAWN_A_SIDE_CAPTURE_PROMOTION_MASK_WITHE) << 7) & board.occupied[1]; + while promotion_targets_a_side_capture > 0 { + let to = SQUARES[promotion_targets_a_side_capture.trailing_zeros() as usize]; + let from = to - 7; + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_Q)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_R)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_B)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_N)); + promotion_targets_a_side_capture &= promotion_targets_a_side_capture - 1; + } + + // 1.4.2.2 H-side capturing promotion + // CORRECTED: Use PAWN_H_SIDE_CAPTURE_PROMOTION_MASK_WITHE (excludes H-file) + let mut promotion_targets_h_side_capture = ((friendly_pawns & PAWN_H_SIDE_CAPTURE_PROMOTION_MASK_WITHE) << 9) & board.occupied[1]; + while promotion_targets_h_side_capture > 0 { + let to = SQUARES[promotion_targets_h_side_capture.trailing_zeros() as usize]; + let from = to - 9; + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_Q)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_R)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_B)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_N)); + promotion_targets_h_side_capture &= promotion_targets_h_side_capture - 1; + } + + // 1.5 En Passant + if let Some(en_passant_target_square) = board.en_passant_target { + // Check if the target square is on the 6th rank (A6=40 to H6=47) + if (en_passant_target_square >= Square::A6) && (en_passant_target_square <= Square::H6) { + let en_passant_target_bb: u64 = 1_u64 << (en_passant_target_square as u64); + + // 1. Check A-Side capture (<< 7, e.g., D5 -> C6) + let attacker_mask_a_side = (en_passant_target_bb >> 7) & PAWN_H_SIDE_CAPTURE_MASK_WITHE; + if (attacker_mask_a_side & friendly_pawns) > 0 { + let from = en_passant_target_square - 7; + list.push(Move::new(from, en_passant_target_square, MOVE_FLAG_EN_PASSANT)); + } + + // 2. Check H-Side capture (<< 9, e.g., B5 -> C6) + let attacker_mask_h_side = (en_passant_target_bb >> 9) & PAWN_A_SIDE_CAPTURE_MASK_WITHE; + if (attacker_mask_h_side & friendly_pawns) > 0 { + let from = en_passant_target_square - 9; + list.push(Move::new(from, en_passant_target_square, MOVE_FLAG_EN_PASSANT)); + } + } + } + // 2. Black + } else { + let friendly_pawns = board.pieces[PieceType::Pawn as usize][1]; + let opponent_occupied = board.occupied[0]; + + // 2.1 Single Push + let mut single_push_targets = ((friendly_pawns & !(RANK1_MASK | RANK2_MASK)) >> 8) & board.empty_squares; + + while single_push_targets > 0 { + let to = SQUARES[single_push_targets.trailing_zeros() as usize]; + let from = to + 8; + list.push(Move::new(from, to, MOVE_FLAG_QUIET)); + single_push_targets &= single_push_targets - 1; + } + + // 2.2 Double Push + let base_rank_pawns = friendly_pawns & RANK7_MASK; + let rank6_unblocked = (base_rank_pawns >> 8) & board.empty_squares; + let mut double_push_targets = (rank6_unblocked >> 8) & board.empty_squares; + + while double_push_targets > 0 { + let to = SQUARES[double_push_targets.trailing_zeros() as usize]; + let from = to + 16; + list.push(Move::new(from, to, MOVE_FLAG_QUIET)); + double_push_targets &= double_push_targets - 1; + } + + // 2.3 Captures + // 2.3.1 A-Side Capture (>> 9) + let mut a_side_capture_targets = (friendly_pawns & PAWN_A_SIDE_CAPTURE_MASK_BLACK) >> 9 & opponent_occupied; + + while a_side_capture_targets > 0 { + let to = SQUARES[a_side_capture_targets.trailing_zeros() as usize]; + let from = to + 9; + list.push(Move::new(from, to, MOVE_FLAG_CAPTURE)); + a_side_capture_targets &= a_side_capture_targets - 1; + } + + // 2.3.2 H-Side Capture (>> 7) + let mut h_side_capture_targets = (friendly_pawns & PAWN_H_SIDE_CAPTURE_MASK_BLACK) >> 7 & opponent_occupied; + + while h_side_capture_targets > 0 { + let to = SQUARES[h_side_capture_targets.trailing_zeros() as usize]; + let from = to + 7; + list.push(Move::new(from, to, MOVE_FLAG_CAPTURE)); + h_side_capture_targets &= h_side_capture_targets - 1; + } + + // 2.4 Promotion + // 2.4.1 Pushing promotion + let mut promotion_targets = ((friendly_pawns & RANK2_MASK) >> 8) & board.empty_squares; + + while promotion_targets > 0 { + let to = SQUARES[promotion_targets.trailing_zeros() as usize]; + let from = to + 8; + list.push(Move::new(from, to, MOVE_FLAG_PROMO_Q)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_R)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_B)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_N)); + promotion_targets &= promotion_targets - 1; + } + + // 2.4.2 Capturing Promotion + // 2.4.2.1 A-side capturing promotion (>> 9) + let mut promotion_targets_a_side_capture = ((friendly_pawns & PAWN_A_SIDE_CAPTURE_PROMOTION_MASK_BLACK) >> 9) & opponent_occupied; + while promotion_targets_a_side_capture > 0 { + let to = SQUARES[promotion_targets_a_side_capture.trailing_zeros() as usize]; + let from = to + 9; + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_Q)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_R)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_B)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_N)); + promotion_targets_a_side_capture &= promotion_targets_a_side_capture - 1; + } + + // 2.4.2.2 H-side capturing promotion (>> 7) + let mut promotion_targets_h_side_capture = ((friendly_pawns & PAWN_H_SIDE_CAPTURE_PROMOTION_MASK_BLACK) >> 7) & opponent_occupied; + while promotion_targets_h_side_capture > 0 { + let to = SQUARES[promotion_targets_h_side_capture.trailing_zeros() as usize]; + let from = to + 7; + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_Q)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_R)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_B)); + list.push(Move::new(from, to, MOVE_FLAG_PROMO_CAP_N)); + promotion_targets_h_side_capture &= promotion_targets_h_side_capture - 1; + } + + // 2.5 En Passant + if let Some(en_passant_target_square) = board.en_passant_target { + // Check if the target square is on the 3rd rank (A3=16 to H3=23) + if (en_passant_target_square >= Square::A3) && (en_passant_target_square <= Square::H3) { + let en_passant_target_bb: u64 = 1_u64 << (en_passant_target_square as u64); + + // 1. Check A-Side capture (>> 9, e.g., B4 -> A3) + let attacker_mask_a_side = (en_passant_target_bb << 9) & PAWN_A_SIDE_CAPTURE_MASK_BLACK; + if (attacker_mask_a_side & friendly_pawns) > 0 { + let from = en_passant_target_square + 9; + list.push(Move::new(from, en_passant_target_square, MOVE_FLAG_EN_PASSANT)); + } + + // 2. Check H-Side capture (>> 7, e.g., G4 -> H3) + let attacker_mask_h_side = (en_passant_target_bb << 7) & PAWN_H_SIDE_CAPTURE_MASK_BLACK; + if (attacker_mask_h_side & friendly_pawns) > 0 { + let from = en_passant_target_square + 7; + list.push(Move::new(from, en_passant_target_square, MOVE_FLAG_EN_PASSANT)); + } + } + } + } +} diff --git a/src/movegen/sliders.rs b/src/movegen/sliders.rs new file mode 100644 index 0000000..24a2dbd --- /dev/null +++ b/src/movegen/sliders.rs @@ -0,0 +1,132 @@ +use crate::board::*; +use crate::r#move::{Move, MoveList, MOVE_FLAG_CAPTURE, MOVE_FLAG_QUIET}; +use crate::square::SQUARES; +use super::tables::*; + +pub fn generate_rook_moves(board: &Board, list: &mut MoveList) { + let mut friendly_rooks = board.pieces[PieceType::Rook as usize][board.side_to_move as usize]; + while friendly_rooks > 0 { + let square_index = friendly_rooks.trailing_zeros() as usize; + + let premask = PREMASKS_ROOK[square_index]; + let magic = MAGICS_ROOK[square_index]; + let relevant_bits = RELEVANT_BITS_ROOK[square_index]; + let shift = 64 - relevant_bits; + let blockers = board.all_occupied & premask; + let attack_table = get_rook_attacks(); + + let magic_index = (blockers.wrapping_mul(magic)) >> shift; + + let movable_squares = attack_table[square_index][magic_index as usize]; + + // 1. Normal moves + let mut quiet_moves = movable_squares & !board.all_occupied; + while quiet_moves > 0 { + let from = SQUARES[square_index]; + let to = SQUARES[quiet_moves.trailing_zeros() as usize]; + list.push(Move::new(from, to, MOVE_FLAG_QUIET)); + quiet_moves &= quiet_moves - 1; + } + + // 2. Captures + let mut capture_moves = movable_squares & board.occupied[!board.side_to_move as usize]; + while capture_moves > 0 { + let from = SQUARES[square_index]; + let to = SQUARES[capture_moves.trailing_zeros() as usize]; + list.push(Move::new(from, to, MOVE_FLAG_CAPTURE)); + capture_moves &= capture_moves - 1; + } + + friendly_rooks &= friendly_rooks - 1; + } +} + +pub fn generate_bishop_moves(board: &Board, list: &mut MoveList) { + let mut friendly_bishops = board.pieces[PieceType::Bishop as usize][board.side_to_move as usize]; + while friendly_bishops > 0 { + let square_index = friendly_bishops.trailing_zeros() as usize; + + let premask = PREMASKS_BISHOP[square_index]; + let magic = MAGICS_BISHOP[square_index]; + let relevant_bits = RELEVANT_BITS_BISHOP[square_index]; + let shift = 64 - relevant_bits; + let blockers = board.all_occupied & premask; + let attack_table = get_bishop_attacks(); + + let magic_index = (blockers.wrapping_mul(magic)) >> shift; + + let movable_squares = attack_table[square_index][magic_index as usize]; + + // 1. Normal moves + let mut quiet_moves = movable_squares & !board.all_occupied; + while quiet_moves > 0 { + let from = SQUARES[square_index]; + let to = SQUARES[quiet_moves.trailing_zeros() as usize]; + list.push(Move::new(from, to, MOVE_FLAG_QUIET)); + quiet_moves &= quiet_moves - 1; + } + + // 2. Captures + let mut capture_moves = movable_squares & board.occupied[!board.side_to_move as usize]; + while capture_moves > 0 { + let from = SQUARES[square_index]; + let to = SQUARES[capture_moves.trailing_zeros() as usize]; + list.push(Move::new(from, to, MOVE_FLAG_CAPTURE)); + capture_moves &= capture_moves - 1; + } + + friendly_bishops &= friendly_bishops - 1; + } +} + +pub fn generate_queen_moves(board: &Board, list: &mut MoveList) { + let mut friendly_queens = board.pieces[PieceType::Queen as usize][board.side_to_move as usize]; + while friendly_queens > 0 { + let square_index = friendly_queens.trailing_zeros() as usize; + + // --- 1. Get Rook Attacks --- + let rook_premask = PREMASKS_ROOK[square_index]; + let rook_magic = MAGICS_ROOK[square_index]; + let rook_relevant_bits = RELEVANT_BITS_ROOK[square_index]; + let rook_shift = 64 - rook_relevant_bits; + let rook_blockers = board.all_occupied & rook_premask; + let rook_attack_table = get_rook_attacks(); + let rook_magic_index = (rook_blockers.wrapping_mul(rook_magic)) >> rook_shift; + let rook_moves = rook_attack_table[square_index][rook_magic_index as usize]; + + // --- 2. Get Bishop Attacks --- + let bishop_premask = PREMASKS_BISHOP[square_index]; + let bishop_magic = MAGICS_BISHOP[square_index]; + let bishop_relevant_bits = RELEVANT_BITS_BISHOP[square_index]; + let bishop_shift = 64 - bishop_relevant_bits; + let bishop_blockers = board.all_occupied & bishop_premask; + let bishop_attack_table = get_bishop_attacks(); + let bishop_magic_index = (bishop_blockers.wrapping_mul(bishop_magic)) >> bishop_shift; + let bishop_moves = bishop_attack_table[square_index][bishop_magic_index as usize]; + + // --- 3. Combine Attacks --- + let movable_squares = rook_moves | bishop_moves; + + // --- 4. Generate Moves (Identical to Rook/Bishop) --- + + // 4a. Normal moves + let mut quiet_moves = movable_squares & !board.all_occupied; + while quiet_moves > 0 { + let from = SQUARES[square_index]; + let to = SQUARES[quiet_moves.trailing_zeros() as usize]; + list.push(Move::new(from, to, MOVE_FLAG_QUIET)); + quiet_moves &= quiet_moves - 1; + } + + // 4b. Captures + let mut capture_moves = movable_squares & board.occupied[!board.side_to_move as usize]; + while capture_moves > 0 { + let from = SQUARES[square_index]; + let to = SQUARES[capture_moves.trailing_zeros() as usize]; + list.push(Move::new(from, to, MOVE_FLAG_CAPTURE)); + capture_moves &= capture_moves - 1; + } + + friendly_queens &= friendly_queens - 1; + } +} diff --git a/src/movegen/tables.rs b/src/movegen/tables.rs new file mode 100644 index 0000000..6c8ee31 --- /dev/null +++ b/src/movegen/tables.rs @@ -0,0 +1,647 @@ +use std::sync::OnceLock; + + +pub const KNIGHT_ATTACKS: [u64; 64] = [ + 0b00000000_00000000_00000000_00000000_00000000_00000010_00000100_00000000, // Square 0 (A1) + 0b00000000_00000000_00000000_00000000_00000000_00000101_00001000_00000000, // Square 1 (B1) + 0b00000000_00000000_00000000_00000000_00000000_00001010_00010001_00000000, // Square 2 (C1) + 0b00000000_00000000_00000000_00000000_00000000_00010100_00100010_00000000, // Square 3 (D1) + 0b00000000_00000000_00000000_00000000_00000000_00101000_01000100_00000000, // Square 4 (E1) + 0b00000000_00000000_00000000_00000000_00000000_01010000_10001000_00000000, // Square 5 (F1) + 0b00000000_00000000_00000000_00000000_00000000_10100000_00010000_00000000, // Square 6 (G1) + 0b00000000_00000000_00000000_00000000_00000000_01000000_00100000_00000000, // Square 7 (H1) + 0b00000000_00000000_00000000_00000000_00000010_00000100_00000000_00000100, // Square 8 (A2) + 0b00000000_00000000_00000000_00000000_00000101_00001000_00000000_00001000, // Square 9 (B2) + 0b00000000_00000000_00000000_00000000_00001010_00010001_00000000_00010001, // Square 10 (C2) + 0b00000000_00000000_00000000_00000000_00010100_00100010_00000000_00100010, // Square 11 (D2) + 0b00000000_00000000_00000000_00000000_00101000_01000100_00000000_01000100, // Square 12 (E2) + 0b00000000_00000000_00000000_00000000_01010000_10001000_00000000_10001000, // Square 13 (F2) + 0b00000000_00000000_00000000_00000000_10100000_00010000_00000000_00010000, // Square 14 (G2) + 0b00000000_00000000_00000000_00000000_01000000_00100000_00000000_00100000, // Square 15 (H2) + 0b00000000_00000000_00000000_00000010_00000100_00000000_00000100_00000010, // Square 16 (A3) + 0b00000000_00000000_00000000_00000101_00001000_00000000_00001000_00000101, // Square 17 (B3) + 0b00000000_00000000_00000000_00001010_00010001_00000000_00010001_00001010, // Square 18 (C3) + 0b00000000_00000000_00000000_00010100_00100010_00000000_00100010_00010100, // Square 19 (D3) + 0b00000000_00000000_00000000_00101000_01000100_00000000_01000100_00101000, // Square 20 (E3) + 0b00000000_00000000_00000000_01010000_10001000_00000000_10001000_01010000, // Square 21 (F3) + 0b00000000_00000000_00000000_10100000_00010000_00000000_00010000_10100000, // Square 22 (G3) + 0b00000000_00000000_00000000_01000000_00100000_00000000_00100000_01000000, // Square 23 (H3) + 0b00000000_00000000_00000010_00000100_00000000_00000100_00000010_00000000, // Square 24 (A4) + 0b00000000_00000000_00000101_00001000_00000000_00001000_00000101_00000000, // Square 25 (B4) + 0b00000000_00000000_00001010_00010001_00000000_00010001_00001010_00000000, // Square 26 (C4) + 0b00000000_00000000_00010100_00100010_00000000_00100010_00010100_00000000, // Square 27 (D4) + 0b00000000_00000000_00101000_01000100_00000000_01000100_00101000_00000000, // Square 28 (E4) + 0b00000000_00000000_01010000_10001000_00000000_10001000_01010000_00000000, // Square 29 (F4) + 0b00000000_00000000_10100000_00010000_00000000_00010000_10100000_00000000, // Square 30 (G4) + 0b00000000_00000000_01000000_00100000_00000000_00100000_01000000_00000000, // Square 31 (H4) + 0b00000000_00000010_00000100_00000000_00000100_00000010_00000000_00000000, // Square 32 (A5) + 0b00000000_00000101_00001000_00000000_00001000_00000101_00000000_00000000, // Square 33 (B5) + 0b00000000_00001010_00010001_00000000_00010001_00001010_00000000_00000000, // Square 34 (C5) + 0b00000000_00010100_00100010_00000000_00100010_00010100_00000000_00000000, // Square 35 (D5) + 0b00000000_00101000_01000100_00000000_01000100_00101000_00000000_00000000, // Square 36 (E5) + 0b00000000_01010000_10001000_00000000_10001000_01010000_00000000_00000000, // Square 37 (F5) + 0b00000000_10100000_00010000_00000000_00010000_10100000_00000000_00000000, // Square 38 (G5) + 0b00000000_01000000_00100000_00000000_00100000_01000000_00000000_00000000, // Square 39 (H5) + 0b00000010_00000100_00000000_00000100_00000010_00000000_00000000_00000000, // Square 40 (A6) + 0b00000101_00001000_00000000_00001000_00000101_00000000_00000000_00000000, // Square 41 (B6) + 0b00001010_00010001_00000000_00010001_00001010_00000000_00000000_00000000, // Square 42 (C6) + 0b00010100_00100010_00000000_00100010_00010100_00000000_00000000_00000000, // Square 43 (D6) + 0b00101000_01000100_00000000_01000100_00101000_00000000_00000000_00000000, // Square 44 (E6) + 0b01010000_10001000_00000000_10001000_01010000_00000000_00000000_00000000, // Square 45 (F6) + 0b10100000_00010000_00000000_00010000_10100000_00000000_00000000_00000000, // Square 46 (G6) + 0b01000000_00100000_00000000_00100000_01000000_00000000_00000000_00000000, // Square 47 (H6) + 0b00000100_00000000_00000100_00000010_00000000_00000000_00000000_00000000, // Square 48 (A7) + 0b00001000_00000000_00001000_00000101_00000000_00000000_00000000_00000000, // Square 49 (B7) + 0b00010001_00000000_00010001_00001010_00000000_00000000_00000000_00000000, // Square 50 (C7) + 0b00100010_00000000_00100010_00010100_00000000_00000000_00000000_00000000, // Square 51 (D7) + 0b01000100_00000000_01000100_00101000_00000000_00000000_00000000_00000000, // Square 52 (E7) + 0b10001000_00000000_10001000_01010000_00000000_00000000_00000000_00000000, // Square 53 (F7) + 0b00010000_00000000_00010000_10100000_00000000_00000000_00000000_00000000, // Square 54 (G7) + 0b00100000_00000000_00100000_01000000_00000000_00000000_00000000_00000000, // Square 55 (H7) + 0b00000000_00000100_00000010_00000000_00000000_00000000_00000000_00000000, // Square 56 (A8) + 0b00000000_00001000_00000101_00000000_00000000_00000000_00000000_00000000, // Square 57 (B8) + 0b00000000_00010001_00001010_00000000_00000000_00000000_00000000_00000000, // Square 58 (C8) + 0b00000000_00100010_00010100_00000000_00000000_00000000_00000000_00000000, // Square 59 (D8) + 0b00000000_01000100_00101000_00000000_00000000_00000000_00000000_00000000, // Square 60 (E8) + 0b00000000_10001000_01010000_00000000_00000000_00000000_00000000_00000000, // Square 61 (F8) + 0b00000000_00010000_10100000_00000000_00000000_00000000_00000000_00000000, // Square 62 (G8) + 0b00000000_00100000_01000000_00000000_00000000_00000000_00000000_00000000, // Square 63 (H8) +]; + +pub const PREMASKS_ROOK: [u64; 64] = [ + 0b00000000_00000001_00000001_00000001_00000001_00000001_00000001_01111110, // Square 0 (A1) + 0b00000000_00000010_00000010_00000010_00000010_00000010_00000010_01111100, // Square 1 (B1) + 0b00000000_00000100_00000100_00000100_00000100_00000100_00000100_01111010, // Square 2 (C1) + 0b00000000_00001000_00001000_00001000_00001000_00001000_00001000_01110110, // Square 3 (D1) + 0b00000000_00010000_00010000_00010000_00010000_00010000_00010000_01101110, // Square 4 (E1) + 0b00000000_00100000_00100000_00100000_00100000_00100000_00100000_01011110, // Square 5 (F1) + 0b00000000_01000000_01000000_01000000_01000000_01000000_01000000_00111110, // Square 6 (G1) + 0b00000000_10000000_10000000_10000000_10000000_10000000_10000000_01111110, // Square 7 (H1) + 0b00000000_00000001_00000001_00000001_00000001_00000001_01111110_00000000, // Square 8 (A2) + 0b00000000_00000010_00000010_00000010_00000010_00000010_01111100_00000000, // Square 9 (B2) + 0b00000000_00000100_00000100_00000100_00000100_00000100_01111010_00000000, // Square 10 (C2) + 0b00000000_00001000_00001000_00001000_00001000_00001000_01110110_00000000, // Square 11 (D2) + 0b00000000_00010000_00010000_00010000_00010000_00010000_01101110_00000000, // Square 12 (E2) + 0b00000000_00100000_00100000_00100000_00100000_00100000_01011110_00000000, // Square 13 (F2) + 0b00000000_01000000_01000000_01000000_01000000_01000000_00111110_00000000, // Square 14 (G2) + 0b00000000_10000000_10000000_10000000_10000000_10000000_01111110_00000000, // Square 15 (H2) + 0b00000000_00000001_00000001_00000001_00000001_01111110_00000001_00000000, // Square 16 (A3) + 0b00000000_00000010_00000010_00000010_00000010_01111100_00000010_00000000, // Square 17 (B3) + 0b00000000_00000100_00000100_00000100_00000100_01111010_00000100_00000000, // Square 18 (C3) + 0b00000000_00001000_00001000_00001000_00001000_01110110_00001000_00000000, // Square 19 (D3) + 0b00000000_00010000_00010000_00010000_00010000_01101110_00010000_00000000, // Square 20 (E3) + 0b00000000_00100000_00100000_00100000_00100000_01011110_00100000_00000000, // Square 21 (F3) + 0b00000000_01000000_01000000_01000000_01000000_00111110_01000000_00000000, // Square 22 (G3) + 0b00000000_10000000_10000000_10000000_10000000_01111110_10000000_00000000, // Square 23 (H3) + 0b00000000_00000001_00000001_00000001_01111110_00000001_00000001_00000000, // Square 24 (A4) + 0b00000000_00000010_00000010_00000010_01111100_00000010_00000010_00000000, // Square 25 (B4) + 0b00000000_00000100_00000100_00000100_01111010_00000100_00000100_00000000, // Square 26 (C4) + 0b00000000_00001000_00001000_00001000_01110110_00001000_00001000_00000000, // Square 27 (D4) + 0b00000000_00010000_00010000_00010000_01101110_00010000_00010000_00000000, // Square 28 (E4) + 0b00000000_00100000_00100000_00100000_01011110_00100000_00100000_00000000, // Square 29 (F4) + 0b00000000_01000000_01000000_01000000_00111110_01000000_01000000_00000000, // Square 30 (G4) + 0b00000000_10000000_10000000_10000000_01111110_10000000_10000000_00000000, // Square 31 (H4) + 0b00000000_00000001_00000001_01111110_00000001_00000001_00000001_00000000, // Square 32 (A5) + 0b00000000_00000010_00000010_01111100_00000010_00000010_00000010_00000000, // Square 33 (B5) + 0b00000000_00000100_00000100_01111010_00000100_00000100_00000100_00000000, // Square 34 (C5) + 0b00000000_00001000_00001000_01110110_00001000_00001000_00001000_00000000, // Square 35 (D5) + 0b00000000_00010000_00010000_01101110_00010000_00010000_00010000_00000000, // Square 36 (E5) + 0b00000000_00100000_00100000_01011110_00100000_00100000_00100000_00000000, // Square 37 (F5) + 0b00000000_01000000_01000000_00111110_01000000_01000000_01000000_00000000, // Square 38 (G5) + 0b00000000_10000000_10000000_01111110_10000000_10000000_10000000_00000000, // Square 39 (H5) + 0b00000000_00000001_01111110_00000001_00000001_00000001_00000001_00000000, // Square 40 (A6) + 0b00000000_00000010_01111100_00000010_00000010_00000010_00000010_00000000, // Square 41 (B6) + 0b00000000_00000100_01111010_00000100_00000100_00000100_00000100_00000000, // Square 42 (C6) + 0b00000000_00001000_01110110_00001000_00001000_00001000_00001000_00000000, // Square 43 (D6) + 0b00000000_00010000_01101110_00010000_00010000_00010000_00010000_00000000, // Square 44 (E6) + 0b00000000_00100000_01011110_00100000_00100000_00100000_00100000_00000000, // Square 45 (F6) + 0b00000000_01000000_00111110_01000000_01000000_01000000_01000000_00000000, // Square 46 (G6) + 0b00000000_10000000_01111110_10000000_10000000_10000000_10000000_00000000, // Square 47 (H6) + 0b00000000_01111110_00000001_00000001_00000001_00000001_00000001_00000000, // Square 48 (A7) + 0b00000000_01111100_00000010_00000010_00000010_00000010_00000010_00000000, // Square 49 (B7) + 0b00000000_01111010_00000100_00000100_00000100_00000100_00000100_00000000, // Square 50 (C7) + 0b00000000_01110110_00001000_00001000_00001000_00001000_00001000_00000000, // Square 51 (D7) + 0b00000000_01101110_00010000_00010000_00010000_00010000_00010000_00000000, // Square 52 (E7) + 0b00000000_01011110_00100000_00100000_00100000_00100000_00100000_00000000, // Square 53 (F7) + 0b00000000_00111110_01000000_01000000_01000000_01000000_01000000_00000000, // Square 54 (G7) + 0b00000000_01111110_10000000_10000000_10000000_10000000_10000000_00000000, // Square 55 (H7) + 0b01111110_00000001_00000001_00000001_00000001_00000001_00000001_00000000, // Square 56 (A8) + 0b01111100_00000010_00000010_00000010_00000010_00000010_00000010_00000000, // Square 57 (B8) + 0b01111010_00000100_00000100_00000100_00000100_00000100_00000100_00000000, // Square 58 (C8) + 0b01110110_00001000_00001000_00001000_00001000_00001000_00001000_00000000, // Square 59 (D8) + 0b01101110_00010000_00010000_00010000_00010000_00010000_00010000_00000000, // Square 60 (E8) + 0b01011110_00100000_00100000_00100000_00100000_00100000_00100000_00000000, // Square 61 (F8) + 0b00111110_01000000_01000000_01000000_01000000_01000000_01000000_00000000, // Square 62 (G8) + 0b01111110_10000000_10000000_10000000_10000000_10000000_10000000_00000000, // Square 63 (H8) +]; + +pub const PREMASKS_BISHOP: [u64; 64] = [ + 0b00000000_01000000_00100000_00010000_00001000_00000100_00000010_00000000, // Square 0 (A1) + 0b00000000_00000000_01000000_00100000_00010000_00001000_00000100_00000000, // Square 1 (B1) + 0b00000000_00000000_00000000_01000000_00100000_00010000_00001010_00000000, // Square 2 (C1) + 0b00000000_00000000_00000000_00000000_01000000_00100010_00010100_00000000, // Square 3 (D1) + 0b00000000_00000000_00000000_00000000_00000010_01000100_00101000_00000000, // Square 4 (E1) + 0b00000000_00000000_00000000_00000010_00000100_00001000_01010000_00000000, // Square 5 (F1) + 0b00000000_00000000_00000010_00000100_00001000_00010000_00100000_00000000, // Square 6 (G1) + 0b00000000_00000010_00000100_00001000_00010000_00100000_01000000_00000000, // Square 7 (H1) + 0b00000000_00100000_00010000_00001000_00000100_00000010_00000000_00000000, // Square 8 (A2) + 0b00000000_01000000_00100000_00010000_00001000_00000100_00000000_00000000, // Square 9 (B2) + 0b00000000_00000000_01000000_00100000_00010000_00001010_00000000_00000000, // Square 10 (C2) + 0b00000000_00000000_00000000_01000000_00100010_00010100_00000000_00000000, // Square 11 (D2) + 0b00000000_00000000_00000000_00000010_01000100_00101000_00000000_00000000, // Square 12 (E2) + 0b00000000_00000000_00000010_00000100_00001000_01010000_00000000_00000000, // Square 13 (F2) + 0b00000000_00000010_00000100_00001000_00010000_00100000_00000000_00000000, // Square 14 (G2) + 0b00000000_00000100_00001000_00010000_00100000_01000000_00000000_00000000, // Square 15 (H2) + 0b00000000_00010000_00001000_00000100_00000010_00000000_00000010_00000000, // Square 16 (A3) + 0b00000000_00100000_00010000_00001000_00000100_00000000_00000100_00000000, // Square 17 (B3) + 0b00000000_01000000_00100000_00010000_00001010_00000000_00001010_00000000, // Square 18 (C3) + 0b00000000_00000000_01000000_00100010_00010100_00000000_00010100_00000000, // Square 19 (D3) + 0b00000000_00000000_00000010_01000100_00101000_00000000_00101000_00000000, // Square 20 (E3) + 0b00000000_00000010_00000100_00001000_01010000_00000000_01010000_00000000, // Square 21 (F3) + 0b00000000_00000100_00001000_00010000_00100000_00000000_00100000_00000000, // Square 22 (G3) + 0b00000000_00001000_00010000_00100000_01000000_00000000_01000000_00000000, // Square 23 (H3) + 0b00000000_00001000_00000100_00000010_00000000_00000010_00000100_00000000, // Square 24 (A4) + 0b00000000_00010000_00001000_00000100_00000000_00000100_00001000_00000000, // Square 25 (B4) + 0b00000000_00100000_00010000_00001010_00000000_00001010_00010000_00000000, // Square 26 (C4) + 0b00000000_01000000_00100010_00010100_00000000_00010100_00100010_00000000, // Square 27 (D4) + 0b00000000_00000010_01000100_00101000_00000000_00101000_01000100_00000000, // Square 28 (E4) + 0b00000000_00000100_00001000_01010000_00000000_01010000_00001000_00000000, // Square 29 (F4) + 0b00000000_00001000_00010000_00100000_00000000_00100000_00010000_00000000, // Square 30 (G4) + 0b00000000_00010000_00100000_01000000_00000000_01000000_00100000_00000000, // Square 31 (H4) + 0b00000000_00000100_00000010_00000000_00000010_00000100_00001000_00000000, // Square 32 (A5) + 0b00000000_00001000_00000100_00000000_00000100_00001000_00010000_00000000, // Square 33 (B5) + 0b00000000_00010000_00001010_00000000_00001010_00010000_00100000_00000000, // Square 34 (C5) + 0b00000000_00100010_00010100_00000000_00010100_00100010_01000000_00000000, // Square 35 (D5) + 0b00000000_01000100_00101000_00000000_00101000_01000100_00000010_00000000, // Square 36 (E5) + 0b00000000_00001000_01010000_00000000_01010000_00001000_00000100_00000000, // Square 37 (F5) + 0b00000000_00010000_00100000_00000000_00100000_00010000_00001000_00000000, // Square 38 (G5) + 0b00000000_00100000_01000000_00000000_01000000_00100000_00010000_00000000, // Square 39 (H5) + 0b00000000_00000010_00000000_00000010_00000100_00001000_00010000_00000000, // Square 40 (A6) + 0b00000000_00000100_00000000_00000100_00001000_00010000_00100000_00000000, // Square 41 (B6) + 0b00000000_00001010_00000000_00001010_00010000_00100000_01000000_00000000, // Square 42 (C6) + 0b00000000_00010100_00000000_00010100_00100010_01000000_00000000_00000000, // Square 43 (D6) + 0b00000000_00101000_00000000_00101000_01000100_00000010_00000000_00000000, // Square 44 (E6) + 0b00000000_01010000_00000000_01010000_00001000_00000100_00000010_00000000, // Square 45 (F6) + 0b00000000_00100000_00000000_00100000_00010000_00001000_00000100_00000000, // Square 46 (G6) + 0b00000000_01000000_00000000_01000000_00100000_00010000_00001000_00000000, // Square 47 (H6) + 0b00000000_00000000_00000010_00000100_00001000_00010000_00100000_00000000, // Square 48 (A7) + 0b00000000_00000000_00000100_00001000_00010000_00100000_01000000_00000000, // Square 49 (B7) + 0b00000000_00000000_00001010_00010000_00100000_01000000_00000000_00000000, // Square 50 (C7) + 0b00000000_00000000_00010100_00100010_01000000_00000000_00000000_00000000, // Square 51 (D7) + 0b00000000_00000000_00101000_01000100_00000010_00000000_00000000_00000000, // Square 52 (E7) + 0b00000000_00000000_01010000_00001000_00000100_00000010_00000000_00000000, // Square 53 (F7) + 0b00000000_00000000_00100000_00010000_00001000_00000100_00000010_00000000, // Square 54 (G7) + 0b00000000_00000000_01000000_00100000_00010000_00001000_00000100_00000000, // Square 55 (H7) + 0b00000000_00000010_00000100_00001000_00010000_00100000_01000000_00000000, // Square 56 (A8) + 0b00000000_00000100_00001000_00010000_00100000_01000000_00000000_00000000, // Square 57 (B8) + 0b00000000_00001010_00010000_00100000_01000000_00000000_00000000_00000000, // Square 58 (C8) + 0b00000000_00010100_00100010_01000000_00000000_00000000_00000000_00000000, // Square 59 (D8) + 0b00000000_00101000_01000100_00000010_00000000_00000000_00000000_00000000, // Square 60 (E8) + 0b00000000_01010000_00001000_00000100_00000010_00000000_00000000_00000000, // Square 61 (F8) + 0b00000000_00100000_00010000_00001000_00000100_00000010_00000000_00000000, // Square 62 (G8) + 0b00000000_01000000_00100000_00010000_00001000_00000100_00000010_00000000, // Square 63 (H8) +]; + +pub const KING_ATTACKS: [u64; 64] = [ + 0b00000000_00000000_00000000_00000000_00000000_00000000_00000011_00000010, // Square 0 (A1) + 0b00000000_00000000_00000000_00000000_00000000_00000000_00000111_00000101, // Square 1 (B1) + 0b00000000_00000000_00000000_00000000_00000000_00000000_00001110_00001010, // Square 2 (C1) + 0b00000000_00000000_00000000_00000000_00000000_00000000_00011100_00010100, // Square 3 (D1) + 0b00000000_00000000_00000000_00000000_00000000_00000000_00111000_00101000, // Square 4 (E1) + 0b00000000_00000000_00000000_00000000_00000000_00000000_01110000_01010000, // Square 5 (F1) + 0b00000000_00000000_00000000_00000000_00000000_00000000_11100000_10100000, // Square 6 (G1) + 0b00000000_00000000_00000000_00000000_00000000_00000000_11000000_01000000, // Square 7 (H1) + 0b00000000_00000000_00000000_00000000_00000000_00000011_00000010_00000011, // Square 8 (A2) + 0b00000000_00000000_00000000_00000000_00000000_00000111_00000101_00000111, // Square 9 (B2) + 0b00000000_00000000_00000000_00000000_00000000_00001110_00001010_00001110, // Square 10 (C2) + 0b00000000_00000000_00000000_00000000_00000000_00011100_00010100_00011100, // Square 11 (D2) + 0b00000000_00000000_00000000_00000000_00000000_00111000_00101000_00111000, // Square 12 (E2) + 0b00000000_00000000_00000000_00000000_00000000_01110000_01010000_01110000, // Square 13 (F2) + 0b00000000_00000000_00000000_00000000_00000000_11100000_10100000_11100000, // Square 14 (G2) + 0b00000000_00000000_00000000_00000000_00000000_11000000_01000000_11000000, // Square 15 (H2) + 0b00000000_00000000_00000000_00000000_00000011_00000010_00000011_00000000, // Square 16 (A3) + 0b00000000_00000000_00000000_00000000_00000111_00000101_00000111_00000000, // Square 17 (B3) + 0b00000000_00000000_00000000_00000000_00001110_00001010_00001110_00000000, // Square 18 (C3) + 0b00000000_00000000_00000000_00000000_00011100_00010100_00011100_00000000, // Square 19 (D3) + 0b00000000_00000000_00000000_00000000_00111000_00101000_00111000_00000000, // Square 20 (E3) + 0b00000000_00000000_00000000_00000000_01110000_01010000_01110000_00000000, // Square 21 (F3) + 0b00000000_00000000_00000000_00000000_11100000_10100000_11100000_00000000, // Square 22 (G3) + 0b00000000_00000000_00000000_00000000_11000000_01000000_11000000_00000000, // Square 23 (H3) + 0b00000000_00000000_00000000_00000011_00000010_00000011_00000000_00000000, // Square 24 (A4) + 0b00000000_00000000_00000000_00000111_00000101_00000111_00000000_00000000, // Square 25 (B4) + 0b00000000_00000000_00000000_00001110_00001010_00001110_00000000_00000000, // Square 26 (C4) + 0b00000000_00000000_00000000_00011100_00010100_00011100_00000000_00000000, // Square 27 (D4) + 0b00000000_00000000_00000000_00111000_00101000_00111000_00000000_00000000, // Square 28 (E4) + 0b00000000_00000000_00000000_01110000_01010000_01110000_00000000_00000000, // Square 29 (F4) + 0b00000000_00000000_00000000_11100000_10100000_11100000_00000000_00000000, // Square 30 (G4) + 0b00000000_00000000_00000000_11000000_01000000_11000000_00000000_00000000, // Square 31 (H4) + 0b00000000_00000000_00000011_00000010_00000011_00000000_00000000_00000000, // Square 32 (A5) + 0b00000000_00000000_00000111_00000101_00000111_00000000_00000000_00000000, // Square 33 (B5) + 0b00000000_00000000_00001110_00001010_00001110_00000000_00000000_00000000, // Square 34 (C5) + 0b00000000_00000000_00011100_00010100_00011100_00000000_00000000_00000000, // Square 35 (D5) + 0b00000000_00000000_00111000_00101000_00111000_00000000_00000000_00000000, // Square 36 (E5) + 0b00000000_00000000_01110000_01010000_01110000_00000000_00000000_00000000, // Square 37 (F5) + 0b00000000_00000000_11100000_10100000_11100000_00000000_00000000_00000000, // Square 38 (G5) + 0b00000000_00000000_11000000_01000000_11000000_00000000_00000000_00000000, // Square 39 (H5) + 0b00000000_00000011_00000010_00000011_00000000_00000000_00000000_00000000, // Square 40 (A6) + 0b00000000_00000111_00000101_00000111_00000000_00000000_00000000_00000000, // Square 41 (B6) + 0b00000000_00001110_00001010_00001110_00000000_00000000_00000000_00000000, // Square 42 (C6) + 0b00000000_00011100_00010100_00011100_00000000_00000000_00000000_00000000, // Square 43 (D6) + 0b00000000_00111000_00101000_00111000_00000000_00000000_00000000_00000000, // Square 44 (E6) + 0b00000000_01110000_01010000_01110000_00000000_00000000_00000000_00000000, // Square 45 (F6) + 0b00000000_11100000_10100000_11100000_00000000_00000000_00000000_00000000, // Square 46 (G6) + 0b00000000_11000000_01000000_11000000_00000000_00000000_00000000_00000000, // Square 47 (H6) + 0b00000011_00000010_00000011_00000000_00000000_00000000_00000000_00000000, // Square 48 (A7) + 0b00000111_00000101_00000111_00000000_00000000_00000000_00000000_00000000, // Square 49 (B7) + 0b00001110_00001010_00001110_00000000_00000000_00000000_00000000_00000000, // Square 50 (C7) + 0b00011100_00010100_00011100_00000000_00000000_00000000_00000000_00000000, // Square 51 (D7) + 0b00111000_00101000_00111000_00000000_00000000_00000000_00000000_00000000, // Square 52 (E7) + 0b01110000_01010000_01110000_00000000_00000000_00000000_00000000_00000000, // Square 53 (F7) + 0b11100000_10100000_11100000_00000000_00000000_00000000_00000000_00000000, // Square 54 (G7) + 0b11000000_01000000_11000000_00000000_00000000_00000000_00000000_00000000, // Square 55 (H7) + 0b00000010_00000011_00000000_00000000_00000000_00000000_00000000_00000000, // Square 56 (A8) + 0b00000101_00000111_00000000_00000000_00000000_00000000_00000000_00000000, // Square 57 (B8) + 0b00001010_00001110_00000000_00000000_00000000_00000000_00000000_00000000, // Square 58 (C8) + 0b00010100_00011100_00000000_00000000_00000000_00000000_00000000_00000000, // Square 59 (D8) + 0b00101000_00111000_00000000_00000000_00000000_00000000_00000000_00000000, // Square 60 (E8) + 0b01010000_01110000_00000000_00000000_00000000_00000000_00000000_00000000, // Square 61 (F8) + 0b10100000_11100000_00000000_00000000_00000000_00000000_00000000_00000000, // Square 62 (G8) + 0b01000000_11000000_00000000_00000000_00000000_00000000_00000000_00000000, // Square 63 (H8) +]; + +pub const MAGICS_ROOK: [u64; 64] = [ + 0b10001010_10000000_00010000_01000000_00000000_10000000_00000000_00100000, // Square 0 (A1) + 0b00000001_01000000_00000000_00100000_00000000_00010000_00000000_01000000, // Square 1 (B1) + 0b00000010_10000000_00011000_10000000_10100000_00000001_01110000_00000001, // Square 2 (C1) + 0b00000001_00000000_00001000_00010000_00000001_00000000_00000100_00100000, // Square 3 (D1) + 0b00000010_00000000_00000010_00000000_00010000_00001000_00000100_00100000, // Square 4 (E1) + 0b00000011_00000000_00011100_00000000_00000010_00000001_00000000_00001000, // Square 5 (F1) + 0b10000100_10000000_00000000_10000000_00000010_00000000_00000001_00000000, // Square 6 (G1) + 0b00100000_10000000_00001000_10000000_00000100_01000000_00101001_00000000, // Square 7 (H1) + 0b00000000_00000000_10000000_00000000_10011000_00100000_01000000_00000000, // Square 8 (A2) + 0b00100000_00100100_01000000_00010000_00000000_00100000_00000000_01000000, // Square 9 (B2) + 0b00000001_00000000_10000000_00100000_00000000_10000000_00010000_00000000, // Square 10 (C2) + 0b00000001_00100000_10000000_00001000_00000000_10000000_00010000_00000000, // Square 11 (D2) + 0b00000010_00001000_10000000_10000000_10001000_00000000_00000100_00000000, // Square 12 (E2) + 0b00000000_00000010_10000000_00100010_00000000_10000000_00000100_00000000, // Square 13 (F2) + 0b00100010_00000000_10000000_00000001_00000000_00000010_00000000_10000000, // Square 14 (G2) + 0b00001000_00000001_00000000_00000000_01100000_10000010_00010001_00000000, // Square 15 (H2) + 0b00000000_10000000_00000100_01000000_00000110_01000010_00100000_00000000, // Square 16 (A3) + 0b00000001_00000000_10000000_10000000_00100000_00000000_01000000_00000000, // Square 17 (B3) + 0b00010010_00010000_10001010_00000000_00010000_00100000_01000010_00000000, // Square 18 (C3) + 0b00000001_01000000_10000100_10000000_00010000_00000000_00001000_00000010, // Square 19 (D3) + 0b00000100_10000001_10000010_10000000_00010100_00000000_00101000_00000000, // Square 20 (E3) + 0b10000000_10010100_00000000_01000000_00000010_00000000_01000001_00000000, // Square 21 (F3) + 0b01000000_00010000_00000100_00000000_00010000_00000001_00001000_00000010, // Square 22 (G3) + 0b00000000_00000000_00000010_00000000_00001000_10000000_01100001_00000100, // Square 23 (H3) + 0b00000001_00000000_01000000_00000000_10000000_00100000_10000000_00000000, // Square 24 (A4) + 0b00100000_01000000_00000000_00100001_00100000_00001000_00010000_00000000, // Square 25 (B4) + 0b00000000_00100001_00100000_00000110_10000000_00010000_00000000_10000001, // Square 26 (C4) + 0b00000000_00100000_00010000_00000000_10000000_00001000_00000000_10000000, // Square 27 (D4) + 0b00000000_00000010_00000000_00001010_00000000_00100000_00000100_00010000, // Square 28 (E4) + 0b00000000_00000000_00000010_00000000_10000000_10000000_00000100_00000000, // Square 29 (F4) + 0b00000000_10000000_00001000_10000100_00000000_00010000_00000001_00000010, // Square 30 (G4) + 0b00000000_10000000_00000000_01000110_00000000_00000100_00101000_10000001, // Square 31 (H4) + 0b01000000_01000000_00000000_10000000_01000000_10000000_00000000_00100000, // Square 32 (A5) + 0b00000100_01000000_00000000_00110000_00000000_00100000_00001000_00000001, // Square 33 (B5) + 0b00000000_00000100_00100000_00000000_00010001_00000000_01000101_00000000, // Square 34 (C5) + 0b00000001_10001000_00000010_00000000_00010000_00010000_00000001_00000000, // Square 35 (D5) + 0b00000000_00010100_10000000_00000100_00000001_10000000_00101000_00000000, // Square 36 (E5) + 0b00100000_10000000_00000100_00000000_10000000_10000000_00000010_00000000, // Square 37 (F5) + 0b00000001_00100100_00001000_00000010_00000100_00000000_00010000_00000001, // Square 38 (G5) + 0b00000010_00000000_00000100_01100101_00000010_00000000_00000100_10000100, // Square 39 (H5) + 0b00000100_10000000_01000000_00000000_10000000_00001000_10000000_00100000, // Square 40 (A6) + 0b00010000_00000000_01000010_00100000_00010000_00000011_01000000_00000000, // Square 41 (B6) + 0b00000000_00110000_00100000_00000001_00000000_00010001_00000000_01000000, // Square 42 (C6) + 0b00000000_00000000_00010000_00000000_00100001_00000001_00000000_00001001, // Square 43 (D6) + 0b00100000_00000010_00001000_00000001_00000000_00010001_00000000_00000100, // Square 44 (E6) + 0b00000010_00000010_00000000_10000000_00000100_00000000_10000000_00000010, // Square 45 (F6) + 0b00000000_00100000_00000010_00000000_00000100_00000001_00000001_00000000, // Square 46 (G6) + 0b00100000_01001000_01000100_00000000_01000000_10000010_00000000_00000001, // Square 47 (H6) + 0b00000001_00000001_00000000_00100010_00000000_01000000_10000010_00000000, // Square 48 (A7) + 0b00000000_01000000_10000000_00100000_00000000_01000000_00010000_10000000, // Square 49 (B7) + 0b01000000_00001000_00010100_00100000_00000100_01000001_00000001_00000000, // Square 50 (C7) + 0b00000010_00000110_00001000_00100000_11000000_00010010_00000010_00000000, // Square 51 (D7) + 0b00000000_00000001_00000000_00010000_00000100_00001000_00000001_00000000, // Square 52 (E7) + 0b00000010_00001100_00000010_00000000_10000000_00000100_00000000_10000000, // Square 53 (F7) + 0b00101001_00110101_01100001_00001000_00110000_00000010_00100100_00000000, // Square 54 (G7) + 0b00000000_01000100_01000100_00000000_01000001_00000000_10010010_00000000, // Square 55 (H7) + 0b00000010_10000000_00000000_00010000_01000000_10000000_00100001_00000001, // Square 56 (A8) + 0b00100001_00000000_00011001_00000000_01000000_00000000_00100000_10000101, // Square 57 (B8) + 0b10000000_11000000_00001000_01000001_00000000_00010000_00100000_00000001, // Square 58 (C8) + 0b01000000_00100100_00001000_00010000_00000001_00000000_00000100_00100001, // Square 59 (D8) + 0b00000000_00000010_00000000_00110000_10100000_00100100_01001000_01110010, // Square 60 (E8) + 0b00000000_00010010_00000000_00010000_00001000_01000001_01000100_00000010, // Square 61 (F8) + 0b00000010_00000000_01100001_00000100_10010000_00001010_00001000_00000100, // Square 62 (G8) + 0b00000000_00000001_00000000_01000000_10000001_00000000_00100100_00000010, // Square 63 (H8) +]; + +pub const MAGICS_BISHOP: [u64; 64] = [ + 0b00000000_01000000_00000100_00001000_01000100_01000000_01000000_10000100, // Square 0 (A1) + 0b00000000_00100000_00000100_00100000_10001010_00000000_01000010_00001000, // Square 1 (B1) + 0b00000000_00010000_00011001_00000000_01000001_00001000_00000010_00000010, // Square 2 (C1) + 0b00000001_00001000_00000110_00001000_01000101_00000100_00100000_00010000, // Square 3 (D1) + 0b00000101_10000001_00010000_01000001_10000000_10000000_00000010_00010000, // Square 4 (E1) + 0b00100001_00010010_00001000_00000100_01000110_00100000_00000000_00010000, // Square 5 (F1) + 0b00010000_10000000_10000010_00001000_00100000_00000110_00000010_00010000, // Square 6 (G1) + 0b00000011_11000000_10000000_10000100_00010000_00100010_00000010_00000000, // Square 7 (H1) + 0b00000000_00000100_00000101_00000100_00000100_01000100_00000100_00000100, // Square 8 (A2) + 0b00000000_00000000_00000010_00010000_00000001_01000010_00000000_10001000, // Square 9 (B2) + 0b00100100_11010000_00001000_00001000_00000001_00001000_00100001_00000010, // Square 10 (C2) + 0b00000000_00000001_00000010_00001010_00001010_00000010_00000100_00000000, // Square 11 (D2) + 0b00000000_00000000_00000100_00000011_00001000_00100000_00000100_00000010, // Square 12 (E2) + 0b00000000_00000100_00000001_00010000_00000010_00010000_00001000_00000000, // Square 13 (F2) + 0b00000100_00000001_01001000_01000001_00000100_00010000_01000000_00000101, // Square 14 (G2) + 0b00001000_00000001_00000001_00000100_00000010_00000010_00000010_00000000, // Square 15 (H2) + 0b00000000_01000000_00000010_00010000_11000011_10001000_00000001_00000000, // Square 16 (A3) + 0b00000100_00000100_00000010_00100000_00100100_00010000_10000010_00000000, // Square 17 (B3) + 0b00001000_00010000_00000001_10000010_00000000_00100000_01000001_00000010, // Square 18 (C3) + 0b00000000_00000100_00000000_00101000_00000001_10100000_00100000_00000011, // Square 19 (D3) + 0b00000000_10000101_00000100_00001000_00100000_00001000_00000100_00000000, // Square 20 (E3) + 0b10000001_00000001_00000010_11001000_00001000_10001000_00000100_00000000, // Square 21 (F3) + 0b00000000_00001110_10010000_00000100_00010000_10001000_01001000_00000000, // Square 22 (G3) + 0b10000000_00000010_00000010_00000100_10000000_10000100_00000001_00000010, // Square 23 (H3) + 0b00000010_00100000_00100000_00001000_01100101_00001001_00000010_00000001, // Square 24 (A4) + 0b00100000_00010000_00010000_00001010_00000010_00000010_00010010_00000010, // Square 25 (B4) + 0b00000001_01010010_00000100_10000100_00001000_00000010_00100100_00000001, // Square 26 (C4) + 0b00000000_00100000_00001000_00000000_00000010_00001000_00010001_00010000, // Square 27 (D4) + 0b01000000_00000001_00000000_00010000_00100001_00000000_01000000_00000000, // Square 28 (E4) + 0b10000000_00000000_01000000_01000000_00001010_00000001_00010000_00000010, // Square 29 (F4) + 0b00000000_11100100_00000000_01000000_10000001_00000001_00010000_00000010, // Square 30 (G4) + 0b00000000_00011100_00000000_01000000_00000001_00000001_00100000_10000000, // Square 31 (H4) + 0b10000000_00000100_00100000_00001001_01100010_10100000_00000010_00100000, // Square 32 (A5) + 0b10000100_00100010_00010000_00000010_00001000_01010000_00000010_00000010, // Square 33 (B5) + 0b00100000_00000000_01000000_00100010_00000000_00110000_00001100_00001000, // Square 34 (C5) + 0b10000110_01000110_00000010_00000000_10000000_00001000_00000000_10000000, // Square 35 (D5) + 0b10000000_00000010_00001010_00000010_00000000_00010000_00001000_00001000, // Square 36 (E5) + 0b00100000_00010000_00000000_01001000_10000000_00010001_00010000_00000000, // Square 37 (F5) + 0b01100010_00110000_00000000_10100000_10000000_00000001_00010100_00000000, // Square 38 (G5) + 0b01000010_00000000_10001100_00000011_01000000_00100000_10010010_00000010, // Square 39 (H5) + 0b00000010_00001001_00011000_10000010_01000000_00000000_00010000_00000000, // Square 40 (A6) + 0b01000000_00000100_00001000_10101000_10000100_00000000_00011000_00000000, // Square 41 (B6) + 0b00000000_00010001_00000100_00000000_10100110_00001000_00000100_00000000, // Square 42 (C6) + 0b00011000_01000000_00000110_00001010_01000100_00000010_00001000_00000000, // Square 43 (D6) + 0b00000000_10010000_00001000_00000001_00000100_00000000_00000000_01000001, // Square 44 (E6) + 0b00000010_00000001_00000001_00010000_00000000_10000000_10000001_00000001, // Square 45 (F6) + 0b00011010_00100010_00001000_00001000_00000101_00000100_11110000_10000000, // Square 46 (G6) + 0b10000000_00010010_00000010_00000110_00000000_00100001_00010010_00010010, // Square 47 (H6) + 0b00000101_00000000_10000110_00010000_00010001_00100100_00000000_00000000, // Square 48 (A7) + 0b00000001_10000000_10000000_01100001_00001000_00100000_00001000_00000000, // Square 49 (B7) + 0b01000000_00000000_00000010_00001110_00000001_00000100_00000000_01000100, // Square 50 (C7) + 0b00110000_00000000_00000000_00100110_00010000_01000100_00000000_00001010, // Square 51 (D7) + 0b00001000_00000010_00100100_00010001_00000010_00000010_00000000_00000010, // Square 52 (E7) + 0b00000000_00100000_10010000_01100000_01100001_00100001_00000000_00000001, // Square 53 (F7) + 0b01011010_10000100_10000100_00010000_00000100_00000001_00000011_00010000, // Square 54 (G7) + 0b00000000_00000100_00000001_00001000_00000001_00000001_00011100_00000100, // Square 55 (H7) + 0b00000000_00001010_00000001_00000001_00001001_01010000_00100010_00000000, // Square 56 (A8) + 0b00000000_00000000_00000000_01001010_00000010_00000001_00100000_00000000, // Square 57 (B8) + 0b01010000_00000010_00000001_00000001_00000000_10011000_10110000_00101000, // Square 58 (C8) + 0b10000000_01000000_00000000_00101000_00010001_00000100_00001001_00000000, // Square 59 (D8) + 0b00000000_00101000_00000000_00000000_00010000_00000010_00000010_00000100, // Square 60 (E8) + 0b00000110_00000000_00000000_00100000_00100000_00101101_00000010_01000000, // Square 61 (F8) + 0b10001001_00011000_10000100_01001000_01000010_00001000_00100010_00000000, // Square 62 (G8) + 0b01000000_00010000_00000001_00010000_00101001_00000010_00000000_00100000, // Square 63 (H8) +]; + +pub const RELEVANT_BITS_ROOK: [u8; 64] = [ + 12, 11, 11, 11, 11, 11, 11, 12, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, + 12, 11, 11, 11, 11, 11, 11, 12, +]; +pub const RELEVANT_BITS_BISHOP: [u8; 64] = [ + 6, 5, 5, 5, 5, 5, 5, 6, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 7, 7, 7, 7, 5, 5, + 5, 5, 7, 9, 9, 7, 5, 5, + 5, 5, 7, 9, 9, 7, 5, 5, + 5, 5, 7, 7, 7, 7, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 6, 5, 5, 5, 5, 5, 5, 6, +]; + + +static ROOK_ATTACKS: OnceLock> = OnceLock::new(); +static BISHOP_ATTACKS: OnceLock> = OnceLock::new(); + + +// HILFSFUNKTION: Berechnet Turmzüge "langsam" +// Diese Funktion wird nur beim "Backen" der Tabellen verwendet. +fn calculate_rook_attacks_slowly(square: usize, blockers: u64) -> u64 { + let mut attacks = 0_u64; + let rank = square / 8; + let file = square % 8; + + // 1. Nach Norden (rank +) + for r in (rank + 1)..=7 { + let target_sq = r * 8 + file; + attacks |= 1_u64 << target_sq; + if (blockers >> target_sq) & 1 == 1 { + break; // Wir treffen einen Blocker, stopp + } + } + + // 2. Nach Süden (rank -) + for r in (0..rank).rev() { + let target_sq = r * 8 + file; + attacks |= 1_u64 << target_sq; + if (blockers >> target_sq) & 1 == 1 { + break; + } + } + + // 3. Nach Osten (file +) + for f in (file + 1)..=7 { + let target_sq = rank * 8 + f; + attacks |= 1_u64 << target_sq; + if (blockers >> target_sq) & 1 == 1 { + break; + } + } + + // 4. Nach Westen (file -) + for f in (0..file).rev() { + let target_sq = rank * 8 + f; + attacks |= 1_u64 << target_sq; + if (blockers >> target_sq) & 1 == 1 { + break; + } + } + + attacks +} + +fn generate_final_rook_attacks() -> Box<[[u64; 4096]; 64]> { + // Heap-Allokation (Dein Code war hier korrekt) + let mut v: Vec<[u64; 4096]> = Vec::with_capacity(64); + for _ in 0..64 { + v.push([0_u64; 4096]); + } + let mut final_rook_attacks: Box<[[u64; 4096]; 64]> = v.try_into().unwrap_or_else(|_| { + panic!("Vec to Box conversion failed."); + }); + + // Haupt-Back-Schleife + for square_index in 0_usize..=63_usize { + let premask = PREMASKS_ROOK[square_index]; // [deine Quelle] + let magic = MAGICS_ROOK[square_index]; // [deine Quelle] + // ACHTUNG: Hier war ein Fehler in deinem Code, du hattest BISHOP statt ROOK + let relevant_bits = RELEVANT_BITS_ROOK[square_index]; // [deine Quelle] + let shift = 64 - relevant_bits; + + // Schleife durch alle 2^n Blocker-Kombinationen + let mut blocker_combination = 0_u64; + loop { + // ---- HIER IST DIE KORREKTE LOGIK ---- + + // 1. Berechne die "echten" Züge für diese Blocker-Kombination (der langsame Weg) + let attack_squares = calculate_rook_attacks_slowly(square_index, blocker_combination); + + // 2. Berechne den "magischen" Speicherort + let magic_index = (blocker_combination.wrapping_mul(magic)) >> shift; + let magic_index_as_usize = magic_index as usize; + + // 3. Speichere die echten Züge an diesem magischen Ort + final_rook_attacks[square_index][magic_index_as_usize] = attack_squares; + + // ---- ENDE DER LOGIK ---- + + // Gehe zur nächsten Blocker-Kombination (Dein Code war hier korrekt) + if blocker_combination == premask { + break; + } + blocker_combination = blocker_combination.wrapping_sub(premask) & premask; + } + } + + final_rook_attacks +} + +pub fn get_rook_attacks() -> &'static Box<[[u64; 4096]; 64]> { + // get_or_init stellt sicher, dass generate_final_rook_attacks() + // nur beim allerersten Aufruf ausgeführt wird. + // Alle anderen Aufrufe geben sofort die fertige Tabelle zurück. + ROOK_ATTACKS.get_or_init(generate_final_rook_attacks) +} + +// HILFSFUNKTION: Berechnet Läuferzüge "langsam" +// Diese Funktion wird nur beim "Backen" der Tabellen verwendet. +fn calculate_bishop_attacks_slowly(square: usize, blockers: u64) -> u64 { + let mut attacks = 0_u64; + let rank = square / 8; + let file = square % 8; + + // Temporäre Rank/File-Iteratoren + let (mut r, mut f); + + // 1. Nach Nord-Osten (rank+, file+) + r = rank + 1; + f = file + 1; + while r <= 7 && f <= 7 { + let target_sq = r * 8 + f; + attacks |= 1_u64 << target_sq; + if (blockers >> target_sq) & 1 == 1 { + break; // Wir treffen einen Blocker, stopp + } + r += 1; + f += 1; + } + + // 2. Nach Süd-Osten (rank-, file+) + r = rank; // Start bei rank, da 0..rank fehlschlägt wenn rank = 0 + f = file + 1; + while r > 0 && f <= 7 { // r > 0 (da r = rank-1 in der 1. Iteration) + r -= 1; + let target_sq = r * 8 + f; + attacks |= 1_u64 << target_sq; + if (blockers >> target_sq) & 1 == 1 { + break; + } + f += 1; + } + + // 3. Nach Süd-Westen (rank-, file-) + r = rank; + f = file; + while r > 0 && f > 0 { // r > 0 und f > 0 + r -= 1; + f -= 1; + let target_sq = r * 8 + f; + attacks |= 1_u64 << target_sq; + if (blockers >> target_sq) & 1 == 1 { + break; + } + } + + // 4. Nach Nord-Westen (rank+, file-) + r = rank + 1; + f = file; + while r <= 7 && f > 0 { // f > 0 + f -= 1; + let target_sq = r * 8 + f; + attacks |= 1_u64 << target_sq; + if (blockers >> target_sq) & 1 == 1 { + break; + } + r += 1; + } + + attacks +} + +fn generate_final_bishop_attacks() -> Box<[[u64; 512]; 64]> { + // Heap-Allokation + // (Array ist kleiner: 512 statt 4096. Stack Overflow wäre hier unwahrscheinlich, + // aber wir bleiben konsistent mit der Turm-Logik.) + let mut v: Vec<[u64; 512]> = Vec::with_capacity(64); + for _ in 0..64 { + v.push([0_u64; 512]); + } + let mut final_bishop_attacks: Box<[[u64; 512]; 64]> = v.try_into().unwrap_or_else(|_| { + panic!("Vec to Box conversion failed for bishops."); + }); + + // Haupt-Back-Schleife + for square_index in 0_usize..=63_usize { + // Verwende die BISHOP-Konstanten aus deiner tables.rs + let premask = PREMASKS_BISHOP[square_index]; + let magic = MAGICS_BISHOP[square_index]; + let relevant_bits = RELEVANT_BITS_BISHOP[square_index]; + let shift = 64 - relevant_bits; + + // Schleife durch alle 2^n Blocker-Kombinationen + let mut blocker_combination = 0_u64; + loop { + // 1. Berechne die "echten" Züge für diese Blocker-Kombination (der langsame Weg) + let attack_squares = calculate_bishop_attacks_slowly(square_index, blocker_combination); + + // 2. Berechne den "magischen" Speicherort + let magic_index = (blocker_combination.wrapping_mul(magic)) >> shift; + let magic_index_as_usize = magic_index as usize; + + // 3. Speichere die echten Züge an diesem magischen Ort + // (Stelle sicher, dass magic_index_as_usize < 512 ist, + // was durch korrekte Magics garantiert wird) + final_bishop_attacks[square_index][magic_index_as_usize] = attack_squares; + + // Gehe zur nächsten Blocker-Kombination + if blocker_combination == premask { + break; + } + blocker_combination = blocker_combination.wrapping_sub(premask) & premask; + } + } + + final_bishop_attacks +} + +pub fn get_bishop_attacks() -> &'static Box<[[u64; 512]; 64]> { + // get_or_init stellt sicher, dass generate_final_bishop_attacks() + // nur beim allerersten Aufruf ausgeführt wird. + // Alle anderen Aufrufe geben sofort die fertige Tabelle zurück. + BISHOP_ATTACKS.get_or_init(generate_final_bishop_attacks) +} \ No newline at end of file diff --git a/src/square.rs b/src/square.rs new file mode 100644 index 0000000..ee066da --- /dev/null +++ b/src/square.rs @@ -0,0 +1,141 @@ +use std::ops::{Sub, Add}; +use std::cmp::Ordering; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum Square { + A1 = 0, + B1 = 1, + C1 = 2, + D1 = 3, + E1 = 4, + F1 = 5, + G1 = 6, + H1 = 7, + A2 = 8, + B2 = 9, + C2 = 10, + D2 = 11, + E2 = 12, + F2 = 13, + G2 = 14, + H2 = 15, + A3 = 16, + B3 = 17, + C3 = 18, + D3 = 19, + E3 = 20, + F3 = 21, + G3 = 22, + H3 = 23, + A4 = 24, + B4 = 25, + C4 = 26, + D4 = 27, + E4 = 28, + F4 = 29, + G4 = 30, + H4 = 31, + A5 = 32, + B5 = 33, + C5 = 34, + D5 = 35, + E5 = 36, + F5 = 37, + G5 = 38, + H5 = 39, + A6 = 40, + B6 = 41, + C6 = 42, + D6 = 43, + E6 = 44, + F6 = 45, + G6 = 46, + H6 = 47, + A7 = 48, + B7 = 49, + C7 = 50, + D7 = 51, + E7 = 52, + F7 = 53, + G7 = 54, + H7 = 55, + A8 = 56, + B8 = 57, + C8 = 58, + D8 = 59, + E8 = 60, + F8 = 61, + G8 = 62, + H8 = 63, +} + +pub const SQUARES: [Square; 64] = [ + Square::A1, Square::B1, Square::C1, Square::D1, Square::E1, Square::F1, Square::G1, Square::H1, + Square::A2, Square::B2, Square::C2, Square::D2, Square::E2, Square::F2, Square::G2, Square::H2, + Square::A3, Square::B3, Square::C3, Square::D3, Square::E3, Square::F3, Square::G3, Square::H3, + Square::A4, Square::B4, Square::C4, Square::D4, Square::E4, Square::F4, Square::G4, Square::H4, + Square::A5, Square::B5, Square::C5, Square::D5, Square::E5, Square::F5, Square::G5, Square::H5, + Square::A6, Square::B6, Square::C6, Square::D6, Square::E6, Square::F6, Square::G6, Square::H6, + Square::A7, Square::B7, Square::C7, Square::D7, Square::E7, Square::F7, Square::G7, Square::H7, + Square::A8, Square::B8, Square::C8, Square::D8, Square::E8, Square::F8, Square::G8, Square::H8, +]; + +impl TryFrom for Square { + type Error = &'static str; + fn try_from(value: u8) -> Result { + if value <= 63 { + Ok(unsafe { std::mem::transmute(value) }) + } else { + Err("Square index out of bounds") + } + } +} + +impl Add for Square { + type Output = Self; + + fn add(self, rhs: u8) -> Self::Output { + let new_val = (self as u8) + rhs; + new_val.try_into().expect("Square addition resulted in an invalid square") + } +} + +impl Sub for Square { + type Output = Self; + + fn sub(self, rhs: u8) -> Self::Output { + let new_val = (self as u8).checked_sub(rhs) + .expect("Square subtraction resulted in an underflow"); + + new_val.try_into().expect("Square subtraction resulted in an invalid square") + } +} + +impl PartialOrd for Square { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + + fn lt(&self, other: &Self) -> bool { + (*self as u8) < (*other as u8) + } + + fn le(&self, other: &Self) -> bool { + (*self as u8) <= (*other as u8) + } + + fn gt(&self, other: &Self) -> bool { + (*self as u8) > (*other as u8) + } + + fn ge(&self, other: &Self) -> bool { + (*self as u8) >= (*other as u8) + } +} + +impl Ord for Square { + fn cmp(&self, other: &Self) -> Ordering { + (*self as u8).cmp(&(*other as u8)) + } +} \ No newline at end of file diff --git a/tests/bishop_move_generation.rs b/tests/bishop_move_generation.rs new file mode 100644 index 0000000..366a73c --- /dev/null +++ b/tests/bishop_move_generation.rs @@ -0,0 +1,85 @@ +use chess_engine::board::Board; +use chess_engine::movegen::sliders::generate_bishop_moves; +use chess_engine::r#move::MoveList; + +/// Compares two move list strings ignoring the order of moves. +fn assert_moves_equal(actual_str: &str, expected_str: &str) { + let mut actual_moves: Vec<&str> = actual_str.split_whitespace().collect(); + let mut expected_moves: Vec<&str> = expected_str.split_whitespace().collect(); + + actual_moves.sort(); + expected_moves.sort(); + + assert_eq!(actual_moves, expected_moves); +} + +#[test] +fn test_bishop_moves_single() { + let fen_standard = "8/8/8/1p1p4/2B5/8/8/8 w - - 0 1"; + let board = Board::from_fen(fen_standard); + let mut list = MoveList::new(); + generate_bishop_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "c4b5 c4d5 c4b3 c4d3 c4a2 c4e2 c4f1"); +} + +#[test] +fn test_bishop_moves_empty_board_center() { + // Läufer in der Mitte (e4) auf einem leeren Brett + let fen = "8/8/8/8/4B3/8/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_bishop_moves(&board, &mut list); + + let expected = "e4d3 e4c2 e4b1 e4f5 e4g6 e4h7 e4d5 e4c6 e4b7 e4a8 e4f3 e4g2 e4h1"; + assert_moves_equal(&list.to_string(), expected); +} + +#[test] +fn test_bishop_moves_from_corner() { + // Läufer in der Ecke (a1) auf einem leeren Brett + let fen = "8/8/8/8/8/8/8/B7 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_bishop_moves(&board, &mut list); + + let expected = "a1b2 a1c3 a1d4 a1e5 a1f6 a1g7 a1h8"; + assert_moves_equal(&list.to_string(), expected); +} + +#[test] +fn test_bishop_moves_friendly_blockers() { + // Läufer auf c1, blockiert von EIGENEN Bauern auf b2 und d2. + // Darf b2/d2 NICHT schlagen und nicht darüber springen. + let fen = "8/8/8/8/8/8/1P1P4/2B5 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_bishop_moves(&board, &mut list); + + // Es sollten KEINE Züge generiert werden. + assert_moves_equal(&list.to_string(), ""); +} + +#[test] +fn test_bishop_moves_mixed_blockers_and_captures() { + let fen = "8/8/1p3P2/8/3B4/8/1p3P2/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_bishop_moves(&board, &mut list); + + let expected = "d4c3 d4e3 d4b2 d4c5 d4b6 d4e5"; + assert_moves_equal(&list.to_string(), expected); +} + +#[test] +fn test_bishop_moves_black_turn() { + // Schwarzer Läufer auf c5, weiße Bauern auf b4 und d4. + let fen = "8/8/8/2b5/1P1P4/8/8/8 b - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_bishop_moves(&board, &mut list); + + // Schlagzüge: b4, d4 + // Ruhige Züge: b6, a7, d6, e7, f8 + let expected = "c5b4 c5d4 c5b6 c5a7 c5d6 c5e7 c5f8"; + assert_moves_equal(&list.to_string(), expected); +} diff --git a/tests/board_to_fen_conversion.rs b/tests/board_to_fen_conversion.rs new file mode 100644 index 0000000..669de85 --- /dev/null +++ b/tests/board_to_fen_conversion.rs @@ -0,0 +1,76 @@ +use chess_engine::board::*; + +#[test] +fn test_fen_roundtrip_standard() { + let fen_standard = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + assert_eq!(Board::from_fen(fen_standard).to_fen(), fen_standard); +} + +#[test] +fn test_fen_roundtrip_kiwipete() { + let fen_kiwipete = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1"; + assert_eq!(Board::from_fen(fen_kiwipete).to_fen(), fen_kiwipete); +} + +#[test] +fn test_fen_roundtrip_en_passant() { + let fen_en_passant = "rnbqkbnr/pppppp1p/8/8/p7/4P3/PPPP1PPP/RNBQKBNR w KQkq e3 0 1"; + assert_eq!(Board::from_fen(fen_en_passant).to_fen(), fen_en_passant); +} + +#[test] +fn test_fen_roundtrip_castle() { + let fen_castle = "r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2QK2R b - - 0 1"; + assert_eq!(Board::from_fen(fen_castle).to_fen(), fen_castle); +} + +#[test] +fn test_fen_roundtrip_just_kings() { + let fen_just_kings = "8/k7/8/8/8/8/7K/8 w - - 0 1"; + assert_eq!(Board::from_fen(fen_just_kings).to_fen(), fen_just_kings); +} + +#[test] +fn test_fen_roundtrip_high_move_values() { + let fen_high_move_values = "8/P1k5/K7/8/8/8/8/8 w - - 0 78"; + assert_eq!(Board::from_fen(fen_high_move_values).to_fen(), fen_high_move_values); +} + +#[test] +fn test_fen_roundtrip_empty_count1() { + let fen_empty_count1 = "1n6/8/8/8/8/8/8/8 w - - 0 1"; + assert_eq!(Board::from_fen(fen_empty_count1).to_fen(), fen_empty_count1); +} + +#[test] +fn test_fen_roundtrip_empty_count2() { + let fen_empty_count2 = "6n1/8/8/8/8/8/8/8 w - - 0 1"; + assert_eq!(Board::from_fen(fen_empty_count2).to_fen(), fen_empty_count2); +} + +#[test] +fn test_board_fen_state() { + let fen_standard = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + let board = Board::from_fen(fen_standard); + assert_eq!(board.pieces[PieceType::Pawn as usize][Color::White as usize], 65280); + assert_eq!(board.pieces[PieceType::Pawn as usize][Color::Black as usize], 71776119061217280); + assert_eq!(board.pieces[PieceType::Knight as usize][Color::White as usize], 66); + assert_eq!(board.pieces[PieceType::Knight as usize][Color::Black as usize], 4755801206503243776); + assert_eq!(board.pieces[PieceType::Bishop as usize][Color::White as usize], 36); + assert_eq!(board.pieces[PieceType::Bishop as usize][Color::Black as usize], 2594073385365405696); + assert_eq!(board.pieces[PieceType::Rook as usize][Color::White as usize], 129); + assert_eq!(board.pieces[PieceType::Rook as usize][Color::Black as usize], 9295429630892703744); + assert_eq!(board.pieces[PieceType::Queen as usize][Color::White as usize], 8); + assert_eq!(board.pieces[PieceType::Queen as usize][Color::Black as usize], 576460752303423488); + assert_eq!(board.pieces[PieceType::King as usize][Color::White as usize], 16); + assert_eq!(board.pieces[PieceType::King as usize][Color::Black as usize], 1152921504606846976); + + assert_eq!(board.occupied[0], 65535); + assert_eq!(board.occupied[1], 18446462598732840960); + assert_eq!(board.all_occupied, 18446462598732906495); + + assert_eq!(board.castling_rights, 15); + assert_eq!(board.en_passant_target, None); + assert_eq!(board.halfmove_clock, 0); + assert_eq!(board.fullmove_number, 1); +} \ No newline at end of file diff --git a/tests/king_move_generation.rs b/tests/king_move_generation.rs new file mode 100644 index 0000000..107b048 --- /dev/null +++ b/tests/king_move_generation.rs @@ -0,0 +1,145 @@ +use chess_engine::board::Board; +use chess_engine::movegen::non_sliders::generate_king_moves; +use chess_engine::r#move::MoveList; + +/// Compares two move list strings ignoring the order of moves. +fn assert_moves_equal(actual_str: &str, expected_str: &str) { + let mut actual_moves: Vec<&str> = actual_str.split_whitespace().collect(); + let mut expected_moves: Vec<&str> = expected_str.split_whitespace().collect(); + + actual_moves.sort(); + expected_moves.sort(); + + assert_eq!(actual_moves, expected_moves); +} + +#[test] +fn test_king_moves_start_pos_blocked() { + let fen_standard = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + let board = Board::from_fen(fen_standard); + let mut list = MoveList::new(); + generate_king_moves(&board, &mut list); + // King is completely blocked in the start position + assert_moves_equal(&list.to_string(), ""); +} + +#[test] +fn test_king_moves_center() { + let fen = "8/8/8/8/4K3/8/8/8 w - - 0 1"; // King on e4 + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_king_moves(&board, &mut list); + // 8 moves from e4 + assert_moves_equal(&list.to_string(), "e4d3 e4d4 e4d5 e4e3 e4e5 e4f3 e4f4 e4f5"); +} + +#[test] +fn test_king_moves_corner() { + let fen = "K7/8/8/8/8/8/8/8 w - - 0 1"; // King on a8 + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_king_moves(&board, &mut list); + // 3 moves from a8 + assert_moves_equal(&list.to_string(), "a8a7 a8b7 a8b8"); +} + +#[test] +fn test_king_moves_blocked_friendly() { + // King on d4, surrounded by friendly pawns + let fen = "8/8/8/3P1P2/3K4/3P1P2/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_king_moves(&board, &mut list); + // d3, d5, f3, f5 are blocked. + // c3, c4, c5, e3, e4, e5 are free. + assert_moves_equal(&list.to_string(), "d4c3 d4c4 d4c5 d4e3 d4e4 d4e5"); +} + +#[test] +fn test_king_moves_capture_and_blocked() { + // King on d4 + // Friendly: c3, e5 + // Enemy: c5, e3 + let fen = "8/8/8/2p1P3/3K4/2P1p3/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_king_moves(&board, &mut list); + // Blocked: c3, e5 + // Captures: c5, e3 + // Empty: c4, d3, d5, e4, f3, f4, f5 + // Note: f3, f4, f5 are valid moves + assert_moves_equal(&list.to_string(), "d4c4 d4d3 d4d5 d4e4 d4c5 d4e3"); +} + +#[test] +fn test_king_moves_black() { + let fen = "8/8/8/8/4k3/8/8/8 b - - 0 1"; // Black king on e4 + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_king_moves(&board, &mut list); + // 8 moves from e4 + assert_moves_equal(&list.to_string(), "e4d3 e4d4 e4d5 e4e3 e4e5 e4f3 e4f4 e4f5"); +} + +#[test] +fn test_king_moves_castling_white_all() { + // King on e1, rooks on a1, h1. All rights. No pieces between. + let fen = "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_king_moves(&board, &mut list); + // 5 standard moves + 2 castling moves (e1g1, e1c1) + assert_moves_equal(&list.to_string(), "e1d1 e1d2 e1e2 e1f1 e1f2 O-O O-O-O"); +} + +#[test] +fn test_king_moves_castling_black_all() { + // King on e8, rooks on a8, h8. All rights. No pieces between. + let fen = "r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_king_moves(&board, &mut list); + // 5 standard moves + 2 castling moves (e8g8, e8c8) + assert_moves_equal(&list.to_string(), "O-O O-O-O e8d8 e8e7 e8f8 e8d7 e8f7"); +} + +#[test] +fn test_king_moves_castling_blocked_pieces_white() { + // White: Queenside blocked by knight + let fen = "r3k2r/8/8/8/8/8/8/RN2K2R w KQkq - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_king_moves(&board, &mut list); + // Should only get Kingside castling (e1g1) and standard moves + assert_moves_equal(&list.to_string(), "e1d1 e1d2 e1e2 e1f1 e1f2 O-O"); +} + +#[test] +fn test_king_moves_castling_blocked_pieces_black() { + // Black: Kingside blocked by bishop + let fen = "r3kb1r/8/8/8/8/8/8/R3K2R b KQkq - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_king_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "e8d8 e8d7 e8e7 e8f7 O-O-O"); +} + +#[test] +fn test_king_moves_castling_no_rights() { + // Same as `test_king_moves_castling_white_all` but no castling rights + let fen = "r3k2r/8/8/8/8/8/8/R3K2R w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_king_moves(&board, &mut list); + // 5 standard moves, 0 castling + assert_moves_equal(&list.to_string(), "e1d1 e1d2 e1e2 e1f1 e1f2"); +} + +#[test] +fn test_king_moves_empty_board() { + let fen = "8/8/8/8/8/8/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_king_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), ""); +} \ No newline at end of file diff --git a/tests/knight_move_generation.rs b/tests/knight_move_generation.rs new file mode 100644 index 0000000..41fae3a --- /dev/null +++ b/tests/knight_move_generation.rs @@ -0,0 +1,96 @@ +use chess_engine::board::Board; +use chess_engine::movegen::non_sliders::generate_knight_moves; +use chess_engine::r#move::MoveList; + +/// Compares two move list strings ignoring the order of moves. +fn assert_moves_equal(actual_str: &str, expected_str: &str) { + let mut actual_moves: Vec<&str> = actual_str.split_whitespace().collect(); + let mut expected_moves: Vec<&str> = expected_str.split_whitespace().collect(); + + actual_moves.sort(); + expected_moves.sort(); + + assert_eq!(actual_moves, expected_moves); +} + +#[test] +fn test_knight_move_generation() { + let fen_standard = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + let board = Board::from_fen(fen_standard); + let mut list = MoveList::new(); + generate_knight_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "b1a3 b1c3 g1f3 g1h3"); +} + +#[test] +fn test_knight_move_generation_black() { + let fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_knight_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "b8a6 b8c6 g8f6 g8h6"); +} + +#[test] +fn test_knight_moves_center() { + let fen = "8/8/8/3N4/8/8/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_knight_moves(&board, &mut list); + // 8 moves from d5 + assert_moves_equal(&list.to_string(), "d5b4 d5b6 d5c3 d5c7 d5e3 d5e7 d5f4 d5f6"); +} + +#[test] +fn test_knight_moves_capture() { + // Black knight on e5, white pawn on d3 + let fen = "8/8/8/4n3/8/3P4/8/8 b - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_knight_moves(&board, &mut list); + // 8 moves, including capture on d3 + assert_moves_equal(&list.to_string(), "e5c4 e5c6 e5d3 e5d7 e5f3 e5f7 e5g4 e5g6"); +} + +#[test] +fn test_knight_moves_blocked_friendly() { + // Knight on d4, some moves are blocked, some are not. + let fen = "8/8/3P1P2/3P1P2/3N4/3P1P2/3P1P2/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_knight_moves(&board, &mut list); + // f3, f5, d3, d5, d6, f6 are blocked. + // c2, e2, b3, b5, c6, e6 are free. + assert_moves_equal(&list.to_string(), "d4c2 d4e2 d4b3 d4b5 d4c6 d4e6"); +} + +#[test] +fn test_knight_moves_corner() { + let fen = "N7/8/8/8/8/8/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_knight_moves(&board, &mut list); + // 2 moves from a8 + assert_moves_equal(&list.to_string(), "a8b6 a8c7"); +} + +#[test] +fn test_knight_moves_capture_and_blocked() { + // White knights on b1 and g1. + // b1: a3 (friendly), c3 (friendly), d2 (enemy) + // g1: f3 (empty), h3 (empty), e2 (empty) + let fen = "8/8/8/8/8/P1P5/3p4/1N4NR w K - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_knight_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "b1d2 g1e2 g1f3 g1h3"); +} + +#[test] +fn test_knight_moves_empty_board() { + let fen = "8/8/8/8/8/8/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_knight_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), ""); +} \ No newline at end of file diff --git a/tests/move_to_algebraic_conversion.rs b/tests/move_to_algebraic_conversion.rs new file mode 100644 index 0000000..3a4f4fc --- /dev/null +++ b/tests/move_to_algebraic_conversion.rs @@ -0,0 +1,92 @@ +use chess_engine::r#move::*; +use chess_engine::square::Square; + +#[test] +fn test_quiet_move_white_pawn() { + // Test 1: Standard Quiet Move (White Pawn) + // (from: E2, to: E4) + // NOTE: This was MOVE_TYPE_FLAG_QUIET, but in the new system it's a specific flag. + // The algebraic notation is the same, so we test with the new specific flag. + let m_quiet = Move::new(Square::E2, Square::E4, MOVE_FLAG_QUIET); + assert_eq!(m_quiet.to_algebraic(), "e2e4"); +} + +#[test] +fn test_quiet_move_black_knight() { + // Test 2: Standard Quiet Move (Black Knight) + // (from: B8, to: C6) + let m_knight = Move::new(Square::B8, Square::C6, MOVE_FLAG_QUIET); + assert_eq!(m_knight.to_algebraic(), "b8c6"); +} + +#[test] +fn test_en_passant_move() { + // Test 3: En Passant Move (Notation is same as quiet move) + // (from: E5, to: F6) + let m_ep = Move::new(Square::E5, Square::F6, MOVE_FLAG_EN_PASSANT); + assert_eq!(m_ep.to_algebraic(), "e5f6"); +} + +#[test] +fn test_promotion_to_queen() { + // Test 4: Promotion to Queen (Push) + // (from: E7, to: E8) + let m_promo_q = Move::new(Square::E7, Square::E8, MOVE_FLAG_PROMO_Q); + assert_eq!(m_promo_q.to_algebraic(), "e7e8q"); +} + +#[test] +fn test_promotion_to_rook() { + // Test 5: Promotion to Rook (Push) + // (from: A7, to: A8) + let m_promo_r = Move::new(Square::A7, Square::A8, MOVE_FLAG_PROMO_R); + assert_eq!(m_promo_r.to_algebraic(), "a7a8r"); +} + +#[test] +fn test_promotion_to_bishop() { + // Test 6: Promotion to Bishop (Capture) + // (from: G2, to: H1) + let m_promo_b = Move::new(Square::G2, Square::H1, MOVE_FLAG_PROMO_CAP_B); + assert_eq!(m_promo_b.to_algebraic(), "g2h1b"); +} + +#[test] +fn test_promotion_to_knight() { + // Test 7: Promotion to Knight (Capture) + // (from: G7, to: F8) + let m_promo_n = Move::new(Square::G7, Square::F8, MOVE_FLAG_PROMO_CAP_N); + assert_eq!(m_promo_n.to_algebraic(), "g7f8n"); +} + +#[test] +fn test_white_kingside_castling() { + // Test 8: White Kingside Castling + // (from: E1, to: G1) + let m_castle_wk = Move::new(Square::E1, Square::G1, MOVE_FLAG_WK_CASTLE); + assert_eq!(m_castle_wk.to_algebraic(), "O-O"); +} + +#[test] +fn test_white_queenside_castling() { + // Test 9: White Queenside Castling + // (from: E1, to: C1) + let m_castle_wq = Move::new(Square::E1, Square::C1, MOVE_FLAG_WQ_CASTLE); + assert_eq!(m_castle_wq.to_algebraic(), "O-O-O"); +} + +#[test] +fn test_black_kingside_castling() { + // Test 10: Black Kingside Castling + // (from: E8, to: G8) + let m_castle_bk = Move::new(Square::E8, Square::G8, MOVE_FLAG_BK_CASTLE); + assert_eq!(m_castle_bk.to_algebraic(), "O-O"); +} + +#[test] +fn test_black_queenside_castling() { + // Test 11: Black Queenside Castling + // (from: E8, to: C8) + let m_castle_bq = Move::new(Square::E8, Square::C8, MOVE_FLAG_BQ_CASTLE); + assert_eq!(m_castle_bq.to_algebraic(), "O-O-O"); +} diff --git a/tests/pawn_move_generation.rs b/tests/pawn_move_generation.rs new file mode 100644 index 0000000..70654d3 --- /dev/null +++ b/tests/pawn_move_generation.rs @@ -0,0 +1,142 @@ +use chess_engine::board::Board; +use chess_engine::movegen::pawns::generate_pawn_moves; +use chess_engine::r#move::MoveList; + +/// Compares two move list strings ignoring the order of moves. +fn assert_moves_equal(actual_str: &str, expected_str: &str) { + let mut actual_moves: Vec<&str> = actual_str.split_whitespace().collect(); + let mut expected_moves: Vec<&str> = expected_str.split_whitespace().collect(); + + actual_moves.sort(); + expected_moves.sort(); + + assert_eq!(actual_moves, expected_moves); +} + +#[test] +fn test_pawn_moves_start_pos() { + let fen_standard = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + let board = Board::from_fen(fen_standard); + let mut list = MoveList::new(); + generate_pawn_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "a2a3 a2a4 b2b3 b2b4 c2c3 c2c4 d2d3 d2d4 e2e3 e2e4 f2f3 f2f4 g2g3 g2g4 h2h3 h2h4"); +} + +#[test] +fn test_pawn_moves_black_start_pos() { + let fen_standard = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1"; + let board = Board::from_fen(fen_standard); + let mut list = MoveList::new(); + generate_pawn_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "a7a6 a7a5 b7b6 b7b5 c7c6 c7c5 d7d6 d7d5 e7e6 e7e5 f7f6 f7f5 g7g6 g7g5 h7h6 h7h5"); +} + +#[test] +fn test_pawn_moves_black_blocked_and_captures() { + // Black pawns on a7, c7, e7, g7. White pawns on b6, d6, f6. + let fen = "8/p1p1p1p1/1P1P1P2/8/8/8/8/8 b - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_pawn_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "a7a6 a7a5 a7b6 c7c6 c7c5 c7b6 c7d6 e7e6 e7e5 e7d6 e7f6 g7g6 g7g5 g7f6"); +} + +#[test] +fn test_pawn_moves_black_side_captures() { + // Test captures on A and H files (no wrap-around) + let fen = "8/8/8/8/8/1p4p1/P6P/8 b - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_pawn_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "b3b2 b3a2 g3g2 g3h2"); +} + +#[test] +fn test_pawn_moves_white_side_captures() { + // Test captures on A and H files (no wrap-around) + let fen = "8/p6p/1P4P1/8/8/8/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_pawn_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "b6b7 b6a7 g6g7 g6h7"); +} + +#[test] +fn test_pawn_moves_black_en_passant() { + // White just moved e2e4, en passant target is e3. Black pawn on d4. + let fen = "rnbqkbnr/ppp1pppp/8/8/3pP3/8/PPP2PPP/RNBQKBNR b KQkq e3 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_pawn_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "a7a6 a7a5 b7b6 b7b5 c7c6 c7c5 d4d3 d4e3 e7e6 e7e5 f7f6 f7f5 g7g6 g7g5 h7h6 h7h5"); +} + +#[test] +fn test_pawn_moves_white_en_passant() { + // Black just moved d7d5, en passant target is d6. White pawn on e5. + let fen = "rnbqkbnr/ppp1pppp/8/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_pawn_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "a2a3 a2a4 b2b3 b2b4 c2c3 c2c4 d2d3 d2d4 e5e6 e5d6 f2f3 f2f4 g2g3 g2g4 h2h3 h2h4"); +} + +#[test] +fn test_pawn_moves_black_promotion() { + let fen = "8/8/8/8/8/8/p1p1p1p1/8 b - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_pawn_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), + "a2a1q a2a1r a2a1b a2a1n c2c1q c2c1r c2c1b c2c1n e2e1q e2e1r e2e1b e2e1n g2g1q g2g1r g2g1b g2g1n"); +} + +#[test] +fn test_pawn_moves_white_promotion() { + let fen = "8/P1P1P1P1/8/8/8/8/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_pawn_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), + "a7a8q a7a8r a7a8b a7a8n c7c8q c7c8r c7c8b c7c8n e7e8q e7e8r e7e8b e7e8n g7g8q g7g8r g7g8b g7g8n"); +} + +#[test] +fn test_pawn_moves_black_promotion_capture() { + // Black pawn on b2. White rooks on a1 and c1. + let fen = "8/8/8/8/8/8/1p6/R1R5 b - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_pawn_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), + "b2b1q b2b1r b2b1b b2b1n b2a1q b2a1r b2a1b b2a1n b2c1q b2c1r b2c1b b2c1n"); +} + +#[test] +fn test_pawn_moves_white_promotion_capture() { + // White pawn on a7. Black rooks on b8 and d8. + let fen = "1r1r4/P7/8/8/8/8/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_pawn_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), + "a7a8q a7a8r a7a8b a7a8n a7b8q a7b8r a7b8b a7b8n"); +} + +#[test] +fn test_pawn_moves_black_midgame_random() { + let fen = "r1bqkb1r/1p2pppp/p1n2n2/3p4/2BNP3/2N5/PPP2PPP/R1BQK2R b KQkq - 1 6"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_pawn_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "a6a5 b7b6 b7b5 d5c4 d5e4 e7e6 e7e5 g7g6 g7g5 h7h6 h7h5"); +} + +#[test] +fn test_pawn_moves_white_midgame_random() { + let fen = "r1bqk2r/2ppbppp/p1n2n2/1p2p3/B1P1P3/5N2/PP1P1PPP/RNBQR1K1 w kq - 0 7"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_pawn_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "a2a3 b2b3 b2b4 c4c5 c4b5 d2d3 d2d4 g2g3 g2g4 h2h3 h2h4"); +} diff --git a/tests/perft.rs b/tests/perft.rs new file mode 100644 index 0000000..c9fae6c --- /dev/null +++ b/tests/perft.rs @@ -0,0 +1,12 @@ +use chess_engine::board::Board; +use chess_engine::movegen::generate_pseudo_legal_moves; +use chess_engine::r#move::MoveList; + + +// "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" 7 3195901860 "false" +// "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -" 5 193690690 "false" +// "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -" 7 178633661 "false" +// "r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1" 6 706045033 "false" +// "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8" 5 89941194 "false" +// "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10" 5 164075551 "false" +// "r7/4p3/5p1q/3P4/4pQ2/4pP2/6pp/R3K1kr w Q - 1 3" 5 11609488 "false" \ No newline at end of file diff --git a/tests/queen_move_generation.rs b/tests/queen_move_generation.rs new file mode 100644 index 0000000..d78f469 --- /dev/null +++ b/tests/queen_move_generation.rs @@ -0,0 +1,93 @@ +use chess_engine::board::Board; +use chess_engine::movegen::sliders::generate_queen_moves; +use chess_engine::r#move::MoveList; + +/// Compares two move list strings ignoring the order of moves. +fn assert_moves_equal(actual_str: &str, expected_str: &str) { + let mut actual_moves: Vec<&str> = actual_str.split_whitespace().collect(); + let mut expected_moves: Vec<&str> = expected_str.split_whitespace().collect(); + + actual_moves.sort(); + expected_moves.sort(); + + assert_eq!(actual_moves, expected_moves); +} + +#[test] +fn test_queen_moves_single_rook() { + let fen_standard = "8/1Q2p3/8/8/8/8/6p1/8 w - - 0 1"; + let board = Board::from_fen(fen_standard); + let mut list = MoveList::new(); + generate_queen_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "b7a8 b7a7 b7a6 b7b8 b7b6 b7b5 b7b4 b7b3 b7b2 b7b1 b7c8 b7c7 b7c6 b7d7 b7e7 b7d5 b7e4 b7f3 b7g2"); +} + +#[test] +fn test_queen_center_open() { + // Queen in the center, open board, should generate 27 moves + let fen = "4k3/8/8/8/3Q4/8/8/4K3 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_queen_moves(&board, &mut list); + let expected = "d4a1 d4b2 d4c3 d4e5 d4f6 d4g7 d4h8 \ + d4a4 d4b4 d4c4 d4e4 d4f4 d4g4 d4h4 \ + d4d1 d4d2 d4d3 d4d5 d4d6 d4d7 d4d8 \ + d4a7 d4b6 d4c5 d4e3 d4f2 d4g1"; + assert_moves_equal(&list.to_string(), expected); +} + +#[test] +fn test_queen_corner_blocked_friendly() { + // Queen in corner, completely blocked by friendly pieces + let fen = "rnbqkbnr/pppppppp/8/8/8/8/PP1PP1PP/QNBQKBNR w Kkq - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_queen_moves(&board, &mut list); + assert_moves_equal(&list.to_string(), "d1a4 d1b3 d1c2"); +} + +#[test] +fn test_queen_multiple_captures_black() { + // Black queen on h8, with multiple white pieces to capture + let fen = "q3k3/P1P1P1P1/8/8/8/P1P1P1P1/8/4K3 b - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_queen_moves(&board, &mut list); + let expected = "a8b8 a8c8 a8d8 a8a7 a8b7 a8c6 a8d5 a8e4 a8f3 a8g2 a8h1"; + assert_moves_equal(&list.to_string(), expected); +} + +#[test] +fn test_multiple_queens() { + let fen = "4k3/8/8/8/8/8/8/Q3K2Q w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_queen_moves(&board, &mut list); + let expected = "a1a2 a1a3 a1a4 a1a5 a1a6 a1a7 a1a8 a1b1 a1c1 a1d1 a1b2 a1c3 a1d4 a1e5 a1f6 a1g7 a1h8 \ + h1h2 h1h3 h1h4 h1h5 h1h6 h1h7 h1h8 h1g1 h1f1 h1g2 h1f3 h1e4 h1d5 h1c6 h1b7 h1a8"; + assert_moves_equal(&list.to_string(), expected); +} + +#[test] +fn test_queen_rook_only() { + // Queen on d4, bishop moves blocked by friendly pawns + let fen = "4k3/8/8/2P1P3/3Q4/2P1P3/8/4K3 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_queen_moves(&board, &mut list); + let expected = "d4a4 d4b4 d4c4 d4e4 d4f4 d4g4 d4h4 \ + d4d1 d4d2 d4d3 d4d5 d4d6 d4d7 d4d8"; + assert_moves_equal(&list.to_string(), expected); +} + +#[test] +fn test_queen_bishop_only() { + // Queen on d4, rook moves blocked by friendly pawns + let fen = "4k3/8/8/3P4/2PQP3/3P4/8/4K3 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_queen_moves(&board, &mut list); + let expected = "d4a1 d4b2 d4c3 d4e5 d4f6 d4g7 d4h8 \ + d4a7 d4b6 d4c5 d4e3 d4f2 d4g1"; + assert_moves_equal(&list.to_string(), expected); +} diff --git a/tests/rook_move_generation.rs b/tests/rook_move_generation.rs new file mode 100644 index 0000000..e6afd4f --- /dev/null +++ b/tests/rook_move_generation.rs @@ -0,0 +1,101 @@ +use chess_engine::board::Board; +use chess_engine::movegen::sliders::generate_rook_moves; +use chess_engine::r#move::MoveList; + +/// Compares two move list strings ignoring the order of moves. +fn assert_moves_equal(actual_str: &str, expected_str: &str) { + let mut actual_moves: Vec<&str> = actual_str.split_whitespace().collect(); + let mut expected_moves: Vec<&str> = expected_str.split_whitespace().collect(); + + actual_moves.sort(); + expected_moves.sort(); + + assert_eq!(actual_moves, expected_moves); +} + +#[test] +fn test_rook_moves_single_rook() { + let fen_standard = "8/8/8/2b5/2Rb4/2b5/8/8 w - - 0 1"; + let board = Board::from_fen(fen_standard); + let mut list = MoveList::new(); + generate_rook_moves(&board, &mut list); + // This FEN has a White Rook at c4, and Black Bishops at c5, d4, and c3. + // It should be able to capture all three and move left to a4/b4. + assert_moves_equal(&list.to_string(), "c4a4 c4b4 c4c3 c4c5 c4d4"); +} + +#[test] +fn test_rook_moves_empty_board() { + let fen = "8/8/8/8/3R4/8/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_rook_moves(&board, &mut list); + let expected = "d4d1 d4d2 d4d3 d4d5 d4d6 d4d7 d4d8 d4a4 d4b4 d4c4 d4e4 d4f4 d4g4 d4h4"; + assert_moves_equal(&list.to_string(), expected); +} + +#[test] +fn test_rook_moves_corner_blocked_black() { + let fen = "r6k/1p6/8/8/8/8/8/K7 b - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_rook_moves(&board, &mut list); + // Black rook at a8. Friendly king at h8, friendly pawn at b7. + // Rook can move down the a-file and right along the 8th rank, stopping before h8. + let expected = "a8a7 a8a6 a8a5 a8a4 a8a3 a8a2 a8a1 a8b8 a8c8 a8d8 a8e8 a8f8 a8g8"; + assert_moves_equal(&list.to_string(), expected); +} + +#[test] +fn test_rook_moves_double_rooks_friendly_block() { + let fen = "8/8/8/8/R3R3/8/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_rook_moves(&board, &mut list); + // Rooks at a4 and e4. They block each other horizontally. + // Rook a4 moves a1-a8 and b4, c4, d4. + // Rook e4 moves e1-e8, f4, g4, h4 AND d4, c4, b4. + let expected = "a4a1 a4a2 a4a3 a4a5 a4a6 a4a7 a4a8 a4b4 a4c4 a4d4 \ + e4e1 e4e2 e4e3 e4e5 e4e6 e4e7 e4e8 e4f4 e4g4 e4h4 \ + e4d4 e4c4 e4b4"; + assert_moves_equal(&list.to_string(), expected); +} + +#[test] +fn test_rook_moves_capture_stops_movegen() { + let fen = "r7/P7/8/8/8/8/8/8 b - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_rook_moves(&board, &mut list); + // Black rook at a8, White pawn at a7. + // The rook can capture at a7, but cannot move past it. + // It can still move horizontally. + let expected = "a8a7 a8b8 a8c8 a8d8 a8e8 a8f8 a8g8 a8h8"; + assert_moves_equal(&list.to_string(), expected); +} + +#[test] +fn test_rook_moves_completely_blocked_friendly() { + let fen = "8/8/8/1P6/PRP5/1P6/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_rook_moves(&board, &mut list); + // White rook at b4. + // Blocked by P(b5), P(b3), P(a4), P(c4). + // Should have 0 moves. + assert_moves_equal(&list.to_string(), ""); +} + +#[test] +fn test_rook_moves_ignores_absolute_pin() { + let fen = "r3k3/8/8/8/R3K3/8/8/8 w - - 0 1"; + let board = Board::from_fen(fen); + let mut list = MoveList::new(); + generate_rook_moves(&board, &mut list); + // White rook at a4 is absolutely pinned to King at e4 by Black rook at a8. + // A pseudo-legal generator should *ignore* the pin. + // It should generate vertical moves (including capture at a8) + // and horizontal moves (stopping before the friendly King at e4). + let expected = "a4a1 a4a2 a4a3 a4a5 a4a6 a4a7 a4a8 a4b4 a4c4 a4d4"; + assert_moves_equal(&list.to_string(), expected); +}