diff --git a/src/search/mod.rs b/src/search/mod.rs index 8d9dbe4..f1097fd 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -1,3 +1,4 @@ +pub mod move_ordering; pub mod negamax; pub mod perft; pub mod quiescence; diff --git a/src/search/move_ordering.rs b/src/search/move_ordering.rs new file mode 100644 index 0000000..1f8974c --- /dev/null +++ b/src/search/move_ordering.rs @@ -0,0 +1,42 @@ +use crate::{board::board::Board, movegen::r#move::Move}; + +// Rows: Aggressors (P, N, B, R, Q, K) +// Columns: Victims (K, Q, R, B, N, P) +const MVV_LVA: [[usize; 6]; 6] = [ + [30, 29, 28, 27, 26, 00], + [25, 24, 23, 22, 21, 00], + [20, 19, 18, 17, 16, 00], + [15, 14, 13, 12, 11, 00], + [10, 09, 08, 07, 06, 00], + [05, 04, 03, 02, 01, 00], +]; + +pub fn score_by_mvv_lva(board: &Board, mv: Move) -> usize { + match (board.piece_type_at(mv.src), board.piece_type_at(mv.dst)) { + (Some(aggressor), Some(victim)) => MVV_LVA[aggressor.idx()][victim.idx()], + _ => 0, + } +} + +#[cfg(test)] +mod tests { + use crate::{ + board::{board::PieceType, fen::from_fen, square::Square}, + movegen::r#move::{Move, MoveType}, + search::move_ordering::{score_by_mvv_lva, MVV_LVA}, + }; + + const FEN: &'static str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1"; + + #[test] + fn test_score_by_mvv_lva() -> Result<(), String> { + let game = from_fen(FEN)?; + let f3f5 = Move::new_with_type(Square::F3, Square::F5, MoveType::Capture); + let actual = score_by_mvv_lva(&game.board, f3f5); + let expected = MVV_LVA[PieceType::Queen.idx()][PieceType::Pawn.idx()]; + + assert_eq!(expected, actual); + + Ok(()) + } +} diff --git a/src/search/negamax.rs b/src/search/negamax.rs index 4ac8b67..5e2f894 100644 --- a/src/search/negamax.rs +++ b/src/search/negamax.rs @@ -4,7 +4,7 @@ use crate::{ movegen::r#move::Move, }; -use super::quiescence::quiescence; +use super::{move_ordering::score_by_mvv_lva, quiescence::quiescence}; pub fn negamax( game: &mut Game, @@ -22,7 +22,10 @@ pub fn negamax( let (mut best_move, mut best_score, mate_score) = (None, MIN_SCORE, -MATE_SCORE + plies as i32); let mut legal_moves = 0; - for mv in game.board.pseudo_moves_all() { + let mut pseudo_legal_moves = game.board.pseudo_moves_all(); + pseudo_legal_moves.sort_unstable_by_key(|mv| score_by_mvv_lva(&game.board, *mv)); + + for mv in pseudo_legal_moves { let move_parameters = MoveParameters::build(&game.board, &mv); game.board.make_move(&mv); @@ -41,7 +44,7 @@ pub fn negamax( } if move_score >= beta { - return (Some(mv), beta); + return (best_move, beta); } alpha = alpha.max(move_score); @@ -64,12 +67,22 @@ mod tests { use crate::movegen::r#move::Move; use crate::search::negamax::negamax; - const FEN_MATE_IN_1: &str = "8/8/8/8/8/4q1k1/8/5K2 b - - 0 1"; + const FEN_MATE_IN_1: [&str; 2] = [ + "8/8/8/8/8/4q1k1/8/5K2 b - - 0 1", + "8/8/8/8/8/4Q1K1/8/5k2 w - - 0 1", + ]; #[test] fn test_negamax() -> Result<(), String> { init_attacks(); - let mut game = from_fen(FEN_MATE_IN_1)?; + let mut game = from_fen(FEN_MATE_IN_1[0])?; + + let e3f2 = Move::new(Square::E3, Square::F2); + let anointed_move = negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0).0.unwrap(); + + assert_eq!(e3f2, anointed_move); + + let mut game = from_fen(FEN_MATE_IN_1[1])?; let e3f2 = Move::new(Square::E3, Square::F2); let anointed_move = negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0).0.unwrap(); diff --git a/src/search/quiescence.rs b/src/search/quiescence.rs index f6e5c61..c186342 100644 --- a/src/search/quiescence.rs +++ b/src/search/quiescence.rs @@ -4,6 +4,8 @@ use crate::{ movegen::r#move::{Move, MoveType}, }; +use super::move_ordering::score_by_mvv_lva; + pub fn quiescence(game: &mut Game, mut alpha: i32, beta: i32) -> (Option, i32) { let color = game.current_player(); let stand_pat = evaluate_position(&game.board); @@ -16,11 +18,14 @@ pub fn quiescence(game: &mut Game, mut alpha: i32, beta: i32) -> (Option, alpha = stand_pat; } - let captures = game - .board - .pseudo_moves_all() - .into_iter() - .filter(|m| !matches!(m.move_type, MoveType::Quiet)); + let mut captures: Vec<_> = game.board.pseudo_moves_all().into_iter().filter(|m| { + matches!( + m.move_type, + MoveType::Capture | MoveType::PromotionCapture(_) + ) + }).collect(); + + captures.sort_unstable_by_key(|mv| score_by_mvv_lva(&game.board, *mv)); for mv in captures { let move_parameters = MoveParameters::build(&game.board, &mv); @@ -41,5 +46,5 @@ pub fn quiescence(game: &mut Game, mut alpha: i32, beta: i32) -> (Option, alpha = alpha.max(move_score); } - (None, 0) + (None, alpha) }