Compare commits
10 commits
main
...
calculate-
Author | SHA1 | Date | |
---|---|---|---|
1c94f157e6 | |||
5444d8ea3a | |||
d3c94356bd | |||
6e8c0d3466 | |||
f658903b54 | |||
7d3fd4b20a | |||
b4536ffbe3 | |||
0a2dd7e09d | |||
21a2a37e1f | |||
89a9588e69 |
9 changed files with 352 additions and 155 deletions
113
README.md
113
README.md
|
@ -1,113 +0,0 @@
|
||||||
ChessFriend
|
|
||||||
===========
|
|
||||||
|
|
||||||
A chess engine written in Rust.
|
|
||||||
|
|
||||||
The project is divided into crates for major components of the engine. These
|
|
||||||
crates are collected in a Cargo workspace. All crates have the `chessfriend_`
|
|
||||||
naming prefix. The directory structure omits this prefix, and I also frequently
|
|
||||||
do when referring to them.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Engine Crates
|
|
||||||
|
|
||||||
The engine is divided into several crates, each providing vital types and
|
|
||||||
functionality.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### `core`
|
|
||||||
|
|
||||||
A collection of types for representing core concepts in a chess game and the
|
|
||||||
engine. Types like `Color` (player or piece color), `Shape` (the shape of a
|
|
||||||
piece: knight, etc), `Piece` (a piece of a particular color and shape), and
|
|
||||||
`Score` (for scoring a board position) live here.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### `bitboard`
|
|
||||||
|
|
||||||
Implements an efficient BitBoard type. Bitboards use a 64-bit bit field to mark a
|
|
||||||
square on a board as occupied or free.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### `board`
|
|
||||||
|
|
||||||
Implements a `Board` type that represents a moment-in-time board position. FEN
|
|
||||||
parsing and production lives here.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### `moves`
|
|
||||||
|
|
||||||
The `Move` type lives here, along with routines for encoding moves in efficient
|
|
||||||
forms, parsing moves from algebraic notation, and recording moves in a game
|
|
||||||
context. Additionally, the move generators for each shape of piece are here.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### `position`
|
|
||||||
|
|
||||||
Exports the `Position` type, representing a board position within the context of
|
|
||||||
a game. As such, it also provides a move list, and methods to make and unmake
|
|
||||||
moves.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Support Crates
|
|
||||||
|
|
||||||
These crates are for debugging and testing.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### `explorer`
|
|
||||||
|
|
||||||
This crate implements a small command-line application for "exploring" board
|
|
||||||
positions. I meant for this program to be a debugging utility so that I could
|
|
||||||
examine bitboards and other board structures live. It has grown over time to
|
|
||||||
also support more aspects of interacting with the engine. So you can also use it
|
|
||||||
to play a game!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### `perft`
|
|
||||||
|
|
||||||
A small Perft utility that executes perft to a given depth from some starting
|
|
||||||
position.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
Build the engine in the usual Rusty way.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ cargo build
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
Test in the usual Rusty way.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ cargo test
|
|
||||||
```
|
|
||||||
|
|
||||||
The engine has a fairly comprehensive unit test suite, as well as a decent pile
|
|
||||||
of integration tests.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Authors
|
|
||||||
|
|
||||||
This engine is built entirely by me, Eryn Wells.
|
|
|
@ -46,6 +46,16 @@ impl BitBoard {
|
||||||
pub const EMPTY: BitBoard = BitBoard(u64::MIN);
|
pub const EMPTY: BitBoard = BitBoard(u64::MIN);
|
||||||
pub const FULL: BitBoard = BitBoard(u64::MAX);
|
pub const FULL: BitBoard = BitBoard(u64::MAX);
|
||||||
|
|
||||||
|
#[deprecated(note = "Use BitBoard::EMPTY instead")]
|
||||||
|
pub const fn empty() -> BitBoard {
|
||||||
|
Self::EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated(note = "Use BitBoard::FULL instead")]
|
||||||
|
pub const fn full() -> BitBoard {
|
||||||
|
Self::FULL
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn new(bits: u64) -> BitBoard {
|
pub const fn new(bits: u64) -> BitBoard {
|
||||||
BitBoard(bits)
|
BitBoard(bits)
|
||||||
}
|
}
|
||||||
|
@ -99,7 +109,7 @@ impl BitBoard {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use chessfriend_bitboard::BitBoard;
|
/// use chessfriend_bitboard::BitBoard;
|
||||||
/// assert!(BitBoard::EMPTY.is_empty());
|
/// assert!(BitBoard::empty().is_empty());
|
||||||
/// assert!(!BitBoard::full().is_empty());
|
/// assert!(!BitBoard::full().is_empty());
|
||||||
/// assert!(!BitBoard::new(0b1000).is_empty());
|
/// assert!(!BitBoard::new(0b1000).is_empty());
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -115,7 +125,7 @@ impl BitBoard {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use chessfriend_bitboard::BitBoard;
|
/// use chessfriend_bitboard::BitBoard;
|
||||||
/// assert!(!BitBoard::EMPTY.is_populated());
|
/// assert!(!BitBoard::empty().is_populated());
|
||||||
/// assert!(BitBoard::full().is_populated());
|
/// assert!(BitBoard::full().is_populated());
|
||||||
/// assert!(BitBoard::new(0b1).is_populated());
|
/// assert!(BitBoard::new(0b1).is_populated());
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -554,8 +564,8 @@ mod tests {
|
||||||
let b = bitboard![B5 G7 H3];
|
let b = bitboard![B5 G7 H3];
|
||||||
|
|
||||||
assert_eq!(a ^ b, bitboard![B5 C5 H3]);
|
assert_eq!(a ^ b, bitboard![B5 C5 H3]);
|
||||||
assert_eq!(a ^ BitBoard::EMPTY, a);
|
assert_eq!(a ^ BitBoard::empty(), a);
|
||||||
assert_eq!(BitBoard::EMPTY ^ BitBoard::EMPTY, BitBoard::EMPTY);
|
assert_eq!(BitBoard::empty() ^ BitBoard::empty(), BitBoard::empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -110,14 +110,14 @@ pub(super) struct MoveLibrary {
|
||||||
impl MoveLibrary {
|
impl MoveLibrary {
|
||||||
const fn new() -> MoveLibrary {
|
const fn new() -> MoveLibrary {
|
||||||
MoveLibrary {
|
MoveLibrary {
|
||||||
rays: [[BitBoard::EMPTY; Direction::NUM]; Square::NUM],
|
rays: [[BitBoard::empty(); Direction::NUM]; Square::NUM],
|
||||||
pawn_attacks: [[BitBoard::EMPTY; Square::NUM]; Color::NUM],
|
pawn_attacks: [[BitBoard::empty(); Square::NUM]; Color::NUM],
|
||||||
pawn_pushes: [[BitBoard::EMPTY; Square::NUM]; Color::NUM],
|
pawn_pushes: [[BitBoard::empty(); Square::NUM]; Color::NUM],
|
||||||
knight_moves: [BitBoard::EMPTY; Square::NUM],
|
knight_moves: [BitBoard::empty(); Square::NUM],
|
||||||
bishop_moves: [BitBoard::EMPTY; Square::NUM],
|
bishop_moves: [BitBoard::empty(); Square::NUM],
|
||||||
rook_moves: [BitBoard::EMPTY; Square::NUM],
|
rook_moves: [BitBoard::empty(); Square::NUM],
|
||||||
queen_moves: [BitBoard::EMPTY; Square::NUM],
|
queen_moves: [BitBoard::empty(); Square::NUM],
|
||||||
king_moves: [BitBoard::EMPTY; Square::NUM],
|
king_moves: [BitBoard::empty(); Square::NUM],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +238,7 @@ impl MoveLibrary {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _generate_ray(sq: BitBoard, shift: fn(&BitBoard) -> BitBoard) -> BitBoard {
|
fn _generate_ray(sq: BitBoard, shift: fn(&BitBoard) -> BitBoard) -> BitBoard {
|
||||||
let mut ray = BitBoard::EMPTY;
|
let mut ray = BitBoard::empty();
|
||||||
|
|
||||||
let mut iter = shift(&sq);
|
let mut iter = shift(&sq);
|
||||||
while !iter.is_empty() {
|
while !iter.is_empty() {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ impl Movement for Piece {
|
||||||
let parameters = Board::castling_parameters(Wing::KingSide, color);
|
let parameters = Board::castling_parameters(Wing::KingSide, color);
|
||||||
parameters.target.king.into()
|
parameters.target.king.into()
|
||||||
} else {
|
} else {
|
||||||
BitBoard::EMPTY
|
BitBoard::empty()
|
||||||
};
|
};
|
||||||
|
|
||||||
let queenside_target_square = if board
|
let queenside_target_square = if board
|
||||||
|
@ -51,7 +51,7 @@ impl Movement for Piece {
|
||||||
let parameters = Board::castling_parameters(Wing::QueenSide, color);
|
let parameters = Board::castling_parameters(Wing::QueenSide, color);
|
||||||
parameters.target.king.into()
|
parameters.target.king.into()
|
||||||
} else {
|
} else {
|
||||||
BitBoard::EMPTY
|
BitBoard::empty()
|
||||||
};
|
};
|
||||||
|
|
||||||
self.sight(square, board) | kingside_target_square | queenside_target_square
|
self.sight(square, board) | kingside_target_square | queenside_target_square
|
||||||
|
@ -99,11 +99,11 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn white_pushes_empty_board() {
|
fn white_pushes_empty_board() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pawn_pushes(Square::E4.into(), Color::White, BitBoard::EMPTY),
|
pawn_pushes(Square::E4.into(), Color::White, BitBoard::empty()),
|
||||||
bitboard![E5]
|
bitboard![E5]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pawn_pushes(Square::E2.into(), Color::White, BitBoard::EMPTY),
|
pawn_pushes(Square::E2.into(), Color::White, BitBoard::empty()),
|
||||||
bitboard![E3 E4]
|
bitboard![E3 E4]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -111,11 +111,11 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn black_pawn_empty_board() {
|
fn black_pawn_empty_board() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pawn_pushes(Square::A4.into(), Color::Black, BitBoard::EMPTY),
|
pawn_pushes(Square::A4.into(), Color::Black, BitBoard::empty()),
|
||||||
bitboard![A3]
|
bitboard![A3]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pawn_pushes(Square::B7.into(), Color::Black, BitBoard::EMPTY),
|
pawn_pushes(Square::B7.into(), Color::Black, BitBoard::empty()),
|
||||||
bitboard![B6 B5]
|
bitboard![B6 B5]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ mod tests {
|
||||||
fn white_pushes_blocker() {
|
fn white_pushes_blocker() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pawn_pushes(Square::C5.into(), Color::White, bitboard![C6]),
|
pawn_pushes(Square::C5.into(), Color::White, bitboard![C6]),
|
||||||
BitBoard::EMPTY
|
BitBoard::empty()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pawn_pushes(Square::D2.into(), Color::White, bitboard![D4]),
|
pawn_pushes(Square::D2.into(), Color::White, bitboard![D4]),
|
||||||
|
@ -132,7 +132,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pawn_pushes(Square::D2.into(), Color::White, bitboard![D3]),
|
pawn_pushes(Square::D2.into(), Color::White, bitboard![D3]),
|
||||||
BitBoard::EMPTY
|
BitBoard::empty()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ mod tests {
|
||||||
fn black_pushes_blocker() {
|
fn black_pushes_blocker() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pawn_pushes(Square::C5.into(), Color::Black, bitboard![C4]),
|
pawn_pushes(Square::C5.into(), Color::Black, bitboard![C4]),
|
||||||
BitBoard::EMPTY
|
BitBoard::empty()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pawn_pushes(Square::D7.into(), Color::Black, bitboard![D5]),
|
pawn_pushes(Square::D7.into(), Color::Black, bitboard![D5]),
|
||||||
|
@ -148,7 +148,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pawn_pushes(Square::D7.into(), Color::Black, bitboard![D6]),
|
pawn_pushes(Square::D7.into(), Color::Black, bitboard![D6]),
|
||||||
BitBoard::EMPTY
|
BitBoard::empty()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,10 +51,11 @@ impl PieceSet {
|
||||||
color_occupancy[color_index] |= bitboard;
|
color_occupancy[color_index] |= bitboard;
|
||||||
shape_occupancy[shape_index] |= bitboard;
|
shape_occupancy[shape_index] |= bitboard;
|
||||||
|
|
||||||
|
counts.increment(color, shape);
|
||||||
|
|
||||||
for square in bitboard.occupied_squares(&IterationDirection::default()) {
|
for square in bitboard.occupied_squares(&IterationDirection::default()) {
|
||||||
let piece = Piece::new(color, shape);
|
let piece = Piece::new(color, shape);
|
||||||
mailbox.set(piece, square);
|
mailbox.set(piece, square);
|
||||||
counts.increment(color, shape);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,21 +17,20 @@ impl Counts {
|
||||||
const SQUARE_NUM: u8 = Square::NUM as u8;
|
const SQUARE_NUM: u8 = Square::NUM as u8;
|
||||||
|
|
||||||
let updated_value = self.0[color as usize][shape as usize] + 1;
|
let updated_value = self.0[color as usize][shape as usize] + 1;
|
||||||
if updated_value > SQUARE_NUM {
|
if updated_value <= SQUARE_NUM {
|
||||||
let shape_name = shape.name();
|
self.0[color as usize][shape as usize] = updated_value;
|
||||||
panic!("piece count for {color} {shape_name} overflowed");
|
} else {
|
||||||
|
unreachable!("piece count for {color} {shape} overflowed");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.0[color as usize][shape as usize] = updated_value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrement(&mut self, color: Color, shape: Shape) {
|
pub fn decrement(&mut self, color: Color, shape: Shape) {
|
||||||
let count = self.0[color as usize][shape as usize];
|
let count = self.0[color as usize][shape as usize];
|
||||||
let updated_count = count.checked_sub(1).unwrap_or_else(|| {
|
if let Some(updated_count) = count.checked_sub(1) {
|
||||||
let shape_name = shape.name();
|
self.0[color as usize][shape as usize] = updated_count;
|
||||||
panic!("piece count for {color} {shape_name} should not underflow");
|
} else {
|
||||||
});
|
unreachable!("piece count for {color} {shape} underflowed");
|
||||||
self.0[color as usize][shape as usize] = updated_count;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -305,7 +305,7 @@ mod tests {
|
||||||
let piece = piece!(White Pawn);
|
let piece = piece!(White Pawn);
|
||||||
let sight = piece.sight(Square::E4, &pos);
|
let sight = piece.sight(Square::E4, &pos);
|
||||||
|
|
||||||
assert_eq!(sight, BitBoard::EMPTY);
|
assert_eq!(sight, BitBoard::empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -96,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