Compare commits
36 commits
874abbf82f
...
1a7763e1f4
Author | SHA1 | Date | |
---|---|---|---|
Bruno BELANYI | 1a7763e1f4 | ||
Bruno BELANYI | 87258c1084 | ||
Bruno BELANYI | 168d9e69ab | ||
Bruno BELANYI | f13d44b8e3 | ||
Bruno BELANYI | cb3b1ee745 | ||
Bruno BELANYI | 934597d63d | ||
Bruno BELANYI | 024a41fa18 | ||
Bruno BELANYI | eefa707c07 | ||
Bruno BELANYI | 2e410ba104 | ||
Bruno BELANYI | a6e8ac06b6 | ||
Bruno BELANYI | 99129e453c | ||
Bruno BELANYI | 9cddda7478 | ||
Bruno BELANYI | b5bb613b5e | ||
Bruno BELANYI | 0cefb05017 | ||
Bruno BELANYI | fb0e289fa0 | ||
Bruno BELANYI | 384f361da2 | ||
Bruno BELANYI | f633c6e224 | ||
Bruno BELANYI | 08ff8db0ac | ||
Bruno BELANYI | 76577718d8 | ||
Bruno BELANYI | 7df442e03c | ||
Bruno BELANYI | dba4d94e35 | ||
Bruno BELANYI | 6f0e2f732b | ||
Bruno BELANYI | 611e12c033 | ||
Bruno BELANYI | dde5b69f81 | ||
Bruno BELANYI | 7e23cb8f77 | ||
Bruno BELANYI | 8102b08cf0 | ||
Bruno BELANYI | e7e5927902 | ||
Bruno BELANYI | 80e3ace8fc | ||
Bruno BELANYI | 02d48fe526 | ||
Bruno BELANYI | 915244b238 | ||
Bruno BELANYI | d2c61a81b5 | ||
Bruno BELANYI | bd43535192 | ||
Bruno BELANYI | 8289204e4b | ||
Bruno BELANYI | d2eda07036 | ||
Bruno BELANYI | 0222ec4c2d | ||
Bruno BELANYI | d97e7d646e |
11
Cargo.toml
11
Cargo.toml
|
@ -2,8 +2,19 @@
|
|||
name = "seer"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
build = "src/build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
random = "0.12.2"
|
||||
|
||||
[build-dependencies]
|
||||
random = "0.12.2"
|
||||
|
||||
# Optimize build scripts to shorten compile times.
|
||||
[profile.dev.build-override]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release.build-override]
|
||||
opt-level = 3
|
||||
|
|
|
@ -11,7 +11,8 @@ impl Iterator for BitboardIterator {
|
|||
} else {
|
||||
let lsb = self.0.trailing_zeros() as usize;
|
||||
self.0 ^= 1 << lsb;
|
||||
Some(crate::board::Square::from_index(lsb))
|
||||
// SAFETY: we know the value is in-bounds
|
||||
Some(unsafe { crate::board::Square::from_index_unchecked(lsb) })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,6 +68,13 @@ impl Bitboard {
|
|||
self == Self::EMPTY
|
||||
}
|
||||
|
||||
/// Return true if there are more than piece in the [Bitboard]. This is faster than testing
|
||||
/// `board.count() > 1`.
|
||||
#[inline(always)]
|
||||
pub fn has_more_than_one(self) -> bool {
|
||||
(self.0 & (self.0.wrapping_sub(1))) != 0
|
||||
}
|
||||
|
||||
/// Iterate over the power-set of a given [Bitboard], yielding each possible sub-set of
|
||||
/// [Square] that belong to the [Bitboard]. In other words, generate all set of [Square] that
|
||||
/// contain all, some, or none of the [Square] that are in the given [Bitboard].
|
||||
|
@ -76,6 +83,18 @@ impl Bitboard {
|
|||
pub fn iter_power_set(self) -> impl Iterator<Item = Self> {
|
||||
BitboardPowerSetIterator::new(self)
|
||||
}
|
||||
|
||||
/// If the given [Bitboard] is a singleton piece on a board, return the [Square] that it is
|
||||
/// occupying. Otherwise return `None`.
|
||||
pub fn try_into_square(self) -> Option<Square> {
|
||||
if self.count() != 1 {
|
||||
None
|
||||
} else {
|
||||
let index = self.0.trailing_zeros() as usize;
|
||||
// SAFETY: we know the value is in-bounds
|
||||
Some(unsafe { Square::from_index_unchecked(index) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure zero-cost (at least size-wise) wrapping.
|
||||
|
@ -117,6 +136,22 @@ impl std::ops::Shr<usize> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat bitboard as a set of squares, shift each square's index left by the amount given.
|
||||
impl std::ops::ShlAssign<usize> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn shl_assign(&mut self, rhs: usize) {
|
||||
*self = *self << rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat bitboard as a set of squares, shift each square's index right by the amount given.
|
||||
impl std::ops::ShrAssign<usize> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn shr_assign(&mut self, rhs: usize) {
|
||||
*self = *self >> rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat bitboard as a set of squares, and invert the set.
|
||||
impl std::ops::Not for Bitboard {
|
||||
type Output = Bitboard;
|
||||
|
@ -147,6 +182,22 @@ impl std::ops::BitOr<Square> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, keep squares that are in either sets.
|
||||
impl std::ops::BitOrAssign<Bitboard> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn bitor_assign(&mut self, rhs: Bitboard) {
|
||||
*self = *self | rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat the [Square] as a singleton bitboard, and apply the operator.
|
||||
impl std::ops::BitOrAssign<Square> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn bitor_assign(&mut self, rhs: Square) {
|
||||
*self = *self | rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, keep squares that are in both sets.
|
||||
impl std::ops::BitAnd<Bitboard> for Bitboard {
|
||||
type Output = Bitboard;
|
||||
|
@ -167,6 +218,22 @@ impl std::ops::BitAnd<Square> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, keep squares that are in both sets.
|
||||
impl std::ops::BitAndAssign<Bitboard> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn bitand_assign(&mut self, rhs: Bitboard) {
|
||||
*self = *self & rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat the [Square] as a singleton bitboard, and apply the operator.
|
||||
impl std::ops::BitAndAssign<Square> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn bitand_assign(&mut self, rhs: Square) {
|
||||
*self = *self & rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, keep squares that are in exactly one of either set.
|
||||
impl std::ops::BitXor<Bitboard> for Bitboard {
|
||||
type Output = Bitboard;
|
||||
|
@ -187,6 +254,22 @@ impl std::ops::BitXor<Square> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, keep squares that are in exactly one of either set.
|
||||
impl std::ops::BitXorAssign<Bitboard> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn bitxor_assign(&mut self, rhs: Bitboard) {
|
||||
*self = *self ^ rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat the [Square] as a singleton bitboard, and apply the operator.
|
||||
impl std::ops::BitXorAssign<Square> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn bitxor_assign(&mut self, rhs: Square) {
|
||||
*self = *self ^ rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, and substract one set from another.
|
||||
impl std::ops::Sub<Bitboard> for Bitboard {
|
||||
type Output = Bitboard;
|
||||
|
@ -207,6 +290,22 @@ impl std::ops::Sub<Square> for Bitboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Treat each bitboard as a set of squares, and substract one set from another.
|
||||
impl std::ops::SubAssign<Bitboard> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn sub_assign(&mut self, rhs: Bitboard) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
||||
|
||||
/// Treat the [Square] as a singleton bitboard, and apply the operator.
|
||||
impl std::ops::SubAssign<Square> for Bitboard {
|
||||
#[inline(always)]
|
||||
fn sub_assign(&mut self, rhs: Square) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::HashSet;
|
||||
|
@ -296,6 +395,16 @@ mod test {
|
|||
assert_eq!(Bitboard::FILES[0] - Square::A1, Bitboard(0xff - 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn more_than_one() {
|
||||
assert!(!Bitboard::EMPTY.has_more_than_one());
|
||||
for square in Square::iter() {
|
||||
assert!(!square.into_bitboard().has_more_than_one())
|
||||
}
|
||||
assert!((Square::A1 | Square::H8).has_more_than_one());
|
||||
assert!(Bitboard::ALL.has_more_than_one());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter_power_set_empty() {
|
||||
assert_eq!(
|
||||
|
@ -370,4 +479,17 @@ mod test {
|
|||
1 << 8
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_square() {
|
||||
for square in Square::iter() {
|
||||
assert_eq!(square.into_bitboard().try_into_square(), Some(square));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_square_invalid() {
|
||||
assert!(Bitboard::EMPTY.try_into_square().is_none());
|
||||
assert!((Square::A1 | Square::A2).try_into_square().is_none())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use super::{Bitboard, Color, File, Square};
|
||||
use super::{Bitboard, Color, File, FromFen, Square};
|
||||
use crate::error::Error;
|
||||
|
||||
/// Current castle rights for a player.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
@ -53,6 +54,26 @@ impl CastleRights {
|
|||
(self.index() & 2) != 0
|
||||
}
|
||||
|
||||
/// Add king-side castling rights.
|
||||
#[inline(always)]
|
||||
pub fn with_king_side(self) -> Self {
|
||||
self.add(Self::KingSide)
|
||||
}
|
||||
|
||||
/// Add queen-side castling rights.
|
||||
#[inline(always)]
|
||||
pub fn with_queen_side(self) -> Self {
|
||||
self.add(Self::QueenSide)
|
||||
}
|
||||
|
||||
/// Add some [CastleRights], and return the resulting [CastleRights].
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
#[inline(always)]
|
||||
pub fn add(self, to_remove: CastleRights) -> Self {
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Self::from_index_unchecked(self.index() | to_remove.index()) }
|
||||
}
|
||||
|
||||
/// Remove king-side castling rights.
|
||||
#[inline(always)]
|
||||
pub fn without_king_side(self) -> Self {
|
||||
|
@ -89,6 +110,39 @@ impl CastleRights {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert the castling rights segment of a FEN string to an array of [CastleRights].
|
||||
impl FromFen for [CastleRights; 2] {
|
||||
type Err = Error;
|
||||
|
||||
fn from_fen(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() > 4 {
|
||||
return Err(Error::InvalidFen);
|
||||
}
|
||||
|
||||
let mut res = [CastleRights::NoSide; 2];
|
||||
|
||||
if s == "-" {
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
for b in s.chars() {
|
||||
let color = if b.is_uppercase() {
|
||||
Color::White
|
||||
} else {
|
||||
Color::Black
|
||||
};
|
||||
let rights = &mut res[color.index()];
|
||||
match b {
|
||||
'k' | 'K' => *rights = rights.with_king_side(),
|
||||
'q' | 'Q' => *rights = rights.with_queen_side(),
|
||||
_ => return Err(Error::InvalidFen),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
1001
src/board/chess_board.rs
Normal file
1001
src/board/chess_board.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,5 @@
|
|||
use super::{Direction, Rank};
|
||||
use super::{Direction, FromFen, Rank};
|
||||
use crate::error::Error;
|
||||
|
||||
/// An enum representing the color of a player.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
@ -11,6 +12,13 @@ impl Color {
|
|||
/// The number of [Color] variants.
|
||||
pub const NUM_VARIANTS: usize = 2;
|
||||
|
||||
const ALL: [Self; Self::NUM_VARIANTS] = [Self::White, Self::Black];
|
||||
|
||||
/// Iterate over all colors in order.
|
||||
pub fn iter() -> impl Iterator<Item = Self> {
|
||||
Self::ALL.iter().cloned()
|
||||
}
|
||||
|
||||
/// Convert from a piece index into a [Color] type.
|
||||
#[inline(always)]
|
||||
pub fn from_index(index: usize) -> Self {
|
||||
|
@ -53,6 +61,16 @@ impl Color {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return the third [Rank] for pieces of the given [Color], where its pawns move to after a
|
||||
/// one-square move on the start position.
|
||||
#[inline(always)]
|
||||
pub fn third_rank(self) -> Rank {
|
||||
match self {
|
||||
Self::White => Rank::Third,
|
||||
Self::Black => Rank::Sixth,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the fourth [Rank] for pieces of the given [Color], where its pawns move to after a
|
||||
/// two-square move.
|
||||
#[inline(always)]
|
||||
|
@ -89,6 +107,20 @@ impl Color {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert a side to move segment of a FEN string to a [Color].
|
||||
impl FromFen for Color {
|
||||
type Err = Error;
|
||||
|
||||
fn from_fen(s: &str) -> Result<Self, Self::Err> {
|
||||
let res = match s {
|
||||
"w" => Color::White,
|
||||
"b" => Color::Black,
|
||||
_ => return Err(Error::InvalidFen),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Not for Color {
|
||||
type Output = Color;
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ impl Direction {
|
|||
|
||||
while !board.is_empty() {
|
||||
board = self.move_board(board);
|
||||
res = res | board;
|
||||
res |= board;
|
||||
if !(board & blockers).is_empty() {
|
||||
break;
|
||||
}
|
||||
|
|
6
src/board/fen.rs
Normal file
6
src/board/fen.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
/// A trait to mark items that can be converted from a FEN input.
|
||||
pub trait FromFen: Sized {
|
||||
type Err;
|
||||
|
||||
fn from_fen(s: &str) -> Result<Self, Self::Err>;
|
||||
}
|
|
@ -4,15 +4,24 @@ pub use bitboard::*;
|
|||
pub mod castle_rights;
|
||||
pub use castle_rights::*;
|
||||
|
||||
pub mod chess_board;
|
||||
pub use chess_board::*;
|
||||
|
||||
pub mod color;
|
||||
pub use color::*;
|
||||
|
||||
pub mod direction;
|
||||
pub use direction::*;
|
||||
|
||||
pub mod fen;
|
||||
pub use fen::*;
|
||||
|
||||
pub mod file;
|
||||
pub use file::*;
|
||||
|
||||
pub mod r#move;
|
||||
pub use r#move::*;
|
||||
|
||||
pub mod piece;
|
||||
pub use piece::*;
|
||||
|
||||
|
|
232
src/board/move.rs
Normal file
232
src/board/move.rs
Normal file
|
@ -0,0 +1,232 @@
|
|||
use super::{Piece, Square};
|
||||
|
||||
type Bitset = u32;
|
||||
|
||||
/// A chess move, containing:
|
||||
/// * Piece type.
|
||||
/// * Starting square.
|
||||
/// * Destination square.
|
||||
/// * Optional capture type.
|
||||
/// * Optional promotion type.
|
||||
/// * Optional captured type.
|
||||
/// * Whether the move was an en-passant capture.
|
||||
/// * Whether the move was a double-step.
|
||||
/// * Whether the move was a castling.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Move(Bitset);
|
||||
|
||||
/// A builder for [Move]. This is the prefered and only way of building a [Move].
|
||||
pub struct MoveBuilder {
|
||||
pub piece: Piece,
|
||||
pub start: Square,
|
||||
pub destination: Square,
|
||||
pub capture: Option<Piece>,
|
||||
pub promotion: Option<Piece>,
|
||||
pub en_passant: bool,
|
||||
pub double_step: bool,
|
||||
pub castling: bool,
|
||||
}
|
||||
|
||||
impl From<MoveBuilder> for Move {
|
||||
#[inline(always)]
|
||||
fn from(builder: MoveBuilder) -> Self {
|
||||
Self::new(
|
||||
builder.piece,
|
||||
builder.start,
|
||||
builder.destination,
|
||||
builder.capture,
|
||||
builder.promotion,
|
||||
builder.en_passant,
|
||||
builder.double_step,
|
||||
builder.castling,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A [Move] is structured as a bitset with the following fields:
|
||||
/// | Field | Size | Range of values | Note |
|
||||
/// |-------------|------|-----------------|-------------------------------------------------|
|
||||
/// | Piece | 3 | 0-6 | Can be interpreted as a [Piece] index |
|
||||
/// | Start | 6 | 0-63 | Can be interpreted as a [Square] index |
|
||||
/// | Destination | 6 | 0-63 | Can be interpreted as a [Square] index |
|
||||
/// | Capture | 3 | 0-7 | Can be interpreted as a [Piece] index if not 7 |
|
||||
/// | Promotion | 3 | 0-7 | Can be interpreted as a [Piece] index if not 7 |
|
||||
/// | En-pasant | 1 | 0-1 | Boolean value |
|
||||
/// | Double-step | 1 | 0-1 | Boolean value |
|
||||
/// | Castling | 1 | 0-1 | Boolean value |
|
||||
mod shift {
|
||||
use super::Bitset;
|
||||
|
||||
pub const PIECE: usize = 0;
|
||||
pub const PIECE_MASK: Bitset = 0b111;
|
||||
|
||||
pub const START: usize = 3;
|
||||
pub const START_MASK: Bitset = 0b11_1111;
|
||||
|
||||
pub const DESTINATION: usize = 9;
|
||||
pub const DESTINATION_MASK: Bitset = 0b11_1111;
|
||||
|
||||
pub const CAPTURE: usize = 15;
|
||||
pub const CAPTURE_MASK: Bitset = 0b111;
|
||||
|
||||
pub const PROMOTION: usize = 18;
|
||||
pub const PROMOTION_MASK: Bitset = 0b111;
|
||||
|
||||
pub const EN_PASSANT: usize = 21;
|
||||
pub const EN_PASSANT_MASK: Bitset = 0b1;
|
||||
|
||||
pub const DOUBLE_STEP: usize = 22;
|
||||
pub const DOUBLE_STEP_MASK: Bitset = 0b1;
|
||||
|
||||
pub const CASTLING: usize = 23;
|
||||
pub const CASTLING_MASK: Bitset = 0b1;
|
||||
}
|
||||
|
||||
impl Move {
|
||||
/// Construct a new move.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[inline(always)]
|
||||
fn new(
|
||||
piece: Piece,
|
||||
start: Square,
|
||||
destination: Square,
|
||||
capture: Option<Piece>,
|
||||
promotion: Option<Piece>,
|
||||
en_passant: bool,
|
||||
double_step: bool,
|
||||
castling: bool,
|
||||
) -> Self {
|
||||
let mut value = 0;
|
||||
value |= (piece.index() as Bitset) << shift::PIECE;
|
||||
value |= (start.index() as Bitset) << shift::START;
|
||||
value |= (destination.index() as Bitset) << shift::DESTINATION;
|
||||
value |=
|
||||
(capture.map(Piece::index).unwrap_or(Piece::NUM_VARIANTS) as Bitset) << shift::CAPTURE;
|
||||
value |= (promotion.map(Piece::index).unwrap_or(Piece::NUM_VARIANTS) as Bitset)
|
||||
<< shift::PROMOTION;
|
||||
value |= (en_passant as Bitset) << shift::EN_PASSANT;
|
||||
value |= (double_step as Bitset) << shift::DOUBLE_STEP;
|
||||
value |= (castling as Bitset) << shift::CASTLING;
|
||||
Self(value)
|
||||
}
|
||||
|
||||
/// Get the [Piece] that is being moved.
|
||||
#[inline(always)]
|
||||
pub fn piece(self) -> Piece {
|
||||
let index = ((self.0 >> shift::PIECE) & shift::PIECE_MASK) as usize;
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Piece::from_index_unchecked(index) }
|
||||
}
|
||||
|
||||
/// Get the [Square] that this move starts from.
|
||||
#[inline(always)]
|
||||
pub fn start(self) -> Square {
|
||||
let index = ((self.0 >> shift::START) & shift::START_MASK) as usize;
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Square::from_index_unchecked(index) }
|
||||
}
|
||||
|
||||
/// Get the [Square] that this move ends on.
|
||||
#[inline(always)]
|
||||
pub fn destination(self) -> Square {
|
||||
let index = ((self.0 >> shift::DESTINATION) & shift::DESTINATION_MASK) as usize;
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Square::from_index_unchecked(index) }
|
||||
}
|
||||
|
||||
/// Get the [Piece] that this move captures, or `None` if there are no captures.
|
||||
#[inline(always)]
|
||||
pub fn capture(self) -> Option<Piece> {
|
||||
let index = ((self.0 >> shift::CAPTURE) & shift::CAPTURE_MASK) as usize;
|
||||
if index < Piece::NUM_VARIANTS {
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Some(Piece::from_index_unchecked(index)) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the [Piece] that this move promotes to, or `None` if there are no promotions.
|
||||
#[inline(always)]
|
||||
pub fn promotion(self) -> Option<Piece> {
|
||||
let index = ((self.0 >> shift::PROMOTION) & shift::PROMOTION_MASK) as usize;
|
||||
if index < Piece::NUM_VARIANTS {
|
||||
// SAFETY: we know the value is in-bounds
|
||||
unsafe { Some(Piece::from_index_unchecked(index)) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the whether or not the move is an en-passant capture.
|
||||
#[inline(always)]
|
||||
pub fn is_en_passant(self) -> bool {
|
||||
let index = (self.0 >> shift::EN_PASSANT) & shift::EN_PASSANT_MASK;
|
||||
index != 0
|
||||
}
|
||||
|
||||
/// Get the whether or not the move is a pawn double step.
|
||||
#[inline(always)]
|
||||
pub fn is_double_step(self) -> bool {
|
||||
let index = (self.0 >> shift::DOUBLE_STEP) & shift::DOUBLE_STEP_MASK;
|
||||
index != 0
|
||||
}
|
||||
|
||||
/// Get the whether or not the move is a king castling.
|
||||
#[inline(always)]
|
||||
pub fn is_castling(self) -> bool {
|
||||
let index = (self.0 >> shift::CASTLING) & shift::CASTLING_MASK;
|
||||
index != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn builder_simple() {
|
||||
let chess_move: Move = MoveBuilder {
|
||||
piece: Piece::Queen,
|
||||
start: Square::A2,
|
||||
destination: Square::A3,
|
||||
capture: None,
|
||||
promotion: None,
|
||||
en_passant: false,
|
||||
double_step: false,
|
||||
castling: false,
|
||||
}
|
||||
.into();
|
||||
assert_eq!(chess_move.piece(), Piece::Queen);
|
||||
assert_eq!(chess_move.start(), Square::A2);
|
||||
assert_eq!(chess_move.destination(), Square::A3);
|
||||
assert_eq!(chess_move.capture(), None);
|
||||
assert_eq!(chess_move.promotion(), None);
|
||||
assert!(!chess_move.is_en_passant());
|
||||
assert!(!chess_move.is_double_step());
|
||||
assert!(!chess_move.is_castling());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builder_all_fields() {
|
||||
let chess_move: Move = MoveBuilder {
|
||||
piece: Piece::Pawn,
|
||||
start: Square::A7,
|
||||
destination: Square::B8,
|
||||
capture: Some(Piece::Queen),
|
||||
promotion: Some(Piece::Knight),
|
||||
en_passant: true,
|
||||
double_step: true,
|
||||
castling: true,
|
||||
}
|
||||
.into();
|
||||
assert_eq!(chess_move.piece(), Piece::Pawn);
|
||||
assert_eq!(chess_move.start(), Square::A7);
|
||||
assert_eq!(chess_move.destination(), Square::B8);
|
||||
assert_eq!(chess_move.capture(), Some(Piece::Queen));
|
||||
assert_eq!(chess_move.promotion(), Some(Piece::Knight));
|
||||
assert!(chess_move.is_en_passant());
|
||||
assert!(chess_move.is_double_step());
|
||||
assert!(chess_move.is_castling());
|
||||
}
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
use super::FromFen;
|
||||
use crate::error::Error;
|
||||
|
||||
/// An enum representing the type of a piece.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum Piece {
|
||||
|
@ -52,6 +55,24 @@ impl Piece {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert a piece in FEN notation to a [Piece].
|
||||
impl FromFen for Piece {
|
||||
type Err = Error;
|
||||
|
||||
fn from_fen(s: &str) -> Result<Self, Self::Err> {
|
||||
let res = match s {
|
||||
"p" | "P" => Self::Pawn,
|
||||
"n" | "N" => Self::Knight,
|
||||
"b" | "B" => Self::Bishop,
|
||||
"r" | "R" => Self::Rook,
|
||||
"q" | "Q" => Self::Queen,
|
||||
"k" | "K" => Self::King,
|
||||
_ => return Err(Error::InvalidFen),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::{Bitboard, File, Rank};
|
||||
use crate::utils::static_assert;
|
||||
use super::{Bitboard, File, FromFen, Rank};
|
||||
use crate::{error::Error, utils::static_assert};
|
||||
|
||||
/// Represent a square on a chessboard. Defined in the same order as the
|
||||
/// [Bitboard] squares.
|
||||
|
@ -107,6 +107,23 @@ impl Square {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert an en-passant target square segment of a FEN string to an optional [Square].
|
||||
impl FromFen for Option<Square> {
|
||||
type Err = Error;
|
||||
|
||||
fn from_fen(s: &str) -> Result<Self, Self::Err> {
|
||||
let res = match s.as_bytes() {
|
||||
[b'-'] => None,
|
||||
[file @ b'a'..=b'h', rank @ b'1'..=b'8'] => Some(Square::new(
|
||||
File::from_index((file - b'a') as usize),
|
||||
Rank::from_index((rank - b'1') as usize),
|
||||
)),
|
||||
_ => return Err(Error::InvalidFen),
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shift the square's index left by the amount given.
|
||||
impl std::ops::Shl<usize> for Square {
|
||||
type Output = Square;
|
||||
|
|
144
src/build.rs
Normal file
144
src/build.rs
Normal file
|
@ -0,0 +1,144 @@
|
|||
use std::io::{Result, Write};
|
||||
|
||||
pub mod board;
|
||||
pub mod error;
|
||||
pub mod movegen;
|
||||
pub mod utils;
|
||||
|
||||
use crate::{
|
||||
board::{Bitboard, Color, File, Square},
|
||||
movegen::{
|
||||
naive::{
|
||||
king::king_moves,
|
||||
knight::knight_moves,
|
||||
pawn::{pawn_captures, pawn_moves},
|
||||
},
|
||||
wizardry::generation::{generate_bishop_magics, generate_rook_magics},
|
||||
Magic,
|
||||
},
|
||||
};
|
||||
|
||||
fn print_magics(out: &mut dyn Write, var_name: &str, magics: &[Magic]) -> Result<()> {
|
||||
writeln!(out, "static {}: [Magic; {}] = [", var_name, magics.len())?;
|
||||
for magic in magics.iter() {
|
||||
writeln!(
|
||||
out,
|
||||
" Magic{{magic: {}, offset: {}, mask: Bitboard({}), shift: {},}},",
|
||||
magic.magic, magic.offset, magic.mask.0, magic.shift
|
||||
)?;
|
||||
}
|
||||
writeln!(out, "];")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_boards(out: &mut dyn Write, var_name: &str, boards: &[Bitboard]) -> Result<()> {
|
||||
writeln!(out, "static {}: [Bitboard; {}] = [", var_name, boards.len())?;
|
||||
for board in boards.iter().cloned() {
|
||||
writeln!(out, " Bitboard({}),", board.0)?;
|
||||
}
|
||||
writeln!(out, "];")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_double_sided_boards(
|
||||
out: &mut dyn Write,
|
||||
var_name: &str,
|
||||
white_boards: &[Bitboard],
|
||||
black_boards: &[Bitboard],
|
||||
) -> Result<()> {
|
||||
assert_eq!(white_boards.len(), black_boards.len());
|
||||
writeln!(
|
||||
out,
|
||||
"static {}: [[Bitboard; {}]; 2] = [",
|
||||
var_name,
|
||||
white_boards.len()
|
||||
)?;
|
||||
for color in Color::iter() {
|
||||
let boards = if color == Color::White {
|
||||
white_boards
|
||||
} else {
|
||||
black_boards
|
||||
};
|
||||
writeln!(out, " [")?;
|
||||
for square in Square::iter() {
|
||||
writeln!(out, " Bitboard({}),", boards[square.index()].0)?;
|
||||
}
|
||||
writeln!(out, " ],")?;
|
||||
}
|
||||
writeln!(out, "];")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::redundant_clone)]
|
||||
fn main() -> Result<()> {
|
||||
// FIXME: rerun-if-changed directives
|
||||
|
||||
let out_dir = std::env::var_os("OUT_DIR").unwrap();
|
||||
let magic_path = std::path::Path::new(&out_dir).join("magic_tables.rs");
|
||||
let mut out = std::fs::File::create(&magic_path).unwrap();
|
||||
|
||||
let rng = random::default().seed([12, 27]);
|
||||
|
||||
{
|
||||
let (magics, moves) = generate_bishop_magics(&mut rng.clone());
|
||||
print_magics(&mut out, "BISHOP_MAGICS", &magics)?;
|
||||
print_boards(&mut out, "BISHOP_MOVES", &moves)?;
|
||||
}
|
||||
|
||||
{
|
||||
let (magics, moves) = generate_rook_magics(&mut rng.clone());
|
||||
print_magics(&mut out, "ROOK_MAGICS", &magics)?;
|
||||
print_boards(&mut out, "ROOK_MOVES", &moves)?;
|
||||
}
|
||||
|
||||
{
|
||||
let moves: Vec<_> = Square::iter().map(knight_moves).collect();
|
||||
print_boards(&mut out, "KNIGHT_MOVES", &moves)?;
|
||||
}
|
||||
|
||||
{
|
||||
let white_moves: Vec<_> = Square::iter()
|
||||
.map(|square| pawn_moves(Color::White, square, Bitboard::EMPTY))
|
||||
.collect();
|
||||
let black_moves: Vec<_> = Square::iter()
|
||||
.map(|square| pawn_moves(Color::Black, square, Bitboard::EMPTY))
|
||||
.collect();
|
||||
print_double_sided_boards(&mut out, "PAWN_MOVES", &white_moves, &black_moves)?;
|
||||
let white_attacks: Vec<_> = Square::iter()
|
||||
.map(|square| pawn_captures(Color::White, square))
|
||||
.collect();
|
||||
let black_attacks: Vec<_> = Square::iter()
|
||||
.map(|square| pawn_captures(Color::Black, square))
|
||||
.collect();
|
||||
print_double_sided_boards(&mut out, "PAWN_ATTACKS", &white_attacks, &black_attacks)?;
|
||||
}
|
||||
|
||||
{
|
||||
let moves: Vec<_> = Square::iter().map(king_moves).collect();
|
||||
print_boards(&mut out, "KING_MOVES", &moves)?;
|
||||
let king_blockers: Vec<_> = Color::iter()
|
||||
.map(|color| {
|
||||
Square::new(File::F, color.first_rank()) | Square::new(File::G, color.first_rank())
|
||||
})
|
||||
.collect();
|
||||
let queen_blockers: Vec<_> = Color::iter()
|
||||
.map(|color| {
|
||||
Square::new(File::B, color.first_rank())
|
||||
| Square::new(File::C, color.first_rank())
|
||||
| Square::new(File::D, color.first_rank())
|
||||
})
|
||||
.collect();
|
||||
print_boards(&mut out, "KING_SIDE_CASTLE_BLOCKERS", &king_blockers)?;
|
||||
print_boards(&mut out, "QUEEN_SIDE_CASTLE_BLOCKERS", &queen_blockers)?;
|
||||
}
|
||||
|
||||
// Include the generated files now that the build script has run.
|
||||
println!("cargo:rustc-cfg=generated_boards");
|
||||
|
||||
// Run the build script only if something in move generation might have changed.
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=movegen/naive/");
|
||||
println!("cargo:rerun-if-changed=movegen/wizardry/");
|
||||
|
||||
Ok(())
|
||||
}
|
19
src/error.rs
Normal file
19
src/error.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
/// A singular type for all errors that could happen when using this library.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
InvalidFen,
|
||||
InvalidPosition,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let error_msg = match self {
|
||||
Self::InvalidFen => "Invalid FEN input",
|
||||
Self::InvalidPosition => "Invalid position",
|
||||
};
|
||||
write!(f, "{}", error_msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod board;
|
||||
pub mod error;
|
||||
pub mod movegen;
|
||||
pub mod utils;
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
use crate::board::Bitboard;
|
||||
|
||||
/// A type representing the magic board indexing a given [Square].
|
||||
pub struct Magic {
|
||||
/// Magic number.
|
||||
pub(crate) magic: u64,
|
||||
/// Base offset into the magic square table.
|
||||
pub(crate) offset: usize,
|
||||
/// Mask to apply to the blocker board before applying the magic.
|
||||
pub(crate) mask: Bitboard,
|
||||
/// Length of the resulting mask after applying the magic.
|
||||
pub(crate) shift: u8,
|
||||
}
|
||||
|
||||
impl Magic {
|
||||
pub fn get_index(&self, blockers: Bitboard) -> usize {
|
||||
let relevant_occupancy = (blockers & self.mask).0;
|
||||
let base_index = ((relevant_occupancy.wrapping_mul(self.magic)) >> self.shift) as usize;
|
||||
base_index + self.offset
|
||||
}
|
||||
}
|
|
@ -1,2 +1,67 @@
|
|||
pub mod magic;
|
||||
pub use magic::*;
|
||||
use crate::board::Bitboard;
|
||||
|
||||
/// A type representing the magic board indexing a given [crate::board::Square].
|
||||
pub struct Magic {
|
||||
/// Magic number.
|
||||
pub(crate) magic: u64,
|
||||
/// Base offset into the magic square table.
|
||||
pub(crate) offset: usize,
|
||||
/// Mask to apply to the blocker board before applying the magic.
|
||||
pub(crate) mask: Bitboard,
|
||||
/// Length of the resulting mask after applying the magic.
|
||||
pub(crate) shift: u8,
|
||||
}
|
||||
|
||||
impl Magic {
|
||||
pub fn get_index(&self, blockers: Bitboard) -> usize {
|
||||
let relevant_occupancy = (blockers & self.mask).0;
|
||||
let base_index = ((relevant_occupancy.wrapping_mul(self.magic)) >> self.shift) as usize;
|
||||
base_index + self.offset
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(generated_boards)]
|
||||
mod moves;
|
||||
pub use moves::*;
|
||||
|
||||
#[cfg(not(generated_boards))]
|
||||
#[allow(unused_variables)]
|
||||
mod moves {
|
||||
use crate::board::{Bitboard, Color, Square};
|
||||
|
||||
pub fn quiet_pawn_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn pawn_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn knight_moves(square: Square) -> Bitboard {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn bishop_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn rook_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn queen_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn king_moves(square: Square) -> Bitboard {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn king_side_castle_blockers(color: Color) -> Bitboard {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn queen_side_castle_blockers(color: Color) -> Bitboard {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
|
71
src/movegen/magic/moves.rs
Normal file
71
src/movegen/magic/moves.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use super::Magic;
|
||||
use crate::board::{Bitboard, Color, Square};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/magic_tables.rs"));
|
||||
|
||||
pub fn quiet_pawn_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard {
|
||||
// If there is a piece in front of the pawn, it can't advance
|
||||
if !(color.backward_direction().move_board(blockers) & square).is_empty() {
|
||||
return Bitboard::EMPTY;
|
||||
}
|
||||
// SAFETY: we know the values are in-bounds
|
||||
unsafe {
|
||||
*PAWN_MOVES
|
||||
.get_unchecked(color.index())
|
||||
.get_unchecked(square.index())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pawn_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard {
|
||||
// SAFETY: we know the values are in-bounds
|
||||
let attacks = unsafe {
|
||||
*PAWN_ATTACKS
|
||||
.get_unchecked(color.index())
|
||||
.get_unchecked(square.index())
|
||||
};
|
||||
quiet_pawn_moves(color, square, blockers) | attacks
|
||||
}
|
||||
|
||||
pub fn knight_moves(square: Square) -> Bitboard {
|
||||
// SAFETY: we know the values are in-bounds
|
||||
unsafe { *KNIGHT_MOVES.get_unchecked(square.index()) }
|
||||
}
|
||||
|
||||
pub fn bishop_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
||||
// SAFETY: we know the values are in-bounds
|
||||
unsafe {
|
||||
let index = BISHOP_MAGICS
|
||||
.get_unchecked(square.index())
|
||||
.get_index(blockers);
|
||||
*BISHOP_MOVES.get_unchecked(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rook_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
||||
// SAFETY: we know the values are in-bounds
|
||||
unsafe {
|
||||
let index = ROOK_MAGICS
|
||||
.get_unchecked(square.index())
|
||||
.get_index(blockers);
|
||||
*ROOK_MOVES.get_unchecked(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queen_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
||||
bishop_moves(square, blockers) | rook_moves(square, blockers)
|
||||
}
|
||||
|
||||
pub fn king_moves(square: Square) -> Bitboard {
|
||||
// SAFETY: we know the values are in-bounds
|
||||
unsafe { *KING_MOVES.get_unchecked(square.index()) }
|
||||
}
|
||||
|
||||
pub fn king_side_castle_blockers(color: Color) -> Bitboard {
|
||||
// SAFETY: we know the values are in-bounds
|
||||
unsafe { *KING_SIDE_CASTLE_BLOCKERS.get_unchecked(color.index()) }
|
||||
}
|
||||
|
||||
pub fn queen_side_castle_blockers(color: Color) -> Bitboard {
|
||||
// SAFETY: we know the values are in-bounds
|
||||
unsafe { *QUEEN_SIDE_CASTLE_BLOCKERS.get_unchecked(color.index()) }
|
||||
}
|
|
@ -2,12 +2,8 @@
|
|||
pub mod magic;
|
||||
pub use magic::*;
|
||||
|
||||
// Move generation implementation details
|
||||
mod bishop;
|
||||
mod king;
|
||||
mod knight;
|
||||
mod pawn;
|
||||
mod rook;
|
||||
// Naive move generation
|
||||
pub mod naive;
|
||||
|
||||
// Magic bitboard generation
|
||||
mod wizardry;
|
||||
pub(crate) mod wizardry;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::board::{Bitboard, Direction, Square};
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn bishop_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
||||
Direction::iter_bishop()
|
||||
.map(|dir| dir.slide_board_with_blockers(square.into_bitboard(), blockers))
|
|
@ -1,7 +1,6 @@
|
|||
use crate::board::{Bitboard, CastleRights, Color, Direction, File, Square};
|
||||
|
||||
// No castling moves included
|
||||
#[allow(unused)]
|
||||
pub fn king_moves(square: Square) -> Bitboard {
|
||||
let board = square.into_bitboard();
|
||||
|
||||
|
@ -10,7 +9,6 @@ pub fn king_moves(square: Square) -> Bitboard {
|
|||
.fold(Bitboard::EMPTY, |lhs, rhs| lhs | rhs)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn king_castling_moves(color: Color, castle_rights: CastleRights) -> Bitboard {
|
||||
let rank = color.first_rank();
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
use crate::board::{Bitboard, Direction, Square};
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn knight_moves(square: Square) -> Bitboard {
|
||||
let board = square.into_bitboard();
|
||||
|
5
src/movegen/naive/mod.rs
Normal file
5
src/movegen/naive/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
pub mod bishop;
|
||||
pub mod king;
|
||||
pub mod knight;
|
||||
pub mod pawn;
|
||||
pub mod rook;
|
|
@ -1,6 +1,5 @@
|
|||
use crate::board::{Bitboard, Color, Direction, Rank, Square};
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn pawn_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard {
|
||||
if (square.rank() == Rank::First) || (square.rank() == Rank::Eighth) {
|
||||
return Bitboard::EMPTY;
|
||||
|
@ -22,7 +21,6 @@ pub fn pawn_moves(color: Color, square: Square, blockers: Bitboard) -> Bitboard
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn pawn_captures(color: Color, square: Square) -> Bitboard {
|
||||
if (square.rank() == Rank::First) || (square.rank() == Rank::Eighth) {
|
||||
return Bitboard::EMPTY;
|
||||
|
@ -38,7 +36,6 @@ pub fn pawn_captures(color: Color, square: Square) -> Bitboard {
|
|||
attack_west | attack_east
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn en_passant_origins(square: Square) -> Bitboard {
|
||||
let board = square.into_bitboard();
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
use crate::board::{Bitboard, Direction, Square};
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn rook_moves(square: Square, blockers: Bitboard) -> Bitboard {
|
||||
Direction::iter_rook()
|
||||
.map(|dir| dir.slide_board_with_blockers(square.into_bitboard(), blockers))
|
|
@ -1,6 +1,5 @@
|
|||
use crate::board::{Bitboard, Square};
|
||||
use crate::movegen::bishop::bishop_moves;
|
||||
use crate::movegen::rook::rook_moves;
|
||||
use crate::movegen::naive::{bishop::bishop_moves, rook::rook_moves};
|
||||
use crate::movegen::Magic;
|
||||
|
||||
use super::mask::{generate_bishop_mask, generate_rook_mask};
|
||||
|
@ -22,42 +21,42 @@ fn generate_magics(
|
|||
mask_fn: impl Fn(Square) -> Bitboard,
|
||||
moves_fn: impl Fn(Square, Bitboard) -> Bitboard,
|
||||
) -> MagicGenerationType {
|
||||
let mut offset = 0;
|
||||
|
||||
let mut magics = Vec::new();
|
||||
let mut boards = Vec::new();
|
||||
|
||||
for square in Square::iter() {
|
||||
let mask = mask_fn(square);
|
||||
let mut candidate: Magic;
|
||||
let potential_occupancy: Vec<_> = mask.iter_power_set().collect();
|
||||
let moves_len = potential_occupancy.len();
|
||||
|
||||
let occupancy_to_moves: Vec<_> = mask
|
||||
.iter_power_set()
|
||||
.map(|occupancy| (occupancy, moves_fn(square, occupancy)))
|
||||
.collect();
|
||||
|
||||
'candidate_search: loop {
|
||||
candidate = Magic {
|
||||
magic: magic_candidate(rng),
|
||||
offset,
|
||||
offset: 0,
|
||||
mask,
|
||||
shift: (64 - mask.count()) as u8,
|
||||
};
|
||||
let mut candidate_moves = Vec::new();
|
||||
candidate_moves.resize(moves_len, Bitboard::EMPTY);
|
||||
let mut candidate_moves = vec![Bitboard::EMPTY; occupancy_to_moves.len()];
|
||||
|
||||
for occupancy in potential_occupancy.iter().cloned() {
|
||||
for (occupancy, moves) in occupancy_to_moves.iter().cloned() {
|
||||
let index = candidate.get_index(occupancy);
|
||||
let moves = moves_fn(square, occupancy);
|
||||
// Non-constructive collision, try with another candidate
|
||||
if candidate_moves[index] != Bitboard::EMPTY && candidate_moves[index] != moves {
|
||||
continue 'candidate_search;
|
||||
}
|
||||
candidate_moves[index] = moves;
|
||||
}
|
||||
|
||||
// We have filled all candidate boards, record the correct offset and add the moves
|
||||
candidate.offset = boards.len();
|
||||
boards.append(&mut candidate_moves);
|
||||
magics.push(candidate);
|
||||
break;
|
||||
}
|
||||
|
||||
magics.push(candidate);
|
||||
offset += moves_len;
|
||||
}
|
||||
|
||||
(magics, boards)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::board::{Bitboard, File, Rank, Square};
|
||||
use crate::movegen::bishop::bishop_moves;
|
||||
use crate::movegen::rook::rook_moves;
|
||||
use crate::movegen::naive::{bishop::bishop_moves, rook::rook_moves};
|
||||
|
||||
pub fn generate_bishop_mask(square: Square) -> Bitboard {
|
||||
let rays = bishop_moves(square, Bitboard::EMPTY);
|
||||
|
@ -19,16 +18,16 @@ pub fn generate_rook_mask(square: Square) -> Bitboard {
|
|||
let mask = {
|
||||
let mut mask = Bitboard::EMPTY;
|
||||
if square.file() != File::A {
|
||||
mask = mask | File::A.into_bitboard()
|
||||
mask |= File::A.into_bitboard()
|
||||
};
|
||||
if square.file() != File::H {
|
||||
mask = mask | File::H.into_bitboard()
|
||||
mask |= File::H.into_bitboard()
|
||||
};
|
||||
if square.rank() != Rank::First {
|
||||
mask = mask | Rank::First.into_bitboard()
|
||||
mask |= Rank::First.into_bitboard()
|
||||
};
|
||||
if square.rank() != Rank::Eighth {
|
||||
mask = mask | Rank::Eighth.into_bitboard()
|
||||
mask |= Rank::Eighth.into_bitboard()
|
||||
};
|
||||
mask
|
||||
};
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
mod generation;
|
||||
pub(crate) mod generation;
|
||||
mod mask;
|
||||
|
|
Loading…
Reference in a new issue