aboutsummaryrefslogtreecommitdiff
path: root/src/board.rs
diff options
context:
space:
mode:
authoreug-vs <eugene@eug-vs.xyz>2023-01-24 21:40:06 +0300
committereug-vs <eugene@eug-vs.xyz>2023-01-24 21:40:06 +0300
commite27b950db851c91231abf3f3a3afebae18af47af (patch)
tree74bffdf56b2a64163204374f5309ff36384d7549 /src/board.rs
parent4a62a723f052ed0506cf9342c009cc315a8379a3 (diff)
downloadchessnost-e27b950db851c91231abf3f3a3afebae18af47af.tar.gz
refactor: separate engine into submodule
Diffstat (limited to 'src/board.rs')
-rw-r--r--src/board.rs839
1 files changed, 0 insertions, 839 deletions
diff --git a/src/board.rs b/src/board.rs
deleted file mode 100644
index 9ff81b1..0000000
--- a/src/board.rs
+++ /dev/null
@@ -1,839 +0,0 @@
-use crate::{bitboard::{Bitboard, serialize_bitboard, bitscan, pop_count}, moves::{Move, MoveKind}, attacks::Attacks, square::Square};
-
-pub enum CastlingSide {
- King,
- Queen,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct Board {
- pub pieces: [Bitboard; 12],
-
- pub occupancy: Bitboard,
- pub ply: u16,
-
- /// En passsant target square
- pub ep_target: Option<Square>,
-
- /// Castling rights indexed by Color and CastlingSide
- /// ```
- /// let can_castle = castling_rights[Color::White as usize][CastlingSide::Queen as usize];
- /// ```
- pub castling_rights: [[bool; 2]; 2],
-
- 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,
-}
-
-const PIECE_CHARS: [&str; 12] = [
- "♟︎", "♞", "♝", "♜", "♛", "♚",
- "♙", "♘", "♗", "♖", "♕", "♔",
-];
-
-
-#[allow(unused)]
-impl Board {
- #[allow(non_snake_case)]
- pub fn from_FEN(fen: String) -> Self {
- let mut pieces = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
-
- let mut rank = 7;
- let mut file = 0i32;
-
- for character in fen.chars() {
- let index = rank * 8 + file;
- let position = 1 << index.clamp(0, 63);
-
- if character.is_numeric() {
- let digit = match character.to_digit(10) {
- None => todo!("What to do here?"),
- Some(digit) => digit,
- };
- if digit > 0 && digit <= 8 {
- file += digit as i32;
- }
- } else {
- match character {
- 'P' => pieces[PieceType::Pawn as usize] |= position,
- 'N' => pieces[PieceType::Knight as usize] |= position,
- 'B' => pieces[PieceType::Bishop as usize] |= position,
- 'R' => pieces[PieceType::Rook as usize] |= position,
- 'Q' => pieces[PieceType::Queen as usize] |= position,
- 'K' => pieces[PieceType::King as usize] |= position,
- 'p' => pieces[PieceType::PawnBlack as usize] |= position,
- 'n' => pieces[PieceType::KnightBlack as usize] |= position,
- 'b' => pieces[PieceType::BishopBlack as usize] |= position,
- 'r' => pieces[PieceType::RookBlack as usize] |= position,
- 'q' => pieces[PieceType::QueenBlack as usize] |= position,
- 'k' => pieces[PieceType::KingBlack as usize] |= position,
- '/' => {
- rank -= 1;
- file = -1; // So it becomes 0
- },
- ' ' => { break }, // TODO: break for now, parse everything else later
- '-' => {}, // TODO
- 'w' => {}, // TODO
- _ => todo!("Unexpected character!"),
- }
- file += 1;
- }
- }
-
- let mut board = Self {
- pieces,
- occupancy: 0,
- ply: 0,
- attacks: Attacks::new(),
- castling_rights: [[true; 2]; 2], // TODO: actualy parse from FEN
- ep_target: None,
- };
- board.update_occupancy();
- board
- }
-
- pub fn new() -> Self {
- let default_fen = String::from("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
- Self::from_FEN(default_fen)
- }
-
- /// Color to move at this ply
- fn color(&self) -> Color {
- Color::from(self.ply as u8 % 2)
- }
-
- fn update_occupancy(&mut self) {
- self.occupancy = 0;
- // TODO: reduce
- for piece in self.pieces {
- self.occupancy |= piece;
- }
- }
-
- fn empty(&self) -> Bitboard {
- !self.occupancy
- }
-
- fn pieces_by_color(&self, color: Color) -> &[Bitboard] {
- match color {
- Color::White => &self.pieces[0..6],
- Color::Black => &self.pieces[6..12],
- }
- }
-
- fn color_occupancy(&self, color: Color) -> Bitboard {
- let mut occupancy = 0;
- for piece in self.pieces_by_color(color) {
- occupancy |= piece;
- }
- occupancy
- }
-
- pub fn color_to_move(&self) -> Color {
- Color::from((self.ply % 2) as u8)
- }
-
- pub fn print(&self) {
- println!();
- for rank in (0..8).rev() {
- print!("{}|", rank + 1);
- for file in 0..8 {
- let index = rank * 8 + file;
- let position: Bitboard = 1 << index;
- let mut found = false;
- for (piece_type, piece_bitboard) in self.pieces.iter().enumerate() {
- if (piece_bitboard & position) > 0 {
- found = true;
- print!("{} ", PIECE_CHARS[piece_type]);
- }
- }
- if !found {
- print!(". ");
- }
- }
- println!();
- }
- println!(" a b c d e f g h");
- }
-
- pub fn generate_pseudolegal_moves(&self, color: Color) -> Vec<Move> {
- let mut moves = Vec::with_capacity(1024);
- let opponent_occupancy = self.color_occupancy(color.flip());
- let empty = self.empty();
- let available_targets = opponent_occupancy | empty;
- let player_pieces = self.pieces_by_color(color);
-
- for (piece_type, piece) in player_pieces.iter().enumerate() {
- match PieceType::from(piece_type) {
- PieceType::Pawn => {
- for source in serialize_bitboard(*piece) {
- let ep_bitboard = match self.ep_target {
- Some(square) => square.to_bitboard(),
- None => 0,
- };
- for target in serialize_bitboard(self.attacks.pawn[color as usize][source as usize] & opponent_occupancy) {
- moves.push(Move { source, target, kind: MoveKind::Capture });
- };
- for target in serialize_bitboard(self.attacks.pawn[color as usize][source as usize] & ep_bitboard) {
- moves.push(Move { source, target, kind: MoveKind::EnPassant });
- }
- for target in serialize_bitboard(self.attacks.pawn_pushes[color as usize][source as usize] & empty) {
- moves.push(Move { source, target, kind: MoveKind::Quiet });
- };
- }
-
- // 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 serialize_bitboard(*piece & able_to_double_push_mask) {
- for target in serialize_bitboard(self.attacks.pawn_double_pushes[color as usize][source as usize] & empty) {
- moves.push(Move { source, target, kind: MoveKind::DoublePush });
- };
- }
- }
- PieceType::King => {
- for source in serialize_bitboard(*piece) {
- for target in serialize_bitboard(self.attacks.king[source as usize] & empty) {
- moves.push(Move { source, target, kind: MoveKind::Quiet });
- };
- for target in serialize_bitboard(self.attacks.king[source as usize] & opponent_occupancy) {
- moves.push(Move { source, target, kind: MoveKind::Capture });
- };
-
- // 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 serialize_bitboard(player_pieces[PieceType::Rook as usize])
- .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] {
- moves.push(Move {
- source: king_home_position,
- target: king_home_position.west_one().west_one(),
- kind: MoveKind::Castle,
- })
- }
- },
- 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] {
- moves.push(Move {
- source: king_home_position,
- target: king_home_position.east_one().east_one(),
- kind: MoveKind::Castle,
- })
- }
- },
- _ => {},
- }
- }
- }
- }
- }
- PieceType::Knight => {
- for source in serialize_bitboard(*piece) {
- for target in serialize_bitboard(self.attacks.knight[source as usize] & empty) {
- moves.push(Move { source, target, kind: MoveKind::Quiet });
- };
- for target in serialize_bitboard(self.attacks.knight[source as usize] & opponent_occupancy) {
- moves.push(Move { source, target, kind: MoveKind::Capture });
- };
- }
- }
- PieceType::Bishop => {
- for source in serialize_bitboard(*piece) {
- for target in serialize_bitboard(self.attacks.bishop(self.occupancy, source) & empty) {
- moves.push(Move { source, target, kind: MoveKind::Quiet });
- };
- for target in serialize_bitboard(self.attacks.bishop(self.occupancy, source) & opponent_occupancy) {
- moves.push(Move { source, target, kind: MoveKind::Capture });
- };
- }
- }
- PieceType::Rook => {
- for source in serialize_bitboard(*piece) {
- for target in serialize_bitboard(self.attacks.rook(self.occupancy, source) & empty) {
- moves.push(Move { source, target, kind: MoveKind::Quiet });
- };
- }
- for source in serialize_bitboard(*piece) {
- for target in serialize_bitboard(self.attacks.rook(self.occupancy, source) & opponent_occupancy) {
- moves.push(Move { source, target, kind: MoveKind::Capture });
- };
- }
- }
- PieceType::Queen => {
- for source in serialize_bitboard(*piece) {
- for target in serialize_bitboard(self.attacks.queen(self.occupancy, source) & empty) {
- moves.push(Move { source, target, kind: MoveKind::Quiet });
- };
- for target in serialize_bitboard(self.attacks.queen(self.occupancy, source) & opponent_occupancy) {
- moves.push(Move { source, target, kind: MoveKind::Capture });
- };
- }
- }
- _ => todo!("Incorrect piece type")
- }
- }
- moves
- }
-
- /// *Blindlessly* apply a move without any validation
- /// Move should be validated beforehand
- pub fn make_move(&mut self, mov: Move) -> Option<PieceType> {
- let move_source_bb = mov.source.to_bitboard();
- let move_target_bb = mov.target.to_bitboard();
-
- // Remove existing piece (if any) from target square
- let mut captured_piece = match self.pieces
- .iter()
- .enumerate()
- .find(|(piece_type, bitboard)| *bitboard & mov.target.to_bitboard() > 0)
- {
- Some((target_piece, _)) => {
- self.pieces[target_piece] ^= move_target_bb;
- Some(PieceType::from(target_piece))
- },
- None => None,
- };
-
- // En Passant captures diffirently
- if mov.kind == MoveKind::EnPassant {
- debug_assert!(captured_piece.is_none(), "No capture should be found at this point");
- let captured_bb = Square::from_coords(mov.source.rank(), mov.target.file()).to_bitboard();
- captured_piece = match self.pieces
- .iter()
- .enumerate()
- .find(|(piece_type, bitboard)| *bitboard & captured_bb > 0)
- {
- Some((pawn_type, _)) => {
- self.pieces[pawn_type] ^= captured_bb;
- Some(PieceType::from(pawn_type))
- }
- None => panic!("Pawn captured by En Passant was not found"),
- }
- }
-
- // Move a piece from source square to target
- let source_piece = match self.pieces
- .iter()
- .enumerate()
- .find(|(piece_type, bitboard)| *bitboard & mov.source.to_bitboard() > 0)
- {
- Some((source_piece, _)) => {
- self.pieces[source_piece] ^= move_source_bb;
- self.occupancy ^= move_source_bb;
-
- self.pieces[source_piece] |= move_target_bb;
- self.occupancy |= move_target_bb;
- PieceType::from(source_piece)
- },
- None => panic!("Move is malformed: source piece not found"),
- };
-
- // When castling, also move a rook
- if mov.kind == MoveKind::Castle {
- debug_assert!(mov.source.file() == 4, "Castle can only be done from E file");
- let (rook_source_file, rook_target_file) = match mov.target.file() {
- 2 => (0, 3),
- 6 => (7, 5),
- _ => panic!("Malformed castle, target square invalid: {:?}", mov),
- };
- let rook_source_bb = Square::from_coords(mov.target.rank(), rook_source_file).to_bitboard();
- let rook_target_bb = Square::from_coords(mov.target.rank(), rook_target_file).to_bitboard();
-
- match self.pieces
- .iter()
- .enumerate()
- .find(|(rook_type, bitboard)| *bitboard & rook_source_bb > 0)
- {
- Some((rook_type, _)) => {
- self.pieces[rook_type] ^= rook_source_bb;
- self.occupancy ^= rook_source_bb;
-
- self.pieces[rook_type] |= rook_target_bb;
- self.occupancy |= rook_target_bb;
- },
- None => panic!("Rook was not found when castling"),
- }
- }
-
- // Double push should set En Passant target square
- self.ep_target = if mov.kind == MoveKind::DoublePush {
- match mov.source.rank() {
- 1 => Some(mov.source.nort_one()),
- 6 => Some(mov.source.sout_one()),
- rank => panic!("Double-push was used from invalid rank({}) when trying to make {:?}", rank, mov),
- }
- } else { None };
-
- // Withdraw castling rights when moving rooks or king
- match source_piece {
- PieceType::King => {
- self.castling_rights[Color::from_piece(source_piece) as usize][CastlingSide::King as usize] = false;
- self.castling_rights[Color::from_piece(source_piece) as usize][CastlingSide::Queen as usize] = false;
- },
- PieceType::Rook => {
- match mov.source.file() {
- 0 => self.castling_rights[Color::from_piece(source_piece) as usize][CastlingSide::Queen as usize] = false,
- 7 => self.castling_rights[Color::from_piece(source_piece) as usize][CastlingSide::King as usize] = false,
- _ => {},
- }
- },
- _ => {},
- }
-
- self.ply += 1;
-
- captured_piece
- }
-
- /// Completely reverse make_move as if it never happened
- pub fn unmake_move(&mut self, mov: Move, captured_piece: Option<PieceType>, previous_ep_target: Option<Square>, previous_castling_rights: [[bool; 2]; 2]) {
- let move_source_bb = mov.source.to_bitboard();
- let move_target_bb = mov.target.to_bitboard();
-
- // Move a piece from target square back to source square
- match self.pieces
- .iter()
- .enumerate()
- .find(|(piece_type, bitboard)| *bitboard & mov.target.to_bitboard() > 0)
- {
- Some((source_piece, _)) => {
- self.pieces[source_piece] ^= move_target_bb;
- self.occupancy ^= move_target_bb;
-
- self.pieces[source_piece] |= move_source_bb;
- self.occupancy |= move_source_bb;
- },
- None => panic!("Trying to unmake move which was not made: no piece was found on target square"),
- };
-
- // If unmaking castle, also return rook to its place
- if mov.kind == MoveKind::Castle {
- let (rook_source_file, rook_target_file) = match mov.target.file() {
- 2 => (0, 3),
- 6 => (7, 5),
- _ => panic!("Malformed castle, target square invalid: {:?}", mov),
- };
- let rook_source_bb = Square::from_coords(mov.target.rank(), rook_source_file).to_bitboard();
- let rook_target_bb = Square::from_coords(mov.target.rank(), rook_target_file).to_bitboard();
-
- match self.pieces
- .iter()
- .enumerate()
- .find(|(rook_type, bitboard)| *bitboard & rook_target_bb > 0)
- {
- Some((rook_type, _)) => {
- self.pieces[rook_type] |= rook_source_bb;
- self.occupancy |= rook_source_bb;
-
- self.pieces[rook_type] ^= rook_target_bb;
- self.occupancy ^= rook_target_bb;
- },
- None => panic!("Rook was not found when castling"),
- }
- }
-
- // Return captured piece to target square
- match captured_piece {
- Some(target_piece) => {
- match mov.kind {
- // Return pawn captured by En Passant pawn if needed
- MoveKind::EnPassant => {
- let original_dead_pawn_bb = Square::from_coords(mov.source.rank(), mov.target.file()).to_bitboard();
- self.pieces[target_piece as usize] |= original_dead_pawn_bb;
- self.occupancy |= original_dead_pawn_bb;
- },
- _ => {
- self.pieces[target_piece as usize] |= move_target_bb;
- self.occupancy |= move_target_bb;
- },
- }
- },
- None => {}
- }
-
-
- self.ep_target = previous_ep_target;
- self.castling_rights = previous_castling_rights;
- self.ply -= 1;
- }
-
- pub fn perft(&mut self, depth: u8, print: bool) -> (u64, u64, u64, u64, u64) {
- if depth == 0 {
- return (1, 0, 0, 0, 0) // This a leaf, exactly one node
- }
- let color = self.color_to_move();
-
- let moves = self.generate_pseudolegal_moves(color);
-
- if print {
- println!("Running perft for depth {}. Color to move is {:?}\n{} moves available", depth, color, moves.len());
- println!("{} moves available", moves.len());
- }
-
- let mut total = 0;
- let mut captures = 0;
- let mut checks = 0;
- let mut castles = 0;
- let mut en_passants = 0;
-
- for mov in moves {
- let ep_target_before = self.ep_target.clone();
- let castling_rights_before = self.castling_rights.clone();
- let captured_piece = self.make_move(mov);
- // King can not be in check after our own move
- if !self.is_king_in_check(color) {
- if depth == 1 {
- match mov.kind {
- MoveKind::Capture => {
- captures += 1;
- }
- MoveKind::EnPassant => {
- en_passants += 1;
- captures += 1;
- }
- MoveKind::Castle => {
- castles += 1;
- }
- _ => {}
- }
- if self.is_king_in_check(color.flip()) {
- checks += 1;
- }
- }
-
- if print {
- println!("{:?}", mov);
- self.print();
- }
- let (children_total, children_tactical, children_checks, children_castles, children_ep) = self.perft(depth - 1, print);
- total += children_total;
- captures += children_tactical;
- checks += children_checks;
- castles += children_castles;
- en_passants += children_ep;
-
- }
- self.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before);
- }
-
- if print {
- println!("Found {} nodes in this subtree (depth {})", total, depth);
- }
-
- (total, captures, checks, castles, en_passants)
- }
-
- fn is_square_attacked(&self, square: Square, attacker_color: Color) -> bool {
- let square_bb = square.to_bitboard();
- for (piece_type, piece) in self.pieces_by_color(attacker_color).iter().enumerate() {
- match PieceType::from(piece_type) {
- PieceType::Pawn => {
- if (self.attacks.pawn[attacker_color.flip() as usize][square as usize] & piece > 0) {
- return true
- }
- }
- PieceType::Knight => {
- if (self.attacks.knight[square as usize] & piece > 0) {
- return true
- }
- }
- PieceType::Bishop => {
- if (self.attacks.bishop(self.occupancy, square) & piece > 0) {
- return true
- }
- }
- PieceType::Rook => {
- if (self.attacks.rook(self.occupancy, square) & piece > 0) {
- return true
- }
- }
- PieceType::Queen => {
- if (self.attacks.queen(self.occupancy, square) & piece > 0) {
- return true
- }
- }
- PieceType::King => {}
- _ => panic!("Unexpected piece type! Pieces by color should be considered white")
- }
- }
- false
- }
-
- fn is_king_in_check(&self, color: Color) -> bool {
- let king_bb = match color {
- Color::White => self.pieces[PieceType::King as usize],
- Color::Black => self.pieces[PieceType::KingBlack as usize],
- };
- let square = bitscan(king_bb);
- self.is_square_attacked(square, color.flip())
- }
-
- fn evaluate(&self) -> f32 {
- let mut eval = 0f32;
- let pieces = self.pieces_by_color(self.color());
- eval += pop_count(pieces[PieceType::Pawn as usize]) as f32;
- eval += pop_count(pieces[PieceType::Bishop as usize]) as f32 * 3.;
- eval += pop_count(pieces[PieceType::Knight as usize]) as f32 * 3.;
- eval += pop_count(pieces[PieceType::Rook as usize]) as f32 * 4.5;
- eval += pop_count(pieces[PieceType::Queen as usize]) as f32 * 9.;
- eval
- }
-
- fn negamax_search(&mut self, mut alpha: f32, beta: f32, depth_left: u8) -> f32 {
- let color = Color::from(self.ply as u8 % 2);
-
- self.print();
-
- if depth_left == 0 {
- return self.evaluate();
- }
- let moves = self.generate_pseudolegal_moves(color);
-
- for mov in moves {
- let ep_target_before = self.ep_target.clone();
- let castling_rights_before = self.castling_rights.clone();
- let captured_piece = self.make_move(mov);
-
- if !self.is_king_in_check(color) {
- let evaluation = -self.negamax_search(-beta, -alpha, depth_left - 1);
- self.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before);
-
- if (evaluation >= beta) {
- return beta; // Fail-hard beta-cutoff
- }
- if (evaluation > alpha) {
- alpha = evaluation
- }
- } else {
- self.unmake_move(mov, captured_piece, ep_target_before, castling_rights_before);
- }
- }
- alpha
- }
-}
-
-
-#[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 std::f32::INFINITY;
-
- use super::*;
- use crate::{bitboard::{pop_count, bitscan, print}, square::Square};
-
- #[test]
- fn square_enum() {
- assert_eq!(Square::A1 as u8, 0);
- assert_eq!(Square::F1 as u8, 5);
- assert_eq!(Square::H8 as u8, 63);
- }
-
- #[test]
- fn new_from_default_fen() {
- let board = Board::new();
-
- board.print();
- print(board.empty(), "Empty squares");
-
- assert_eq!(pop_count(board.pieces[PieceType::Pawn as usize]), 8);
- assert_eq!(pop_count(board.pieces[PieceType::Knight as usize]), 2);
- assert_eq!(pop_count(board.pieces[PieceType::Bishop as usize]), 2);
- assert_eq!(pop_count(board.pieces[PieceType::Rook as usize]), 2);
- assert_eq!(pop_count(board.pieces[PieceType::Queen as usize]), 1);
- assert_eq!(pop_count(board.pieces[PieceType::King as usize]), 1);
-
- assert_eq!(pop_count(board.pieces[PieceType::PawnBlack as usize]), 8);
- assert_eq!(pop_count(board.pieces[PieceType::KnightBlack as usize]), 2);
- assert_eq!(pop_count(board.pieces[PieceType::BishopBlack as usize]), 2);
- assert_eq!(pop_count(board.pieces[PieceType::RookBlack as usize]), 2);
- assert_eq!(pop_count(board.pieces[PieceType::QueenBlack as usize]), 1);
- assert_eq!(pop_count(board.pieces[PieceType::KingBlack as usize]), 1);
-
- assert_eq!(bitscan(board.pieces[PieceType::King as usize]), Square::E1);
- assert_eq!(bitscan(board.pieces[PieceType::QueenBlack as usize]), Square::D8);
-
- assert_eq!(pop_count(board.occupancy), 32);
- assert_eq!(pop_count(board.empty()), 32);
- assert_eq!(pop_count(board.color_occupancy(Color::White)), 16);
- assert_eq!(pop_count(board.color_occupancy(Color::Black)), 16);
- }
-
- #[test]
- fn generate_pseudolegal_moves_starting_position() {
- let board = Board::new();
- let moves = board.generate_pseudolegal_moves(Color::White);
- let black_moves = board.generate_pseudolegal_moves(Color::Black);
-
- assert_eq!(moves.len(), 20);
- assert_eq!(black_moves.len(), 20);
-
- for mov in moves {
- mov.print();
- }
- }
-
- #[test]
- fn make_move() {
- let fen = String::from("q1b2k2/5p1p/4p1pb/pPPp4/3N4/3nPB2/P2QKnR1/1R6 w - - 0 25");
- let mut board = Board::from_FEN(fen);
- let initial_board = board.clone();
- board.print();
-
- let black_move = Move { source: Square::F7, target: Square::F5, kind: MoveKind::Quiet };
- println!("\n{:?}", black_move);
-
- match board.make_move(black_move) {
- Some(..) => panic!("No piece should be captured"),
- None => {},
- };
-
- board.print();
-
- assert!(board.pieces[PieceType::PawnBlack as usize] & Square::F7.to_bitboard() == 0);
- assert!(board.pieces[PieceType::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),
- None => panic!("A piece should be captured"),
- };
-
- board.print();
-
- assert!(board.pieces[PieceType::PawnBlack as usize] & Square::A5.to_bitboard() == 0, "Target piece should be captured");
- assert!(board.pieces[PieceType::Queen as usize] & Square::D2.to_bitboard() == 0);
- assert!(board.pieces[PieceType::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);
- }
-
- #[test]
- fn unmake_move() {
- let fen = String::from("q1b2k2/5p1p/4p1pb/pPPp4/3N4/3nPB2/P2QKnR1/1R6 w - - 0 25");
- let mut board = Board::from_FEN(fen);
- let initial_board = board.clone();
-
- let mov = Move { source: Square::D2, target: Square::A5, kind: MoveKind::Capture };
-
- board.print();
-
- let captured_piece = board.make_move(mov);
- board.print();
-
- board.unmake_move(mov, captured_piece, None, board.castling_rights);
- board.print();
-
- assert_eq!(board, initial_board, "Board state after unmake_move should be the same as before make_move");
- }
-
- #[test]
- fn perft() {
- let mut board = Board::new();
-
- assert_eq!(board.perft(0, false), (1, 0, 0, 0, 0));
- assert_eq!(board.perft(1, false), (20, 0, 0, 0, 0));
- assert_eq!(board.perft(2, false), (400, 0, 0, 0, 0));
- assert_eq!(board.perft(3, false), (8902, 34, 12, 0, 0));
- assert_eq!(board.perft(4, false), (197281, 1576, 469, 0, 0));
- // assert_eq!(board.perft(5, false), (4865609, 82719, 27351, 0, 258));
- // assert_eq!(board.perft(6, false), (119060324, 2812008, 809099, 0, 5248));
- }
-
- #[test]
- fn position_perft() {
- let fen = String::from("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - ");
- let mut board = Board::from_FEN(fen);
- assert_eq!(board.perft(0, false), (1, 0, 0, 0, 0));
- assert_eq!(board.perft(1, false), (48, 8, 0, 2, 0));
- assert_eq!(board.perft(2, false), (2039, 351, 3, 91, 1));
- assert_eq!(board.perft(3, false), (97862, 17102, 993, 3162, 45));
- }
-
- #[test]
- fn is_square_attacked() {
- let board = Board::new();
-
- assert_eq!(board.is_square_attacked(Square::E2, Color::White), true);
- assert_eq!(board.is_square_attacked(Square::E2, Color::Black), false);
- assert_eq!(board.is_square_attacked(Square::E4, Color::White), false);
- assert_eq!(board.is_square_attacked(Square::B6, Color::Black), true);
- }
-
- #[test]
- fn negamax_search() {
- let fen = String::from("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - ");
- let mut board = Board::from_FEN(fen);
-
- let eval = board.negamax_search(-INFINITY, INFINITY, 4);
- println!("{}", eval);
- }
-}