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 b8fca56..2ad473c 100644 Binary files a/progress_tracking/progress.xlsx and b/progress_tracking/progress.xlsx differ diff --git a/src/bin/suite.rs b/src/bin/suite.rs index d7ab2ad..d1cae29 100644 --- a/src/bin/suite.rs +++ b/src/bin/suite.rs @@ -3,7 +3,6 @@ use std::io::{self, BufRead}; use chess_engine::engine::Engine; use std::time::{Instant, Duration}; use chess_engine::zobrist::init_zobrist; -// EACH TEST CAN ONLY TAKE ONE SECOND MAX TO KEEP RESULTS COMPARABLE fn load_csv(path: &str) -> 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