From e547d7596f40ecaea67e70973b38fc793cf77301 Mon Sep 17 00:00:00 2001 From: Paul-Nicolas Madelaine Date: Wed, 22 Oct 2025 02:06:15 +0200 Subject: [PATCH] setup readers --- src/position.rs | 91 ++++++++------- src/setup.rs | 295 ++++++++++++++++++++++++------------------------ 2 files changed, 192 insertions(+), 194 deletions(-) diff --git a/src/position.rs b/src/position.rs index 523784a..c61da04 100644 --- a/src/position.rs +++ b/src/position.rs @@ -59,14 +59,6 @@ pub struct Position(Setup); const MAX_LEGAL_MOVES: usize = 218; -impl std::fmt::Debug for Position { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f.debug_tuple("Position") - .field(&self.as_setup().to_text_record()) - .finish() - } -} - impl Position { /// Returns the initial position of a chess game. /// @@ -100,18 +92,6 @@ impl Position { .and_then(|setup| setup.try_into().ok()) } - /// Returns the text record of the position. - /// - /// This is a shortcut for: - /// ``` - /// # |position: eschac::position::Position| { - /// position.as_setup().to_text_record() - /// # }; - #[inline] - pub fn to_text_record(&self) -> String { - self.as_setup().to_text_record() - } - /// Returns all the legal moves on the position. #[inline] pub fn legal_moves<'l>(&'l self) -> Moves<'l> { @@ -179,33 +159,6 @@ impl Position { self.0.en_passant = OptionSquare::None; } - /// Returns the occupancy of a square. - #[inline] - pub fn get(&self, square: Square) -> Option { - self.0.get(square) - } - - /// Returns the color whose turn it is to play. - #[inline] - pub fn turn(&self) -> Color { - self.0.turn() - } - - /// Returns `true` if castling is available for the given color and side. - #[inline] - pub fn castling_rights(&self, color: Color, side: CastlingSide) -> bool { - self.0.castling_rights(color, side) - } - - /// Returns the en passant target square if it exists. - /// - /// Note that if an en passant target square exists, it does not mean that taking en passant is - /// legal or even pseudo-legal. - #[inline] - pub fn en_passant_target_square(&self) -> Option { - self.0.en_passant_target_square() - } - /// Discards the castling rights for the given color and side. #[inline] pub fn remove_castling_rights(&mut self, color: Color, side: CastlingSide) { @@ -283,7 +236,9 @@ impl Position { Some(depth) => aux(self, depth), } } +} +impl Position { pub(crate) fn move_from_uci<'l>(&'l self, uci: UciMove) -> Result, InvalidUciMove> { struct VisitorImpl { role: Role, @@ -487,6 +442,48 @@ impl Position { } } +impl Position { + #[inline] + pub fn to_text_record(&self) -> String { + self.0.to_text_record() + } + #[inline] + pub fn write_text_record(&self, w: &mut W) -> std::fmt::Result + where + W: std::fmt::Write, + { + self.0.write_text_record(w) + } + #[inline] + pub fn get(&self, square: Square) -> Option { + self.0.get(square) + } + #[inline] + pub fn turn(&self) -> Color { + self.0.turn() + } + #[inline] + pub fn castling_rights(&self, color: Color, side: CastlingSide) -> bool { + self.0.castling_rights(color, side) + } + #[inline] + pub fn en_passant_target_square(&self) -> Option { + self.0.en_passant_target_square() + } + #[inline] + pub fn pieces(&self) -> ByColor> { + self.0.pieces() + } +} + +impl std::fmt::Debug for Position { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + f.debug_tuple("Position") + .field(&self.to_text_record()) + .finish() + } +} + /// A legal move. #[derive(Clone, Copy)] pub struct Move<'l> { diff --git a/src/setup.rs b/src/setup.rs index f2fc478..520404e 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -140,119 +140,6 @@ impl Setup { Ok(setup) } - /// Returns the text record of the position. - pub fn to_text_record(&self) -> String { - let mut record = String::with_capacity(81); - self.write_text_record(&mut record).unwrap(); - record - } - - /// Writes the text record of the position. - pub fn write_text_record(&self, w: &mut W) -> std::fmt::Result - where - W: std::fmt::Write, - { - for rank in Rank::all().into_iter().rev() { - let mut count = 0; - for file in File::all() { - match self.get(Square::from_coords(file, rank)) { - Some(piece) => { - if count > 0 { - w.write_char(char::from_u32('0' as u32 + count).unwrap())?; - } - count = 0; - w.write_char(match piece.color { - Color::White => piece.role.to_char_uppercase(), - Color::Black => piece.role.to_char_lowercase(), - })?; - } - None => { - count += 1; - } - } - } - if count > 0 { - w.write_char(char::from_u32('0' as u32 + count).unwrap())?; - } - if rank != Rank::First { - w.write_char('/')?; - } - } - - w.write_char(' ')?; - - w.write_char(match self.turn { - Color::White => 'w', - Color::Black => 'b', - })?; - - w.write_char(' ')?; - - let mut no_castle_available = true; - if self.castling_rights(Color::White, CastlingSide::Short) { - w.write_char('K')?; - no_castle_available = false; - } - if self.castling_rights(Color::White, CastlingSide::Long) { - w.write_char('Q')?; - no_castle_available = false; - } - if self.castling_rights(Color::Black, CastlingSide::Short) { - w.write_char('k')?; - no_castle_available = false; - } - if self.castling_rights(Color::Black, CastlingSide::Long) { - w.write_char('q')?; - no_castle_available = false; - } - if no_castle_available { - w.write_char('-')?; - } - - w.write_char(' ')?; - - match self.en_passant.try_into_square() { - Some(sq) => { - w.write_str(sq.to_str())?; - } - None => { - w.write_char('-')?; - } - } - - Ok(()) - } - - /// Returns the occupancy of a square. - #[inline] - pub fn get(&self, square: Square) -> Option { - Some(Piece { - role: self.get_role(square)?, - color: match (self.w & square.bitboard()).is_empty() { - false => Color::White, - true => Color::Black, - }, - }) - } - - /// Returns the color to play. - #[inline] - pub fn turn(&self) -> Color { - self.turn - } - - /// Returns `true` if castling is available for the given color and side. - #[inline] - pub fn castling_rights(&self, color: Color, side: CastlingSide) -> bool { - self.castling_rights.get(color, side) - } - - /// Returns the optional en passant target square. - #[inline] - pub fn en_passant_target_square(&self) -> Option { - self.en_passant.try_into_square() - } - /// Sets the occupancy of a square. #[inline] pub fn set(&mut self, square: Square, piece: Option) { @@ -339,40 +226,6 @@ impl Setup { } } - /// Returns the bitboard of each kind of piece. - #[inline] - pub fn pieces(&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::new(|color| { - let mask = match color { - Color::White => w, - Color::Black => !w, - }; - ByRole::new(|kind| { - mask & match kind { - Role::Pawn => p, - Role::Knight => n, - Role::Bishop => b, - Role::Rook => r, - Role::Queen => q, - Role::King => k, - } - }) - }) - } - /// Tries to validate the position, i.e. converting it to a [`Position`]. /// /// See [`IllegalPositionReason`] for details. @@ -514,6 +367,154 @@ impl Setup { } } +impl Setup { + #[inline] + pub fn to_text_record(&self) -> String { + let mut record = String::with_capacity(81); + self.write_text_record(&mut record).unwrap(); + record + } + + pub fn write_text_record(&self, w: &mut W) -> std::fmt::Result + where + W: std::fmt::Write, + { + for rank in Rank::all().into_iter().rev() { + let mut count = 0; + for file in File::all() { + match self.get(Square::from_coords(file, rank)) { + Some(piece) => { + if count > 0 { + w.write_char(char::from_u32('0' as u32 + count).unwrap())?; + } + count = 0; + w.write_char(match piece.color { + Color::White => piece.role.to_char_uppercase(), + Color::Black => piece.role.to_char_lowercase(), + })?; + } + None => { + count += 1; + } + } + } + if count > 0 { + w.write_char(char::from_u32('0' as u32 + count).unwrap())?; + } + if rank != Rank::First { + w.write_char('/')?; + } + } + + w.write_char(' ')?; + + w.write_char(match self.turn { + Color::White => 'w', + Color::Black => 'b', + })?; + + w.write_char(' ')?; + + let mut no_castle_available = true; + if self.castling_rights(Color::White, CastlingSide::Short) { + w.write_char('K')?; + no_castle_available = false; + } + if self.castling_rights(Color::White, CastlingSide::Long) { + w.write_char('Q')?; + no_castle_available = false; + } + if self.castling_rights(Color::Black, CastlingSide::Short) { + w.write_char('k')?; + no_castle_available = false; + } + if self.castling_rights(Color::Black, CastlingSide::Long) { + w.write_char('q')?; + no_castle_available = false; + } + if no_castle_available { + w.write_char('-')?; + } + + w.write_char(' ')?; + + match self.en_passant.try_into_square() { + Some(sq) => { + w.write_str(sq.to_str())?; + } + None => { + w.write_char('-')?; + } + } + + Ok(()) + } + + /// Returns the occupancy of a square. + #[inline] + pub fn get(&self, square: Square) -> Option { + Some(Piece { + role: self.get_role(square)?, + color: match (self.w & square.bitboard()).is_empty() { + false => Color::White, + true => Color::Black, + }, + }) + } + + /// Returns the color to play. + #[inline] + pub fn turn(&self) -> Color { + self.turn + } + + /// Returns `true` if castling is available for the given color and side. + #[inline] + pub fn castling_rights(&self, color: Color, side: CastlingSide) -> bool { + self.castling_rights.get(color, side) + } + + /// Returns the optional en passant target square. + #[inline] + pub fn en_passant_target_square(&self) -> Option { + self.en_passant.try_into_square() + } + + /// Returns the bitboard of each kind of piece. + #[inline] + pub fn pieces(&self) -> ByColor> { + let Setup { + 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::new(|color| { + let mask = match color { + Color::White => w, + Color::Black => !w, + }; + ByRole::new(|kind| { + mask & match kind { + Role::Pawn => p, + Role::Knight => n, + Role::Bishop => b, + Role::Rook => r, + Role::Queen => q, + Role::King => k, + } + }) + }) + } +} + impl TryFrom for Position { type Error = IllegalPosition; fn try_from(setup: Setup) -> Result {