Compare commits
1 commit
main
...
core-float
Author | SHA1 | Date | |
---|---|---|---|
d1950def00 |
10 changed files with 119 additions and 244 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());
|
||||
/// ```
|
||||
|
@ -150,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 {
|
||||
|
@ -201,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());
|
||||
/// ```
|
||||
|
@ -223,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)
|
||||
|
@ -265,24 +243,6 @@ impl BitBoard {
|
|||
TrailingBitScanner::new(self.0)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn first_occupied_square_direction(&self, direction: Direction) -> Option<Square> {
|
||||
match direction {
|
||||
Direction::North | Direction::NorthEast | Direction::NorthWest | Direction::East => {
|
||||
self.first_occupied_square_trailing()
|
||||
}
|
||||
Direction::SouthEast | Direction::South | Direction::SouthWest | Direction::West => {
|
||||
self.first_occupied_square_leading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
|
@ -554,8 +514,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() {
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -138,6 +138,20 @@ struct SightInfo {
|
|||
friendly_occupancy: BitBoard,
|
||||
}
|
||||
|
||||
macro_rules! ray_in_direction {
|
||||
($square:expr, $blockers:expr, $direction:ident, $first_occupied_square:tt) => {{
|
||||
let ray = BitBoard::ray($square, Direction::$direction);
|
||||
let ray_blockers = ray & $blockers;
|
||||
if let Some(first_occupied_square) = ray_blockers.$first_occupied_square() {
|
||||
let remainder = BitBoard::ray(first_occupied_square, Direction::$direction);
|
||||
let attack_ray = ray & !remainder;
|
||||
attack_ray
|
||||
} else {
|
||||
ray
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Compute sight of a white pawn.
|
||||
fn white_pawn_sight(info: &SightInfo, en_passant_square: BitBoard) -> BitBoard {
|
||||
let possible_squares = !info.friendly_occupancy | en_passant_square;
|
||||
|
@ -161,27 +175,15 @@ fn knight_sight(info: &SightInfo) -> BitBoard {
|
|||
BitBoard::knight_moves(info.square)
|
||||
}
|
||||
|
||||
fn ray_in_direction(square: Square, blockers: BitBoard, direction: Direction) -> BitBoard {
|
||||
let ray = BitBoard::ray(square, direction);
|
||||
|
||||
let ray_blockers = ray & blockers;
|
||||
if let Some(first_occupied_square) = ray_blockers.first_occupied_square_direction(direction) {
|
||||
let remainder = BitBoard::ray(first_occupied_square, direction);
|
||||
let attack_ray = ray & !remainder;
|
||||
attack_ray
|
||||
} else {
|
||||
ray
|
||||
}
|
||||
}
|
||||
|
||||
fn bishop_sight(info: &SightInfo) -> BitBoard {
|
||||
let bishop = info.square;
|
||||
let occupancy = info.occupancy;
|
||||
|
||||
let sight = ray_in_direction(bishop, occupancy, Direction::NorthEast)
|
||||
| ray_in_direction(bishop, occupancy, Direction::SouthEast)
|
||||
| ray_in_direction(bishop, occupancy, Direction::SouthWest)
|
||||
| ray_in_direction(bishop, occupancy, Direction::NorthWest);
|
||||
#[rustfmt::skip]
|
||||
let sight = ray_in_direction!(bishop, occupancy, NorthEast, first_occupied_square_trailing)
|
||||
| ray_in_direction!(bishop, occupancy, SouthEast, first_occupied_square_leading)
|
||||
| ray_in_direction!(bishop, occupancy, SouthWest, first_occupied_square_leading)
|
||||
| ray_in_direction!(bishop, occupancy, NorthWest, first_occupied_square_trailing);
|
||||
|
||||
sight
|
||||
}
|
||||
|
@ -190,10 +192,11 @@ fn rook_sight(info: &SightInfo) -> BitBoard {
|
|||
let rook = info.square;
|
||||
let occupancy = info.occupancy;
|
||||
|
||||
let sight = ray_in_direction(rook, occupancy, Direction::North)
|
||||
| ray_in_direction(rook, occupancy, Direction::East)
|
||||
| ray_in_direction(rook, occupancy, Direction::South)
|
||||
| ray_in_direction(rook, occupancy, Direction::West);
|
||||
#[rustfmt::skip]
|
||||
let sight = ray_in_direction!(rook, occupancy, North, first_occupied_square_trailing)
|
||||
| ray_in_direction!(rook, occupancy, East, first_occupied_square_trailing)
|
||||
| ray_in_direction!(rook, occupancy, South, first_occupied_square_leading)
|
||||
| ray_in_direction!(rook, occupancy, West, first_occupied_square_leading);
|
||||
|
||||
sight
|
||||
}
|
||||
|
@ -202,14 +205,15 @@ fn queen_sight(info: &SightInfo) -> BitBoard {
|
|||
let queen = info.square;
|
||||
let occupancy = info.occupancy;
|
||||
|
||||
let sight = ray_in_direction(queen, occupancy, Direction::NorthWest)
|
||||
| ray_in_direction(queen, occupancy, Direction::North)
|
||||
| ray_in_direction(queen, occupancy, Direction::NorthEast)
|
||||
| ray_in_direction(queen, occupancy, Direction::East)
|
||||
| ray_in_direction(queen, occupancy, Direction::SouthEast)
|
||||
| ray_in_direction(queen, occupancy, Direction::South)
|
||||
| ray_in_direction(queen, occupancy, Direction::SouthWest)
|
||||
| ray_in_direction(queen, occupancy, Direction::West);
|
||||
#[rustfmt::skip]
|
||||
let sight = ray_in_direction!(queen, occupancy, NorthWest, first_occupied_square_trailing)
|
||||
| ray_in_direction!(queen, occupancy, North, first_occupied_square_trailing)
|
||||
| ray_in_direction!(queen, occupancy, NorthEast, first_occupied_square_trailing)
|
||||
| ray_in_direction!(queen, occupancy, East, first_occupied_square_trailing)
|
||||
| ray_in_direction!(queen, occupancy, SouthEast, first_occupied_square_leading)
|
||||
| ray_in_direction!(queen, occupancy, South, first_occupied_square_leading)
|
||||
| ray_in_direction!(queen, occupancy, SouthWest, first_occupied_square_leading)
|
||||
| ray_in_direction!(queen, occupancy, West, first_occupied_square_leading);
|
||||
|
||||
sight
|
||||
}
|
||||
|
@ -305,7 +309,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]
|
||||
|
|
|
@ -6,6 +6,7 @@ use std::{
|
|||
};
|
||||
|
||||
pub(crate) type Value = i32;
|
||||
pub(crate) type FloatValue = f32;
|
||||
|
||||
/// A score for a position in centipawns.
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
|
@ -23,7 +24,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,13 +32,31 @@ 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 {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
/// Create a [`Score`] from a floating point value. This method assumes the
|
||||
/// value is a point value where 1 point = 1 pawn. The floating point value
|
||||
/// will be truncated as part of this conversion.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```
|
||||
/// use chessfriend_core::score::Score;
|
||||
/// assert_eq!(Score::from_float(3.1415926), Score(314));
|
||||
/// assert_ne!(Score::from_float(2.7182818).to_float(), 2.7182818);
|
||||
/// ```
|
||||
///
|
||||
#[must_use]
|
||||
pub const fn from_float(value: FloatValue) -> Self {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
Self((value * Self::CENTIPAWNS_PER_POINT) as Value)
|
||||
}
|
||||
|
||||
/// Returns `true` if this [`Score`] is zero.
|
||||
///
|
||||
/// ## Examples
|
||||
|
@ -52,6 +71,14 @@ impl Score {
|
|||
pub const fn is_zero(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
/// Return a floating point value in points where 1 point = 1 pawn. This
|
||||
/// conversion loses precision.
|
||||
#[must_use]
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
pub const fn to_float(&self) -> FloatValue {
|
||||
self.0 as f32 / Self::CENTIPAWNS_PER_POINT
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Score {
|
||||
|
|
|
@ -70,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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue