diff --git a/bitboard/src/bitboard.rs b/bitboard/src/bitboard.rs index ccee9bd..f896e57 100644 --- a/bitboard/src/bitboard.rs +++ b/bitboard/src/bitboard.rs @@ -160,9 +160,9 @@ impl BitBoard { /// /// ``` /// use chessfriend_bitboard::BitBoard; - /// assert_eq!(BitBoard::EMPTY.population_count(), 0); + /// assert_eq!(BitBoard::empty().population_count(), 0); /// assert_eq!(BitBoard::new(0b01011110010).population_count(), 6); - /// assert_eq!(BitBoard::FULL.population_count(), 64); + /// assert_eq!(BitBoard::full().population_count(), 64); /// ``` #[must_use] pub const fn population_count(&self) -> u32 { @@ -211,8 +211,8 @@ impl BitBoard { /// /// ``` /// use chessfriend_bitboard::BitBoard; - /// assert!(!BitBoard::EMPTY.is_single_square(), "Empty bitboards represent no squares"); - /// assert!(!BitBoard::FULL.is_single_square(), "Full bitboards represent all the squares"); + /// assert!(!BitBoard::empty().is_single_square(), "Empty bitboards represent no squares"); + /// assert!(!BitBoard::full().is_single_square(), "Full bitboards represent all the squares"); /// assert!(!BitBoard::new(0b010011110101101100).is_single_square(), "This bitboard represents a bunch of squares"); /// assert!(BitBoard::new(0b10000000000000).is_single_square()); /// ``` @@ -233,38 +233,6 @@ impl BitBoard { } } - /// Iterate through the occupied squares in a direction specified by a - /// compass direction. This method is mose useful for bitboards of slider - /// rays so that iteration proceeds in order along the ray's direction. - /// - /// ## Examples - /// - /// ``` - /// use chessfriend_bitboard::BitBoard; - /// use chessfriend_core::{Direction, Square}; - /// - /// let ray = BitBoard::ray(Square::E4, Direction::North); - /// assert_eq!( - /// ray.occupied_squares_direction(Direction::North).collect::>(), - /// vec![Square::E5, Square::E6, Square::E7, Square::E8] - /// ); - /// ``` - /// - #[must_use] - pub fn occupied_squares_direction( - &self, - direction: Direction, - ) -> Box> { - match direction { - Direction::North | Direction::NorthEast | Direction::NorthWest | Direction::East => { - Box::new(self.occupied_squares_trailing()) - } - Direction::SouthEast | Direction::South | Direction::SouthWest | Direction::West => { - Box::new(self.occupied_squares_leading()) - } - } - } - #[must_use] pub fn occupied_squares_leading(&self) -> LeadingBitScanner { LeadingBitScanner::new(self.0) @@ -287,12 +255,6 @@ impl BitBoard { } } - /// Get the first occupied square in the given direction. - /// - /// ## To-Do - /// - /// - Take `direction` by value instead of reference - /// #[must_use] pub fn first_occupied_square(&self, direction: &IterationDirection) -> Option { match direction { diff --git a/board/src/castle.rs b/board/src/castle.rs index 4ba9a4b..5acdaaf 100644 --- a/board/src/castle.rs +++ b/board/src/castle.rs @@ -46,7 +46,7 @@ impl Board { let color = self.unwrap_color(color); - if !self.has_castling_right_unwrapped(color, wing) { + if !self.has_castling_right_unwrapped(color, wing.into()) { return Err(CastleEvaluationError::NoRights { color, wing }); } diff --git a/board/src/check.rs b/board/src/check.rs index ca7eca2..6d8ceba 100644 --- a/board/src/check.rs +++ b/board/src/check.rs @@ -2,241 +2,27 @@ use crate::Board; use chessfriend_bitboard::BitBoard; -use chessfriend_core::{Piece, Shape, Slider, Square}; -use std::{convert::Into, ops::BitOr}; +use chessfriend_core::{Color, Piece}; impl Board { /// Return whether the active color is in check. #[must_use] pub fn is_in_check(&self) -> bool { let color = self.active_color(); - let king = self.kings(self.active_color()); + let king = self.king_bitboard(color); let opposing_sight = self.opposing_sight(color); (king & opposing_sight).is_populated() } - /// Calculate checks on the board. - /// - /// ## Panics - /// - /// If the board has multiple kings for any color, this method will panic. - /// - #[must_use] - pub fn calculate_checks( - &self, - piece_moved: &Piece, - last_ply_origin: Square, - last_ply_target: Square, - ) -> CheckingPieces { - let active_king = self.kings(self.active_color()); - - let active_king_square: Square = active_king - .try_into() - .expect("active color has more than one king on the board"); - - let mut checks: Vec = vec![]; - - // Calculate a direct check, where the piece that just moved attacks the - // king. - - if let Ok(sight) = self.is_king_attacked_from_square(last_ply_target) { - checks.push(CheckInfo { - square: last_ply_target, - piece: *piece_moved, - sight, - }); - } - - // Look for revealed checks, where moving a piece opens up an attacking - // ray from a slider to the king. - - let inactive_color = self.active_color().other(); - - for slider in Slider::ALL { - let shape: Shape = slider.into(); - let piece = Piece::new(inactive_color, shape); - - for square in self.find_pieces(piece).occupied_squares_trailing() { - if let Some(check) = - self.calculate_revealed_check(slider, square, active_king, last_ply_origin) - { - checks.push(check); - } - } - } - - let king_moves = BitBoard::king_moves(active_king_square); - // TODO: Bitwise OR with the sight mask of the opposing color. - - CheckingPieces::new(&checks, active_king_square, king_moves) - } - - fn is_king_attacked_from_square(&self, square: Square) -> Result { - let active_king = self.kings(self.active_color()); - let sight_from_square = self.sight_piece(square); - if (active_king & sight_from_square).is_populated() { - Ok(sight_from_square) - } else { - Err(()) - } - } - - fn calculate_revealed_check( - &self, - slider: Slider, - square: Square, - active_king: BitBoard, - last_ply_origin: Square, - ) -> Option { - println!( - "Examining {} on {square} for checks", - Into::::into(slider).name() - ); - - let origin: BitBoard = last_ply_origin.into(); - - let sight = self.sight_piece(square); - let piece_is_attacking_origin = (sight & origin).is_populated(); - if !piece_is_attacking_origin { - println!("does not attack move origin"); - return None; - } - - let origin_and_king = active_king | origin; - println!("origin and king\n{origin_and_king}"); - - for direction in slider.ray_directions() { - let ray = BitBoard::ray(square, direction); - println!("ray\n{ray}"); - - let ray_attacks = ray & origin_and_king; - println!("ray attacks\n{ray_attacks}"); - - // When a check by a slider is revealed by moving another piece, the - // slider will first attack the square the piece was moved from - // (a.k.a. the origin), and also attack the king square. The - // attacked king square must immediately follow the attacked origin - // square, and the attacked origin square must be the first attacked - // square in the ray. - - let mut occupied_squares = ray_attacks.occupied_squares_direction(direction); - if let (Some(first_square), Some(second_square)) = - (occupied_squares.next(), occupied_squares.next()) - { - if first_square != last_ply_origin { - continue; - } - - if Into::::into(second_square) != active_king { - continue; - } - - println!("found check ray\n{ray}"); - - return Some(CheckInfo { - square, - piece: Piece::new(self.active_color(), slider.into()), - sight: ray, - }); - } - } - - None - } -} - -struct CheckInfo { - /// The square the checking piece is on in the current position. - square: Square, - - /// The piece checking the king. - piece: Piece, - - /// The complete sight or direct ray that attacks the king. - sight: BitBoard, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct CheckingPieces { - /// Number of checking pieces - count: usize, - - /// A [`BitBoard`] representing the set of pieces to which the color in - /// check can move a piece to block the check. This bitboard includes - /// squares along sight rays of opposing slider pieces. - push_mask: BitBoard, - - /// A [`BitBoard`] representing the set of pieces that can be captured to - /// resolve the check. - capture_mask: BitBoard, - - /// A [`BitBoard`] representing the moves the king can make to get out of - /// check. - king_moves: BitBoard, -} - -impl CheckingPieces { - fn new(checks: &[CheckInfo], king: Square, king_moves: BitBoard) -> Self { - if checks.is_empty() { - return Self { - count: 0, - push_mask: BitBoard::FULL, - capture_mask: BitBoard::EMPTY, - king_moves, - }; - } - - let count = checks.len(); - let push_mask = CheckingPieces::calculate_push_mask(king, checks); - let capture_mask = CheckingPieces::calculate_capture_mask(checks); - - Self { - count, - push_mask, - capture_mask, - king_moves, - } - } - - pub fn is_empty(&self) -> bool { - self.count == 0 - } - - /// The number of checking pieces. - pub fn len(&self) -> usize { - self.count - } - - /// A [`BitBoard`] representing the set of pieces that must be captured to - /// resolve check. - fn calculate_capture_mask(checks: &[CheckInfo]) -> BitBoard { - if checks.is_empty() { - BitBoard::FULL - } else { - checks - .iter() - .map(|c| Into::::into(c.square)) - .fold(BitBoard::EMPTY, BitOr::bitor) - } - } - - /// A [`BitBoard`] representing the set of squares to which a player can - /// move a piece to block a checking piece. - fn calculate_push_mask(king: Square, checks: &[CheckInfo]) -> BitBoard { - let king_moves = BitBoard::king_moves(king); - let push_mask = checks - .iter() - .map(|c| c.sight) - .fold(BitBoard::EMPTY, BitOr::bitor); - - king_moves & !push_mask + fn king_bitboard(&self, color: Color) -> BitBoard { + self.find_pieces(Piece::king(color)) } } #[cfg(test)] mod tests { use crate::test_board; - use chessfriend_core::{Square, piece}; + use chessfriend_core::Color; #[test] fn active_color_is_in_check() { @@ -257,43 +43,4 @@ mod tests { assert!(!board.is_in_check()); } - - #[test] - fn king_is_attacked_from_square() { - let board = test_board!( - White King on D3, - Black Rook on H3, - Black Queen on F4 - ); - - assert!(board.is_king_attacked_from_square(Square::H3).is_ok()); - assert!(board.is_king_attacked_from_square(Square::F4).is_err()); - assert!(board.is_king_attacked_from_square(Square::A3).is_err()); - } - - #[test] - fn calculate_two_checks() { - let board = test_board!( - White King on D3, - Black Rook on H3, - Black Queen on F5 - ); - - let checks = board.calculate_checks(&piece!(Black Queen), Square::F3, Square::F5); - - assert!(!checks.is_empty()); - assert_eq!(checks.len(), 2); - } - - #[test] - fn calculate_en_passant_revealed_check() { - let board = test_board!(White, [ - White King on D4, - Black Pawn on E3, - Black Rook on H4 - ], E4); - - let checks = board.calculate_checks(&piece!(Black Pawn), Square::F4, Square::E3); - assert_eq!(checks.len(), 1); - } } diff --git a/core/src/score.rs b/core/src/score.rs index 3528861..2dfd7c9 100644 --- a/core/src/score.rs +++ b/core/src/score.rs @@ -23,7 +23,7 @@ impl Score { /// /// ``` /// use chessfriend_core::score::Score; - /// assert_eq!(-Score::MIN, Score::MAX); + /// assert_eq!(!Score::MIN, Score::MAX); /// ``` /// pub const MIN: Score = Score(Value::MIN + 1); @@ -31,7 +31,7 @@ impl Score { /// The maximum possible value of a score. pub const MAX: Score = Score(Value::MAX); - pub(crate) const CENTIPAWNS_PER_POINT: f32 = 100.0; + const CENTIPAWNS_PER_POINT: f32 = 100.0; #[must_use] pub const fn new(value: Value) -> Self { diff --git a/core/src/shapes.rs b/core/src/shapes.rs index ba3a57b..0cedc7c 100644 --- a/core/src/shapes.rs +++ b/core/src/shapes.rs @@ -1,9 +1,10 @@ // Eryn Wells -use crate::{Direction, score::Score}; use std::{array, fmt, slice, str::FromStr}; use thiserror::Error; +use crate::score::Score; + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum Shape { Pawn = 0, @@ -69,21 +70,18 @@ impl Shape { } #[must_use] - pub const fn is_promotable(&self) -> bool { + pub fn is_promotable(&self) -> bool { matches!(self, Self::Knight | Self::Bishop | Self::Rook | Self::Queen) } #[must_use] - pub const fn score(self) -> Score { - #[allow(clippy::cast_possible_truncation)] - const CP_PER_PT: i32 = Score::CENTIPAWNS_PER_POINT as i32; - + pub fn score(self) -> Score { match self { - Shape::Pawn => Score::new(CP_PER_PT), - Shape::Knight | Shape::Bishop => Score::new(3 * CP_PER_PT), - Shape::Rook => Score::new(5 * CP_PER_PT), - Shape::Queen => Score::new(9 * CP_PER_PT), - Shape::King => Score::new(200 * CP_PER_PT), + Shape::Pawn => Score::new(100), + Shape::Knight | Shape::Bishop => Score::new(300), + Shape::Rook => Score::new(500), + Shape::Queen => Score::new(900), + Shape::King => Score::new(20000), } } } @@ -95,54 +93,6 @@ pub enum Slider { Queen, } -impl Slider { - pub const NUM: usize = 3; - pub const ALL: [Self; Self::NUM] = [Slider::Bishop, Slider::Rook, Slider::Queen]; - - /// Return the set of directions a slider can move. - /// - /// ## Examples - /// - /// ``` - /// use chessfriend_core::{Direction, Slider}; - /// - /// assert_eq!( - /// Slider::Bishop.ray_directions().collect::>(), - /// vec![ - /// Direction::NorthWest, - /// Direction::NorthEast, - /// Direction::SouthEast, - /// Direction::SouthWest - /// ] - /// ); - /// ``` - /// - #[must_use] - pub fn ray_directions(self) -> Box> { - match self { - Slider::Bishop => Box::new( - [ - Direction::NorthWest, - Direction::NorthEast, - Direction::SouthEast, - Direction::SouthWest, - ] - .into_iter(), - ), - Slider::Rook => Box::new( - [ - Direction::North, - Direction::East, - Direction::South, - Direction::West, - ] - .into_iter(), - ), - Slider::Queen => Box::new(Direction::ALL.into_iter()), - } - } -} - impl From for Shape { fn from(value: Slider) -> Self { match value {