diff --git a/src/display.rs b/src/display.rs index 38cebda..4f5124d 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,5 +1,5 @@ use crate::board::{Board, Color, PieceType}; -use crate::r#move::MoveList; +use crate::r#move::{MoveList, Move}; use crate::square::Square; use std::fmt; @@ -213,3 +213,9 @@ impl fmt::Display for Square { write!(f, "{}{}", file, rank) } } + +impl fmt::Display for Move { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_algebraic()) + } +} diff --git a/src/move.rs b/src/move.rs index 38f64c7..352a707 100644 --- a/src/move.rs +++ b/src/move.rs @@ -80,7 +80,6 @@ impl MoveList { } } - #[inline(always)] pub fn push(&mut self, mv: Move) { debug_assert!(self.count < 256, "Move list overflow!"); @@ -88,20 +87,21 @@ impl MoveList { self.count += 1; } - #[inline(always)] pub fn len(&self) -> usize { self.count } - #[inline(always)] pub fn is_empty(&self) -> bool { self.count == 0 } - #[inline(always)] pub fn iter(&self) -> slice::Iter<'_, Move> { self.moves[..self.count].iter() } + + pub fn contains(&self, mv: &Move) -> bool { + self.moves.contains(mv) + } } pub struct UndoMove { diff --git a/src/movegen/non_sliders.rs b/src/movegen/non_sliders.rs index 594cfa0..1d96876 100644 --- a/src/movegen/non_sliders.rs +++ b/src/movegen/non_sliders.rs @@ -1,4 +1,5 @@ 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}; @@ -30,7 +31,6 @@ pub fn generate_knight_moves(board: &Board, list: &mut MoveList) { } pub fn generate_king_moves(board: &Board, list: &mut MoveList) { - // TODO no castle when square or piece under attack let enemy_occupied = board.occupied[!board.side_to_move as usize]; let friendly_king = board.pieces[PieceType::King as usize][board.side_to_move as usize]; @@ -57,32 +57,59 @@ 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 + if is_square_attacked(board, Square::E1, Color::Black) { + return; + } + // Kingside (OO) if (board.castling_rights & CASTLING_WK_FLAG) != 0 { if (board.all_occupied & CASTLING_WK_MASK) == 0 { - list.push(Move::new(Square::E1, Square::G1, MOVE_FLAG_WK_CASTLE)); + // Check F1 (path) and G1 (landing) + if !is_square_attacked(board, Square::F1, Color::Black) && + !is_square_attacked(board, Square::G1, 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 { - list.push(Move::new(Square::E1, Square::C1, MOVE_FLAG_WQ_CASTLE)); + // Check D1 (path) and C1 (landing). B1 is irrelevant. + if !is_square_attacked(board, Square::D1, Color::Black) && + !is_square_attacked(board, Square::C1, Color::Black) { + list.push(Move::new(Square::E1, Square::C1, MOVE_FLAG_WQ_CASTLE)); + } } } } 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 { - list.push(Move::new(Square::E8, Square::G8, MOVE_FLAG_BK_CASTLE)); + // Check F8 (path) and G8 (landing) + if !is_square_attacked(board, Square::F8, Color::White) && + !is_square_attacked(board, Square::G8, 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 { - list.push(Move::new(Square::E8, Square::C8, MOVE_FLAG_BQ_CASTLE)); + // Check D8 (path) and C8 (landing). B8 is irrelevant. + if !is_square_attacked(board, Square::D8, Color::White) && + !is_square_attacked(board, Square::C8, Color::White) { + list.push(Move::new(Square::E8, Square::C8, MOVE_FLAG_BQ_CASTLE)); + } } } } -} +} \ No newline at end of file diff --git a/tests/castling_under_attack.rs b/tests/castling_under_attack.rs new file mode 100644 index 0000000..08e11da --- /dev/null +++ b/tests/castling_under_attack.rs @@ -0,0 +1,55 @@ +use chess_engine::board::Board; +use chess_engine::movegen::generate_pseudo_legal_moves; +use chess_engine::r#move::{Move, MoveList, MOVE_FLAG_WK_CASTLE, MOVE_FLAG_WQ_CASTLE, MOVE_FLAG_BK_CASTLE, MOVE_FLAG_BQ_CASTLE}; +use chess_engine::square::Square; + +fn assert_move_generated(fen:&str, mv: Move, contains: bool) { + let mut list = MoveList::new(); + let mut board = Board::from_fen(fen); + generate_pseudo_legal_moves(&mut board, &mut list); + assert_eq!(list.contains(&mv), contains, "board '{fen}' contains move '{mv}': {contains}") +} + +#[test] +fn test_wk_castle_under_attacks() { + let kingside_castle = Move::new(Square::E1, Square::G1, MOVE_FLAG_WK_CASTLE); + assert_move_generated("4k3/8/3r4/8/8/8/8/4K2R w K - 0 1", kingside_castle, true); + assert_move_generated("4k3/8/4r3/8/8/8/8/4K2R w K - 0 1", kingside_castle, false); + assert_move_generated("4k3/8/5r2/8/8/8/8/4K2R w K - 0 1", kingside_castle, false); + assert_move_generated("4k3/8/6r1/8/8/8/8/4K2R w K - 0 1", kingside_castle, false); + assert_move_generated("4k3/8/7r/8/8/8/8/4K2R w K - 0 1", kingside_castle, true); + assert_move_generated("4k3/8/8/8/8/8/8/r3K2R w K - 0 1", kingside_castle, false); +} + +#[test] +fn test_wq_castle_under_attacks() { + let kingside_castle = Move::new(Square::E1, Square::C1, MOVE_FLAG_WQ_CASTLE); + assert_move_generated("4k3/8/5r2/8/8/8/8/R3K3 w Q - 0 1", kingside_castle, true); + assert_move_generated("4k3/8/4r3/8/8/8/8/R3K3 w Q - 0 1", kingside_castle, false); + assert_move_generated("4k3/8/3r4/8/8/8/8/R3K3 w Q - 0 1", kingside_castle, false); + assert_move_generated("4k3/8/2r5/8/8/8/8/R3K3 w Q - 0 1", kingside_castle, false); + assert_move_generated("4k3/8/1r6/8/8/8/8/R3K3 w Q - 0 1", kingside_castle, true); + assert_move_generated("4k3/8/r7/8/8/8/8/R3K3 w Q - 0 1", kingside_castle, true); +} + +#[test] +fn test_bk_castle_under_attacks() { + let kingside_castle = Move::new(Square::E8, Square::G8, MOVE_FLAG_BK_CASTLE); + assert_move_generated("4k2r/8/8/8/8/3R4/8/4K3 b k - 0 1", kingside_castle, true); + assert_move_generated("4k2r/8/8/8/8/4R3/8/4K3 b k - 0 1", kingside_castle, false); + assert_move_generated("4k2r/8/8/8/8/5R2/8/4K3 b k - 0 1", kingside_castle, false); + assert_move_generated("4k2r/8/8/8/8/6R1/8/4K3 b k - 0 1", kingside_castle, false); + assert_move_generated("4k2r/8/8/8/8/7R/8/4K3 b k - 0 1", kingside_castle, true); + assert_move_generated("2R1k2r/8/8/8/8/8/8/4K3 b k - 0 1", kingside_castle, false); +} + +#[test] +fn test_bq_castle_under_attacks() { + let kingside_castle = Move::new(Square::E8, Square::C8, MOVE_FLAG_BQ_CASTLE); + assert_move_generated("r3k3/8/8/8/8/5R2/8/4K3 b q - 0 1", kingside_castle, true); + assert_move_generated("r3k3/8/8/8/8/4R3/8/4K3 b q - 0 1", kingside_castle, false); + assert_move_generated("r3k3/8/8/8/8/3R4/8/4K3 b q - 0 1", kingside_castle, false); + assert_move_generated("r3k3/8/8/8/8/2R5/8/4K3 b q - 0 1", kingside_castle, false); + assert_move_generated("r3k3/8/8/8/8/1R6/8/4K3 b q - 0 1", kingside_castle, true); + assert_move_generated("r3k3/8/8/8/8/R7/8/4K3 b q - 0 1", kingside_castle, true); +} \ No newline at end of file