diff --git a/.gitignore b/.gitignore index 181f3ef..c487998 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target /.idea /Cargo.lock +progress_tracking/~$progress.xlsx \ No newline at end of file diff --git a/progress_tracking/progress.xlsx b/progress_tracking/progress.xlsx new file mode 100644 index 0000000..d5fadec Binary files /dev/null and b/progress_tracking/progress.xlsx differ diff --git a/src/bin/suite.rs b/src/bin/suite.rs index 92bf46c..3759c2f 100644 --- a/src/bin/suite.rs +++ b/src/bin/suite.rs @@ -40,7 +40,7 @@ fn main() { // Record start time let start_time = Instant::now(); - let result = engine.search(4); + let result = engine.search(5); // Calculate duration let duration = start_time.elapsed(); @@ -57,6 +57,8 @@ fn main() { if result == *bm { correct_tests += 1.0; } + + println!("{}%", (total_tests / (sts.len() as f32 / 100.0)) as u8); } println!("{}", correct_tests / (total_tests / 100.0)); diff --git a/src/engine.rs b/src/engine.rs index 3eeae77..a3aa051 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,7 +1,7 @@ // ... (your use statements) use crate::board::Board; use crate::r#move::Move; -use crate::search::minimax::minimax; +use crate::search::alpha_beta::alpha_beta; pub struct Engine { pub name: String, @@ -33,7 +33,7 @@ impl Engine { } pub fn search(&mut self, depth: u8) -> String { - let (opt_move, _score) = minimax(&mut self.board, depth, 0); + let (opt_move, _score) = alpha_beta(&mut self.board, depth, 0, -i32::MAX, i32::MAX); if let Some(mv) = opt_move { mv.to_algebraic() diff --git a/src/search/alpha_beta.rs b/src/search/alpha_beta.rs new file mode 100644 index 0000000..a284833 --- /dev/null +++ b/src/search/alpha_beta.rs @@ -0,0 +1,78 @@ +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 alpha_beta( + board: &mut Board, + depth: u8, + ply: u8, + mut alpha: i32, + beta: i32, +) -> (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; // This is our local "worst case" + 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 with negated and swapped alpha/beta + let (_, score) = alpha_beta(board, depth - 1, ply + 1, -beta, -alpha); + let current_score = -score; + + if current_score > best_score { + best_score = current_score; + best_move = Some(*mv); + } + + board.undo_move(undo_mv); + + // Alpha-Beta Pruning logic + if best_score > alpha { + alpha = best_score; + } + + if alpha >= beta { + break; // Beta cutoff (Pruning) + } + } + + 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 index 4ccb1d9..30df11f 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -1 +1,2 @@ -pub mod minimax; \ No newline at end of file +pub mod minimax; +pub mod alpha_beta; \ No newline at end of file