diff --git a/perftree_script.sh b/perftree_script.sh new file mode 100755 index 0000000..1742a65 --- /dev/null +++ b/perftree_script.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +./target/debug/Mouse "$@" \ No newline at end of file diff --git a/src/backend/mod.rs b/src/backend/mod.rs index a3c2c98..a14b2cc 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,6 +1,3 @@ -pub mod moove; pub(crate) mod movegen; pub mod perft; -pub mod piece; -pub mod square; pub(crate) mod state; diff --git a/src/backend/movegen/check_decider.rs b/src/backend/movegen/check_decider.rs index 8cfdd6f..346ab3b 100644 --- a/src/backend/movegen/check_decider.rs +++ b/src/backend/movegen/check_decider.rs @@ -1,7 +1,10 @@ -use crate::backend::movegen::compile_time::move_cache_non_sliders::get_moves_cache_for_piece; -use crate::backend::piece::{Piece, PieceColor, PieceType}; -use crate::backend::square::Square; -use crate::backend::state::game_state::GameState; +use crate::backend::movegen::compile_time::move_cache_non_sliders::{ + KING_MOVES, KNIGHT_MOVES, PAWN_CAPTURE_MOVES, +}; +use crate::backend::state::board::bitboard::BitBoard; +use crate::backend::state::game::game_state::GameState; +use crate::backend::state::piece::{Piece, PieceColor, PieceType}; +use crate::backend::state::square::Square; /// Checks if a given player's king is in check in the current game state. /// @@ -38,17 +41,16 @@ pub fn is_in_check(game_state: &GameState, color: PieceColor) -> bool { // SLIDER: I think this needs to be changed for sliders. // Iterate over all pieces. Let`s assume we are checking for knights. for piece_type in PieceType::get_all_types() { - // Get the move cache for the knight... - let moves_cache = get_moves_cache_for_piece(piece_type); - // ...and get the potential moves for the knight from the square of the king. - let piece_move_bitboard = moves_cache[king_square.square_to_index()]; + // Get the bitboard that represents all possible attacks. + let attack_bitboard = + get_attack_bitboard_for_piece_and_square(piece_type, color, king_square); // Get bitboard that marks where enemy knights are standing. let enemy_piece = Piece::new(piece_type, color.opposite()); let enemy_piece_bitboard = game_state.bit_board_manager().get_bitboard(enemy_piece); // Check if at least one of the places we could move to contains an enemy knight. - let resulting_bitboard = piece_move_bitboard & *enemy_piece_bitboard; + let resulting_bitboard = attack_bitboard & *enemy_piece_bitboard; // If so, we know that the king is in check. if resulting_bitboard.is_not_empty() { return true; @@ -64,3 +66,16 @@ fn get_kings_square(game_state: &GameState, color: PieceColor) -> Square { let king_square = king_bitboard.get_all_true_squares(); king_square[0] } + +fn get_attack_bitboard_for_piece_and_square( + piece_type: PieceType, + piece_color: PieceColor, + square: Square, +) -> BitBoard { + match piece_type { + PieceType::King => KING_MOVES[square.square_to_index()], + PieceType::Knight => KNIGHT_MOVES[square.square_to_index()], + PieceType::Pawn => PAWN_CAPTURE_MOVES[piece_color as usize][square.square_to_index()], + _ => panic!("Not implemented yet"), + } +} diff --git a/src/backend/movegen/compile_time/move_cache_non_sliders.rs b/src/backend/movegen/compile_time/move_cache_non_sliders.rs index 7ef36c8..1232bb2 100644 --- a/src/backend/movegen/compile_time/move_cache_non_sliders.rs +++ b/src/backend/movegen/compile_time/move_cache_non_sliders.rs @@ -1,7 +1,7 @@ -use crate::backend::piece::PieceType; -use crate::backend::square::Square; -use crate::backend::state::bitboard::BitBoard; -use crate::constants::SQUARES_AMOUNT; +use crate::backend::state::board::bitboard::BitBoard; +use crate::backend::state::piece::{PieceColor, PieceType}; +use crate::backend::state::square::Square; +use crate::constants::{SIDES, SQUARES_AMOUNT}; /// All of this gets generated at compile time, in the functions below. /// At runtime, we only have to read the values. @@ -25,15 +25,25 @@ pub const KING_MOVES: [BitBoard; SQUARES_AMOUNT] = calculate_potential_moves_cac pub const KNIGHT_MOVES: [BitBoard; SQUARES_AMOUNT] = calculate_potential_moves_cache(PieceType::Knight); -pub fn get_moves_cache_for_piece(piece_type: PieceType) -> [BitBoard; SQUARES_AMOUNT] { - match piece_type { - PieceType::Knight => KNIGHT_MOVES, - PieceType::King => KING_MOVES, - _ => panic!("Invalid piece type"), - } +enum PawnMoveType { + Quiet, + Capture, + DoublePush, } -/// Initializes a collection of bitboards representing all possible king moves for each square. +/// All quiet moves for pawns. +pub const PAWN_QUIET_MOVES: [[BitBoard; SQUARES_AMOUNT]; SIDES] = + generate_pawn_moves(PawnMoveType::Quiet); + +/// All capture moves for pawns. +pub const PAWN_CAPTURE_MOVES: [[BitBoard; SQUARES_AMOUNT]; SIDES] = + generate_pawn_moves(PawnMoveType::Capture); + +/// All capture moves for pawns. +pub const PAWN_DOUBLE_PUSH_MOVES: [[BitBoard; SQUARES_AMOUNT]; SIDES] = + generate_pawn_moves(PawnMoveType::DoublePush); + +/// Initializes a collection of bitboards representing all possible moves for each square. /// /// Since this function is const, it can be evaluated at compile time. /// # Parameters @@ -138,3 +148,90 @@ const fn generate_knight_moves(square: Square) -> BitBoard { bitboard } + +const fn generate_pawn_moves(pawn_move_type: PawnMoveType) -> [[BitBoard; SQUARES_AMOUNT]; SIDES] { + let mut quiet_moves = [[BitBoard::new(); SQUARES_AMOUNT]; SIDES]; + + let mut side_index = 0; + while side_index < 2 { + let active_color = match side_index { + 0 => PieceColor::White, + 1 => PieceColor::Black, + _ => panic!("Invalid side index"), + }; + let mut potential_moves = [BitBoard::new(); SQUARES_AMOUNT]; + + // iterate over all squares + let mut square_index: usize = 0; + while square_index < SQUARES_AMOUNT { + let mut bitboard = BitBoard::new(); + // generate a square struct from the index + let square = Square::index_to_square(square_index as i8); + + match pawn_move_type { + PawnMoveType::Quiet => { + generate_pawn_quiet_moves(square, &mut bitboard, active_color); + } + PawnMoveType::Capture => { + generate_pawn_attack_moves(square, &mut bitboard, active_color); + } + PawnMoveType::DoublePush => { + generate_pawn_double_push_moves(square, &mut bitboard, active_color); + } + } + + // and generate the moves for that square + potential_moves[square_index] = bitboard; + + square_index += 1; + } + + quiet_moves[side_index] = potential_moves; + + side_index += 1; + } + + quiet_moves +} + +const fn generate_pawn_quiet_moves( + square: Square, + bitboard: &mut BitBoard, + active_color: PieceColor, +) { + let forward_square = square.forward_by_one(active_color); + if forward_square.is_valid() { + bitboard.fill_square(forward_square); + } +} + +const fn generate_pawn_attack_moves( + square: Square, + bitboard: &mut BitBoard, + active_color: PieceColor, +) { + let right_diagonal_square = square.right_by_one().forward_by_one(active_color); + let left_diagonal_square = square.left_by_one().forward_by_one(active_color); + + if right_diagonal_square.is_valid() { + bitboard.fill_square(right_diagonal_square); + } + if left_diagonal_square.is_valid() { + bitboard.fill_square(left_diagonal_square); + } +} + +const fn generate_pawn_double_push_moves( + square: Square, + bitboard: &mut BitBoard, + active_color: PieceColor, +) { + if !square.is_pawn_start(active_color) { + return; + } + + let double_pushed_square = square + .forward_by_one(active_color) + .forward_by_one(active_color); + bitboard.fill_square(double_pushed_square); +} diff --git a/src/backend/movegen/mod.rs b/src/backend/movegen/mod.rs index a24810d..d612286 100644 --- a/src/backend/movegen/mod.rs +++ b/src/backend/movegen/mod.rs @@ -1,4 +1,5 @@ pub mod check_decider; pub mod compile_time; +pub mod moove; pub mod move_gen; -mod move_gen_non_sliders; +mod move_gen_pawn_util; diff --git a/src/backend/moove.rs b/src/backend/movegen/moove.rs similarity index 51% rename from src/backend/moove.rs rename to src/backend/movegen/moove.rs index 17d51cc..c862db9 100644 --- a/src/backend/moove.rs +++ b/src/backend/movegen/moove.rs @@ -1,19 +1,9 @@ -use crate::backend::square::Square; +use crate::backend::state::piece::PieceType; +use crate::backend::state::piece::PieceType::{Bishop, Knight, Queen, Rook}; +use crate::backend::state::square::Square; use getset::{CloneGetters, Setters}; use std::fmt::{Display, Formatter}; -/// Represents the various types of promotions that can occur in a game of chess. -/// -/// Has an additional `NONE` option to represent no promotion. -#[derive(Copy, Clone, Debug)] -pub enum PromotionType { - Rook, - Knight, - Bishop, - Queen, - None, -} - /// This encodes a single move. Sidenote: This is called Moove, since Move is a keyword in Rust... /// It knows where a piece moved from and where it moved to. /// Also stores to which piece a pawn promoted if one did at all. @@ -25,14 +15,14 @@ pub enum PromotionType { /// Sure, the move would be smaller, but accessing a variable would be slower, since it requires bit shifting etc. /// In the end it comes down to a trade-off between cache locality and number of instructions per read. /// 2. It would certainly make the code less readable. -#[derive(Copy, Clone, Debug, CloneGetters, Setters)] +#[derive(Copy, Clone, Debug, CloneGetters, Setters, Ord, Eq, PartialEq, PartialOrd)] pub struct Moove { #[getset(get_clone = "pub", set = "pub")] from: Square, #[getset(get_clone = "pub", set = "pub")] to: Square, #[getset(get_clone = "pub", set = "pub")] - promotion_type: PromotionType, + promotion_type: Option, } impl Moove { @@ -41,7 +31,35 @@ impl Moove { Moove { from, to, - promotion_type: PromotionType::None, + promotion_type: Option::None, + } + } + + /// This assumes that the moved piece is a pawn and only checks if the rank changed by 2. + pub fn is_double_pawn_push(&self) -> bool { + (self.from.rank() - self.to.rank()).abs() == 2 + } + + pub fn new_from_uci_notation(uci_notation: &str) -> Moove { + let from = Square::new_from_uci_notation(&uci_notation[0..2]); + let to = Square::new_from_uci_notation(&uci_notation[2..4]); + + let promotion_char = uci_notation.chars().nth(4); + let promotion_type = match promotion_char { + None => Option::None, + Some(char) => match char { + 'r' => Some(Rook), + 'n' => Some(Knight), + 'b' => Some(Bishop), + 'q' => Some(Queen), + _ => panic!("Invalid promotion type {:?}", uci_notation), + }, + }; + + Moove { + from, + to, + promotion_type, } } } @@ -54,11 +72,14 @@ impl Display for Moove { result.push_str(&self.from.to_string()); result.push_str(&self.to.to_string()); result.push_str(match self.promotion_type { - PromotionType::Rook => "r", - PromotionType::Knight => "n", - PromotionType::Bishop => "b", - PromotionType::Queen => "q", - PromotionType::None => "", + None => "", + Some(promotion_type) => match promotion_type { + Rook => "r", + Knight => "n", + Bishop => "b", + Queen => "q", + _ => panic!("Invalid promotion type {:?}", promotion_type), + }, }); write!(f, "{}", result) diff --git a/src/backend/movegen/move_gen.rs b/src/backend/movegen/move_gen.rs index ff2344c..dc0cdfc 100644 --- a/src/backend/movegen/move_gen.rs +++ b/src/backend/movegen/move_gen.rs @@ -1,13 +1,19 @@ -use crate::backend::moove::Moove; -use crate::backend::movegen::compile_time::move_cache_non_sliders::get_moves_cache_for_piece; -use crate::backend::movegen::move_gen_non_sliders::get_moves_for_non_slider_piece; -use crate::backend::piece::{Piece, PieceColor, PieceType}; -use crate::backend::state::bitboard::BitBoard; -use crate::backend::state::bitboard_manager::BitBoardManager; -use crate::backend::state::game_state::GameState; +use crate::backend::movegen::compile_time::move_cache_non_sliders::{ + KING_MOVES, KNIGHT_MOVES, PAWN_CAPTURE_MOVES, PAWN_QUIET_MOVES, +}; +use crate::backend::movegen::moove::Moove; +use crate::backend::movegen::move_gen_pawn_util::{ + create_pawn_capture_mask, get_double_pawn_push_moves, promotion_logic, +}; +use crate::backend::state::board::bitboard::BitBoard; +use crate::backend::state::board::bitboard_manager::BitBoardManager; +use crate::backend::state::game::game_state::GameState; +use crate::backend::state::piece::PieceType::{King, Knight, Pawn}; +use crate::backend::state::piece::{Piece, PieceColor}; +use crate::backend::state::square::Square; use crate::constants::SQUARES_AMOUNT; -/// Generates and returns all the valid moves for the current player's pieces +/// Generates and returns all the pseudo legal moves for the current player's pieces /// based on the provided game state. This is the entry point for the move generation. /// /// # Parameters @@ -19,47 +25,196 @@ use crate::constants::SQUARES_AMOUNT; /// /// * A `Vec` containing all the computed legal moves for the current player's /// pieces. Currently, this implementation only calculates the moves for the king piece. -pub fn get_moves(game_state: &GameState) -> Vec { +pub fn get_pseudo_legal_moves(game_state: &GameState) -> Vec { // Idea: have separate functions for each piece type // and then call them from here // each of them should iterate over their relevant bitboard let bitboard_manager = game_state.bit_board_manager(); // Bitboard containing all pieces of the active color. These block moves. - let friendly_pieces_bitboard = bitboard_manager.get_all_pieces_off(game_state.active_color()); + let friendly_pieces_bb = bitboard_manager.get_all_pieces_off(game_state.active_color()); + let enemy_pieces_bb = bitboard_manager.get_all_pieces_off(game_state.active_color().opposite()); let mut all_pseudo_legal_moves = Vec::new(); let active_color = game_state.active_color(); - // Iterate over all piece types. This might need adjustment for sliding pieces. - for piece_type in PieceType::get_all_types() { - // Get the moves cache for the current piece type. - let moves_cache = get_moves_cache_for_piece(piece_type); - // Get all moves for the current piece type. - get_moves_for_piece( - &mut all_pseudo_legal_moves, - piece_type, - active_color, - moves_cache, - bitboard_manager, - friendly_pieces_bitboard, - ); - } + gen_king_moves( + bitboard_manager, + friendly_pieces_bb, + &mut all_pseudo_legal_moves, + active_color, + ); + + gen_knight_moves( + bitboard_manager, + friendly_pieces_bb, + &mut all_pseudo_legal_moves, + active_color, + ); + + gen_pawn_moves( + game_state, + bitboard_manager, + friendly_pieces_bb, + enemy_pieces_bb, + &mut all_pseudo_legal_moves, + active_color, + ); all_pseudo_legal_moves } -/// Gets all pseudo legal moves for one piece. -fn get_moves_for_piece( +fn gen_king_moves( + bitboard_manager: &BitBoardManager, + friendly_pieces_bb: BitBoard, all_pseudo_legal_moves: &mut Vec, - piece_type: PieceType, active_color: PieceColor, - moves_cache: [BitBoard; SQUARES_AMOUNT], +) { + // King moves + let piece_bitboard = bitboard_manager.get_bitboard(Piece::new(King, active_color)); + let mut moves = + iterate_over_bitboard_for_non_slider(KING_MOVES, *piece_bitboard, friendly_pieces_bb); + all_pseudo_legal_moves.append(&mut moves); +} + +fn gen_knight_moves( bitboard_manager: &BitBoardManager, - friendly_pieces_bitboard: BitBoard, + friendly_pieces_bb: BitBoard, + all_pseudo_legal_moves: &mut Vec, + active_color: PieceColor, ) { - let piece = Piece::new(piece_type, active_color); - let piece_bitboard = bitboard_manager.get_bitboard(piece); + // Knight moves + let piece_bitboard = bitboard_manager.get_bitboard(Piece::new(Knight, active_color)); let mut moves = - get_moves_for_non_slider_piece(moves_cache, *piece_bitboard, friendly_pieces_bitboard); + iterate_over_bitboard_for_non_slider(KNIGHT_MOVES, *piece_bitboard, friendly_pieces_bb); + all_pseudo_legal_moves.append(&mut moves); +} + +fn gen_pawn_moves( + game_state: &GameState, + bitboard_manager: &BitBoardManager, + friendly_pieces_bb: BitBoard, + enemy_pieces_bb: BitBoard, + all_pseudo_legal_moves: &mut Vec, + active_color: PieceColor, +) { + // Quiet pawn moves + let mut moves = iterate_over_bitboard_for_non_slider( + PAWN_QUIET_MOVES[active_color as usize], + *bitboard_manager.get_bitboard(Piece::new(Pawn, active_color)), + friendly_pieces_bb | enemy_pieces_bb, + ); + promotion_logic(&mut moves); + all_pseudo_legal_moves.append(&mut moves); + + // Capture pawn moves + let mut moves = iterate_over_bitboard_for_non_slider( + PAWN_CAPTURE_MOVES[active_color as usize], + *bitboard_manager.get_bitboard(Piece::new(Pawn, active_color)), + create_pawn_capture_mask(game_state, enemy_pieces_bb), + ); + promotion_logic(&mut moves); + all_pseudo_legal_moves.append(&mut moves); + + // Double pawn push moves + let mut moves = get_double_pawn_push_moves( + bitboard_manager, + active_color, + friendly_pieces_bb | enemy_pieces_bb, + ); all_pseudo_legal_moves.append(&mut moves); } + +// ------------------------------------ +// Move gen core logic +// ------------------------------------ + +fn iterate_over_bitboard_for_non_slider( + moves_cache: [BitBoard; SQUARES_AMOUNT], + piece_bitboard: BitBoard, + mask_bitboard: BitBoard, +) -> Vec { + // PERF: Instead of creating a new vector for each piece, we could reuse the same vector and append to it. + let mut moves: Vec = Vec::new(); + + // Example: We are doing this for all knights. + // The `moves_cache` array would for each square contain all viable moves for a knight. + + // Assuming we are in the starting position as white `squares_with_piece` would be [B1, G1]. + let squares_with_piece = piece_bitboard.get_all_true_squares(); + // We then iterate over all these squares... + for square in squares_with_piece.iter() { + // ... get the potential moves for the piece on that square... + // SLIDER: (This only works this easily for non-sliders) + let potential_moves_bitboard = moves_cache[square.square_to_index()]; + //... and get all the moves for the piece on that square. + let mut moves_for_square = + convert_moves_and_mask_bb_to_moves(potential_moves_bitboard, mask_bitboard, *square); + // Lastly, we append them :) + moves.append(moves_for_square.as_mut()); + } + + moves +} + +fn convert_moves_and_mask_bb_to_moves( + potential_moves_bitboard: BitBoard, + mask_bitboard: BitBoard, + square: Square, +) -> Vec { + // SLIDER: I think the following code should also work for sliders. + + // `potential_moves_bitboard` is a BitBoard with a 1 at every square the piece can move to if nothing is blocking it. + // For a king at A1 it would look like this: + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // X X _ _ _ _ _ _ + // _ X _ _ _ _ _ _ + + // The friendly pieces bitboard might look like this if we have the king on A1 and a pawn on A2 and B2: + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // X X _ _ _ _ _ _ + // X _ _ _ _ _ _ _ + + // If we negate it, it represents all squares that are either empty or occupied by an enemy and looks like this: + // X X X X X X X X + // X X X X X X X X + // X X X X X X X X + // X X X X X X X X + // X X X X X X X X + // X X X X X X X X + // _ _ X X X X X X + // _ X X X X X X X + + // We can then and this negated bitboard with the potential moves' bitboard. + // This will result in a bitboard with a 1 at every square the piece can move to. + // Which looks like this: + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ _ _ _ _ _ _ _ + // _ X _ _ _ _ _ _ + let moves_bitboard = potential_moves_bitboard & !mask_bitboard; + + // Now take the resulting bitboard and convert all true squares to a list of squares. + let squares_we_can_move_to = moves_bitboard.get_all_true_squares(); + + // generate all the moves + let mut moves: Vec = Vec::with_capacity(squares_we_can_move_to.len()); + for to_square in squares_we_can_move_to { + moves.push(Moove::new(square, to_square)) + } + // Done :) + moves +} diff --git a/src/backend/movegen/move_gen_non_sliders.rs b/src/backend/movegen/move_gen_non_sliders.rs deleted file mode 100644 index 8516ac8..0000000 --- a/src/backend/movegen/move_gen_non_sliders.rs +++ /dev/null @@ -1,123 +0,0 @@ -use crate::backend::moove::Moove; -use crate::backend::square::Square; -use crate::backend::state::bitboard::BitBoard; -use crate::constants::SQUARES_AMOUNT; - -/// Calculates all possible moves for a given piece or set of pieces. -/// -/// # Parameters -/// - `moves_cache`: A precomputed array of potential moves for each square on the board, represented -/// as a [`BitBoard`] array. -/// - `piece_bitboard`: A [`BitBoard`] representing the position(s) of the piece(s) whose moves -/// need to be calculated. -/// - `friendly_pieces_bitboard`: A [`BitBoard`] representing the positions of friendly pieces -/// on the board. These pieces are used to determine which moves are blocked and thus not valid. -/// -/// # Returns -/// A `Vec` containing all the possible moves for the given piece(s). -pub fn get_moves_for_non_slider_piece( - moves_cache: [BitBoard; SQUARES_AMOUNT], - piece_bitboard: BitBoard, - friendly_pieces_bitboard: BitBoard, -) -> Vec { - // PERF: Instead of creating a new vector for each piece, we could reuse the same vector and append to it. - let mut moves: Vec = Vec::new(); - - // Example: We are doing this for all knights. - // The `moves_cache` array would for each square contain all viable moves for a knight. - - // Assuming we are in the starting position as white `squares_with_piece` would be [B1, G1]. - let squares_with_piece = piece_bitboard.get_all_true_squares(); - // We then iterate over all these squares... - for square in squares_with_piece.iter() { - // ... get the potential moves for the piece on that square... - // SLIDER: (This only works this easily for non-sliders) - let potential_moves_bitboard = moves_cache[square.square_to_index()]; - //... and get all the moves for the piece on that square. - let mut moves_for_square = - get_moves_for_square(potential_moves_bitboard, *square, friendly_pieces_bitboard); - // Lastly, we append them :) - moves.append(moves_for_square.as_mut()); - } - - moves -} - -/// Computes all possible moves for a given square and friendly piece positions. -/// SLIDER: Should also work for sliders. -/// -/// # Parameters -/// -/// - `moves_cache`: A precomputed array of `BitBoard`s where each entry represents -/// the potential moves for a piece on a specific square if no obstructions are present. -/// - `square`: The `Square` for which moves are being calculated. This represents -/// the current position of the piece. -/// - `friendly_pieces_bitboard`: A `BitBoard` representing positions of all friendly -/// pieces. Pieces located in these positions will block movement. -/// -/// # Returns -/// -/// A `Vec` of `Moove` structs that represent all legal moves -/// the piece on the provided square can make. -fn get_moves_for_square( - potential_moves_bitboard: BitBoard, - square: Square, - friendly_pieces_bitboard: BitBoard, -) -> Vec { - // SLIDER: I think the following code should also work for sliders. - - // `potential_moves_bitboard` is a BitBoard with a 1 at every square the piece can move to if nothing is blocking it. - // For a king at A1 it would look like this: - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // X X _ _ _ _ _ _ - // _ X _ _ _ _ _ _ - - // The friendly pieces bitboard might look like this if we have the king on A1 and a pawn on A2 and B2: - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // X X _ _ _ _ _ _ - // X _ _ _ _ _ _ _ - - // If we negate it, it represents all squares that are either empty or occupied by an enemy and looks like this: - // X X X X X X X X - // X X X X X X X X - // X X X X X X X X - // X X X X X X X X - // X X X X X X X X - // X X X X X X X X - // _ _ X X X X X X - // _ X X X X X X X - - // We can then and this negated bitboard with the potential moves' bitboard. - // This will result in a bitboard with a 1 at every square the piece can move to. - // Which looks like this: - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ _ _ _ _ _ _ _ - // _ X _ _ _ _ _ _ - let moves_bitboard = potential_moves_bitboard & !friendly_pieces_bitboard; - - // Now take the resulting bitboard and convert all true squares to a list of squares. - let squares_we_can_move_to = moves_bitboard.get_all_true_squares(); - - // generate all the moves - let mut moves: Vec = Vec::with_capacity(squares_we_can_move_to.len() + 20); - for to_square in squares_we_can_move_to { - moves.push(Moove::new(square, to_square)) - } - // Done :) - moves -} diff --git a/src/backend/movegen/move_gen_pawn_util.rs b/src/backend/movegen/move_gen_pawn_util.rs new file mode 100644 index 0000000..c269206 --- /dev/null +++ b/src/backend/movegen/move_gen_pawn_util.rs @@ -0,0 +1,92 @@ +use crate::backend::movegen::compile_time::move_cache_non_sliders::{ + PAWN_DOUBLE_PUSH_MOVES, PAWN_QUIET_MOVES, +}; +use crate::backend::movegen::moove::Moove; +use crate::backend::state::board::bitboard::BitBoard; +use crate::backend::state::board::bitboard_manager::BitBoardManager; +use crate::backend::state::game::game_state::GameState; +use crate::backend::state::piece::PieceType::{Pawn, Queen}; +use crate::backend::state::piece::{Piece, PieceColor, PieceType}; + +pub fn get_double_pawn_push_moves( + bitboard_manager: &BitBoardManager, + active_color: PieceColor, + all_pieces_bb: BitBoard, +) -> Vec { + let mut moves: Vec = Vec::new(); + + let mut pawn_bitboard = *bitboard_manager.get_bitboard(Piece::new(Pawn, active_color)); + + let starting_bitboard = match active_color { + PieceColor::White => BitBoard::new_from_rank(1), + PieceColor::Black => BitBoard::new_from_rank(6), + }; + + pawn_bitboard &= starting_bitboard; + + for square in pawn_bitboard.get_all_true_squares() { + let mut single_push_bb = PAWN_QUIET_MOVES[active_color as usize][square.square_to_index()]; + single_push_bb &= all_pieces_bb; + if !single_push_bb.is_empty() { + continue; + } + + let mut double_push_bb = + PAWN_DOUBLE_PUSH_MOVES[active_color as usize][square.square_to_index()]; + double_push_bb &= all_pieces_bb; + if !double_push_bb.is_empty() { + continue; + } + + let moove = Moove::new( + square, + square + .forward_by_one(active_color) + .forward_by_one(active_color), + ); + moves.push(moove); + } + + moves +} + +pub fn create_pawn_capture_mask( + game_state: &GameState, + enemy_pieces_bitboard: BitBoard, +) -> BitBoard { + // Capture pawn moves + let mut pawn_capture_mask = enemy_pieces_bitboard; + match game_state + .irreversible_data_stack() + .last() + .unwrap() + .en_passant_square() + { + None => {} + Some(ep_square) => { + pawn_capture_mask.fill_square(ep_square); + } + } + pawn_capture_mask = !pawn_capture_mask; + pawn_capture_mask +} + +pub fn promotion_logic(moves: &mut Vec) { + if moves.is_empty() { + return; + } + for index in (0..moves.len()).rev() { + let moove = moves[index]; + if moove.to().is_on_promotion_rank() { + for piece_type in PieceType::get_promotable_types() { + if piece_type == Queen { + moves[index].set_promotion_type(Some(Queen)); + continue; + } + let mut moove = moove; + moove.set_promotion_type(Some(piece_type)); + moves.push(moove); + } + } + } +} diff --git a/src/backend/perft.rs b/src/backend/perft.rs index 695f2ca..0213e19 100644 --- a/src/backend/perft.rs +++ b/src/backend/perft.rs @@ -1,6 +1,8 @@ use crate::backend::movegen::check_decider::is_in_check; -use crate::backend::movegen::move_gen::get_moves; -use crate::backend::state::game_state::GameState; +use crate::backend::movegen::moove::Moove; +use crate::backend::movegen::move_gen::get_pseudo_legal_moves; +use crate::backend::state::game::game_state::GameState; +use std::env::Args; pub fn perft(game_state: &mut GameState, depth: u8) -> u64 { // PERFT: I would love to make this work, but it does not atm. @@ -12,7 +14,7 @@ pub fn perft(game_state: &mut GameState, depth: u8) -> u64 { return 1; } - let moves = get_moves(game_state); + let moves = get_pseudo_legal_moves(game_state); let mut nodes = 0; for chess_move in moves { game_state.make_move(chess_move); @@ -29,12 +31,42 @@ pub fn perft(game_state: &mut GameState, depth: u8) -> u64 { nodes } +// --------------------------------------------- // +// PERFTREE DEBUGGING +// https://github.com/agausmann/perftree +// --------------------------------------------- // + +pub fn run_perftree_debug(mut input: Args) { + // Remove the first useless input. + input.next(); + + let depth = input.next().unwrap(); + let depth = depth.parse::().unwrap(); + + let fen = &input.next().unwrap(); + let mut game_state = GameState::new_from_fen(fen); + + for mooves in input { + // Code golfing + mooves + .split_whitespace() + .map(Moove::new_from_uci_notation) + .for_each(|moove| { + game_state.make_move(moove); + }); + } + + root_debug_perft(&mut game_state, depth as u8); +} + fn root_debug_perft(game_state: &mut GameState, depth: u8) -> u64 { // Total nodes searched. let mut nodes = 0; // Generate all root moves. - let moves = get_moves(game_state); + let mut moves = get_pseudo_legal_moves(game_state); + // Sort them in the same way as perftree does + moves.sort(); for chess_move in moves { game_state.make_move(chess_move); // If we are in check after making the move -> skip. @@ -60,6 +92,19 @@ fn root_debug_perft(game_state: &mut GameState, depth: u8) -> u64 { // TESTING // --------------------------------------------- // +// let mut game_state = GameState::new_parse_fen("1n2k3/8/8/8/8/8/8/1N2K3 w - - 0 1"); +// +// Start timer to calculate nodes per second. +// let now = Instant::now(); +// +// let nodes = root_debug_perft(&mut game_state, 9); +// let nodes = perft(&mut game_state, 9); +// +// let elapsed = now.elapsed(); +// println!("Nodes searched: {:?}", nodes); +// let nodes_per_second = nodes as f64 / elapsed.as_secs_f64(); +// println!("with {:?} nodes per second,", nodes_per_second); // 577620 nps in dev - 8.676.006 nps in release - 25.104.754 nps +// println!("took {:?}.", elapsed); #[cfg(test)] mod tests { use super::*; @@ -67,8 +112,7 @@ mod tests { #[test] fn test_perft_01() { - let mut game_state = - GameState::new_parse_fen("8/1n2k3/5n1n/2n5/4N3/2N5/1N2KN2/8 w - - 0 1"); + let mut game_state = GameState::new_from_fen("8/1n2k3/5n1n/2n5/4N3/2N5/1N2KN2/8 w - - 0 1"); let nodes = perft(&mut game_state, 4); assert_eq!(nodes, 472915); @@ -76,4 +120,77 @@ mod tests { // let nodes = perft(&mut game_state, 5); // assert_eq!(nodes, 11949411); } + + #[test] + fn test_perft_02() { + let mut game_state = GameState::new_from_fen("4k3/pppppppp/8/8/8/8/PPPPPPPP/4K3 w - - 0 1"); + let nodes = perft(&mut game_state, 4); + assert_eq!(nodes, 98766); + } + + #[test] + fn test_perft_03() { + let mut game_state = GameState::new_from_fen("4k3/ppp5/7p/8/8/8/PPP5/4K3 w - - 0 1"); + + let nodes = perft(&mut game_state, 4); + assert_eq!(nodes, 17684); + } + + #[test] + fn test_perft_04() { + let mut game_state = GameState::new_from_fen("4k3/ppp5/7p/8/8/8/PPP5/4K3 w - - 0 1"); + + let nodes = perft(&mut game_state, 5); + assert_eq!(nodes, 197056); + } + + #[test] + fn test_perft_05() { + let mut game_state = GameState::new_from_fen("7k/3p4/8/2P5/8/8/8/7K b - - 0 1"); + + let nodes = root_debug_perft(&mut game_state, 4); + assert_eq!(nodes, 896); + + let nodes = root_debug_perft(&mut game_state, 5); + assert_eq!(nodes, 6583); + } + + #[test] + fn test_perft_06() { + let mut game_state = GameState::new_from_fen("7k/8/8/8/8/2K5/2P5/8 w - - 0 1"); + + let nodes = root_debug_perft(&mut game_state, 1); + assert_eq!(nodes, 7); + } + + #[test] + fn test_perft_07() { + let mut game_state = GameState::new_from_fen("8/3P1k2/8/8/8/8/8/7K b - - 0 1"); + + let nodes = root_debug_perft(&mut game_state, 1); + assert_eq!(nodes, 7); + + let nodes = root_debug_perft(&mut game_state, 2); + assert_eq!(nodes, 49); + + // Missing slider logic atm + // let nodes = root_debug_perft(&mut game_state, 3); + // assert_eq!(nodes, 289); + } + + #[test] + fn test_perft_08() { + let mut game_state = GameState::new_from_fen("8/1ppP1k2/1n6/3P2P1/8/8/8/7K b - - 0 1"); + + let nodes = root_debug_perft(&mut game_state, 2); + assert_eq!(nodes, 117); + } + + #[test] + fn test_perft_09() { + let mut game_state = GameState::new_from_fen("7k/P7/8/8/8/8/8/7K w - - 0 1"); + + let nodes = root_debug_perft(&mut game_state, 1); + assert_eq!(nodes, 7); + } } diff --git a/src/backend/state/bitboard.rs b/src/backend/state/board/bitboard.rs similarity index 88% rename from src/backend/state/bitboard.rs rename to src/backend/state/board/bitboard.rs index 46fd8b3..e50376c 100644 --- a/src/backend/state/bitboard.rs +++ b/src/backend/state/board/bitboard.rs @@ -1,7 +1,7 @@ -use crate::backend::square::Square; +use crate::backend::state::square::Square; use crate::constants::{FILES_AMOUNT, SQUARES_AMOUNT}; use std::fmt::{Display, Formatter}; -use std::ops::{BitAnd, BitOr, BitOrAssign, BitXor, Not}; +use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, Not}; /// A struct that represents a BitBoard. /// Each bit in the `u64` value represents a specific position on the board. @@ -15,10 +15,24 @@ pub struct BitBoard { impl BitBoard { /// Creates a new `BitBoard` instance with an initial value of 0. + /// This cant be converted to a default variant cause i need it to be const. + #[allow(clippy::new_without_default)] pub const fn new() -> Self { BitBoard { value: 0 } } + pub const fn new_from_rank(rank: i8) -> Self { + let mut bitboard = BitBoard::new(); + + let mut file = 0; + while file < FILES_AMOUNT { + bitboard.fill_square(Square::new(file as i8, rank)); + file += 1; + } + + bitboard + } + /// Checks if the value of the current instance is empty or zero. /// /// # Returns @@ -129,6 +143,12 @@ impl BitAnd for BitBoard { } } +impl BitAndAssign for BitBoard { + fn bitand_assign(&mut self, rhs: Self) { + self.value &= rhs.value; + } +} + impl BitOr for BitBoard { type Output = Self; @@ -139,6 +159,12 @@ impl BitOr for BitBoard { } } +impl BitOrAssign for &mut BitBoard { + fn bitor_assign(&mut self, rhs: BitBoard) { + self.value |= rhs.value; + } +} + impl BitOrAssign for BitBoard { fn bitor_assign(&mut self, rhs: Self) { self.value |= rhs.value; diff --git a/src/backend/state/bitboard_manager.rs b/src/backend/state/board/bitboard_manager.rs similarity index 97% rename from src/backend/state/bitboard_manager.rs rename to src/backend/state/board/bitboard_manager.rs index a26bede..9478b91 100644 --- a/src/backend/state/bitboard_manager.rs +++ b/src/backend/state/board/bitboard_manager.rs @@ -1,6 +1,6 @@ -use crate::backend::piece::{Piece, PieceColor, PieceType}; -use crate::backend::square::Square; -use crate::backend::state::bitboard::BitBoard; +use crate::backend::state::board::bitboard::BitBoard; +use crate::backend::state::piece::{Piece, PieceColor, PieceType}; +use crate::backend::state::square::Square; use crate::constants::{PIECE_TYPE_COUNT, SIDES}; const WHITE_START_INDEX: usize = 0; diff --git a/src/backend/state/board/mod.rs b/src/backend/state/board/mod.rs new file mode 100644 index 0000000..7f7ff55 --- /dev/null +++ b/src/backend/state/board/mod.rs @@ -0,0 +1,2 @@ +pub mod bitboard; +pub mod bitboard_manager; diff --git a/src/backend/state/fen_parser.rs b/src/backend/state/game/fen_parser.rs similarity index 90% rename from src/backend/state/fen_parser.rs rename to src/backend/state/game/fen_parser.rs index 4445fdf..ca188e1 100644 --- a/src/backend/state/fen_parser.rs +++ b/src/backend/state/game/fen_parser.rs @@ -1,7 +1,7 @@ -use crate::backend::piece::{Piece, PieceColor, PieceType}; -use crate::backend::square::Square; -use crate::backend::state::bitboard_manager::BitBoardManager; -use crate::backend::state::irreversible_data::IrreversibleData; +use crate::backend::state::board::bitboard_manager::BitBoardManager; +use crate::backend::state::game::irreversible_data::IrreversibleData; +use crate::backend::state::piece::{Piece, PieceColor, PieceType}; +use crate::backend::state::square::Square; /// Parses a FEN (Forsyth-Edwards Notation) string and updates the corresponding game state. /// https://www.chessprogramming.org/Forsyth-Edwards_Notation @@ -42,20 +42,24 @@ pub fn parse_fen( } fn parse_en_passant(irreversible_data: &mut IrreversibleData, en_passant_file_string: &str) { + if en_passant_file_string == "-" { + irreversible_data.set_en_passant_square(None); + return; + } + + let mut en_passant_square = Square::new(0, 0); for char in en_passant_file_string.chars() { match char { - '-' => { - irreversible_data.set_en_passant_file(None); - } 'a'..='h' => { - irreversible_data.set_en_passant_file(Some(char.to_digit(10).unwrap() as i8 - 1)); + en_passant_square.set_file(char.to_digit(36).unwrap() as i8 - 10); } '3' | '6' => { - // I don't store this data as it is redundant. + en_passant_square.set_rank(char.to_digit(10).unwrap() as i8 - 1); } _ => panic!("Invalid character in FEN string"), } } + irreversible_data.set_en_passant_square(Some(en_passant_square)); } fn parse_castling_rights(irreversible_data: &mut IrreversibleData, castling_rights_string: &str) { diff --git a/src/backend/state/game_state.rs b/src/backend/state/game/game_state.rs similarity index 50% rename from src/backend/state/game_state.rs rename to src/backend/state/game/game_state.rs index 8a3943b..f532e4b 100644 --- a/src/backend/state/game_state.rs +++ b/src/backend/state/game/game_state.rs @@ -1,14 +1,14 @@ -use crate::backend::moove::Moove; -use crate::backend::piece::{Piece, PieceColor}; -use crate::backend::state::bitboard_manager::BitBoardManager; -use crate::backend::state::fen_parser::parse_fen; -use crate::backend::state::irreversible_data::IrreversibleData; +use crate::backend::movegen::moove::Moove; +use crate::backend::state::board::bitboard_manager::BitBoardManager; +use crate::backend::state::game::fen_parser::parse_fen; +use crate::backend::state::game::irreversible_data::IrreversibleData; +use crate::backend::state::piece::PieceType::Pawn; +use crate::backend::state::piece::{Piece, PieceColor}; use getset::{CloneGetters, Getters, MutGetters}; #[derive(Debug, Getters, MutGetters, CloneGetters)] pub struct GameState { - // TODO: Remove this mutable getter. It`s only needed for the tests atm. - #[getset(get = "pub", get_mut = "pub")] + #[getset(get = "pub")] bit_board_manager: BitBoardManager, #[getset(get = "pub")] irreversible_data_stack: Vec, @@ -24,13 +24,13 @@ impl GameState { GameState { bit_board_manager: BitBoardManager::new(), active_color: PieceColor::White, - irreversible_data_stack: vec![], + irreversible_data_stack: vec![IrreversibleData::new()], half_move_clock: 0, } } /// Creates a new `GameState` instance based on the fen string. - pub fn new_parse_fen(fen_string: &str) -> GameState { + pub fn new_from_fen(fen_string: &str) -> GameState { let mut bit_board_manager = BitBoardManager::new(); let mut active_color = PieceColor::White; let mut irreversible_data = IrreversibleData::new(); @@ -61,8 +61,35 @@ impl GameState { // The new irreversible data. let mut irreversible_data = IrreversibleData::new(); - // Get the bitboard for the piece that was captured if it exists. - let captured_piece = self.bit_board_manager.get_piece_at_square(moove.to()); + // Get the type of moved piece. + let moved_piece = self + .bit_board_manager + .get_piece_at_square(moove.from()) + .unwrap(); + + // Usually the piece something was captured on (if something was captured at all) the square we moved to... + let mut capture_square = moove.to(); + + // ... unless this is an en passant capture ... + let ep_square = self + .irreversible_data_stack + .last() + .unwrap() + .en_passant_square(); + + // if we moved a pawn and an en passant square exists + if let Some(ep_square) = ep_square + && moved_piece.piece_type() == Pawn + { + // and if we moved to the ep_square + if ep_square == moove.to() { + // update the captured square to the ep_square - offset + capture_square = moove.to().back_by_one(self.active_color); + } + } + + // Get the type of the captured piece if it exists. + let captured_piece = self.bit_board_manager.get_piece_at_square(capture_square); // Clear the square on the captured piece's bitboard if it exists. if let Some(captured_piece) = captured_piece { @@ -70,18 +97,32 @@ impl GameState { irreversible_data.set_captured_piece(Some(captured_piece.piece_type())); // Remove the captured piece from its bitboard. let captured_piece_bitboard = self.bit_board_manager.get_bitboard_mut(captured_piece); - captured_piece_bitboard.clear_square(moove.to()); + captured_piece_bitboard.clear_square(capture_square); + } + + // Check if a double pawn push was played and store the en passant file + if moved_piece.piece_type() == Pawn && moove.is_double_pawn_push() { + // the pawn starting square and one forward + let ep_square = moove.to().back_by_one(self.active_color); + + irreversible_data.set_en_passant_square(Some(ep_square)); } // Get the bitboard for the piece that was moved. - let moved_piece_bitboard = self - .bit_board_manager - .get_bitboard_for_piece_at_square_mut(moove.from()) - .unwrap(); + let mut moved_piece_bitboard = self.bit_board_manager.get_bitboard_mut(moved_piece); // Clear the square that the piece was moved from. moved_piece_bitboard.clear_square(moove.from()); + // Update the moved piece bb if it was a pawn promotion + match moove.promotion_type() { + None => {} + Some(promotion_type) => { + moved_piece_bitboard = self + .bit_board_manager + .get_bitboard_mut(Piece::new(promotion_type, self.active_color)); + } + } // Fill the square it moved to. moved_piece_bitboard.fill_square(moove.to()); @@ -95,30 +136,54 @@ impl GameState { /// /// # Arguments /// * `chess_move` - A `Moove` struct representing the chess move that needs to be reverted. - pub fn unmake_move(&mut self, chess_move: Moove) { + pub fn unmake_move(&mut self, moove: Moove) { // Flip whose turn it is. self.active_color = self.active_color.opposite(); + // Get the last irreversible data. + let irreversible_data = self.irreversible_data_stack.pop().unwrap(); // Get the bitboard for the piece that was moved. - let moved_piece_bitboard = self + let mut moved_piece_bitboard = self .bit_board_manager - .get_bitboard_for_piece_at_square_mut(chess_move.to()) + .get_bitboard_for_piece_at_square_mut(moove.to()) .unwrap(); - // Fill the square that the piece was moved from. - moved_piece_bitboard.fill_square(chess_move.from()); - // Clear the square it moved to. - moved_piece_bitboard.clear_square(chess_move.to()); + moved_piece_bitboard.clear_square(moove.to()); + + // Update the moved piece bb if it was a pawn promotion + match moove.promotion_type() { + None => {} + Some(_) => { + moved_piece_bitboard = self + .bit_board_manager + .get_bitboard_mut(Piece::new(Pawn, self.active_color)); + } + } - // Get the last irreversible data. - let irreversible_data = self.irreversible_data_stack.pop().unwrap(); + // Fill the square that the piece was moved from. + moved_piece_bitboard.fill_square(moove.from()); // If some piece was captured, put it back on the board. if let Some(captured_piece) = irreversible_data.captured_piece() { let piece = Piece::new(captured_piece, self.active_color.opposite()); let bitboard = self.bit_board_manager.get_bitboard_mut(piece); - bitboard.fill_square(chess_move.to()); + + let mut capture_square = moove.to(); + // en passant + let en_passant_square = self + .irreversible_data_stack + .last() + .unwrap() + .en_passant_square(); + + if let Some(en_passant_square) = en_passant_square + && moove.to() == en_passant_square + { + capture_square = moove.to().back_by_one(self.active_color); + } + + bitboard.fill_square(capture_square); } } } diff --git a/src/backend/state/irreversible_data.rs b/src/backend/state/game/irreversible_data.rs similarity index 87% rename from src/backend/state/irreversible_data.rs rename to src/backend/state/game/irreversible_data.rs index b8e1292..c825665 100644 --- a/src/backend/state/irreversible_data.rs +++ b/src/backend/state/game/irreversible_data.rs @@ -1,4 +1,5 @@ -use crate::backend::piece::PieceType; +use crate::backend::state::piece::PieceType; +use crate::backend::state::square::Square; use getset::{CloneGetters, Setters}; /// The `IrreversibleData` struct stores data that is irreversible. @@ -10,7 +11,7 @@ pub struct IrreversibleData { #[getset(get_clone = "pub", set = "pub")] captured_piece: Option, #[getset(get_clone = "pub", set = "pub")] - en_passant_file: Option, + en_passant_square: Option, #[getset(get_clone = "pub", set = "pub")] white_long_castle_rights: bool, #[getset(get_clone = "pub", set = "pub")] @@ -26,7 +27,7 @@ impl IrreversibleData { IrreversibleData { half_move_clock: 0, captured_piece: None, - en_passant_file: None, + en_passant_square: None, white_long_castle_rights: true, white_short_castle_rights: true, black_long_castle_rights: true, diff --git a/src/backend/state/game/mod.rs b/src/backend/state/game/mod.rs new file mode 100644 index 0000000..a318ad6 --- /dev/null +++ b/src/backend/state/game/mod.rs @@ -0,0 +1,3 @@ +mod fen_parser; +pub mod game_state; +pub mod irreversible_data; diff --git a/src/backend/state/mod.rs b/src/backend/state/mod.rs index 81b17a2..0fa6b33 100644 --- a/src/backend/state/mod.rs +++ b/src/backend/state/mod.rs @@ -1,5 +1,4 @@ -pub mod bitboard; -pub mod bitboard_manager; -mod fen_parser; -pub mod game_state; -mod irreversible_data; +pub(crate) mod board; +pub(crate) mod game; +pub mod piece; +pub mod square; diff --git a/src/backend/piece.rs b/src/backend/state/piece.rs similarity index 68% rename from src/backend/piece.rs rename to src/backend/state/piece.rs index 40e45fa..9d219b0 100644 --- a/src/backend/piece.rs +++ b/src/backend/state/piece.rs @@ -1,7 +1,7 @@ use getset::CloneGetters; /// Represents the different pieces. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Ord, Eq, PartialEq, PartialOrd)] pub enum PieceType { Pawn, Rook, @@ -13,8 +13,18 @@ pub enum PieceType { impl PieceType { /// Returns an array containing all piece types. For now, it's just Knight and King. - pub fn get_all_types() -> [PieceType; 2] { - [PieceType::Knight, PieceType::King] + pub fn get_all_types() -> [PieceType; 3] { + [PieceType::Knight, PieceType::King, PieceType::Pawn] + } + + /// Returns an array containing all piece types that a pawn can promote to. + pub fn get_promotable_types() -> [PieceType; 4] { + [ + PieceType::Rook, + PieceType::Knight, + PieceType::Bishop, + PieceType::Queen, + ] } } @@ -27,7 +37,7 @@ pub enum PieceColor { impl PieceColor { /// Returns the opposite color of the current `PieceColor`. - pub fn opposite(self) -> PieceColor { + pub const fn opposite(self) -> PieceColor { match self { PieceColor::White => PieceColor::Black, PieceColor::Black => PieceColor::White, diff --git a/src/backend/square.rs b/src/backend/state/square.rs similarity index 70% rename from src/backend/square.rs rename to src/backend/state/square.rs index 58f6549..62f1016 100644 --- a/src/backend/square.rs +++ b/src/backend/state/square.rs @@ -1,3 +1,4 @@ +use crate::backend::state::piece::PieceColor; use getset::Setters; use std::fmt::{Display, Formatter}; @@ -7,7 +8,7 @@ use std::fmt::{Display, Formatter}; /// /// To make it easier to memorize: file => the letter part, rank => the number part /// or put differently: file => vertical / x part, rank => horizontal / y part -#[derive(Copy, Clone, Debug, Setters)] +#[derive(Copy, Clone, Debug, Setters, Ord, Eq, PartialEq, PartialOrd)] pub struct Square { #[getset(set = "pub")] file: i8, @@ -21,6 +22,24 @@ impl Square { Self { file, rank } } + pub fn new_from_uci_notation(uci_notation: &str) -> Self { + let mut file = 0; + let mut rank = 0; + + for char in uci_notation.chars() { + match char { + 'a'..='h' => file = char.to_digit(36).unwrap() - 10, + '1'..='8' => rank = char.to_digit(10).unwrap() - 1, + _ => panic!("Invalid uci notation"), + } + } + + Square { + file: file as i8, + rank: rank as i8, + } + } + /// Converts a given index (i8) to a `Square` object. /// /// # Parameters @@ -84,6 +103,37 @@ impl Square { pub const fn rank(&self) -> i8 { self.rank } + + pub fn is_on_promotion_rank(&self) -> bool { + self.rank == 0 || self.rank == 7 + } + + pub const fn is_pawn_start(&self, color: PieceColor) -> bool { + match color { + PieceColor::White => self.rank == 1, + PieceColor::Black => self.rank == 6, + } + } + + // forward means the direction that the pawns travel in for that side + pub const fn forward_by_one(&self, color: PieceColor) -> Square { + match color { + PieceColor::White => Square::new(self.file, self.rank + 1), + PieceColor::Black => Square::new(self.file, self.rank - 1), + } + } + + pub const fn back_by_one(&self, color: PieceColor) -> Square { + self.forward_by_one(color.opposite()) + } + + pub const fn right_by_one(&self) -> Square { + Square::new(self.file + 1, self.rank) + } + + pub const fn left_by_one(&self) -> Square { + Square::new(self.file - 1, self.rank) + } } /// Turns a `Square` instance into a string like "a1". diff --git a/src/main.rs b/src/main.rs index 1b40269..4330ce3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,10 @@ -use crate::backend::perft::perft; -use backend::state::game_state::GameState; -use std::time::Instant; +use crate::backend::perft::run_perftree_debug; +use std::env; mod backend; mod constants; fn main() { - let mut game_state = GameState::new_parse_fen("1n2k3/8/8/8/8/8/8/1N2K3 w - - 0 1"); - - // Start timer to calculate nodes per second. - let now = Instant::now(); - - // let nodes = root_debug_perft(&mut game_state, 9); - let nodes = perft(&mut game_state, 9); - - let elapsed = now.elapsed(); - println!("Nodes searched: {:?}", nodes); - let nodes_per_second = nodes as f64 / elapsed.as_secs_f64(); - println!("with {:?} nodes per second,", nodes_per_second); // 577620 nps in dev - 8.676.006 nps in release - 25.104.754 nps - println!("took {:?}.", elapsed); + let args = env::args(); + run_perftree_debug(args); }