From 779f247bc84e0bf955f4e462d3d781b3ed79ca7f Mon Sep 17 00:00:00 2001 From: Bendik Samseth <b.samseth@gmail.com> Date: Wed, 21 Aug 2024 09:23:15 +0200 Subject: [PATCH] Start implementing see --- engine/src/movelist.rs | 147 +++++++++++++++++++++++++++++++++++++- engine/src/search/cuts.rs | 1 + 2 files changed, 147 insertions(+), 1 deletion(-) diff --git a/engine/src/movelist.rs b/engine/src/movelist.rs index 37f9972..8378253 100644 --- a/engine/src/movelist.rs +++ b/engine/src/movelist.rs @@ -1,4 +1,4 @@ -use chess::{Board, ChessMove, MoveGen, Piece}; +use chess::{BitBoard, Board, ChessMove, Color, MoveGen, Piece}; use crate::chessmove::ChessMoveExt; use crate::{newtypes::Value, opts::OPTS}; @@ -123,6 +123,97 @@ impl MoveVec { self } + + pub fn see(board: &Board, capture: ChessMove) -> Value { + debug_assert!(capture.captures(board).is_some()); + + let initial_capture = capture + .captures(board) + .expect("should only call see on captures"); + let target_square = capture.get_dest(); + let initial_colour = board.side_to_move(); + let mut blockers = board.combined() ^ BitBoard::from_square(capture.get_source()); + let white_pieces = board.color_combined(Color::White); + let black_pieces = board.color_combined(Color::Black); + + let mut attackers = + chess::get_king_moves(target_square) & blockers & board.pieces(Piece::King) + | chess::get_knight_moves(target_square) & blockers & board.pieces(Piece::Knight) + | chess::get_rook_moves(target_square, blockers) + & blockers + & (board.pieces(Piece::Rook) | board.pieces(Piece::Queen)) + | chess::get_bishop_moves(target_square, blockers) + & blockers + & (board.pieces(Piece::Bishop) | board.pieces(Piece::Queen)) + | chess::get_pawn_attacks(target_square, Color::Black, *white_pieces) + | chess::get_pawn_attacks(target_square, Color::White, *black_pieces); + + let mut target_piece = board.piece_on(capture.get_source()).unwrap(); + let mut colour = !initial_colour; + let mut gains = vec![piece_value(initial_capture)]; + + 'exchange: loop { + for attacker_piece in [ + Piece::Pawn, + Piece::Knight, + Piece::Bishop, + Piece::Rook, + Piece::Queen, + Piece::King, + ] { + let our_attacker = + attackers & board.color_combined(colour) & board.pieces(attacker_piece); + + if our_attacker == chess::EMPTY { + continue; + } + + let attacker_square = our_attacker.to_square(); + let victim_value = piece_value(target_piece); + gains.push(victim_value); + + if target_piece == Piece::King { + break; + } + + blockers ^= BitBoard::from_square(attacker_square); + attackers ^= BitBoard::from_square(attacker_square); + + target_piece = attacker_piece; + + if matches!(attacker_piece, Piece::Rook | Piece::Queen) { + attackers |= chess::get_rook_moves(target_square, blockers) + & blockers + & (board.pieces(Piece::Rook) | board.pieces(Piece::Queen)); + } + + if matches!(attacker_piece, Piece::Pawn | Piece::Bishop | Piece::Queen) { + attackers |= chess::get_bishop_moves(target_square, blockers) + & blockers + & (board.pieces(Piece::Bishop) | board.pieces(Piece::Queen)); + } + + colour = !colour; + + continue 'exchange; + } + + while gains.len() > 1 { + let forced = gains.len() == 2; + + let their_gain = gains.pop().unwrap(); + let our_gain = gains.last_mut().unwrap(); + + *our_gain -= their_gain; + + if !forced && *our_gain < 0 { + *our_gain = 0; + } + } + + return Value::new(gains.pop().unwrap()); + } + } } /// Piece values for use with MVV/LVA only. @@ -156,3 +247,57 @@ impl std::ops::DerefMut for MoveVec { &mut self.0 } } + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! test_see { + ($name:ident, $fen:expr, $capture:expr, $expected:expr) => { + #[test] + fn $name() { + let board: Board = $fen.parse().unwrap(); + let capture: ChessMove = $capture.parse().unwrap(); + let expected = Value::new($expected); + + assert_eq!(expected, MoveVec::see(&board, capture)); + } + }; + } + + test_see!( + test_see_pawn_takes_protected_pawn, + "4R3/2r3p1/5bk1/1p1r3p/p2PR1P1/P1BK4/1P6/8 b - - 0 1", + "h5g4", + 0 + ); + test_see!( + test_see_two_pawn_takes_doubly_protocted_pawn, + "4R3/2r3p1/5bk1/1p1r1p1p/p2PR1P1/P1BK1P2/1P6/8 b - - 0 1", + "h5g4", + 0 + ); + + //#[test] + //fn test_see_enough_attackers_but_wrong_order() { + // let board: Board = "k2r2q1/2n2b2/2p5/3n4/2K1P3/3QNB2/3R4/8 w - - 0 1" + // .parse() + // .unwrap(); + // let capture: ChessMove = "e4d5".parse().unwrap(); + // + // //assert_eq!(Value::INFINITE, MoveVec::see(&board, capture)); + // + // for capture in MoveGen::new_legal(&board) { + // if capture.captures(&board).is_none() { + // continue; + // } + // + // println!( + // "{} SEE: {}", + // capture, + // MoveVec::see(&board, capture).as_inner() + // ); + // } + // panic!(); + //} +} diff --git a/engine/src/search/cuts.rs b/engine/src/search/cuts.rs index 3f77610..8df0359 100644 --- a/engine/src/search/cuts.rs +++ b/engine/src/search/cuts.rs @@ -72,6 +72,7 @@ impl Searcher<'_> { /// Early return with [`Value::DRAW`] if the position is a draw. /// TODO: check for upcomming draw by repetition and increase alpha if alpha<draw + /// TODO: Check for draw by insufficient material #[inline] pub fn return_if_draw(&self, board: &Board, ply: Ply) -> Result<(), Value> { if self.is_draw(board, ply) {