From 85c7c8eb528c9eca9d5264bd7f6d72141b0d57a6 Mon Sep 17 00:00:00 2001 From: Paul-Nicolas Madelaine Date: Wed, 26 Nov 2025 23:03:23 +0100 Subject: [PATCH] update board representation --- src/moves.rs | 4 +- src/position.rs | 175 ++++++++++++++++++++++++++++-------------------- src/setup.rs | 152 +++++++++++++++++++++-------------------- 3 files changed, 184 insertions(+), 147 deletions(-) diff --git a/src/moves.rs b/src/moves.rs index fafb6e8..14ba2c0 100644 --- a/src/moves.rs +++ b/src/moves.rs @@ -67,9 +67,9 @@ impl<'l> Move<'l> { /// Returns `true` if the move is a capture. #[inline] pub fn is_capture(self) -> bool { - let setup = self.position.as_setup(); + let [p_b_q, n_b_k, r_q_k, _] = self.position.as_setup().bitboards; self.raw.kind == MoveType::EnPassant - || !((setup.p_b_q | setup.n_b_k | setup.r_q_k) & self.to().bitboard()).is_empty() + || !((p_b_q | n_b_k | r_q_k) & self.to().bitboard()).is_empty() } /// Returns the type of piece that is captured, if the move is a capture. diff --git a/src/position.rs b/src/position.rs index bdfe247..4753dec 100644 --- a/src/position.rs +++ b/src/position.rs @@ -86,10 +86,12 @@ impl Position { #[inline] pub fn new() -> Self { Self(Setup { - w: Bitboard(0x000000000000FFFF), - p_b_q: Bitboard(0x2CFF00000000FF2C), - n_b_k: Bitboard(0x7600000000000076), - r_q_k: Bitboard(0x9900000000000099), + bitboards: [ + Bitboard(0x2CFF00000000FF2C), + Bitboard(0x7600000000000076), + Bitboard(0x9900000000000099), + Bitboard(0x000000000000FFFF), + ], turn: Color::White, castling_rights: CastlingRights::full(), en_passant: OptionSquare::None, @@ -227,29 +229,34 @@ impl Position { /// and `None` if the king in check. On success, this inverts the color to /// play and discards the en passant square. pub fn pass(&self) -> Option { - let setup = &self.0; - let blockers = setup.p_b_q | setup.n_b_k | setup.r_q_k; - let k = setup.n_b_k & setup.r_q_k; - let q = setup.p_b_q & setup.r_q_k; - let b = setup.p_b_q & setup.n_b_k; - let n = setup.n_b_k ^ b ^ k; - let r = setup.r_q_k ^ q ^ k; - let p = setup.p_b_q ^ b ^ q; - let (us, them) = match setup.turn { - Color::White => (setup.w, blockers ^ setup.w), - Color::Black => (blockers ^ setup.w, setup.w), + let Setup { + bitboards: [p_b_q, n_b_k, r_q_k, w], + turn, + en_passant: _, + castling_rights: _, + } = self.0; + let blockers = p_b_q | n_b_k | r_q_k; + let k = n_b_k & r_q_k; + let q = p_b_q & r_q_k; + let b = p_b_q & n_b_k; + let n = n_b_k ^ b ^ k; + let r = r_q_k ^ q ^ k; + let p = p_b_q ^ b ^ q; + let (us, them) = match turn { + Color::White => (w, blockers ^ w), + Color::Black => (blockers ^ w, w), }; let king_square = (us & k).next().unwrap(); let checkers = them - & (lookup::pawn_attack(setup.turn, king_square) & p + & (lookup::pawn_attack(turn, king_square) & p | lookup::knight(king_square) & n | lookup::bishop(king_square, blockers) & (q | b) | lookup::rook(king_square, blockers) & (q | r)); checkers.is_empty().then(|| { Self(Setup { - turn: !setup.turn, + turn: !turn, en_passant: OptionSquare::None, - ..setup.clone() + ..self.0.clone() }) }) } @@ -573,10 +580,7 @@ impl Position { let global_mask_to = moves.to(); let Setup { - w, - p_b_q, - n_b_k, - r_q_k, + bitboards: [p_b_q, n_b_k, r_q_k, w], turn, en_passant, castling_rights, @@ -934,8 +938,9 @@ impl Position { MoveType::EnPassant => { let direction = !setup.turn.forward(); let x = (unsafe { to.trans_unchecked(direction) }).bitboard(); - setup.p_b_q ^= x; - setup.w &= !x; + let [p_b_q, _, _, w] = &mut setup.bitboards; + *p_b_q ^= x; + *w &= !x; aux_play_pawn_advance(setup, Role::Pawn, from, to); } } @@ -945,90 +950,116 @@ impl Position { } #[inline] -fn aux_play_normal(setup: &mut Setup, role: Role, from: Square, target: Square) { +fn aux_play_normal( + Setup { + bitboards: [p_b_q, n_b_k, r_q_k, w], + turn, + en_passant: _, + castling_rights, + }: &mut Setup, + role: Role, + from: Square, + target: Square, +) { let from = from.bitboard(); let to = target.bitboard(); let mask = !(from | to); - setup.w &= mask; - setup.p_b_q &= mask; - setup.n_b_k &= mask; - setup.r_q_k &= mask; - if target == Square::from_coords(File::H, setup.turn.promotion_rank()) { - setup - .castling_rights - .unset(!setup.turn, CastlingSide::Short); + *p_b_q &= mask; + *n_b_k &= mask; + *r_q_k &= mask; + *w &= mask; + if target == Square::from_coords(File::H, turn.promotion_rank()) { + castling_rights.unset(!*turn, CastlingSide::Short); } - if target == Square::from_coords(File::A, setup.turn.promotion_rank()) { - setup.castling_rights.unset(!setup.turn, CastlingSide::Long); + if target == Square::from_coords(File::A, turn.promotion_rank()) { + castling_rights.unset(!*turn, CastlingSide::Long); } match role { Role::King => { - setup.n_b_k |= to; - setup.r_q_k |= to; - setup.castling_rights.unset(setup.turn, CastlingSide::Short); - setup.castling_rights.unset(setup.turn, CastlingSide::Long); + *n_b_k |= to; + *r_q_k |= to; + castling_rights.unset(*turn, CastlingSide::Short); + castling_rights.unset(*turn, CastlingSide::Long); } Role::Queen => { - setup.p_b_q |= to; - setup.r_q_k |= to; + *p_b_q |= to; + *r_q_k |= to; } Role::Bishop => { - setup.p_b_q |= to; - setup.n_b_k |= to; + *p_b_q |= to; + *n_b_k |= to; } Role::Knight => { - setup.n_b_k |= to; + *n_b_k |= to; } Role::Rook => { - setup.r_q_k |= to; - if from == Square::from_coords(File::H, setup.turn.home_rank()).bitboard() { - setup.castling_rights.unset(setup.turn, CastlingSide::Short); + *r_q_k |= to; + if from == Square::from_coords(File::H, turn.home_rank()).bitboard() { + castling_rights.unset(*turn, CastlingSide::Short); } - if from == Square::from_coords(File::A, setup.turn.home_rank()).bitboard() { - setup.castling_rights.unset(setup.turn, CastlingSide::Long); + if from == Square::from_coords(File::A, turn.home_rank()).bitboard() { + castling_rights.unset(*turn, CastlingSide::Long); } } Role::Pawn => { - setup.p_b_q |= to; + *p_b_q |= to; } } - if setup.turn == Color::White { - setup.w |= to; + if let Color::White = *turn { + *w |= to; } } #[inline] -fn aux_play_pawn_advance(setup: &mut Setup, role: Role, from: Square, to: Square) { +fn aux_play_pawn_advance( + Setup { + bitboards: [p_b_q, n_b_k, r_q_k, w], + turn, + en_passant: _, + castling_rights: _, + }: &mut Setup, + role: Role, + from: Square, + to: Square, +) { let from = from.bitboard(); let to = to.bitboard(); match role { Role::King => unreachable!(), Role::Queen => { - setup.p_b_q ^= from | to; - setup.r_q_k |= to; + *p_b_q ^= from | to; + *r_q_k |= to; } Role::Bishop => { - setup.p_b_q ^= from | to; - setup.n_b_k |= to; + *p_b_q ^= from | to; + *n_b_k |= to; } Role::Knight => { - setup.p_b_q ^= from; - setup.n_b_k |= to; + *p_b_q ^= from; + *n_b_k |= to; } Role::Rook => { - setup.p_b_q ^= from; - setup.r_q_k |= to; + *p_b_q ^= from; + *r_q_k |= to; } - Role::Pawn => setup.p_b_q ^= from | to, + Role::Pawn => *p_b_q ^= from | to, } - if setup.turn == Color::White { - setup.w ^= from | to; + if let Color::White = *turn { + *w ^= from | to; } } #[inline] -fn aux_play_castle(setup: &mut Setup, side: CastlingSide) { - let rank = setup.turn.home_rank(); +fn aux_play_castle( + Setup { + bitboards: [_, n_b_k, r_q_k, w], + turn, + en_passant: _, + castling_rights, + }: &mut Setup, + side: CastlingSide, +) { + let rank = turn.home_rank(); let (king_flip, rook_flip) = match side { CastlingSide::Short => ( Square::from_coords(File::E, rank).bitboard() @@ -1044,14 +1075,14 @@ fn aux_play_castle(setup: &mut Setup, side: CastlingSide) { ), }; - if setup.turn == Color::White { - setup.w ^= king_flip | rook_flip; + if let Color::White = *turn { + *w ^= king_flip | rook_flip; } - setup.n_b_k ^= king_flip; - setup.r_q_k ^= king_flip | rook_flip; + *n_b_k ^= king_flip; + *r_q_k ^= king_flip | rook_flip; - setup.castling_rights.unset(setup.turn, CastlingSide::Short); - setup.castling_rights.unset(setup.turn, CastlingSide::Long); + castling_rights.unset(*turn, CastlingSide::Short); + castling_rights.unset(*turn, CastlingSide::Long); } pub(crate) struct MateMoveGenImpl { diff --git a/src/setup.rs b/src/setup.rs index 2d70be0..5174d42 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -29,12 +29,8 @@ use alloc::string::String; /// [`from_text_record`](Setup::from_text_record) and [`to_text_record`](Setup::to_text_record). #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Setup { - pub(crate) w: Bitboard, - - pub(crate) p_b_q: Bitboard, - pub(crate) n_b_k: Bitboard, - pub(crate) r_q_k: Bitboard, - + /// bitboards = [pawns | bishops | queens, knights | bishops | kings, rooks | queens | kings, white] + pub(crate) bitboards: [Bitboard; 4], pub(crate) turn: Color, pub(crate) en_passant: OptionSquare, pub(crate) castling_rights: CastlingRights, @@ -45,10 +41,7 @@ impl Setup { #[inline] pub fn new() -> Self { Self { - w: Bitboard(0), - p_b_q: Bitboard(0), - n_b_k: Bitboard(0), - r_q_k: Bitboard(0), + bitboards: [Bitboard(0); 4], turn: Color::White, en_passant: OptionSquare::None, castling_rights: CastlingRights::new(), @@ -232,9 +225,9 @@ impl Setup { pub fn get(&self, square: Square) -> Option { Some(Piece { role: self.role(square)?, - color: match (self.w & square.bitboard()).is_empty() { - false => Color::White, - true => Color::Black, + color: match self.is_white(square) { + true => Color::White, + false => Color::Black, }, }) } @@ -261,39 +254,40 @@ impl Setup { #[inline] pub fn set(&mut self, square: Square, piece: Option) { let mask = !square.bitboard(); - self.w &= mask; - self.p_b_q &= mask; - self.n_b_k &= mask; - self.r_q_k &= mask; + let [p_b_q, n_b_k, r_q_k, w] = &mut self.bitboards; + *p_b_q &= mask; + *n_b_k &= mask; + *r_q_k &= mask; + *w &= mask; if let Some(piece) = piece { let to = square.bitboard(); - match piece.color { - Color::White => self.w |= to, - Color::Black => (), - } match piece.role { Role::Pawn => { - self.p_b_q |= to; + *p_b_q |= to; } Role::Knight => { - self.n_b_k |= to; + *n_b_k |= to; } Role::Bishop => { - self.p_b_q |= to; - self.n_b_k |= to; + *p_b_q |= to; + *n_b_k |= to; } Role::Rook => { - self.r_q_k |= to; + *r_q_k |= to; } Role::Queen => { - self.p_b_q |= to; - self.r_q_k |= to; + *p_b_q |= to; + *r_q_k |= to; } Role::King => { - self.n_b_k |= to; - self.r_q_k |= to; + *n_b_k |= to; + *r_q_k |= to; } } + match piece.color { + Color::White => *w |= to, + Color::Black => (), + } } } @@ -318,38 +312,16 @@ impl Setup { self.en_passant = OptionSquare::new(square); } - /// Returns, for each color and each type of piece, the bitboard of all squares occupied. + /// Returns the quad-bitboard representation of the board. + /// + /// This returns, in order: + /// - the squares occupied by the pawns, bishops and queens + /// - the squares occupied by the knights, bishops and kings + /// - the squares occupied by the rooks, queens and kings + /// - the squares occupied by the white pieces #[inline] - pub fn bitboards(&self) -> ByColor> { - let Self { - w, - p_b_q, - n_b_k, - r_q_k, - .. - } = self.clone(); - let k = n_b_k & r_q_k; - let q = p_b_q & r_q_k; - let b = p_b_q & n_b_k; - let n = n_b_k ^ b ^ k; - let r = r_q_k ^ q ^ k; - let p = p_b_q ^ b ^ q; - ByColor::with(|color| { - let mask = match color { - Color::White => w, - Color::Black => !w, - }; - ByRole::with(|role| { - mask & match role { - Role::Pawn => p, - Role::Knight => n, - Role::Bishop => b, - Role::Rook => r, - Role::Queen => q, - Role::King => k, - } - }) - }) + pub fn bitboards(&self) -> [Bitboard; 4] { + self.bitboards } /// Returns the mirror image of the position. @@ -362,11 +334,14 @@ impl Setup { /// is `rnbqkbnr/pppp1ppp/8/4p3/8/8/PPPPPPPP/RNBQKBNR w Qk e6`. #[inline] pub fn mirror(&self) -> Self { + let [p_b_q, n_b_k, r_q_k, w] = self.bitboards; Self { - w: (self.w ^ (self.p_b_q | self.n_b_k | self.r_q_k)).mirror(), - p_b_q: self.p_b_q.mirror(), - n_b_k: self.n_b_k.mirror(), - r_q_k: self.r_q_k.mirror(), + bitboards: [ + p_b_q.mirror(), + n_b_k.mirror(), + r_q_k.mirror(), + (w ^ (p_b_q | n_b_k | r_q_k)).mirror(), + ], turn: !self.turn, en_passant: self .en_passant @@ -381,13 +356,37 @@ impl Setup { /// /// Some unreachable positions are rejected, see [`IllegalPositionReason`] for details. pub fn into_position(self) -> Result { - debug_assert!((self.w & !(self.p_b_q | self.n_b_k | self.r_q_k)).is_empty()); - debug_assert!((self.p_b_q & self.n_b_k & self.r_q_k).is_empty()); - let mut reasons = IllegalPositionReasons::new(); - let blockers = self.p_b_q | self.n_b_k | self.r_q_k; - let pieces = self.bitboards(); + let [p_b_q, n_b_k, r_q_k, w] = self.bitboards; + + debug_assert!((!(p_b_q | n_b_k | r_q_k) & w).is_empty()); + debug_assert!((p_b_q & n_b_k & r_q_k).is_empty()); + + let k = n_b_k & r_q_k; + let q = p_b_q & r_q_k; + let b = p_b_q & n_b_k; + let n = n_b_k ^ b ^ k; + let r = r_q_k ^ q ^ k; + let p = p_b_q ^ b ^ q; + let pieces = ByColor::with(|color| { + let mask = match color { + Color::White => w, + Color::Black => !w, + }; + ByRole::with(|role| { + mask & match role { + Role::Pawn => p, + Role::Knight => n, + Role::Bishop => b, + Role::Rook => r, + Role::Queen => q, + Role::King => k, + } + }) + }); + + let blockers = p_b_q | n_b_k | r_q_k; if Color::all() .into_iter() @@ -508,14 +507,21 @@ impl Setup { #[inline] pub(crate) fn role(&self, square: Square) -> Option { let mask = square.bitboard(); - let bit0 = (self.p_b_q & mask).0 >> square as u8; - let bit1 = (self.n_b_k & mask).0 >> square as u8; - let bit2 = (self.r_q_k & mask).0 >> square as u8; + let [p_b_q, n_b_k, r_q_k, _] = self.bitboards; + let bit0 = (p_b_q & mask).0 >> square as u8; + let bit1 = (n_b_k & mask).0 >> square as u8; + let bit2 = (r_q_k & mask).0 >> square as u8; match bit0 | bit1 << 1 | bit2 << 2 { 0 => None, i => Some(unsafe { Role::transmute(i as u8) }), } } + + #[inline] + pub(crate) fn is_white(&self, square: Square) -> bool { + let [_, _, _, w] = self.bitboards; + w.contains(square) + } } pub(crate) struct TextRecord<'a>(pub(crate) &'a Setup);