Compare commits
	
		
			10 commits
		
	
	
		
			a904e4a5bb
			...
			1c94f157e6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1c94f157e6 | |||
| 5444d8ea3a | |||
| d3c94356bd | |||
| 6e8c0d3466 | |||
| f658903b54 | |||
| 7d3fd4b20a | |||
| b4536ffbe3 | |||
| 0a2dd7e09d | |||
| 21a2a37e1f | |||
| 89a9588e69 | 
					 5 changed files with 362 additions and 21 deletions
				
			
		| 
						 | 
					@ -160,9 +160,9 @@ impl BitBoard {
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// ```
 | 
					    /// ```
 | 
				
			||||||
    /// use chessfriend_bitboard::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::new(0b01011110010).population_count(), 6);
 | 
				
			||||||
    /// assert_eq!(BitBoard::full().population_count(), 64);
 | 
					    /// assert_eq!(BitBoard::FULL.population_count(), 64);
 | 
				
			||||||
    /// ```
 | 
					    /// ```
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub const fn population_count(&self) -> u32 {
 | 
					    pub const fn population_count(&self) -> u32 {
 | 
				
			||||||
| 
						 | 
					@ -211,8 +211,8 @@ impl BitBoard {
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// ```
 | 
					    /// ```
 | 
				
			||||||
    /// use chessfriend_bitboard::BitBoard;
 | 
					    /// use chessfriend_bitboard::BitBoard;
 | 
				
			||||||
    /// assert!(!BitBoard::empty().is_single_square(), "Empty bitboards represent no 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::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(0b010011110101101100).is_single_square(), "This bitboard represents a bunch of squares");
 | 
				
			||||||
    /// assert!(BitBoard::new(0b10000000000000).is_single_square());
 | 
					    /// assert!(BitBoard::new(0b10000000000000).is_single_square());
 | 
				
			||||||
    /// ```
 | 
					    /// ```
 | 
				
			||||||
| 
						 | 
					@ -233,6 +233,38 @@ 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>>(),
 | 
				
			||||||
 | 
					    ///     vec![Square::E5, Square::E6, Square::E7, Square::E8]
 | 
				
			||||||
 | 
					    /// );
 | 
				
			||||||
 | 
					    /// ```
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    #[must_use]
 | 
				
			||||||
 | 
					    pub fn occupied_squares_direction(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        direction: Direction,
 | 
				
			||||||
 | 
					    ) -> Box<dyn Iterator<Item = Square>> {
 | 
				
			||||||
 | 
					        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]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn occupied_squares_leading(&self) -> LeadingBitScanner {
 | 
					    pub fn occupied_squares_leading(&self) -> LeadingBitScanner {
 | 
				
			||||||
        LeadingBitScanner::new(self.0)
 | 
					        LeadingBitScanner::new(self.0)
 | 
				
			||||||
| 
						 | 
					@ -255,6 +287,12 @@ impl BitBoard {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get the first occupied square in the given direction.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// ## To-Do
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// - Take `direction` by value instead of reference
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn first_occupied_square(&self, direction: &IterationDirection) -> Option<Square> {
 | 
					    pub fn first_occupied_square(&self, direction: &IterationDirection) -> Option<Square> {
 | 
				
			||||||
        match direction {
 | 
					        match direction {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,7 @@ impl Board {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let color = self.unwrap_color(color);
 | 
					        let color = self.unwrap_color(color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if !self.has_castling_right_unwrapped(color, wing.into()) {
 | 
					        if !self.has_castling_right_unwrapped(color, wing) {
 | 
				
			||||||
            return Err(CastleEvaluationError::NoRights { color, wing });
 | 
					            return Err(CastleEvaluationError::NoRights { color, wing });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,27 +2,241 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::Board;
 | 
					use crate::Board;
 | 
				
			||||||
use chessfriend_bitboard::BitBoard;
 | 
					use chessfriend_bitboard::BitBoard;
 | 
				
			||||||
use chessfriend_core::{Color, Piece};
 | 
					use chessfriend_core::{Piece, Shape, Slider, Square};
 | 
				
			||||||
 | 
					use std::{convert::Into, ops::BitOr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Board {
 | 
					impl Board {
 | 
				
			||||||
    /// Return whether the active color is in check.
 | 
					    /// Return whether the active color is in check.
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn is_in_check(&self) -> bool {
 | 
					    pub fn is_in_check(&self) -> bool {
 | 
				
			||||||
        let color = self.active_color();
 | 
					        let color = self.active_color();
 | 
				
			||||||
        let king = self.king_bitboard(color);
 | 
					        let king = self.kings(self.active_color());
 | 
				
			||||||
        let opposing_sight = self.opposing_sight(color);
 | 
					        let opposing_sight = self.opposing_sight(color);
 | 
				
			||||||
        (king & opposing_sight).is_populated()
 | 
					        (king & opposing_sight).is_populated()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn king_bitboard(&self, color: Color) -> BitBoard {
 | 
					    /// Calculate checks on the board.
 | 
				
			||||||
        self.find_pieces(Piece::king(color))
 | 
					    ///
 | 
				
			||||||
 | 
					    /// ## 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<CheckInfo> = 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<BitBoard, ()> {
 | 
				
			||||||
 | 
					        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<CheckInfo> {
 | 
				
			||||||
 | 
					        println!(
 | 
				
			||||||
 | 
					            "Examining {} on {square} for checks",
 | 
				
			||||||
 | 
					            Into::<Shape>::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::<BitBoard>::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::<BitBoard>::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
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
    use crate::test_board;
 | 
					    use crate::test_board;
 | 
				
			||||||
    use chessfriend_core::Color;
 | 
					    use chessfriend_core::{Square, piece};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn active_color_is_in_check() {
 | 
					    fn active_color_is_in_check() {
 | 
				
			||||||
| 
						 | 
					@ -43,4 +257,43 @@ mod tests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert!(!board.is_in_check());
 | 
					        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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,7 @@ impl Score {
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// ```
 | 
					    /// ```
 | 
				
			||||||
    /// use chessfriend_core::score::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);
 | 
					    pub const MIN: Score = Score(Value::MIN + 1);
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@ impl Score {
 | 
				
			||||||
    /// The maximum possible value of a score.
 | 
					    /// The maximum possible value of a score.
 | 
				
			||||||
    pub const MAX: Score = Score(Value::MAX);
 | 
					    pub const MAX: Score = Score(Value::MAX);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const CENTIPAWNS_PER_POINT: f32 = 100.0;
 | 
					    pub(crate) const CENTIPAWNS_PER_POINT: f32 = 100.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub const fn new(value: Value) -> Self {
 | 
					    pub const fn new(value: Value) -> Self {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,9 @@
 | 
				
			||||||
// Eryn Wells <eryn@erynwells.me>
 | 
					// Eryn Wells <eryn@erynwells.me>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{Direction, score::Score};
 | 
				
			||||||
use std::{array, fmt, slice, str::FromStr};
 | 
					use std::{array, fmt, slice, str::FromStr};
 | 
				
			||||||
use thiserror::Error;
 | 
					use thiserror::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::score::Score;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
 | 
					#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
 | 
				
			||||||
pub enum Shape {
 | 
					pub enum Shape {
 | 
				
			||||||
    Pawn = 0,
 | 
					    Pawn = 0,
 | 
				
			||||||
| 
						 | 
					@ -70,18 +69,21 @@ impl Shape {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn is_promotable(&self) -> bool {
 | 
					    pub const fn is_promotable(&self) -> bool {
 | 
				
			||||||
        matches!(self, Self::Knight | Self::Bishop | Self::Rook | Self::Queen)
 | 
					        matches!(self, Self::Knight | Self::Bishop | Self::Rook | Self::Queen)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[must_use]
 | 
					    #[must_use]
 | 
				
			||||||
    pub fn score(self) -> Score {
 | 
					    pub const fn score(self) -> Score {
 | 
				
			||||||
 | 
					        #[allow(clippy::cast_possible_truncation)]
 | 
				
			||||||
 | 
					        const CP_PER_PT: i32 = Score::CENTIPAWNS_PER_POINT as i32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match self {
 | 
					        match self {
 | 
				
			||||||
            Shape::Pawn => Score::new(100),
 | 
					            Shape::Pawn => Score::new(CP_PER_PT),
 | 
				
			||||||
            Shape::Knight | Shape::Bishop => Score::new(300),
 | 
					            Shape::Knight | Shape::Bishop => Score::new(3 * CP_PER_PT),
 | 
				
			||||||
            Shape::Rook => Score::new(500),
 | 
					            Shape::Rook => Score::new(5 * CP_PER_PT),
 | 
				
			||||||
            Shape::Queen => Score::new(900),
 | 
					            Shape::Queen => Score::new(9 * CP_PER_PT),
 | 
				
			||||||
            Shape::King => Score::new(20000),
 | 
					            Shape::King => Score::new(200 * CP_PER_PT),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -93,6 +95,54 @@ pub enum Slider {
 | 
				
			||||||
    Queen,
 | 
					    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>>(),
 | 
				
			||||||
 | 
					    ///     vec![
 | 
				
			||||||
 | 
					    ///         Direction::NorthWest,
 | 
				
			||||||
 | 
					    ///         Direction::NorthEast,
 | 
				
			||||||
 | 
					    ///         Direction::SouthEast,
 | 
				
			||||||
 | 
					    ///         Direction::SouthWest
 | 
				
			||||||
 | 
					    ///     ]
 | 
				
			||||||
 | 
					    /// );
 | 
				
			||||||
 | 
					    /// ```
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    #[must_use]
 | 
				
			||||||
 | 
					    pub fn ray_directions(self) -> Box<dyn Iterator<Item = Direction>> {
 | 
				
			||||||
 | 
					        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<Slider> for Shape {
 | 
					impl From<Slider> for Shape {
 | 
				
			||||||
    fn from(value: Slider) -> Self {
 | 
					    fn from(value: Slider) -> Self {
 | 
				
			||||||
        match value {
 | 
					        match value {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue