diff --git a/src/backend/game_state/fen_parser.rs b/src/backend/game_state/fen_parser.rs index c0f3aec..4caf434 100644 --- a/src/backend/game_state/fen_parser.rs +++ b/src/backend/game_state/fen_parser.rs @@ -267,20 +267,16 @@ pub fn moove_from_uci_notation(uci_notation: &str) -> Moove { let to = square_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), + if let Some(char) = promotion_char { + let promotion_type = match char { + 'r' => (Rook), + 'n' => (Knight), + 'b' => (Bishop), + 'q' => (Queen), _ => panic!("Invalid promotion type {:?}", uci_notation), - }, + }; + return Moove::new_promotion(from, to, promotion_type) }; - Moove { - from, - to, - promotion_type, - } + Moove::new(from, to) } diff --git a/src/backend/game_state/state.rs b/src/backend/game_state/state.rs index b97fa22..b5bf361 100644 --- a/src/backend/game_state/state.rs +++ b/src/backend/game_state/state.rs @@ -73,10 +73,10 @@ impl State { let mut next_ir_data = IrreversibleData::new_from_previous_state(&self.irreversible_data); // Get the type of moved piece. - let moved_piece = self.bb_manager.get_piece_at_square(moove.from).unwrap(); + let moved_piece = self.bb_manager.get_piece_at_square(moove.get_from()).unwrap(); // Usually the square something was captured on (if something was captured at all) is the square we moved to... - let mut capture_square = moove.to; + let mut capture_square = moove.get_to(); if moved_piece == Pawn { // ... unless this is an en passant capture, we then need to update the capture square. next_state.make_move_ep_capture(moove, &mut capture_square); @@ -91,26 +91,26 @@ impl State { let mut moved_piece_bb = next_state.bb_manager.get_piece_bb_mut(moved_piece); // Clear the square that the piece was moved from. - moved_piece_bb.clear_square(moove.from); + moved_piece_bb.clear_square(moove.get_from()); // Update the moved piece bb if it was a pawn promotion - match moove.promotion_type { + match moove.get_promotion_type() { None => {} Some(promotion_type) => { moved_piece_bb = next_state.bb_manager.get_piece_bb_mut(promotion_type); } } // Fill the square it moved to. - moved_piece_bb.fill_square(moove.to); + moved_piece_bb.fill_square(moove.get_to()); next_state .bb_manager .get_all_pieces_bb_off_mut(self.active_color) - .fill_square(moove.to); + .fill_square(moove.get_to()); next_state .bb_manager .get_all_pieces_bb_off_mut(self.active_color) - .clear_square(moove.from); + .clear_square(moove.get_from()); // Some special king handling if moved_piece == King { @@ -120,7 +120,7 @@ impl State { next_state.make_move_castling_rights_on_rook_move_or_capture( &mut next_ir_data, moved_piece, - moove.from, + moove.get_from(), self.active_color, ); @@ -136,10 +136,10 @@ impl State { // if an en passant square exists if let Some(ep_square) = ep_square // and if we moved to the ep_square - && ep_square == moove.to + && ep_square == moove.get_to() { // update the captured square to the ep_square - offset - *capture_square = back_by_one(moove.to, self.active_color); + *capture_square = back_by_one(moove.get_to(), self.active_color); } } @@ -178,7 +178,7 @@ impl State { ) { if moove.is_double_pawn_push() { // the pawn starting square and one forward - let ep_square = back_by_one(moove.to, self.active_color); + let ep_square = back_by_one(moove.get_to(), self.active_color); irreversible_data.en_passant_square = Some(ep_square); } diff --git a/src/backend/movegen/move_gen_pawn.rs b/src/backend/movegen/move_gen_pawn.rs index 1f050bf..e39b160 100644 --- a/src/backend/movegen/move_gen_pawn.rs +++ b/src/backend/movegen/move_gen_pawn.rs @@ -172,8 +172,7 @@ fn pawn_bb_to_moves_promotion( let rank = get_rank(square); let offset_square = square_from_rank_and_file(rank + rank_offset, file + file_offset); for piece_type in PROMOTABLE_PIECES { - let mut moove = Moove::new(offset_square, square); - moove.promotion_type = Some(piece_type); + let moove = Moove::new_promotion(offset_square, square, piece_type); moves.push(moove); } } diff --git a/src/backend/types/moove.rs b/src/backend/types/moove.rs index fe9f7bb..2983334 100644 --- a/src/backend/types/moove.rs +++ b/src/backend/types/moove.rs @@ -1,5 +1,6 @@ +use std::cmp::Ordering; use std::fmt::{Display, Formatter}; -use crate::backend::types::piece::Piece; +use crate::backend::types::piece::{Piece, PROMOTABLE_PIECES}; use crate::backend::types::square::{get_file, square_to_string, Square}; #[derive(Copy, Clone)] @@ -18,46 +19,63 @@ impl CastleType { /// 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. /// -/// PERF: This could be squeezed into a bitfield like, for example, stockfish does: -/// Around line 370: https://github.com/official-stockfish/Stockfish/blob/master/src/types.h -/// I have not done this yet for two reasons: -/// 1. I'm not sure, without any benchmarks if it gains any performance. -/// 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. -/// -/// Alternatively, each Move could store a bitboard, with one bit set where we currently are and one where we are going to. -/// To "make" this move we then would only need to xor it with the bitboard for the piece. -#[derive(Copy, Clone, Debug, Ord, Eq, PartialEq, PartialOrd)] +/// Based on: https://github.com/official-stockfish/Stockfish/blob/master/src/types.h +/// It's stored in 16 bits +/// The first six are for the from index, the next six for the to index, leaving us with 4 bits remaining. +/// Two of those are used to encode the type of promotion piece. Either Rook, Knight, Bishop, or Queen +/// The next stores whether promotion has occurred +/// One bit and thus three possible data points free! +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Moove { - pub from: Square, - pub to: Square, - pub promotion_type: Option, + bitfield: u16 } impl Moove { /// Creates a new `Move` instance with 'promotion_type' set to 0. pub const fn new(from: Square, to: Square) -> Moove { - Moove { - from, - to, - promotion_type: Option::None, + let mut result = from as u16 | ((to as u16) << 6); + result |= 0; + Moove { bitfield: result } + } + + pub fn new_promotion(from: Square, to: Square, promotion_type: Piece) -> Moove { + Moove { bitfield: from as u16 | ((to as u16) << 6) | (promotion_type as u16) << 12 | 1 << 14 } + } + + pub fn get_from(&self) -> Square { + let mask = 0b0000_0000_0011_1111u16; + (self.bitfield & mask) as Square + } + + pub fn get_to(&self) -> Square { + let mask = 0b0000_1111_1100_0000u16; + ((self.bitfield & mask) >> 6) as Square + } + + pub fn get_promotion_type(&self) -> Option { + let promo_mask = 0b0100_0000_0000_0000u16; + if (self.bitfield & promo_mask) == 0 { + return None; } + + let type_mask = 0b0011_0000_0000_0000u16; + let piece_index = (self.bitfield & type_mask) >> 12; + + Some(PROMOTABLE_PIECES[piece_index as usize]) } /// 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.abs_diff(self.to) == 16 + self.get_from().abs_diff(self.get_to()) == 16 } /// This assumes that the moved piece is a king and only checks if the file changed by 2. - /// TODO: Not sure if this is bug free but i think so lol pub fn is_castle(&self) -> bool { - self.from.abs_diff(self.to) == 2 + self.get_from().abs_diff(self.get_to()) == 2 } pub fn get_castle_type(&self) -> CastleType { - if get_file(self.to) == 6 { + if get_file(self.get_to()) == 6 { CastleType::Short } else { CastleType::Long @@ -70,9 +88,9 @@ impl Display for Moove { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut result = String::new(); - result.push_str(&square_to_string(self.from)); - result.push_str(&square_to_string(self.to)); - result.push_str(match self.promotion_type { + result.push_str(&square_to_string(self.get_from())); + result.push_str(&square_to_string(self.get_to())); + result.push_str(match self.get_promotion_type() { None => "", Some(promotion_type) => match promotion_type { Piece::Rook => "r", @@ -86,3 +104,33 @@ impl Display for Moove { write!(f, "{}", result) } } + +/// Implements ordering. Needed to sort them when comparing with perftree output. +/// This should only be called during debugging, not for performance-critical operations. +impl PartialOrd for Moove { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } +} + +impl Ord for Moove { + fn cmp(&self, other: &Self) -> Ordering { + let own_from = self.get_from(); + let other_from = other.get_from(); + + let own_to = self.get_to(); + let other_to = self.get_to(); + + if own_from > other_from { + return Ordering::Greater; + } else if own_from < other_from { + return Ordering::Less; + } + + if own_to > other_to { + return Ordering::Greater; + } else if own_to < other_to { + return Ordering::Less; + } + Ordering::Equal + } +} + diff --git a/src/backend/types/piece.rs b/src/backend/types/piece.rs index e3ce9fa..cd47a99 100644 --- a/src/backend/types/piece.rs +++ b/src/backend/types/piece.rs @@ -1,21 +1,25 @@ /// Represents the different pieces. +/// +/// Dear god, this is a bit hacky. +/// Do not change the order of this to differ from the PROMOTABLE_PIECES list. #[derive(Copy, Clone, Debug, Ord, Eq, PartialEq, PartialOrd)] pub enum Piece { - Pawn, Rook, Knight, Bishop, Queen, King, + Pawn, } +/// The order of this has to match with the Piece enum. pub const ALL_PIECES: [Piece; 6] = [ - Piece::Pawn, Piece::Rook, Piece::Knight, Piece::Bishop, Piece::Queen, Piece::King, + Piece::Pawn, ]; pub const PROMOTABLE_PIECES: [Piece; 4] = [Piece::Rook, Piece::Knight, Piece::Bishop, Piece::Queen];