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 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 {
|
||||
BitBoard(bits)
|
||||
}
|
||||
|
@ -99,7 +109,7 @@ impl BitBoard {
|
|||
///
|
||||
/// ```
|
||||
/// use chessfriend_bitboard::BitBoard;
|
||||
/// assert!(BitBoard::EMPTY.is_empty());
|
||||
/// assert!(BitBoard::empty().is_empty());
|
||||
/// assert!(!BitBoard::full().is_empty());
|
||||
/// assert!(!BitBoard::new(0b1000).is_empty());
|
||||
/// ```
|
||||
|
@ -115,7 +125,7 @@ impl BitBoard {
|
|||
///
|
||||
/// ```
|
||||
/// use chessfriend_bitboard::BitBoard;
|
||||
/// assert!(!BitBoard::EMPTY.is_populated());
|
||||
/// assert!(!BitBoard::empty().is_populated());
|
||||
/// assert!(BitBoard::full().is_populated());
|
||||
/// assert!(BitBoard::new(0b1).is_populated());
|
||||
/// ```
|
||||
|
@ -554,8 +564,8 @@ mod tests {
|
|||
let b = bitboard![B5 G7 H3];
|
||||
|
||||
assert_eq!(a ^ b, bitboard![B5 C5 H3]);
|
||||
assert_eq!(a ^ BitBoard::EMPTY, a);
|
||||
assert_eq!(BitBoard::EMPTY ^ BitBoard::EMPTY, BitBoard::EMPTY);
|
||||
assert_eq!(a ^ BitBoard::empty(), a);
|
||||
assert_eq!(BitBoard::empty() ^ BitBoard::empty(), BitBoard::empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -110,14 +110,14 @@ pub(super) struct MoveLibrary {
|
|||
impl MoveLibrary {
|
||||
const fn new() -> MoveLibrary {
|
||||
MoveLibrary {
|
||||
rays: [[BitBoard::EMPTY; Direction::NUM]; Square::NUM],
|
||||
pawn_attacks: [[BitBoard::EMPTY; Square::NUM]; Color::NUM],
|
||||
pawn_pushes: [[BitBoard::EMPTY; Square::NUM]; Color::NUM],
|
||||
knight_moves: [BitBoard::EMPTY; Square::NUM],
|
||||
bishop_moves: [BitBoard::EMPTY; Square::NUM],
|
||||
rook_moves: [BitBoard::EMPTY; Square::NUM],
|
||||
queen_moves: [BitBoard::EMPTY; Square::NUM],
|
||||
king_moves: [BitBoard::EMPTY; Square::NUM],
|
||||
rays: [[BitBoard::empty(); Direction::NUM]; Square::NUM],
|
||||
pawn_attacks: [[BitBoard::empty(); Square::NUM]; Color::NUM],
|
||||
pawn_pushes: [[BitBoard::empty(); Square::NUM]; Color::NUM],
|
||||
knight_moves: [BitBoard::empty(); Square::NUM],
|
||||
bishop_moves: [BitBoard::empty(); Square::NUM],
|
||||
rook_moves: [BitBoard::empty(); Square::NUM],
|
||||
queen_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 {
|
||||
let mut ray = BitBoard::EMPTY;
|
||||
let mut ray = BitBoard::empty();
|
||||
|
||||
let mut iter = shift(&sq);
|
||||
while !iter.is_empty() {
|
||||
|
|
|
@ -2,27 +2,241 @@
|
|||
|
||||
use crate::Board;
|
||||
use chessfriend_bitboard::BitBoard;
|
||||
use chessfriend_core::{Color, Piece};
|
||||
use chessfriend_core::{Piece, Shape, Slider, Square};
|
||||
use std::{convert::Into, ops::BitOr};
|
||||
|
||||
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.king_bitboard(color);
|
||||
let king = self.kings(self.active_color());
|
||||
let opposing_sight = self.opposing_sight(color);
|
||||
(king & opposing_sight).is_populated()
|
||||
}
|
||||
|
||||
fn king_bitboard(&self, color: Color) -> BitBoard {
|
||||
self.find_pieces(Piece::king(color))
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::test_board;
|
||||
use chessfriend_core::Color;
|
||||
use chessfriend_core::{Square, piece};
|
||||
|
||||
#[test]
|
||||
fn active_color_is_in_check() {
|
||||
|
@ -43,4 +257,43 @@ 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ impl Movement for Piece {
|
|||
let parameters = Board::castling_parameters(Wing::KingSide, color);
|
||||
parameters.target.king.into()
|
||||
} else {
|
||||
BitBoard::EMPTY
|
||||
BitBoard::empty()
|
||||
};
|
||||
|
||||
let queenside_target_square = if board
|
||||
|
@ -51,7 +51,7 @@ impl Movement for Piece {
|
|||
let parameters = Board::castling_parameters(Wing::QueenSide, color);
|
||||
parameters.target.king.into()
|
||||
} else {
|
||||
BitBoard::EMPTY
|
||||
BitBoard::empty()
|
||||
};
|
||||
|
||||
self.sight(square, board) | kingside_target_square | queenside_target_square
|
||||
|
@ -99,11 +99,11 @@ mod tests {
|
|||
#[test]
|
||||
fn white_pushes_empty_board() {
|
||||
assert_eq!(
|
||||
pawn_pushes(Square::E4.into(), Color::White, BitBoard::EMPTY),
|
||||
pawn_pushes(Square::E4.into(), Color::White, BitBoard::empty()),
|
||||
bitboard![E5]
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_pushes(Square::E2.into(), Color::White, BitBoard::EMPTY),
|
||||
pawn_pushes(Square::E2.into(), Color::White, BitBoard::empty()),
|
||||
bitboard![E3 E4]
|
||||
);
|
||||
}
|
||||
|
@ -111,11 +111,11 @@ mod tests {
|
|||
#[test]
|
||||
fn black_pawn_empty_board() {
|
||||
assert_eq!(
|
||||
pawn_pushes(Square::A4.into(), Color::Black, BitBoard::EMPTY),
|
||||
pawn_pushes(Square::A4.into(), Color::Black, BitBoard::empty()),
|
||||
bitboard![A3]
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_pushes(Square::B7.into(), Color::Black, BitBoard::EMPTY),
|
||||
pawn_pushes(Square::B7.into(), Color::Black, BitBoard::empty()),
|
||||
bitboard![B6 B5]
|
||||
);
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ mod tests {
|
|||
fn white_pushes_blocker() {
|
||||
assert_eq!(
|
||||
pawn_pushes(Square::C5.into(), Color::White, bitboard![C6]),
|
||||
BitBoard::EMPTY
|
||||
BitBoard::empty()
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_pushes(Square::D2.into(), Color::White, bitboard![D4]),
|
||||
|
@ -132,7 +132,7 @@ mod tests {
|
|||
);
|
||||
assert_eq!(
|
||||
pawn_pushes(Square::D2.into(), Color::White, bitboard![D3]),
|
||||
BitBoard::EMPTY
|
||||
BitBoard::empty()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,7 @@ mod tests {
|
|||
fn black_pushes_blocker() {
|
||||
assert_eq!(
|
||||
pawn_pushes(Square::C5.into(), Color::Black, bitboard![C4]),
|
||||
BitBoard::EMPTY
|
||||
BitBoard::empty()
|
||||
);
|
||||
assert_eq!(
|
||||
pawn_pushes(Square::D7.into(), Color::Black, bitboard![D5]),
|
||||
|
@ -148,7 +148,7 @@ mod tests {
|
|||
);
|
||||
assert_eq!(
|
||||
pawn_pushes(Square::D7.into(), Color::Black, bitboard![D6]),
|
||||
BitBoard::EMPTY
|
||||
BitBoard::empty()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,10 +51,11 @@ impl PieceSet {
|
|||
color_occupancy[color_index] |= bitboard;
|
||||
shape_occupancy[shape_index] |= bitboard;
|
||||
|
||||
counts.increment(color, shape);
|
||||
|
||||
for square in bitboard.occupied_squares(&IterationDirection::default()) {
|
||||
let piece = Piece::new(color, shape);
|
||||
mailbox.set(piece, square);
|
||||
counts.increment(color, shape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,21 +17,20 @@ impl Counts {
|
|||
const SQUARE_NUM: u8 = Square::NUM as u8;
|
||||
|
||||
let updated_value = self.0[color as usize][shape as usize] + 1;
|
||||
if updated_value > SQUARE_NUM {
|
||||
let shape_name = shape.name();
|
||||
panic!("piece count for {color} {shape_name} overflowed");
|
||||
if updated_value <= SQUARE_NUM {
|
||||
self.0[color as usize][shape as usize] = updated_value;
|
||||
} 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) {
|
||||
let count = self.0[color as usize][shape as usize];
|
||||
let updated_count = count.checked_sub(1).unwrap_or_else(|| {
|
||||
let shape_name = shape.name();
|
||||
panic!("piece count for {color} {shape_name} should not underflow");
|
||||
});
|
||||
self.0[color as usize][shape as usize] = updated_count;
|
||||
if let Some(updated_count) = count.checked_sub(1) {
|
||||
self.0[color as usize][shape as usize] = updated_count;
|
||||
} else {
|
||||
unreachable!("piece count for {color} {shape} underflowed");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -305,7 +305,7 @@ mod tests {
|
|||
let piece = piece!(White Pawn);
|
||||
let sight = piece.sight(Square::E4, &pos);
|
||||
|
||||
assert_eq!(sight, BitBoard::EMPTY);
|
||||
assert_eq!(sight, BitBoard::empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
// 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,
|
||||
|
@ -96,6 +95,54 @@ 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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue