Compare commits

..

No commits in common. "1c94f157e671e07522843b27216c722d7c21f1fe" and "a904e4a5bb2163c3bcfcb25a520878cbfc189365" have entirely different histories.

5 changed files with 21 additions and 362 deletions

View file

@ -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>>(),
/// 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]
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<Square> {
match direction {

View file

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

View file

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

View file

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

View file

@ -1,9 +1,10 @@
// Eryn Wells <eryn@erynwells.me>
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>>(),
/// 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 {
fn from(value: Slider) -> Self {
match value {