From a93bfe258cf8fcd87a9d2960641b9699851fc16c Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 19 Nov 2025 22:05:37 +0100 Subject: [PATCH] restructure --- benches/eval.rs | 2 +- progress_tracking/progress.xlsx | Bin 9671 -> 6153 bytes src/bin/suite.rs | 16 ++--- src/board.rs | 38 +---------- src/engine.rs | 11 +-- src/{eval/basic.rs => eval.rs} | 17 +---- src/eval/mod.rs | 2 - src/lib.rs | 3 +- src/move.rs | 1 - src/movegen/legal_check.rs | 3 - src/movegen/non_sliders.rs | 67 +++++++++---------- src/movegen/pawns.rs | 1 - src/movegen/sliders.rs | 2 +- src/movegen/tables.rs | 59 ++-------------- src/parsing.rs | 17 ++--- src/{eval/piece_square_tables.rs => psqt.rs} | 3 +- src/{search/alpha_beta.rs => search.rs} | 47 +++++-------- src/search/minimax.rs | 63 ----------------- src/search/mod.rs | 2 - src/tt.rs | 2 - src/uci.rs | 4 -- src/zobrist.rs | 27 +++----- tests/perft.rs | 3 - 23 files changed, 83 insertions(+), 307 deletions(-) rename src/{eval/basic.rs => eval.rs} (67%) delete mode 100644 src/eval/mod.rs rename src/{eval/piece_square_tables.rs => psqt.rs} (97%) rename src/{search/alpha_beta.rs => search.rs} (74%) delete mode 100644 src/search/minimax.rs delete mode 100644 src/search/mod.rs diff --git a/benches/eval.rs b/benches/eval.rs index 1e977f4..c689183 100644 --- a/benches/eval.rs +++ b/benches/eval.rs @@ -1,6 +1,6 @@ use chess_engine::board::Board; use criterion::{criterion_group, criterion_main, Criterion}; -use chess_engine::eval::basic::evaluate_board; +use chess_engine::eval::evaluate_board; use chess_engine::zobrist::init_zobrist; fn run_eval_benchmark(c: &mut Criterion) { diff --git a/progress_tracking/progress.xlsx b/progress_tracking/progress.xlsx index b8fca5609831f3f384584f84d16fcfd89e8c8ba1..2ad473c2ad78a78061e9347505968752aa42e5b9 100644 GIT binary patch literal 6153 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 Vgvah506=+sg&w!42JiRZ{{RmM-l6~i literal 9671 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 io::Result>> { let file = File::open(path)?; @@ -25,30 +24,25 @@ fn load_csv(path: &str) -> io::Result>> { fn main() { init_zobrist(); + let time_limit_ms = 1000_u64; + let time_limit = Duration::from_millis(time_limit_ms); + let mut total_tests: f32 = 0.0; let mut correct_tests: f32 = 0.0; let sts = load_csv("src/bin/stockfish_testsuite.csv").unwrap(); let mut engine = Engine::new("Yakari".to_string(), "EiSiMo".to_string()); - - // Set the time limit to 1 second - let time_limit = Duration::from_millis(1000); - - + for test in &sts { let fen = &test[0]; let bm = &test[1]; engine.setpos_fen(fen); - // Record start time let start_time = Instant::now(); - let result = engine.search(990_u64); + let result = engine.search(time_limit_ms-1); - // Calculate duration let duration = start_time.elapsed(); - - // Check if the test exceeded the time limit if duration > time_limit { panic!( "Test exceeded 1 second limit: {:?} for FEN: {}", diff --git a/src/board.rs b/src/board.rs index a94242c..15049cc 100644 --- a/src/board.rs +++ b/src/board.rs @@ -1,6 +1,6 @@ use crate::r#move::*; use crate::square::Square; -use crate::zobrist::{self, zobrist_keys}; // Import Zobrist +use crate::zobrist::{self, zobrist_keys}; use std::ops::Not; pub const CASTLING_WK_FLAG: u8 = 1; @@ -78,12 +78,10 @@ pub struct Board { pub halfmove_clock: u8, pub fullmove_number: u16, - // Added Zobrist Hash pub hash: u64, } impl Board { - // Helper to get the EP file index (0-7) or 8 if None fn ep_file_index(ep: Option) -> usize { match ep { Some(sq) => (sq as usize) % 8, @@ -91,12 +89,10 @@ impl Board { } } - // Should be called after loading FEN or creating board pub fn recalculate_hash(&mut self) { let keys = zobrist_keys(); let mut hash = 0; - // 1. Pieces for sq in 0..64 { if let Some(pt) = self.pieces_on_squares[sq] { let color = if (self.pieces[pt as usize][Color::White as usize] & (1 << sq)) != 0 { @@ -107,23 +103,16 @@ impl Board { hash ^= keys.pieces[zobrist::piece_index(pt, color)][sq]; } } - - // 2. Castling hash ^= keys.castling[self.castling_rights as usize]; - - // 3. En Passant hash ^= keys.en_passant[Self::ep_file_index(self.en_passant_target)]; - // 4. Side to move if self.side_to_move == Color::Black { hash ^= keys.side_to_move; } self.hash = hash; } - - // --- Original methods with added hashing --- - + fn rm_piece( &mut self, target_square: Square, @@ -132,7 +121,6 @@ impl Board { let target_square_bitboard = target_square.to_bitboard(); let piece_type = self.pieces_on_squares[target_square as usize].unwrap(); - // UPDATE HASH: Remove piece let keys = zobrist_keys(); self.hash ^= keys.pieces[zobrist::piece_index(piece_type, color)][target_square as usize]; @@ -149,7 +137,6 @@ impl Board { fn put_piece(&mut self, target_square: Square, color: Color, piece_type: PieceType) { let target_square_bitboard = target_square.to_bitboard(); - // UPDATE HASH: Add piece let keys = zobrist_keys(); self.hash ^= keys.pieces[zobrist::piece_index(piece_type, color)][target_square as usize]; @@ -169,12 +156,9 @@ impl Board { pub fn make_move(&mut self, mv: Move) -> UndoMove { let keys = zobrist_keys(); - // HASH UPDATE: Remove old state (EP and Castling) - // XORing removes the old value from the hash self.hash ^= keys.en_passant[Self::ep_file_index(self.en_passant_target)]; self.hash ^= keys.castling[self.castling_rights as usize]; - // 1. Extract parts from move let from = mv.get_from(); let to = mv.get_to(); let flags = mv.get_flags(); @@ -189,7 +173,6 @@ impl Board { let mut opt_captured_piece: Option = None; let mut opt_en_passant_target: Option = None; - // 4. Make the actual moves (rm_piece/put_piece update piece hashes automatically) match flags { MOVE_FLAG_QUIET => { self.move_piece(from, to, self.side_to_move); @@ -262,7 +245,6 @@ impl Board { _ => { panic!("unable to make_move: invalid flags: {}", flags); } } - // 5. Update castling rights let wk = self.pieces[PieceType::King as usize][Color::White as usize]; let wr = self.pieces[PieceType::Rook as usize][Color::White as usize]; let bk = self.pieces[PieceType::King as usize][Color::Black as usize]; @@ -275,16 +257,12 @@ impl Board { castling_right_wk | castling_right_wq | castling_right_bk | castling_right_bq; self.castling_rights = self.castling_rights & new_castling_rights; - // 6. Update en passant target self.en_passant_target = opt_en_passant_target; - // HASH UPDATE: Add new state self.hash ^= keys.en_passant[Self::ep_file_index(self.en_passant_target)]; self.hash ^= keys.castling[self.castling_rights as usize]; - // HASH UPDATE: Side to move (always changes) self.hash ^= keys.side_to_move; - // 7. Update halfmove clock let new_friendly_pawns = self.pieces[PieceType::Pawn as usize][self.side_to_move as usize]; let new_total_pieces = self.all_occupied.count_ones(); let pawns_changed = old_friendly_pawns ^ new_friendly_pawns; @@ -292,13 +270,10 @@ impl Board { let increase_halfmove_clock = ((pawns_changed + piece_captured) == 0) as u8; self.halfmove_clock = increase_halfmove_clock * (self.halfmove_clock + 1); - // 8. Increase fullmove self.fullmove_number += self.side_to_move as u16; - // 9. Flip side self.side_to_move = !self.side_to_move; - // 10. Return Undo UndoMove::new( mv, opt_captured_piece, @@ -311,35 +286,26 @@ impl Board { pub fn undo_move(&mut self, undo_info: UndoMove) { let keys = zobrist_keys(); - // HASH UPDATE: We must remove the CURRENT state hash before overwriting state variables. - // 1. Remove current side hash (effectively flipping it back) self.hash ^= keys.side_to_move; - // 2. Remove current castling and EP hash self.hash ^= keys.castling[self.castling_rights as usize]; self.hash ^= keys.en_passant[Self::ep_file_index(self.en_passant_target)]; - // 1. Restore simple state self.castling_rights = undo_info.old_castling_rights; self.en_passant_target = undo_info.old_en_passant_square; self.halfmove_clock = undo_info.old_halfmove_clock; - // HASH UPDATE: Restore OLD state hash self.hash ^= keys.castling[self.castling_rights as usize]; self.hash ^= keys.en_passant[Self::ep_file_index(self.en_passant_target)]; - // 2. Flip side *before* piece ops self.side_to_move = !self.side_to_move; - // 3. Decrement fullmove self.fullmove_number -= self.side_to_move as u16; - // 4. Extract move data let mv = undo_info.mv; let from = mv.get_from(); let to = mv.get_to(); let flags = mv.get_flags(); - // 5. Reverse pieces (helpers will update hash automatically) match flags { MOVE_FLAG_QUIET => { self.move_piece(to, from, self.side_to_move); diff --git a/src/engine.rs b/src/engine.rs index 2d7c882..389f881 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,6 @@ use crate::board::Board; use crate::r#move::Move; -use crate::search::alpha_beta::alpha_beta; +use crate::search::alpha_beta; use crate::tt::TranspositionTable; // Import TT use std::time::{Instant, Duration}; @@ -42,14 +42,8 @@ impl Engine { let start_time = Instant::now(); let time_limit = Duration::from_millis(time_limit_ms); - // We usually clear the TT or age it before a new search, - // but for now we keep it to learn from previous moves. - // self.tt.clear(); - let mut nodes = 0; - - // Initial search at depth 1 - // Note: We pass &mut self.tt to alpha_beta + let (mut opt_move, mut _score) = alpha_beta( &mut self.board, 1, @@ -64,7 +58,6 @@ impl Engine { let mut depth = 2; - // Iterative Deepening while start_time.elapsed() < time_limit { let (new_move, new_score) = alpha_beta( &mut self.board, diff --git a/src/eval/basic.rs b/src/eval.rs similarity index 67% rename from src/eval/basic.rs rename to src/eval.rs index a02dac1..5a0ae5f 100644 --- a/src/eval/basic.rs +++ b/src/eval.rs @@ -1,5 +1,5 @@ use crate::board::*; -use crate::eval::piece_square_tables::PSQT; +use crate::psqt::PSQT; pub fn evaluate_board(board: &Board) -> i32 { let mut mg_score = 0_i32; @@ -7,28 +7,21 @@ pub fn evaluate_board(board: &Board) -> i32 { let mut phase = 0_i32; // We use a macro to force loop unrolling. - // This enables the compiler to use constant offsets for PSQT access - // instead of calculating addresses at runtime based on a loop variable. macro_rules! score_piece { ($pt:expr, $phase_weight:expr) => { - // --- WHITE --- let mut pieces = board.pieces[$pt][Color::White as usize]; if pieces > 0 { - // Phase calculation uses count_ones (POPPCNT) which is very fast phase += (pieces.count_ones() as i32) * $phase_weight; while pieces > 0 { let sq = pieces.trailing_zeros() as usize; pieces &= pieces - 1; // Clear LS1B - // Material is already baked into PSQT, so we just add the table value - // Since $pt is a const literal here, this compiles to a direct memory access mg_score += PSQT[$pt][Color::White as usize][0][sq]; eg_score += PSQT[$pt][Color::White as usize][1][sq]; } } - // --- BLACK --- let mut pieces = board.pieces[$pt][Color::Black as usize]; if pieces > 0 { phase += (pieces.count_ones() as i32) * $phase_weight; @@ -44,21 +37,13 @@ pub fn evaluate_board(board: &Board) -> i32 { }; } - // Explicitly unrolled execution order - // Pawn (0), Weight 0 score_piece!(0, 0); - // Knight (1), Weight 1 score_piece!(1, 1); - // Bishop (2), Weight 1 score_piece!(2, 1); - // Rook (3), Weight 2 score_piece!(3, 2); - // Queen (4), Weight 4 score_piece!(4, 4); - // King (5), Weight 0 (Phase doesn't change) score_piece!(5, 0); - // Tapered Evaluation let phase = phase.min(24); let mg_phase = phase; let eg_phase = 24 - phase; diff --git a/src/eval/mod.rs b/src/eval/mod.rs deleted file mode 100644 index c822368..0000000 --- a/src/eval/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod basic; -pub mod piece_square_tables; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 1e6d087..71e2d90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,8 +5,9 @@ pub mod movegen; pub mod eval; pub mod display; pub mod parsing; -pub mod search; pub mod engine; pub mod uci; pub mod tt; pub mod zobrist; +pub mod search; +pub mod psqt; diff --git a/src/move.rs b/src/move.rs index be737fd..e430bce 100644 --- a/src/move.rs +++ b/src/move.rs @@ -87,7 +87,6 @@ impl MoveList { self.count += 1; } - // Added swap method as requested pub fn swap(&mut self, a: usize, b: usize) { self.moves[..self.count].swap(a, b); } diff --git a/src/movegen/legal_check.rs b/src/movegen/legal_check.rs index bf32dbf..27ef8e5 100644 --- a/src/movegen/legal_check.rs +++ b/src/movegen/legal_check.rs @@ -7,15 +7,12 @@ pub fn is_current_king_attacked(board: &Board) -> bool { is_square_attacked(board, SQUARES[king.trailing_zeros() as usize], board.side_to_move) } -/// Checks if the king of the side that is NOT to move is in check pub fn is_other_king_attacked(board: &Board) -> bool { let king = board.pieces[PieceType::King as usize][!board.side_to_move as usize]; is_square_attacked(board, SQUARES[king.trailing_zeros() as usize], board.side_to_move) } -// TODO check if castle is legal (squares in between) -/// calculate if a square on the board is attacked by a color pub fn is_square_attacked(board: &Board, square: Square, color: Color) -> bool { // 1. Non sliding // 1.1 Pawn diff --git a/src/movegen/non_sliders.rs b/src/movegen/non_sliders.rs index 70ade3d..7d2d39e 100644 --- a/src/movegen/non_sliders.rs +++ b/src/movegen/non_sliders.rs @@ -1,17 +1,18 @@ -// FILENAME: non_sliders.rs -use crate::board::*; -use crate::movegen::legal_check::is_square_attacked; -use crate::r#move::*; -use crate::square::*; use super::tables::{KING_ATTACKS, KNIGHT_ATTACKS}; +use crate::board::*; +use crate::r#move::*; +use crate::movegen::legal_check::is_square_attacked; +use crate::square::*; 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]; + 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]; + 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]; @@ -58,7 +59,6 @@ pub fn generate_king_moves(board: &Board, list: &mut MoveList) { attacks &= attacks - 1; } - // TODO Optimize the is attacked testing on castling // 2. Generate castling king moves if board.side_to_move == Color::White { // King must not be in check to castle @@ -67,46 +67,39 @@ pub fn generate_king_moves(board: &Board, list: &mut MoveList) { } // Kingside (OO) - if (board.castling_rights & CASTLING_WK_FLAG) != 0 { - if (board.all_occupied & CASTLING_WK_MASK) == 0 { - // Check F1 (path). G1 (landing) is checked by perft function. - if !is_square_attacked(board, Square::F1, Color::Black) { + if (board.castling_rights & CASTLING_WK_FLAG) != 0 + && (board.all_occupied & CASTLING_WK_MASK) == 0 + && !is_square_attacked(board, Square::F1, Color::Black) { list.push(Move::new(Square::E1, Square::G1, MOVE_FLAG_WK_CASTLE)); } - } - } + // Queenside (OOO) - if (board.castling_rights & CASTLING_WQ_FLAG) != 0 { - if (board.all_occupied & CASTLING_WQ_MASK) == 0 { - // Check D1 (path). C1 (landing) is checked by perft function. B1 is irrelevant. - if !is_square_attacked(board, Square::D1, Color::Black) { - list.push(Move::new(Square::E1, Square::C1, MOVE_FLAG_WQ_CASTLE)); - } - } + if (board.castling_rights & CASTLING_WQ_FLAG) != 0 + && (board.all_occupied & CASTLING_WQ_MASK) == 0 + && !is_square_attacked(board, Square::D1, Color::Black) + { + list.push(Move::new(Square::E1, Square::C1, MOVE_FLAG_WQ_CASTLE)); } - } else { // Black + } else { + // Black // King must not be in check to castle if is_square_attacked(board, Square::E8, Color::White) { return; } // Kingside (OO) - if (board.castling_rights & CASTLING_BK_FLAG) != 0 { - if (board.all_occupied & CASTLING_BK_MASK) == 0 { - // Check F8 (path). G8 (landing) is checked by perft function. - if !is_square_attacked(board, Square::F8, Color::White) { - list.push(Move::new(Square::E8, Square::G8, MOVE_FLAG_BK_CASTLE)); - } - } + if (board.castling_rights & CASTLING_BK_FLAG) != 0 + && (board.all_occupied & CASTLING_BK_MASK) == 0 + && !is_square_attacked(board, Square::F8, Color::White) + { + list.push(Move::new(Square::E8, Square::G8, MOVE_FLAG_BK_CASTLE)); } // Queenside (OOO) - if (board.castling_rights & CASTLING_BQ_FLAG) != 0 { - if (board.all_occupied & CASTLING_BQ_MASK) == 0 { - // Check D8 (path). C8 (landing) is checked by perft function. B8 is irrelevant. - if !is_square_attacked(board, Square::D8, Color::White) { - list.push(Move::new(Square::E8, Square::C8, MOVE_FLAG_BQ_CASTLE)); - } - } + if (board.castling_rights & CASTLING_BQ_FLAG) != 0 + && (board.all_occupied & CASTLING_BQ_MASK) == 0 + && !is_square_attacked(board, Square::D8, Color::White) + { + list.push(Move::new(Square::E8, Square::C8, MOVE_FLAG_BQ_CASTLE)); } } -} \ No newline at end of file +} diff --git a/src/movegen/pawns.rs b/src/movegen/pawns.rs index 0f58cc9..c7a09ce 100644 --- a/src/movegen/pawns.rs +++ b/src/movegen/pawns.rs @@ -93,7 +93,6 @@ pub fn generate_pawn_moves(board: &Board, list: &mut MoveList) { // 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]; diff --git a/src/movegen/sliders.rs b/src/movegen/sliders.rs index 24a2dbd..fe5fee8 100644 --- a/src/movegen/sliders.rs +++ b/src/movegen/sliders.rs @@ -1,5 +1,5 @@ use crate::board::*; -use crate::r#move::{Move, MoveList, MOVE_FLAG_CAPTURE, MOVE_FLAG_QUIET}; +use crate::r#move::*; use crate::square::SQUARES; use super::tables::*; diff --git a/src/movegen/tables.rs b/src/movegen/tables.rs index 3bb3a4c..cf1d7e0 100644 --- a/src/movegen/tables.rs +++ b/src/movegen/tables.rs @@ -563,15 +563,11 @@ pub const RELEVANT_BITS_BISHOP: [u8; 64] = [ 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; @@ -580,7 +576,6 @@ fn calculate_rook_attacks_slowly(square: usize, blockers: u64) -> u64 { } } - // 2. Nach Süden (rank -) for r in (0..rank).rev() { let target_sq = r * 8 + file; attacks |= 1_u64 << target_sq; @@ -589,7 +584,6 @@ fn calculate_rook_attacks_slowly(square: usize, blockers: u64) -> u64 { } } - // 3. Nach Osten (file +) for f in (file + 1)..=7 { let target_sq = rank * 8 + f; attacks |= 1_u64 << target_sq; @@ -598,7 +592,6 @@ fn calculate_rook_attacks_slowly(square: usize, blockers: u64) -> u64 { } } - // 4. Nach Westen (file -) for f in (0..file).rev() { let target_sq = rank * 8 + f; attacks |= 1_u64 << target_sq; @@ -611,7 +604,6 @@ fn calculate_rook_attacks_slowly(square: usize, blockers: u64) -> u64 { } 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]); @@ -620,32 +612,18 @@ fn generate_final_rook_attacks() -> Box<[[u64; 4096]; 64]> { 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 premask = PREMASKS_ROOK[square_index]; + let magic = MAGICS_ROOK[square_index]; + let relevant_bits = RELEVANT_BITS_ROOK[square_index]; 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; } @@ -657,39 +635,31 @@ fn generate_final_rook_attacks() -> Box<[[u64; 4096]; 64]> { } 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 + break; } r += 1; f += 1; } - // 2. Nach Süd-Osten (rank-, file+) - r = rank; // Start bei rank, da 0..rank fehlschlägt wenn rank = 0 + r = rank; f = file + 1; - while r > 0 && f <= 7 { // r > 0 (da r = rank-1 in der 1. Iteration) + while r > 0 && f <= 7 { r -= 1; let target_sq = r * 8 + f; attacks |= 1_u64 << target_sq; @@ -699,7 +669,6 @@ fn calculate_bishop_attacks_slowly(square: usize, blockers: u64) -> u64 { f += 1; } - // 3. Nach Süd-Westen (rank-, file-) r = rank; f = file; while r > 0 && f > 0 { // r > 0 und f > 0 @@ -712,7 +681,6 @@ fn calculate_bishop_attacks_slowly(square: usize, blockers: u64) -> u64 { } } - // 4. Nach Nord-Westen (rank+, file-) r = rank + 1; f = file; while r <= 7 && f > 0 { // f > 0 @@ -729,9 +697,6 @@ fn calculate_bishop_attacks_slowly(square: usize, blockers: u64) -> u64 { } 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]); @@ -740,30 +705,21 @@ fn generate_final_bishop_attacks() -> Box<[[u64; 512]; 64]> { 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; } @@ -775,8 +731,5 @@ fn generate_final_bishop_attacks() -> Box<[[u64; 512]; 64]> { } 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/parsing.rs b/src/parsing.rs index 1d40200..761025e 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -4,12 +4,9 @@ use crate::r#move::*; use crate::square::Square; 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 die Arrays let mut pieces = [[0u64; 2]; 6]; let mut occupied = [0u64; 2]; let mut pieces_on_squares = [None; 64]; @@ -20,7 +17,7 @@ impl Board { let mut file = 0; for c in placement.chars() { - if c.is_digit(10) { + if c.is_ascii_digit() { file += c.to_digit(10).unwrap_or(0) as usize; } else if c == '/' { rank -= 1; @@ -122,8 +119,8 @@ impl Board { "-" => 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 file = chars[0] as u8 - b'a'; + let rank = chars[1] as u8 - b'1'; let sq_index = rank * 8 + file; Some(unsafe { mem::transmute::(sq_index) }) } @@ -223,8 +220,8 @@ impl Board { 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; + let file = sq_index % 8; + let rank = sq_index / 8; fen.push((b'a' + file) as char); fen.push((b'1' + rank) as char); } else { @@ -254,8 +251,8 @@ impl Move { /// Converts algebraic notation (e.g., "a1") to a Square. /// Assumes valid input. fn alg_to_square(alg: &str) -> Square { - let file = (alg.as_bytes()[0] - b'a') as u8; - let rank = (alg.as_bytes()[1] - b'1') as u8; + let file = alg.as_bytes()[0] - b'a'; + let rank = alg.as_bytes()[1] - b'1'; let sq_index = rank * 8 + file; // This is unsafe, but we assume valid algebraic notation unsafe { mem::transmute::(sq_index) } diff --git a/src/eval/piece_square_tables.rs b/src/psqt.rs similarity index 97% rename from src/eval/piece_square_tables.rs rename to src/psqt.rs index 18d1ca7..32c5d26 100644 --- a/src/eval/piece_square_tables.rs +++ b/src/psqt.rs @@ -1,4 +1,3 @@ -// --- Material Values (PeSTO Standard) --- const MG_PAWN_VAL: i32 = 82; const EG_PAWN_VAL: i32 = 94; const MG_KNIGHT_VAL: i32 = 337; @@ -9,7 +8,7 @@ const MG_ROOK_VAL: i32 = 477; const EG_ROOK_VAL: i32 = 512; const MG_QUEEN_VAL: i32 = 1025; const EG_QUEEN_VAL: i32 = 936; -const MG_KING_VAL: i32 = 0; // King usually has no material value in eval summation (captured = game over) +const MG_KING_VAL: i32 = 0; const EG_KING_VAL: i32 = 0; pub const MG_PAWN_TABLE: [i32; 64] = [ diff --git a/src/search/alpha_beta.rs b/src/search.rs similarity index 74% rename from src/search/alpha_beta.rs rename to src/search.rs index 1f2305c..506b7b5 100644 --- a/src/search/alpha_beta.rs +++ b/src/search.rs @@ -1,5 +1,5 @@ use crate::board::{Board, Color}; -use crate::eval::basic::evaluate_board; +use crate::eval::evaluate_board; use crate::movegen::generate_pseudo_legal_moves; use crate::movegen::legal_check::*; use crate::r#move::{Move, MoveList}; @@ -51,28 +51,20 @@ pub fn alpha_beta( nodes: &mut u64, tt: &mut TranspositionTable, // Added TT parameter ) -> (Option, i32) { - // Check for time usage - if *nodes % 4096 == 0 { - if start_time.elapsed() > time_limit { - return (None, 0); - } + if (*nodes).is_multiple_of(4096) + && start_time.elapsed() > time_limit { + return (None, 0); } *nodes += 1; - - // ----------------------- - // 1. TT PROBE - // ----------------------- - // We assume board.hash holds the current Zobrist key (u64) + let tt_key = board.hash; let mut tt_move: Option = None; if let Some(entry) = tt.probe(tt_key) { - // We remember the move from TT to sort it first later - if entry.bm.0 != 0 { // Check if move is valid (not 0) + if entry.bm.0 != 0 { tt_move = Some(entry.bm); } - // Can we use the score for a cutoff? if entry.depth >= depth { let tt_score = score_from_tt(entry.score, ply); @@ -99,13 +91,8 @@ pub fn alpha_beta( let mut list = MoveList::new(); generate_pseudo_legal_moves(board, &mut list); - - // ----------------------- - // MOVE ORDERING (TT Move First) - // ----------------------- - // If we have a move from TT, we want to search it first! + if let Some(tm) = tt_move { - // Find the move in the list and swap it to the front (index 0) for i in 0..list.len() { if list[i] == tm { list.swap(0, i); @@ -117,13 +104,12 @@ pub fn alpha_beta( let mut best_move: Option = None; let mut best_score: i32 = -i32::MAX; let mut legal_moves_found = false; - let alpha_orig = alpha; // Save original alpha to determine NodeType later + let alpha_orig = alpha; for i in 0..list.len() { let mv = list[i]; let undo_mv = board.make_move(mv); - // Optimization: Check legality locally if possible, but for now rely on King check let is_illegal = is_other_king_attacked(board); if is_illegal { board.undo_move(undo_mv); @@ -133,7 +119,7 @@ pub fn alpha_beta( let (_, score) = alpha_beta(board, depth - 1, ply + 1, -beta, -alpha, start_time, time_limit, nodes, tt); - if *nodes % 4096 == 0 && start_time.elapsed() > time_limit { + if (*nodes).is_multiple_of(4096) && start_time.elapsed() > time_limit { board.undo_move(undo_mv); return (None, 0); } @@ -152,7 +138,7 @@ pub fn alpha_beta( } if alpha >= beta { - break; // Beta cutoff + break; } } @@ -163,19 +149,16 @@ pub fn alpha_beta( return (None, 0); } } - - // ----------------------- - // 2. TT STORE - // ----------------------- + let node_type = if best_score <= alpha_orig { - NodeType::Alpha // We didn't improve alpha (Fail Low) -> Upper Bound + NodeType::Alpha } else if best_score >= beta { - NodeType::Beta // We caused a cutoff (Fail High) -> Lower Bound + NodeType::Beta } else { - NodeType::Exact // We found a score between alpha and beta + NodeType::Exact }; - let save_move = best_move.unwrap_or(Move(0)); // Use dummy 0 if no best move + let save_move = best_move.unwrap_or(Move(0)); let save_score = score_to_tt(best_score, ply); tt.store(tt_key, save_score, depth, node_type, save_move); diff --git a/src/search/minimax.rs b/src/search/minimax.rs deleted file mode 100644 index 146dc92..0000000 --- a/src/search/minimax.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::board::{Board, Color}; // <-- Assuming you have a Color enum (e.g., Color::White, Color::Black) -use crate::eval::basic::evaluate_board; -use crate::movegen::generate_pseudo_legal_moves; -use crate::movegen::legal_check::is_other_king_attacked; -use crate::r#move::{Move, MoveList}; - -// A score high enough to be > any material eval, but low enough to not overflow when adding ply -const MATE_SCORE: i32 = 1_000_000; - -fn evaluate_board_relative(board: &Board) -> i32 { - let static_eval = evaluate_board(board); - match board.side_to_move { - Color::White => static_eval, - Color::Black => -static_eval, - } -} - -pub fn minimax(board: &mut Board, depth: u8, ply: u8) -> (Option, i32) { - if depth == 0 { - return (None, evaluate_board_relative(board)); - } - - let mut list = MoveList::new(); - generate_pseudo_legal_moves(board, &mut list); - let mut best_move: Option = None; - let mut best_score: i32 = -i32::MAX; // Start with the worst possible score - let mut legal_moves_found = false; - - for mv in list.iter() { - let undo_mv = board.make_move(*mv); - let is_illegal = is_other_king_attacked(board); - if is_illegal { - board.undo_move(undo_mv); - continue; - } - legal_moves_found = true; - - // Recursive call, incrementing ply - let (_, score) = minimax(board, depth - 1, ply + 1); - let current_score = -score; - - if current_score > best_score { - best_score = current_score; - best_move = Some(*mv); - } - - board.undo_move(undo_mv); - } - - if !legal_moves_found { - if is_other_king_attacked(board) { - // Checkmate - // The score is *less* negative the *longer* it takes to be mated (higher ply) - // This translates to a *higher* score for the winner for a *faster* mate - return (None, -MATE_SCORE + (ply as i32)); - } else { - // Stalemate - return (None, 0); - } - } - - (best_move, best_score) -} \ No newline at end of file diff --git a/src/search/mod.rs b/src/search/mod.rs deleted file mode 100644 index 30df11f..0000000 --- a/src/search/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod minimax; -pub mod alpha_beta; \ No newline at end of file diff --git a/src/tt.rs b/src/tt.rs index fe71214..64f3155 100644 --- a/src/tt.rs +++ b/src/tt.rs @@ -1,6 +1,4 @@ use std::mem::size_of; -// I assume you have a move.rs file. -// If you call the file "move.rs", you must import it as r#move because "move" is a keyword. use crate::r#move::Move; #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/src/uci.rs b/src/uci.rs index 53526ad..f8354d1 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -1,11 +1,8 @@ -// uci.rs - use std::io::{self, BufRead}; use crate::engine::Engine; pub fn uci_mainloop(engine: &mut Engine) { loop { - // Start the main UCI loop for line in io::stdin().lock().lines() { let input = line.unwrap_or_else(|_| "quit".to_string()); let tokens: Vec<&str> = input.split_whitespace().collect(); @@ -41,7 +38,6 @@ pub fn uci_mainloop(engine: &mut Engine) { } } "go" => { - // TODO add a lot functionality println!("bestmove {}", engine.search(1000_u64)); } "stop" => { diff --git a/src/zobrist.rs b/src/zobrist.rs index a33f807..7376467 100644 --- a/src/zobrist.rs +++ b/src/zobrist.rs @@ -1,8 +1,6 @@ use crate::board::{Color, PieceType}; -use crate::square::Square; use std::sync::OnceLock; -// We use a simple Xorshift generator to avoid external dependencies like 'rand' struct Xorshift { state: u64, } @@ -24,17 +22,15 @@ impl Xorshift { #[derive(Debug)] pub struct ZobristKeys { - pub pieces: [[u64; 64]; 12], // [PieceType 0-5 + Color offset][Square] - pub castling: [u64; 16], // 16 combinations of castling rights - pub en_passant: [u64; 9], // 8 files + 1 for "no ep" + pub pieces: [[u64; 64]; 12], + pub castling: [u64; 16], + pub en_passant: [u64; 9], pub side_to_move: u64, } -// Thread-safe, write-once global storage static KEYS: OnceLock = OnceLock::new(); pub fn init_zobrist() { - // If already initialized, do nothing if KEYS.get().is_some() { return; } @@ -42,20 +38,20 @@ pub fn init_zobrist() { let mut rng = Xorshift::new(1070372); // Fixed seed for reproducibility let mut pieces = [[0; 64]; 12]; - for i in 0..12 { - for j in 0..64 { - pieces[i][j] = rng.next(); + for piece_squares in pieces.iter_mut() { + for square_key in piece_squares.iter_mut() { + *square_key = rng.next(); } } let mut castling = [0; 16]; - for i in 0..16 { - castling[i] = rng.next(); + for c in castling.iter_mut() { + *c = rng.next(); } let mut en_passant = [0; 9]; - for i in 0..9 { - en_passant[i] = rng.next(); + for ep in en_passant.iter_mut() { + *ep = rng.next(); } let side_to_move = rng.next(); @@ -67,16 +63,13 @@ pub fn init_zobrist() { side_to_move, }; - // Set the global keys. Unwrap panics if set is called twice (should not happen). KEYS.set(keys).expect("Zobrist keys already initialized"); } -// Safe accessor without unsafe block pub fn zobrist_keys() -> &'static ZobristKeys { KEYS.get().expect("Zobrist keys not initialized! Call init_zobrist() in main.") } -// Helper to map piece+color to index 0-11 pub fn piece_index(pt: PieceType, c: Color) -> usize { let offset = match c { Color::White => 0, diff --git a/tests/perft.rs b/tests/perft.rs index 90e194c..9c05816 100644 --- a/tests/perft.rs +++ b/tests/perft.rs @@ -13,14 +13,11 @@ fn count_legal_moves_recursive(board: &mut Board, depth: u8) -> u64 { let mut leaf_nodes = 0_u64; for mv in list.iter() { - // Store the undo info when making the move let undo_info = board.make_move(*mv); if !is_other_king_attacked(board) { leaf_nodes += count_legal_moves_recursive(board, depth - 1); } - - // Undo the move to restore the board state for the next iteration board.undo_move(undo_info); } leaf_nodes