Compare commits

...

7 commits

Author SHA1 Message Date
3a0541a2c3 [bitboard] Add a doc comment to BitBoard::first_occupied_square 2025-07-12 20:27:47 -07:00
b6d27356ac [bitboard] Implement BitBoard::occupied_squares_direction
Iterate a BitBoard in a direction (from leading or trailing edge) based on a
board direction.
2025-07-12 20:27:47 -07:00
146e4d34d3 [core] Fix an incorrect assertion in the Score doc test
Negate with - instead of with !.
2025-07-12 20:27:47 -07:00
b505606925 [core] Export Score::CENTIPAWNS_PER_POINT to the crate
This constant is a conversion factor of points to the internal fixed point unit
of centipawns. Points are more familiar to people because pawns are worth 1 pt.

Calculate the scores of the various piece shapes with this constant.
2025-07-12 20:27:47 -07:00
b3ff8dec49 [core] Make Shape::is_promotable() const 2025-07-12 20:27:47 -07:00
484fcf342e [board] Remove a useless .into() call
Clippy pointed this out to me. This .into() call serves no purpose.
2025-07-12 20:27:47 -07:00
45183c910c [bitboard] Replace some references to BitBoard::full() and BitBoard::empty() with the const values
Two doc tests reference the methods instead of the const variables. Update them.
2025-07-12 20:27:47 -07:00
4 changed files with 55 additions and 14 deletions

View file

@ -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 {

View file

@ -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 });
} }

View file

@ -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 {

View file

@ -70,18 +70,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),
} }
} }
} }