diff --git a/benches/eval.rs b/benches/eval.rs index c689183..8760931 100644 --- a/benches/eval.rs +++ b/benches/eval.rs @@ -1,10 +1,8 @@ use chess_engine::board::Board; use criterion::{criterion_group, criterion_main, Criterion}; use chess_engine::eval::evaluate_board; -use chess_engine::zobrist::init_zobrist; fn run_eval_benchmark(c: &mut Criterion) { - init_zobrist(); let board = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); c.bench_function("standard_board_evaluation", |b| { b.iter(|| { diff --git a/benches/perft.rs b/benches/perft.rs index 673b898..ce09f5c 100644 --- a/benches/perft.rs +++ b/benches/perft.rs @@ -1,19 +1,18 @@ use chess_engine::board::Board; -use chess_engine::movegen::generate_pseudo_legal_moves; +use chess_engine::movegen::picker::MoveGenerator; use chess_engine::movegen::legal_check::is_other_king_attacked; -use chess_engine::r#move::MoveList; use criterion::{criterion_group, criterion_main, Criterion}; -use chess_engine::zobrist::init_zobrist; fn count_legal_moves_recursive(board: &mut Board, depth: u8) -> u64 { if depth == 0 { return 1_u64; } - let mut list = MoveList::new(); - generate_pseudo_legal_moves(&board, &mut list); + + let mut generator = MoveGenerator::new(); let mut leaf_nodes = 0_u64; - for mv in list.iter() { - let undo_info = board.make_move(*mv); + + while let Some(mv) = generator.next(board) { + let undo_info = board.make_move(mv); if !is_other_king_attacked(board) { leaf_nodes += count_legal_moves_recursive(board, depth - 1); } @@ -22,9 +21,8 @@ fn count_legal_moves_recursive(board: &mut Board, depth: u8) -> u64 { leaf_nodes } - fn run_perft_benchmark(c: &mut Criterion) { - init_zobrist(); + // init_zobrist() is no longer needed let mut board = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); c.bench_function("standard_perft5", |b| { @@ -35,4 +33,4 @@ fn run_perft_benchmark(c: &mut Criterion) { } criterion_group!(benches, run_perft_benchmark); -criterion_main!(benches); +criterion_main!(benches); \ No newline at end of file diff --git a/progress_tracking/progress.xlsx b/progress_tracking/progress.xlsx index 2ad473c..1533e56 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 d1cae29..0dfdeb1 100644 --- a/src/bin/suite.rs +++ b/src/bin/suite.rs @@ -2,7 +2,6 @@ use std::fs::File; use std::io::{self, BufRead}; use chess_engine::engine::Engine; use std::time::{Instant, Duration}; -use chess_engine::zobrist::init_zobrist; fn load_csv(path: &str) -> io::Result>> { let file = File::open(path)?; @@ -23,15 +22,14 @@ 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()); - + for test in &sts { let fen = &test[0]; let bm = &test[1]; @@ -40,7 +38,7 @@ fn main() { let start_time = Instant::now(); - let result = engine.search(time_limit_ms-1); + let result = engine.search(time_limit_ms-5); let duration = start_time.elapsed(); if duration > time_limit { diff --git a/src/main.rs b/src/main.rs index 341e205..bd43db4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,7 @@ use chess_engine::engine::Engine; use chess_engine::uci::uci_mainloop; -use chess_engine::zobrist::init_zobrist; fn main() { - init_zobrist(); let mut engine = Engine::new("Yakari".to_string(), "EiSiMo".to_string()); uci_mainloop(&mut engine); } diff --git a/src/move.rs b/src/move.rs index e430bce..13f89f8 100644 --- a/src/move.rs +++ b/src/move.rs @@ -87,6 +87,14 @@ impl MoveList { self.count += 1; } + pub fn pull(&mut self) -> Option { + if self.count > 0 { + self.count -= 1; + return Some(self.moves[self.count]); + } + None + } + pub fn swap(&mut self, a: usize, b: usize) { self.moves[..self.count].swap(a, b); } @@ -106,6 +114,8 @@ impl MoveList { pub fn contains(&self, mv: &Move) -> bool { self.moves.contains(mv) } + + pub fn clear(&mut self) { self.count = 0 } } impl Index for MoveList { diff --git a/src/movegen/mod.rs b/src/movegen/mod.rs index 8c77d95..b1a11a4 100644 --- a/src/movegen/mod.rs +++ b/src/movegen/mod.rs @@ -1,20 +1,6 @@ -use crate::board::Board; -use crate::movegen::non_sliders::{generate_king_moves, generate_knight_moves}; -use crate::movegen::pawns::generate_pawn_moves; -use crate::movegen::sliders::{generate_bishop_moves, generate_queen_moves, generate_rook_moves}; -use crate::r#move::MoveList; - pub mod non_sliders; pub mod sliders; pub mod pawns; pub mod tables; pub mod legal_check; - -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 +pub mod picker; \ No newline at end of file diff --git a/src/movegen/picker.rs b/src/movegen/picker.rs new file mode 100644 index 0000000..834132e --- /dev/null +++ b/src/movegen/picker.rs @@ -0,0 +1,70 @@ +use crate::board::Board; +use crate::movegen::non_sliders::{generate_king_moves, generate_knight_moves}; +use crate::movegen::pawns::generate_pawn_moves; +use crate::movegen::sliders::*; +use crate::r#move::{Move, MoveList}; + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +enum GenStage { + Pawns = 1, + Knights = 2, + Bishops = 3, + Rooks = 4, + Queens = 5, + King = 6, + Done = 7, +} + +impl GenStage { + pub fn next(&self) -> Option { + match self { + Self::Pawns => Some(Self::Knights), + Self::Knights => Some(Self::Bishops), + Self::Bishops => Some(Self::Rooks), + Self::Rooks => Some(Self::Queens), + Self::Queens => Some(Self::King), + Self::King => Some(Self::Done), + Self::Done => None, + } + } +} + +pub struct MoveGenerator { + buffer: MoveList, + stage: GenStage, +} + +impl MoveGenerator { + pub fn new() -> Self { + Self { + buffer: MoveList::new(), + stage: GenStage::Pawns, + } + } + + fn generate_next_batch(&mut self, board: &Board) { + self.buffer.clear(); + + match self.stage { + GenStage::Pawns => { generate_pawn_moves(board, &mut self.buffer) } + GenStage::Knights => { generate_knight_moves(board, &mut self.buffer) } + GenStage::Bishops => { generate_bishop_moves(board, &mut self.buffer) } + GenStage::Rooks => { generate_rook_moves(board, &mut self.buffer) } + GenStage::Queens => { generate_queen_moves(board, &mut self.buffer) } + GenStage::King => { generate_king_moves(board, &mut self.buffer) } + GenStage::Done => {} + } + if let Some(next_stage) = self.stage.next() { + self.stage = next_stage; + } + } + + pub fn next(&mut self, board: &Board) -> Option { + loop { + if let Some(mv) = self.buffer.pull() { return Some(mv) } + if self.stage == GenStage::Done { return None } + self.generate_next_batch(board); + } + } +} \ No newline at end of file diff --git a/src/search.rs b/src/search.rs index 506b7b5..ff3e06f 100644 --- a/src/search.rs +++ b/src/search.rs @@ -1,12 +1,11 @@ use crate::board::{Board, Color}; use crate::eval::evaluate_board; -use crate::movegen::generate_pseudo_legal_moves; use crate::movegen::legal_check::*; -use crate::r#move::{Move, MoveList}; -use crate::tt::{TranspositionTable, NodeType, TTEntry}; // Import TT types +use crate::r#move::Move; +use crate::movegen::picker::MoveGenerator; +use crate::tt::{TranspositionTable, NodeType}; use std::time::{Instant, Duration}; -// 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 { @@ -17,9 +16,6 @@ fn evaluate_board_relative(board: &Board) -> i32 { } } -// Helper to adjust mate scores for the TT. -// TT stores "pure" scores, independent of ply. -// Search uses "mated in X" relative to current ply. fn score_to_tt(score: i32, ply: u8) -> i32 { if score > MATE_SCORE - 1000 { score + (ply as i32) @@ -49,14 +45,14 @@ pub fn alpha_beta( start_time: Instant, time_limit: Duration, nodes: &mut u64, - tt: &mut TranspositionTable, // Added TT parameter + tt: &mut TranspositionTable, ) -> (Option, i32) { if (*nodes).is_multiple_of(4096) && start_time.elapsed() > time_limit { return (None, 0); } *nodes += 1; - + let tt_key = board.hash; let mut tt_move: Option = None; @@ -89,25 +85,35 @@ pub fn alpha_beta( return (None, evaluate_board_relative(board)); } - let mut list = MoveList::new(); - generate_pseudo_legal_moves(board, &mut list); - - if let Some(tm) = tt_move { - for i in 0..list.len() { - if list[i] == tm { - list.swap(0, i); - break; - } - } - } - let mut best_move: Option = None; let mut best_score: i32 = -i32::MAX; let mut legal_moves_found = false; let alpha_orig = alpha; - for i in 0..list.len() { - let mv = list[i]; + let mut picker = MoveGenerator::new(); + let mut moves_tried = 0; + + loop { + // Move selection logic: + // 1. Try TT move first (if exists) + // 2. Then use the picker for the rest + let mv = if moves_tried == 0 && tt_move.is_some() { + tt_move.unwrap() + } else { + match picker.next(board) { + Some(m) => { + // Important: skip the TT move if we see it again in the generator + if Some(m) == tt_move { + continue; + } + m + } + None => break, // No more moves + } + }; + + moves_tried += 1; + let undo_mv = board.make_move(mv); let is_illegal = is_other_king_attacked(board); @@ -149,7 +155,7 @@ pub fn alpha_beta( return (None, 0); } } - + let node_type = if best_score <= alpha_orig { NodeType::Alpha } else if best_score >= beta { diff --git a/src/zobrist.rs b/src/zobrist.rs index 7376467..83641d3 100644 --- a/src/zobrist.rs +++ b/src/zobrist.rs @@ -30,11 +30,8 @@ pub struct ZobristKeys { static KEYS: OnceLock = OnceLock::new(); -pub fn init_zobrist() { - if KEYS.get().is_some() { - return; - } - +// Helper function to generate keys, used by the lazy initializer +fn generate_keys() -> ZobristKeys { let mut rng = Xorshift::new(1070372); // Fixed seed for reproducibility let mut pieces = [[0; 64]; 12]; @@ -56,18 +53,17 @@ pub fn init_zobrist() { let side_to_move = rng.next(); - let keys = ZobristKeys { + ZobristKeys { pieces, castling, en_passant, side_to_move, - }; - - KEYS.set(keys).expect("Zobrist keys already initialized"); + } } + pub fn zobrist_keys() -> &'static ZobristKeys { - KEYS.get().expect("Zobrist keys not initialized! Call init_zobrist() in main.") + KEYS.get_or_init(generate_keys) } pub fn piece_index(pt: PieceType, c: Color) -> usize { diff --git a/tests/perft.rs b/tests/perft.rs index 9c05816..f2bb9b5 100644 --- a/tests/perft.rs +++ b/tests/perft.rs @@ -1,20 +1,17 @@ use chess_engine::board::Board; -use chess_engine::movegen::generate_pseudo_legal_moves; use chess_engine::movegen::legal_check::is_other_king_attacked; -use chess_engine::r#move::MoveList; +use chess_engine::movegen::picker::MoveGenerator; fn count_legal_moves_recursive(board: &mut Board, depth: u8) -> u64 { if depth == 0 { return 1_u64; } - let mut list = MoveList::new(); - generate_pseudo_legal_moves(&board, &mut list); - + let mut generator = MoveGenerator::new(); let mut leaf_nodes = 0_u64; - for mv in list.iter() { - let undo_info = board.make_move(*mv); + while let Some(mv) = generator.next(board) { + let undo_info = board.make_move(mv); if !is_other_king_attacked(board) { leaf_nodes += count_legal_moves_recursive(board, depth - 1); }