aboutsummaryrefslogtreecommitdiff
path: root/src/board
diff options
context:
space:
mode:
Diffstat (limited to 'src/board')
-rw-r--r--src/board/color.rs24
-rw-r--r--src/board/engine.rs310
-rw-r--r--src/board/io.rs58
-rw-r--r--src/board/mod.rs431
-rw-r--r--src/board/piece.rs36
-rw-r--r--src/board/zobrist.rs8
6 files changed, 418 insertions, 449 deletions
diff --git a/src/board/color.rs b/src/board/color.rs
new file mode 100644
index 0000000..80d5b4e
--- /dev/null
+++ b/src/board/color.rs
@@ -0,0 +1,24 @@
+use super::piece::Piece;
+
+#[derive(Debug, Clone, Copy, PartialEq, num_enum::FromPrimitive)]
+#[repr(u8)]
+pub enum Color {
+ #[default]
+ White,
+ Black,
+}
+impl Color {
+ pub fn flip(&self) -> Self {
+ match self {
+ Self::White => Self::Black,
+ Self::Black => Self::White,
+ }
+ }
+ pub fn from_piece(piece: Piece) -> Self {
+ if (piece as u8) < 6 {
+ Self::White
+ } else {
+ Self::Black
+ }
+ }
+}
diff --git a/src/board/engine.rs b/src/board/engine.rs
index 5a2b2bf..2dc93eb 100644
--- a/src/board/engine.rs
+++ b/src/board/engine.rs
@@ -155,19 +155,303 @@ impl Board {
result
}
+ fn ep_bitboard(&self) -> Bitboard {
+ let color = self.color();
+ match self.ep_target {
+ Some(square) => {
+ let rank = square.rank();
+ if (rank == 2 && color == Color::Black) || (rank == 5 && color == Color::White) {
+ square.to_bitboard()
+ } else {
+ 0
+ }
+ }
+ None => 0,
+ }
+ }
+
+ pub fn generate_pseudolegal_moves(&self) -> Vec<Move> {
+ let mut result = Vec::new();
+ result.append(&mut self.generate_moves_core(true));
+ result.append(&mut self.generate_moves_core(false));
+ result
+ }
+
+ pub fn generate_moves_core(&self, tactical_only: bool) -> Vec<Move> {
+ let color = self.color();
+ let player_pieces = self.pieces_by_color(color);
+ let mut moves = Vec::with_capacity(256);
+ let empty = self.empty();
+
+ let targets = if tactical_only {
+ self.color_occupancy(color.flip()) ^ match color {
+ // Exclude opponent king because we can't capture it
+ Color::White => self.piece_sets[Piece::KingBlack as usize],
+ Color::Black => self.piece_sets[Piece::King as usize],
+ }
+ } else {
+ empty
+ };
+
+ let kind = if tactical_only {
+ MoveKind::Capture
+ } else {
+ MoveKind::Quiet
+ };
+
+ for (piece_id, piece) in player_pieces.iter().enumerate() {
+ for source in piece.serialize() {
+ let piece_type = Piece::from(piece_id);
+ let move_targets = match piece_type {
+ Piece::Bishop => self.attacks.bishop(self.occupancy, source),
+ Piece::Rook => self.attacks.rook(self.occupancy, source),
+ Piece::Queen => self.attacks.queen(self.occupancy, source),
+ Piece::Knight => self.attacks.knight[source as usize],
+ Piece::King => self.attacks.king[source as usize],
+ Piece::Pawn => if tactical_only {
+ self.attacks.pawn[color as usize][source as usize]
+ } else {
+ self.attacks.pawn_pushes[color as usize][source as usize]
+ },
+ _ => panic!("Incorrect piece type"),
+ };
+
+ // Generalized attack/move pattern for all pieces
+ for target in (move_targets & targets).serialize() {
+ moves.push(Move { source, target, kind });
+
+ // Promotions
+ if piece_type == Piece::Pawn {
+ if target.rank() == 7 {
+ for promo_type in [Piece::Bishop, Piece::Knight, Piece::Rook, Piece::Queen] {
+ moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)})
+ }
+ } else if target.rank() == 0 {
+ for promo_type in [Piece::BishopBlack, Piece::KnightBlack, Piece::RookBlack, Piece::QueenBlack] {
+ moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)})
+ }
+ }
+ }
+ }
+
+ // En Passant
+ if piece_type == Piece::Pawn && tactical_only {
+ for target in (move_targets & self.ep_bitboard()).serialize() {
+ moves.push(Move { source, target, kind: MoveKind::EnPassant });
+ }
+ }
+ }
+ }
+
+ if !tactical_only {
+ // Double Pushes
+ // Make sure no blocking piece is standing in front of the pawns
+ // that are potential double-push sources
+ let able_to_double_push_mask = match color {
+ Color::White => empty >> 8,
+ Color::Black => empty << 8,
+ };
+ for source in (player_pieces[Piece::Pawn as usize] & able_to_double_push_mask).serialize() {
+ for target in (self.attacks.pawn_double_pushes[color as usize][source as usize] & empty).serialize() {
+ moves.push(Move { source, target, kind: MoveKind::DoublePush });
+ };
+ }
+
+ // Castling
+ let king_home_position = match color {
+ Color::White => Square::E1,
+ Color::Black => Square::E8,
+ };
+ if player_pieces[Piece::King as usize].bitscan() == king_home_position {
+ for rook_square in player_pieces[Piece::Rook as usize]
+ .serialize()
+ .iter()
+ .filter(|rook_square| rook_square.rank() == king_home_position.rank())
+ {
+ match rook_square.file() {
+ 0 => {
+ let all_empty = [
+ king_home_position.west_one(),
+ king_home_position.west_one().west_one(),
+ king_home_position.west_one().west_one().west_one(),
+
+ ].iter().all(|square| empty & square.to_bitboard() > 0);
+
+ let any_checks = [
+ king_home_position,
+ king_home_position.west_one(),
+ king_home_position.west_one().west_one(),
+ ].iter().any(|square| self.is_square_attacked(*square, color.flip()));
+
+ if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::Queen as usize] {
+ moves.push(Move {
+ source: king_home_position,
+ target: king_home_position.west_one().west_one(),
+ kind: MoveKind::Castle,
+ })
+ }
+ },
+ 7 => {
+ let all_empty = [
+ king_home_position.east_one(),
+ king_home_position.east_one().east_one(),
+
+ ].iter().all(|square| empty & square.to_bitboard() > 0);
+
+ let any_checks = [
+ king_home_position,
+ king_home_position.east_one(),
+ king_home_position.east_one().east_one(),
+ ].iter().any(|square| self.is_square_attacked(*square, color.flip()));
+
+ if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::King as usize] {
+ moves.push(Move {
+ source: king_home_position,
+ target: king_home_position.east_one().east_one(),
+ kind: MoveKind::Castle,
+ })
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+ return moves
+ }
+
+ /// Count pseudo-legal moves without actually generating them
+ /// Also exclude all moves that put a piece under attack of a pawn - so called safe mobility
+ pub fn mobility(&self, color: Color) -> f32 {
+ let mut mobility = 0.;
+ let opponent_occupancy = self.color_occupancy(color.flip());
+ let player_pieces = self.pieces_by_color(color);
+
+ let opponent_pawns = match color {
+ Color::Black => self.piece_sets[Piece::Pawn as usize],
+ Color::White => self.piece_sets[Piece::PawnBlack as usize],
+ };
+
+ let pawn_attacked_squares = opponent_pawns.serialize().iter().fold(0u64, |acc, square| {
+ acc | self.attacks.pawn[color.flip() as usize][*square as usize]
+ });
+
+ // Exclude squares controlled by enemy pawns from mobility
+ let empty = self.empty() & !pawn_attacked_squares;
+
+ for (piece_type, piece) in player_pieces.iter().enumerate() {
+ match Piece::from(piece_type) {
+ Piece::Pawn => {
+ for source in piece.serialize() {
+ let ep_bitboard = match self.ep_target {
+ Some(square) => {
+ let rank = square.rank();
+ if (rank == 2 && color == Color::Black) || (rank == 5 && color == Color::White) {
+ square.to_bitboard()
+ } else {
+ 0
+ }
+ }
+ None => 0,
+ };
+ mobility += (self.attacks.pawn[color as usize][source as usize] & (opponent_occupancy | ep_bitboard)).pop_count() as f32;
+ mobility += (self.attacks.pawn_pushes[color as usize][source as usize] & empty).pop_count() as f32;
+ }
+ let able_to_double_push_mask = match color {
+ Color::White => empty >> 8,
+ Color::Black => empty << 8,
+ };
+ for source in (*piece & able_to_double_push_mask).serialize() {
+ mobility += (self.attacks.pawn_double_pushes[color as usize][source as usize] & empty).pop_count() as f32;
+ }
+ }
+ Piece::King => {
+ for source in piece.serialize() {
+ mobility += (self.attacks.king[source as usize] & (empty | opponent_occupancy)).pop_count() as f32;
+
+ // Castling
+ let king_home_position = match color {
+ Color::White => Square::E1,
+ Color::Black => Square::E8,
+ };
+ if *piece == king_home_position.to_bitboard() {
+ for rook_square in player_pieces[Piece::Rook as usize]
+ .serialize()
+ .iter()
+ .filter(|rook_square| rook_square.rank() == king_home_position.rank())
+ {
+ match rook_square.file() {
+ 0 => {
+ let castle_line = [
+ king_home_position.west_one(),
+ king_home_position.west_one().west_one(),
+ ];
+
+ let all_empty = castle_line.iter().all(|square| empty & square.to_bitboard() > 0);
+ let any_checks = castle_line.iter().any(|square| self.is_square_attacked(*square, color.flip()));
+
+ if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::Queen as usize] {
+ mobility += 1.;
+ }
+ },
+ 7 => {
+ let castle_line = [
+ king_home_position.east_one(),
+ king_home_position.east_one().east_one(),
+ ];
+
+ let all_empty = castle_line.iter().all(|square| empty & square.to_bitboard() > 0);
+ let any_checks = castle_line.iter().any(|square| self.is_square_attacked(*square, color.flip()));
+
+ if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::King as usize] {
+ mobility += 1.;
+ }
+ },
+ _ => {},
+ }
+ }
+ }
+ }
+ }
+ Piece::Knight => {
+ for source in piece.serialize() {
+ mobility += (self.attacks.knight[source as usize] & (empty | opponent_occupancy)).pop_count() as f32;
+ }
+ }
+ Piece::Bishop => {
+ for source in piece.serialize() {
+ mobility += (self.attacks.bishop(self.occupancy, source) & (empty | opponent_occupancy)).pop_count() as f32;
+ }
+ }
+ Piece::Rook => {
+ for source in piece.serialize() {
+ mobility += (self.attacks.rook(self.occupancy, source) & (empty | opponent_occupancy)).pop_count() as f32;
+ }
+ }
+ Piece::Queen => {
+ // Do not account queen in mobility
+ }
+ incorrect_type => panic!("Incorrect piece type: {:?}", incorrect_type),
+ }
+ }
+
+ mobility
+ }
+
+
/// Count player pieces' material, giving bonus for pieces standing well
pub fn material(&self, color: Color) -> f32 {
let mut material = 0f32;
for (piece_index, bitboard) in self.pieces_by_color(color).iter().enumerate() {
- let piece_type = PieceType::from(piece_index);
+ let piece_type = Piece::from(piece_index);
let bonus_table = match piece_type {
- PieceType::Pawn => PAWN_BONUS,
- PieceType::Knight => KNIGHT_BONUS,
- PieceType::Bishop => BISHOP_BONUS,
- PieceType::Rook => ROOK_BONUS,
- PieceType::King => KING_BONUS,
- PieceType::Queen => QUEEN_BONUS,
+ Piece::Pawn => PAWN_BONUS,
+ Piece::Knight => KNIGHT_BONUS,
+ Piece::Bishop => BISHOP_BONUS,
+ Piece::Rook => ROOK_BONUS,
+ Piece::King => KING_BONUS,
+ Piece::Queen => QUEEN_BONUS,
_ => panic!("Unreachable")
};
@@ -189,8 +473,8 @@ impl Board {
let mut result = 0.0;
let pawns = match color {
- Color::White => self.piece_sets[PieceType::Pawn as usize],
- Color::Black => self.piece_sets[PieceType::PawnBlack as usize],
+ Color::White => self.piece_sets[Piece::Pawn as usize],
+ Color::Black => self.piece_sets[Piece::PawnBlack as usize],
};
for file in 0..8 {
@@ -225,18 +509,18 @@ impl Board {
let mut result = 0.0;
let king_square = match color {
- Color::White => self.piece_sets[PieceType::King as usize],
- Color::Black => self.piece_sets[PieceType::KingBlack as usize],
+ Color::White => self.piece_sets[Piece::King as usize],
+ Color::Black => self.piece_sets[Piece::KingBlack as usize],
}.bitscan();
for (piece_type, bitboard) in self.pieces_by_color(color.flip()).iter().enumerate() {
- if piece_type != PieceType::King as usize && piece_type != PieceType::Pawn as usize {
+ if piece_type != Piece::King as usize && piece_type != Piece::Pawn as usize {
for square in bitboard.serialize() {
let distance =
(king_square.rank() as f32 - square.rank() as f32).abs() +
(king_square.file() as f32 - square.file() as f32).abs();
- result += distance / PieceType::from(piece_type).static_eval();
+ result += distance / Piece::from(piece_type).static_eval();
}
}
}
diff --git a/src/board/io.rs b/src/board/io.rs
index b5cd95e..30d487f 100644
--- a/src/board/io.rs
+++ b/src/board/io.rs
@@ -2,7 +2,7 @@ use std::io::{stdin, stdout, Write};
use crate::{bitboard::Bitboard, attacks::Attacks, moves::Move, square::Square};
-use super::{Board, PieceType, ttable::TTABLE_SIZE, zobrist::Zobrist};
+use super::{Board, Piece, ttable::TTABLE_SIZE, zobrist::Zobrist};
const PIECE_CHARS: [&str; 12] = [
"♟︎", "♞", "♝", "♜", "♛", "♚",
@@ -65,18 +65,18 @@ impl IO for Board {
}
} else {
match character {
- 'P' => piece_sets[PieceType::Pawn as usize] |= position,
- 'N' => piece_sets[PieceType::Knight as usize] |= position,
- 'B' => piece_sets[PieceType::Bishop as usize] |= position,
- 'R' => piece_sets[PieceType::Rook as usize] |= position,
- 'Q' => piece_sets[PieceType::Queen as usize] |= position,
- 'K' => piece_sets[PieceType::King as usize] |= position,
- 'p' => piece_sets[PieceType::PawnBlack as usize] |= position,
- 'n' => piece_sets[PieceType::KnightBlack as usize] |= position,
- 'b' => piece_sets[PieceType::BishopBlack as usize] |= position,
- 'r' => piece_sets[PieceType::RookBlack as usize] |= position,
- 'q' => piece_sets[PieceType::QueenBlack as usize] |= position,
- 'k' => piece_sets[PieceType::KingBlack as usize] |= position,
+ 'P' => piece_sets[Piece::Pawn as usize] |= position,
+ 'N' => piece_sets[Piece::Knight as usize] |= position,
+ 'B' => piece_sets[Piece::Bishop as usize] |= position,
+ 'R' => piece_sets[Piece::Rook as usize] |= position,
+ 'Q' => piece_sets[Piece::Queen as usize] |= position,
+ 'K' => piece_sets[Piece::King as usize] |= position,
+ 'p' => piece_sets[Piece::PawnBlack as usize] |= position,
+ 'n' => piece_sets[Piece::KnightBlack as usize] |= position,
+ 'b' => piece_sets[Piece::BishopBlack as usize] |= position,
+ 'r' => piece_sets[Piece::RookBlack as usize] |= position,
+ 'q' => piece_sets[Piece::QueenBlack as usize] |= position,
+ 'k' => piece_sets[Piece::KingBlack as usize] |= position,
'/' => {
rank -= 1;
file = -1; // So it becomes 0
@@ -151,22 +151,22 @@ mod tests {
board.print();
board.empty().print("Empty squares");
- assert_eq!(board.piece_sets[PieceType::Pawn as usize].pop_count(), 8);
- assert_eq!(board.piece_sets[PieceType::Knight as usize].pop_count(), 2);
- assert_eq!(board.piece_sets[PieceType::Bishop as usize].pop_count(), 2);
- assert_eq!(board.piece_sets[PieceType::Rook as usize].pop_count(), 2);
- assert_eq!(board.piece_sets[PieceType::Queen as usize].pop_count(), 1);
- assert_eq!(board.piece_sets[PieceType::King as usize].pop_count(), 1);
-
- assert_eq!(board.piece_sets[PieceType::PawnBlack as usize].pop_count(), 8);
- assert_eq!(board.piece_sets[PieceType::KnightBlack as usize].pop_count(), 2);
- assert_eq!(board.piece_sets[PieceType::BishopBlack as usize].pop_count(), 2);
- assert_eq!(board.piece_sets[PieceType::RookBlack as usize].pop_count(), 2);
- assert_eq!(board.piece_sets[PieceType::QueenBlack as usize].pop_count(), 1);
- assert_eq!(board.piece_sets[PieceType::KingBlack as usize].pop_count(), 1);
-
- assert_eq!(board.piece_sets[PieceType::King as usize].bitscan(), Square::E1);
- assert_eq!(board.piece_sets[PieceType::QueenBlack as usize].bitscan(), Square::D8);
+ assert_eq!(board.piece_sets[Piece::Pawn as usize].pop_count(), 8);
+ assert_eq!(board.piece_sets[Piece::Knight as usize].pop_count(), 2);
+ assert_eq!(board.piece_sets[Piece::Bishop as usize].pop_count(), 2);
+ assert_eq!(board.piece_sets[Piece::Rook as usize].pop_count(), 2);
+ assert_eq!(board.piece_sets[Piece::Queen as usize].pop_count(), 1);
+ assert_eq!(board.piece_sets[Piece::King as usize].pop_count(), 1);
+
+ assert_eq!(board.piece_sets[Piece::PawnBlack as usize].pop_count(), 8);
+ assert_eq!(board.piece_sets[Piece::KnightBlack as usize].pop_count(), 2);
+ assert_eq!(board.piece_sets[Piece::BishopBlack as usize].pop_count(), 2);
+ assert_eq!(board.piece_sets[Piece::RookBlack as usize].pop_count(), 2);
+ assert_eq!(board.piece_sets[Piece::QueenBlack as usize].pop_count(), 1);
+ assert_eq!(board.piece_sets[Piece::KingBlack as usize].pop_count(), 1);
+
+ assert_eq!(board.piece_sets[Piece::King as usize].bitscan(), Square::E1);
+ assert_eq!(board.piece_sets[Piece::QueenBlack as usize].bitscan(), Square::D8);
assert_eq!(board.occupancy.pop_count(), 32);
assert_eq!(board.empty().pop_count(), 32);
diff --git a/src/board/mod.rs b/src/board/mod.rs
index 5317cc2..e6a6446 100644
--- a/src/board/mod.rs
+++ b/src/board/mod.rs
@@ -1,7 +1,9 @@
use crate::{bitboard::{Bitboard, BitboardFns}, moves::{Move, MoveKind}, attacks::Attacks, square::Square, board::io::IO};
-use self::{ttable::{TranspositionTable, TTABLE_SIZE}, zobrist::{ZobristSeed, Zobrist}};
+use self::{ttable::{TranspositionTable, TTABLE_SIZE}, zobrist::{ZobristSeed, Zobrist}, piece::Piece, color::Color};
pub mod io;
+pub mod color;
+pub mod piece;
mod zobrist;
mod engine;
mod ttable;
@@ -32,45 +34,6 @@ pub struct Board {
attacks: Attacks,
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)]
-#[repr(usize)]
-pub enum PieceType {
- #[default]
- Pawn,
- Knight,
- Bishop,
- Rook,
- Queen,
- King,
- PawnBlack,
- KnightBlack,
- BishopBlack,
- RookBlack,
- QueenBlack,
- KingBlack,
-}
-
-impl PieceType {
- pub fn without_color(&self) -> Self {
- let index = *self as usize;
- Self::from(index % 6)
- }
- // Return the price of the peice
- pub fn static_eval(&self) -> f32 {
- match self.without_color() {
- PieceType::Pawn => 1.0,
- PieceType::Bishop => 3.3,
- PieceType::Knight => 3.2,
- PieceType::Rook => 5.0,
- PieceType::Queen => 9.0,
- PieceType::King => 0.,
- _ => panic!("Piece should be without color"),
- }
- }
-}
-
-
-
impl Board {
pub fn new() -> Self {
let default_fen = String::from("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
@@ -101,303 +64,18 @@ impl Board {
self.pieces_by_color(color).iter().fold(0, |acc, bitboard| acc | bitboard)
}
-
-
- fn ep_bitboard(&self) -> Bitboard {
- let color = self.color();
- match self.ep_target {
- Some(square) => {
- let rank = square.rank();
- if (rank == 2 && color == Color::Black) || (rank == 5 && color == Color::White) {
- square.to_bitboard()
- } else {
- 0
- }
- }
- None => 0,
- }
- }
-
- pub fn generate_pseudolegal_moves(&self) -> Vec<Move> {
- let mut result = Vec::new();
- result.append(&mut self.generate_moves_core(true));
- result.append(&mut self.generate_moves_core(false));
- result
- }
-
- pub fn generate_moves_core(&self, tactical_only: bool) -> Vec<Move> {
- let color = self.color();
- let player_pieces = self.pieces_by_color(color);
- let mut moves = Vec::with_capacity(256);
- let empty = self.empty();
-
- let targets = if tactical_only {
- self.color_occupancy(color.flip()) ^ match color {
- // Exclude opponent king because we can't capture it
- Color::White => self.piece_sets[PieceType::KingBlack as usize],
- Color::Black => self.piece_sets[PieceType::King as usize],
- }
- } else {
- empty
- };
-
- let kind = if tactical_only {
- MoveKind::Capture
- } else {
- MoveKind::Quiet
- };
-
- for (piece_id, piece) in player_pieces.iter().enumerate() {
- for source in piece.serialize() {
- let piece_type = PieceType::from(piece_id);
- let move_targets = match piece_type {
- PieceType::Bishop => self.attacks.bishop(self.occupancy, source),
- PieceType::Rook => self.attacks.rook(self.occupancy, source),
- PieceType::Queen => self.attacks.queen(self.occupancy, source),
- PieceType::Knight => self.attacks.knight[source as usize],
- PieceType::King => self.attacks.king[source as usize],
- PieceType::Pawn => if tactical_only {
- self.attacks.pawn[color as usize][source as usize]
- } else {
- self.attacks.pawn_pushes[color as usize][source as usize]
- },
- _ => panic!("Incorrect piece type"),
- };
-
- // Generalized attack/move pattern for all pieces
- for target in (move_targets & targets).serialize() {
- moves.push(Move { source, target, kind });
-
- // Promotions
- if piece_type == PieceType::Pawn {
- if target.rank() == 7 {
- for promo_type in [PieceType::Bishop, PieceType::Knight, PieceType::Rook, PieceType::Queen] {
- moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)})
- }
- } else if target.rank() == 0 {
- for promo_type in [PieceType::BishopBlack, PieceType::KnightBlack, PieceType::RookBlack, PieceType::QueenBlack] {
- moves.push(Move { source, target, kind: MoveKind::Promotion(promo_type)})
- }
- }
- }
- }
-
- // En Passant
- if piece_type == PieceType::Pawn && tactical_only {
- for target in (move_targets & self.ep_bitboard()).serialize() {
- moves.push(Move { source, target, kind: MoveKind::EnPassant });
- }
- }
- }
- }
-
- if !tactical_only {
- // Double Pushes
- // Make sure no blocking piece is standing in front of the pawns
- // that are potential double-push sources
- let able_to_double_push_mask = match color {
- Color::White => empty >> 8,
- Color::Black => empty << 8,
- };
- for source in (player_pieces[PieceType::Pawn as usize] & able_to_double_push_mask).serialize() {
- for target in (self.attacks.pawn_double_pushes[color as usize][source as usize] & empty).serialize() {
- moves.push(Move { source, target, kind: MoveKind::DoublePush });
- };
- }
-
- // Castling
- let king_home_position = match color {
- Color::White => Square::E1,
- Color::Black => Square::E8,
- };
- if player_pieces[PieceType::King as usize].bitscan() == king_home_position {
- for rook_square in player_pieces[PieceType::Rook as usize]
- .serialize()
- .iter()
- .filter(|rook_square| rook_square.rank() == king_home_position.rank())
- {
- match rook_square.file() {
- 0 => {
- let all_empty = [
- king_home_position.west_one(),
- king_home_position.west_one().west_one(),
- king_home_position.west_one().west_one().west_one(),
-
- ].iter().all(|square| empty & square.to_bitboard() > 0);
-
- let any_checks = [
- king_home_position,
- king_home_position.west_one(),
- king_home_position.west_one().west_one(),
- ].iter().any(|square| self.is_square_attacked(*square, color.flip()));
-
- if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::Queen as usize] {
- moves.push(Move {
- source: king_home_position,
- target: king_home_position.west_one().west_one(),
- kind: MoveKind::Castle,
- })
- }
- },
- 7 => {
- let all_empty = [
- king_home_position.east_one(),
- king_home_position.east_one().east_one(),
-
- ].iter().all(|square| empty & square.to_bitboard() > 0);
-
- let any_checks = [
- king_home_position,
- king_home_position.east_one(),
- king_home_position.east_one().east_one(),
- ].iter().any(|square| self.is_square_attacked(*square, color.flip()));
-
- if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::King as usize] {
- moves.push(Move {
- source: king_home_position,
- target: king_home_position.east_one().east_one(),
- kind: MoveKind::Castle,
- })
- }
- },
- _ => {},
- }
- }
- }
- }
- return moves
- }
-
- /// Count pseudo-legal moves without actually generating them
- /// Also exclude all moves that put a piece under attack of a pawn - so called safe mobility
- pub fn mobility(&self, color: Color) -> f32 {
- let mut mobility = 0.;
- let opponent_occupancy = self.color_occupancy(color.flip());
- let player_pieces = self.pieces_by_color(color);
-
- let opponent_pawns = match color {
- Color::Black => self.piece_sets[PieceType::Pawn as usize],
- Color::White => self.piece_sets[PieceType::PawnBlack as usize],
- };
-
- let pawn_attacked_squares = opponent_pawns.serialize().iter().fold(0u64, |acc, square| {
- acc | self.attacks.pawn[color.flip() as usize][*square as usize]
- });
-
- // Exclude squares controlled by enemy pawns from mobility
- let empty = self.empty() & !pawn_attacked_squares;
-
- for (piece_type, piece) in player_pieces.iter().enumerate() {
- match PieceType::from(piece_type) {
- PieceType::Pawn => {
- for source in piece.serialize() {
- let ep_bitboard = match self.ep_target {
- Some(square) => {
- let rank = square.rank();
- if (rank == 2 && color == Color::Black) || (rank == 5 && color == Color::White) {
- square.to_bitboard()
- } else {
- 0
- }
- }
- None => 0,
- };
- mobility += (self.attacks.pawn[color as usize][source as usize] & (opponent_occupancy | ep_bitboard)).pop_count() as f32;
- mobility += (self.attacks.pawn_pushes[color as usize][source as usize] & empty).pop_count() as f32;
- }
- let able_to_double_push_mask = match color {
- Color::White => empty >> 8,
- Color::Black => empty << 8,
- };
- for source in (*piece & able_to_double_push_mask).serialize() {
- mobility += (self.attacks.pawn_double_pushes[color as usize][source as usize] & empty).pop_count() as f32;
- }
- }
- PieceType::King => {
- for source in piece.serialize() {
- mobility += (self.attacks.king[source as usize] & (empty | opponent_occupancy)).pop_count() as f32;
-
- // Castling
- let king_home_position = match color {
- Color::White => Square::E1,
- Color::Black => Square::E8,
- };
- if *piece == king_home_position.to_bitboard() {
- for rook_square in player_pieces[PieceType::Rook as usize]
- .serialize()
- .iter()
- .filter(|rook_square| rook_square.rank() == king_home_position.rank())
- {
- match rook_square.file() {
- 0 => {
- let castle_line = [
- king_home_position.west_one(),
- king_home_position.west_one().west_one(),
- ];
-
- let all_empty = castle_line.iter().all(|square| empty & square.to_bitboard() > 0);
- let any_checks = castle_line.iter().any(|square| self.is_square_attacked(*square, color.flip()));
-
- if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::Queen as usize] {
- mobility += 1.;
- }
- },
- 7 => {
- let castle_line = [
- king_home_position.east_one(),
- king_home_position.east_one().east_one(),
- ];
-
- let all_empty = castle_line.iter().all(|square| empty & square.to_bitboard() > 0);
- let any_checks = castle_line.iter().any(|square| self.is_square_attacked(*square, color.flip()));
-
- if all_empty && !any_checks && self.castling_rights[color as usize][CastlingSide::King as usize] {
- mobility += 1.;
- }
- },
- _ => {},
- }
- }
- }
- }
- }
- PieceType::Knight => {
- for source in piece.serialize() {
- mobility += (self.attacks.knight[source as usize] & (empty | opponent_occupancy)).pop_count() as f32;
- }
- }
- PieceType::Bishop => {
- for source in piece.serialize() {
- mobility += (self.attacks.bishop(self.occupancy, source) & (empty | opponent_occupancy)).pop_count() as f32;
- }
- }
- PieceType::Rook => {
- for source in piece.serialize() {
- mobility += (self.attacks.rook(self.occupancy, source) & (empty | opponent_occupancy)).pop_count() as f32;
- }
- }
- PieceType::Queen => {
- // Do not account queen in mobility
- }
- incorrect_type => panic!("Incorrect piece type: {:?}", incorrect_type),
- }
- }
-
- mobility
- }
-
- fn piece_by_square(&self, square: Square) -> Option<PieceType> {
+ fn piece_by_square(&self, square: Square) -> Option<Piece> {
let square_bb = square.to_bitboard();
self.piece_sets
.iter()
.enumerate()
.find(|(_, bitboard)| *bitboard & square_bb > 0)
- .and_then(|(pt, _)| Some(PieceType::from(pt)))
+ .and_then(|(pt, _)| Some(Piece::from(pt)))
}
/// *Blindlessly* apply a move without any validation
/// Legality test should still be performed
- pub fn make_move(&mut self, mov: Move) -> Option<PieceType> {
+ pub fn make_move(&mut self, mov: Move) -> Option<Piece> {
let move_source_bb = mov.source.to_bitboard();
let move_target_bb = mov.target.to_bitboard();
@@ -452,7 +130,7 @@ impl Board {
self.zobrist_toggle_piece(source_piece, mov.target);
}
}
- PieceType::from(source_piece)
+ Piece::from(source_piece)
},
None => {
self.print();
@@ -502,13 +180,13 @@ impl Board {
// Withdraw castling rights when moving rooks or king
let source_color = Color::from_piece(source_piece);
match source_piece.without_color() {
- PieceType::King => {
+ Piece::King => {
self.castling_rights[source_color as usize][CastlingSide::King as usize] = false;
self.castling_rights[source_color as usize][CastlingSide::Queen as usize] = false;
self.zobrist_toggle_castling_right(source_color, CastlingSide::King);
self.zobrist_toggle_castling_right(source_color, CastlingSide::Queen);
},
- PieceType::Rook => {
+ Piece::Rook => {
match mov.source.file() {
0 => {
self.castling_rights[source_color as usize][CastlingSide::Queen as usize] = false;
@@ -534,7 +212,7 @@ impl Board {
pub fn unmake_move(
&mut self,
mov: Move,
- captured_piece: Option<PieceType>,
+ captured_piece: Option<Piece>,
previous_ep_target: Option<Square>,
previous_castling_rights: [[bool; 2]; 2],
previous_hash: u64,
@@ -552,8 +230,8 @@ impl Board {
self.occupancy ^= move_target_bb;
let source_id = match Color::from_piece(promotion_piece) {
- Color::White => PieceType::Pawn,
- Color::Black => PieceType::PawnBlack,
+ Color::White => Piece::Pawn,
+ Color::Black => Piece::PawnBlack,
} as usize;
self.piece_sets[source_id] |= move_source_bb;
@@ -626,33 +304,33 @@ impl Board {
fn is_square_attacked(&self, square: Square, attacker_color: Color) -> bool {
for (piece_type, piece) in self.pieces_by_color(attacker_color).iter().enumerate() {
- match PieceType::from(piece_type) {
- PieceType::Queen => {
+ match Piece::from(piece_type) {
+ Piece::Queen => {
if self.attacks.queen(self.occupancy, square) & piece > 0 {
return true
}
}
- PieceType::Knight => {
+ Piece::Knight => {
if self.attacks.knight[square as usize] & piece > 0 {
return true
}
}
- PieceType::Bishop => {
+ Piece::Bishop => {
if self.attacks.bishop(self.occupancy, square) & piece > 0 {
return true
}
}
- PieceType::Rook => {
+ Piece::Rook => {
if self.attacks.rook(self.occupancy, square) & piece > 0 {
return true
}
}
- PieceType::Pawn => {
+ Piece::Pawn => {
if self.attacks.pawn[attacker_color.flip() as usize][square as usize] & piece > 0 {
return true
}
}
- PieceType::King => {
+ Piece::King => {
if self.attacks.king[square as usize] & piece > 0 {
return true
}
@@ -665,8 +343,8 @@ impl Board {
fn is_king_in_check(&self, color: Color) -> bool {
let king_bb = match color {
- Color::White => self.piece_sets[PieceType::King as usize],
- Color::Black => self.piece_sets[PieceType::KingBlack as usize],
+ Color::White => self.piece_sets[Piece::King as usize],
+ Color::Black => self.piece_sets[Piece::KingBlack as usize],
};
let square = king_bb.bitscan();
self.is_square_attacked(square, color.flip())
@@ -674,33 +352,10 @@ impl Board {
}
-#[derive(Debug, Clone, Copy, PartialEq, num_enum::FromPrimitive)]
-#[repr(u8)]
-pub enum Color {
- #[default]
- White,
- Black,
-}
-impl Color {
- pub fn flip(&self) -> Self {
- match self {
- Self::White => Self::Black,
- Self::Black => Self::White,
- }
- }
- pub fn from_piece(piece: PieceType) -> Self {
- if (piece as u8) < 6 {
- Self::White
- } else {
- Self::Black
- }
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
- use crate::{bitboard::BitboardFns, square::Square, board::zobrist::Zobrist};
+ use crate::{square::Square, board::zobrist::Zobrist};
#[test]
fn square_enum() {
@@ -710,36 +365,6 @@ mod tests {
}
#[test]
- fn new_from_default_fen() {
- let board = Board::new();
-
- board.print();
- board.empty().print("Empty squares");
-
- assert_eq!(board.piece_sets[PieceType::Pawn as usize].pop_count(), 8);
- assert_eq!(board.piece_sets[PieceType::Knight as usize].pop_count(), 2);
- assert_eq!(board.piece_sets[PieceType::Bishop as usize].pop_count(), 2);
- assert_eq!(board.piece_sets[PieceType::Rook as usize].pop_count(), 2);
- assert_eq!(board.piece_sets[PieceType::Queen as usize].pop_count(), 1);
- assert_eq!(board.piece_sets[PieceType::King as usize].pop_count(), 1);
-
- assert_eq!(board.piece_sets[PieceType::PawnBlack as usize].pop_count(), 8);
- assert_eq!(board.piece_sets[PieceType::KnightBlack as usize].pop_count(), 2);
- assert_eq!(board.piece_sets[PieceType::BishopBlack as usize].pop_count(), 2);
- assert_eq!(board.piece_sets[PieceType::RookBlack as usize].pop_count(), 2);
- assert_eq!(board.piece_sets[PieceType::QueenBlack as usize].pop_count(), 1);
- assert_eq!(board.piece_sets[PieceType::KingBlack as usize].pop_count(), 1);
-
- assert_eq!(board.piece_sets[PieceType::King as usize].bitscan(), Square::E1);
- assert_eq!(board.piece_sets[PieceType::QueenBlack as usize].bitscan(), Square::D8);
-
- assert_eq!(board.occupancy.pop_count(), 32);
- assert_eq!(board.empty().pop_count(), 32);
- assert_eq!(board.color_occupancy(Color::White).pop_count(), 16);
- assert_eq!(board.color_occupancy(Color::Black).pop_count(), 16);
- }
-
- #[test]
fn generate_pseudolegal_moves_starting_position() {
let mut board = Board::new();
let moves = board.generate_pseudolegal_moves();
@@ -785,23 +410,23 @@ mod tests {
board.compute_hash();
assert_eq!(hash, board.hash, "Hash should be correctly updated after move");
- assert!(board.piece_sets[PieceType::PawnBlack as usize] & Square::F7.to_bitboard() == 0);
- assert!(board.piece_sets[PieceType::PawnBlack as usize] & Square::F5.to_bitboard() > 0);
+ assert!(board.piece_sets[Piece::PawnBlack as usize] & Square::F7.to_bitboard() == 0);
+ assert!(board.piece_sets[Piece::PawnBlack as usize] & Square::F5.to_bitboard() > 0);
assert!(board.ply == 1);
let white_move = Move { source: Square::D2, target: Square::A5, kind: MoveKind::Capture };
println!("\n{:?}", white_move);
match board.make_move(white_move) {
- Some(captured) => assert!(captured == PieceType::PawnBlack),
+ Some(captured) => assert!(captured == Piece::PawnBlack),
None => panic!("A piece should be captured"),
};
board.print();
- assert!(board.piece_sets[PieceType::PawnBlack as usize] & Square::A5.to_bitboard() == 0, "Target piece should be captured");
- assert!(board.piece_sets[PieceType::Queen as usize] & Square::D2.to_bitboard() == 0);
- assert!(board.piece_sets[PieceType::Queen as usize] & Square::A5.to_bitboard() > 0);
+ assert!(board.piece_sets[Piece::PawnBlack as usize] & Square::A5.to_bitboard() == 0, "Target piece should be captured");
+ assert!(board.piece_sets[Piece::Queen as usize] & Square::D2.to_bitboard() == 0);
+ assert!(board.piece_sets[Piece::Queen as usize] & Square::A5.to_bitboard() > 0);
assert_ne!(board.occupancy, initial_board.occupancy, "Occupancy should change after make_move");
assert!(board.ply == 2);
}
diff --git a/src/board/piece.rs b/src/board/piece.rs
new file mode 100644
index 0000000..1889e94
--- /dev/null
+++ b/src/board/piece.rs
@@ -0,0 +1,36 @@
+#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)]
+#[repr(usize)]
+pub enum Piece {
+ #[default]
+ Pawn,
+ Knight,
+ Bishop,
+ Rook,
+ Queen,
+ King,
+ PawnBlack,
+ KnightBlack,
+ BishopBlack,
+ RookBlack,
+ QueenBlack,
+ KingBlack,
+}
+
+impl Piece {
+ pub fn without_color(&self) -> Self {
+ let index = *self as usize;
+ Self::from(index % 6)
+ }
+ // Return the price of the peice
+ pub fn static_eval(&self) -> f32 {
+ match self.without_color() {
+ Piece::Pawn => 1.0,
+ Piece::Bishop => 3.3,
+ Piece::Knight => 3.2,
+ Piece::Rook => 5.0,
+ Piece::Queen => 9.0,
+ Piece::King => 0.,
+ _ => panic!("Piece should be without color"),
+ }
+ }
+}
diff --git a/src/board/zobrist.rs b/src/board/zobrist.rs
index 8ea0038..5286a73 100644
--- a/src/board/zobrist.rs
+++ b/src/board/zobrist.rs
@@ -2,7 +2,7 @@ use rand::{rngs::StdRng,SeedableRng,Rng};
use crate::{square::Square, bitboard::BitboardFns};
-use super::{Board, Color, PieceType, CastlingSide};
+use super::{Board, Color, Piece, CastlingSide};
const TOTAL_PIECES: usize = 12;
@@ -20,7 +20,7 @@ pub trait Zobrist {
/// https://www.chessprogramming.org/Zobrist_Hashing
fn compute_hash(&mut self) -> ();
- fn zobrist_toggle_piece(&mut self, piece_type: PieceType, square: Square) -> ();
+ fn zobrist_toggle_piece(&mut self, piece_type: Piece, square: Square) -> ();
fn zobrist_toggle_castling_right(&mut self, color: Color, side: CastlingSide) -> ();
fn zobrist_toggle_ep_square(&mut self, ep_square: Square) -> ();
fn zobrist_toggle_color(&mut self) -> ();
@@ -37,7 +37,7 @@ impl Zobrist for Board {
for piece_id in 0..self.piece_sets.len() {
for square in self.piece_sets[piece_id].serialize() {
- self.zobrist_toggle_piece(PieceType::from(piece_id), square);
+ self.zobrist_toggle_piece(Piece::from(piece_id), square);
}
}
for color in [Color::White, Color::Black] {
@@ -58,7 +58,7 @@ impl Zobrist for Board {
}
}
- fn zobrist_toggle_piece(&mut self, piece_type: PieceType, square: Square) -> () {
+ fn zobrist_toggle_piece(&mut self, piece_type: Piece, square: Square) -> () {
self.hash ^= self.zobrist_seed[piece_type as usize * TOTAL_SQUARES + square as usize];
}