From 263bb0ddb60cd3fab03ad8701217213aa4324eab Mon Sep 17 00:00:00 2001 From: Paul-Nicolas Madelaine Date: Mon, 3 Nov 2025 16:20:47 +0100 Subject: [PATCH] new constructor for squares --- src/bitboard.rs | 14 +++++++------- src/board.rs | 34 ++++++++++++++++++++-------------- src/lookup.rs | 2 +- src/position.rs | 30 +++++++++++++++++------------- src/san.rs | 2 +- src/setup.rs | 17 +++++++++++------ src/uci.rs | 4 ++-- 7 files changed, 59 insertions(+), 44 deletions(-) diff --git a/src/bitboard.rs b/src/bitboard.rs index b4c18d3..b2d728b 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -22,7 +22,7 @@ impl Bitboard { let mask = self.0; match mask { 0 => None, - _ => Some(unsafe { Square::transmute(mask.trailing_zeros() as u8) }), + _ => Some(unsafe { Square::new_unchecked(mask.trailing_zeros() as u8) }), } } @@ -31,9 +31,9 @@ impl Bitboard { let mask = self.0; match mask { 0 => None, - _ => { - Some(unsafe { Square::transmute(63_u8.unchecked_sub(mask.leading_zeros() as u8)) }) - } + _ => Some(unsafe { + Square::new_unchecked(63_u8.unchecked_sub(mask.leading_zeros() as u8)) + }), } } @@ -42,7 +42,7 @@ impl Bitboard { let Self(ref mut mask) = self; let square = match mask { 0 => None, - _ => Some(unsafe { Square::transmute(mask.trailing_zeros() as u8) }), + _ => Some(unsafe { Square::new_unchecked(mask.trailing_zeros() as u8) }), }; *mask &= mask.wrapping_sub(1); square @@ -145,7 +145,7 @@ impl Iterator for Bitboard { { let mut mask = self.0; while mask != 0 { - f(unsafe { Square::transmute(mask.trailing_zeros() as u8) }); + f(unsafe { Square::new_unchecked(mask.trailing_zeros() as u8) }); mask &= mask.wrapping_sub(1); } } @@ -159,7 +159,7 @@ impl Iterator for Bitboard { let mut acc = init; while mask != 0 { acc = f(acc, unsafe { - Square::transmute(mask.trailing_zeros() as u8) + Square::new_unchecked(mask.trailing_zeros() as u8) }); mask &= mask.wrapping_sub(1); } diff --git a/src/board.rs b/src/board.rs index 9833d61..1794cdd 100644 --- a/src/board.rs +++ b/src/board.rs @@ -264,17 +264,26 @@ impl Square { ] } - pub(crate) const fn from_index(index: u8) -> Option { + pub const fn new(index: u8) -> Option { if index < 64 { - Some(unsafe { Self::transmute(index) }) + Some(unsafe { Self::new_unchecked(index) }) } else { None } } + /// ## Safety + /// + /// The caller must ensure that `index < 64`. #[inline] - pub fn new(file: File, rank: Rank) -> Self { - unsafe { Self::transmute(((rank as u8) << 3) | file as u8) } + pub const unsafe fn new_unchecked(index: u8) -> Self { + debug_assert!(index < 64); + std::mem::transmute(index) + } + + #[inline] + pub fn from_coords(file: File, rank: Rank) -> Self { + unsafe { Self::new_unchecked(((rank as u8) << 3) | file as u8) } } #[inline] @@ -290,7 +299,7 @@ impl Square { #[inline] pub fn mirror(self) -> Self { let sq = self as u8; - unsafe { Self::transmute(sq & 0b000111 | (!sq & 0b111000)) } + unsafe { Self::new_unchecked(sq & 0b000111 | (!sq & 0b111000)) } } #[inline] @@ -324,7 +333,10 @@ impl Square { #[inline] pub(crate) fn from_ascii(s: &[u8; 2]) -> Option { let [f, r] = *s; - Some(Self::new(File::from_ascii(f)?, Rank::from_ascii(r)?)) + Some(Self::from_coords( + File::from_ascii(f)?, + Rank::from_ascii(r)?, + )) } #[inline] @@ -342,7 +354,7 @@ impl Square { debug_assert!(self.check_trans(direction)); let i = self as u8; unsafe { - Self::transmute(match direction { + Self::new_unchecked(match direction { Direction::East => i.unchecked_add(1), Direction::NorthEast => i.unchecked_add(9), Direction::North => i.unchecked_add(8), @@ -371,12 +383,6 @@ impl Square { Direction::West => file > 0, } } - - #[inline] - pub(crate) const unsafe fn transmute(value: u8) -> Self { - debug_assert!(value < 64); - std::mem::transmute(value) - } } impl std::fmt::Display for Square { @@ -434,7 +440,7 @@ impl OptionSquare { unsafe { match self { Self::None => None, - _ => Some(Square::transmute(self as u8)), + _ => Some(Square::new_unchecked(self as u8)), } } } diff --git a/src/lookup.rs b/src/lookup.rs index 408c750..743ee42 100644 --- a/src/lookup.rs +++ b/src/lookup.rs @@ -36,7 +36,7 @@ macro_rules! by_square { let mut $sq: u8 = 0; while $sq < 64 { res[$sq as usize] = { - let $sq = Square::from_index($sq).unwrap(); + let $sq = Square::new($sq).unwrap(); $e }; $sq += 1; diff --git a/src/position.rs b/src/position.rs index c492edf..0987054 100644 --- a/src/position.rs +++ b/src/position.rs @@ -420,14 +420,14 @@ impl Position { SanInner::Castle(CastlingSide::Short) => ( Role::King, Role::King, - Square::new(File::E, self.turn().home_rank()).bitboard(), - Square::new(File::G, self.turn().home_rank()).bitboard(), + Square::from_coords(File::E, self.turn().home_rank()).bitboard(), + Square::from_coords(File::G, self.turn().home_rank()).bitboard(), ), SanInner::Castle(CastlingSide::Long) => ( Role::King, Role::King, - Square::new(File::E, self.turn().home_rank()).bitboard(), - Square::new(File::C, self.turn().home_rank()).bitboard(), + Square::from_coords(File::E, self.turn().home_rank()).bitboard(), + Square::from_coords(File::C, self.turn().home_rank()).bitboard(), ), SanInner::Normal { role, @@ -1278,7 +1278,7 @@ impl Position { MoveType::PawnAttackPromotion => aux_play_normal(setup, role, from, to), MoveType::PawnDoubleAdvance => { aux_play_pawn_advance(setup, Role::Pawn, from, to); - setup.en_passant = OptionSquare::new(Some(Square::new( + setup.en_passant = OptionSquare::new(Some(Square::from_coords( from.file(), match setup.turn { Color::White => Rank::Third, @@ -1308,12 +1308,12 @@ fn aux_play_normal(setup: &mut Setup, role: Role, from: Square, target: Square) setup.p_b_q &= mask; setup.n_b_k &= mask; setup.r_q_k &= mask; - if target == Square::new(File::H, setup.turn.promotion_rank()) { + if target == Square::from_coords(File::H, setup.turn.promotion_rank()) { setup .castling_rights .unset(!setup.turn, CastlingSide::Short); } - if target == Square::new(File::A, setup.turn.promotion_rank()) { + if target == Square::from_coords(File::A, setup.turn.promotion_rank()) { setup.castling_rights.unset(!setup.turn, CastlingSide::Long); } match role { @@ -1336,10 +1336,10 @@ fn aux_play_normal(setup: &mut Setup, role: Role, from: Square, target: Square) } Role::Rook => { setup.r_q_k |= to; - if from == Square::new(File::H, setup.turn.home_rank()).bitboard() { + if from == Square::from_coords(File::H, setup.turn.home_rank()).bitboard() { setup.castling_rights.unset(setup.turn, CastlingSide::Short); } - if from == Square::new(File::A, setup.turn.home_rank()).bitboard() { + if from == Square::from_coords(File::A, setup.turn.home_rank()).bitboard() { setup.castling_rights.unset(setup.turn, CastlingSide::Long); } } @@ -1386,12 +1386,16 @@ fn aux_play_castle(setup: &mut Setup, side: CastlingSide) { let rank = setup.turn.home_rank(); let (king_flip, rook_flip) = match side { CastlingSide::Short => ( - Square::new(File::E, rank).bitboard() | Square::new(File::G, rank).bitboard(), - Square::new(File::H, rank).bitboard() | Square::new(File::F, rank).bitboard(), + Square::from_coords(File::E, rank).bitboard() + | Square::from_coords(File::G, rank).bitboard(), + Square::from_coords(File::H, rank).bitboard() + | Square::from_coords(File::F, rank).bitboard(), ), CastlingSide::Long => ( - Square::new(File::E, rank).bitboard() | Square::new(File::C, rank).bitboard(), - Square::new(File::A, rank).bitboard() | Square::new(File::D, rank).bitboard(), + Square::from_coords(File::E, rank).bitboard() + | Square::from_coords(File::C, rank).bitboard(), + Square::from_coords(File::A, rank).bitboard() + | Square::from_coords(File::D, rank).bitboard(), ), }; diff --git a/src/san.rs b/src/san.rs index 2cf6491..1f4459d 100644 --- a/src/san.rs +++ b/src/san.rs @@ -179,7 +179,7 @@ impl San { } let target_rank = Rank::from_ascii(cur)?; let target_file = File::from_ascii(r.next()?)?; - let target = Square::new(target_file, target_rank); + let target = Square::from_coords(target_file, target_rank); let mut cur = r.next(); let capture = cur == Some(b'x'); if capture { diff --git a/src/setup.rs b/src/setup.rs index ac06172..eb30431 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -91,7 +91,9 @@ impl Setup { }; (file < 8).then_some(())?; setup.set( - unsafe { Square::new(File::transmute(file), Rank::transmute(rank)) }, + unsafe { + Square::from_coords(File::transmute(file), Rank::transmute(rank)) + }, Some(Piece { role, color }), ); file += 1; @@ -126,7 +128,7 @@ impl Setup { match s.next()? { b'-' => (), - file => setup.set_en_passant_target_square(Some(Square::new( + file => setup.set_en_passant_target_square(Some(Square::from_coords( File::from_ascii(file)?, Rank::from_ascii(s.next()?)?, ))), @@ -154,7 +156,7 @@ impl Setup { for rank in Rank::all().into_iter().rev() { let mut count = 0; for file in File::all() { - match self.get(Square::new(file, rank)) { + match self.get(Square::from_coords(file, rank)) { Some(piece) => { if count > 0 { w.write_char(char::from_u32('0' as u32 + count).unwrap())?; @@ -411,11 +413,14 @@ impl Setup { && !(pieces .get(color) .get(Role::King) - .contains(Square::new(File::E, color.home_rank())) + .contains(Square::from_coords(File::E, color.home_rank())) && pieces .get(color) .get(Role::Rook) - .contains(Square::new(side.rook_origin_file(), color.home_rank()))) + .contains(Square::from_coords( + side.rook_origin_file(), + color.home_rank(), + ))) }) }) { reasons.add(IllegalPositionReason::InvalidCastlingRights); @@ -426,7 +431,7 @@ impl Setup { Color::White => (Rank::Sixth, Rank::Fifth), Color::Black => (Rank::Third, Rank::Fourth), }; - let pawn_square = Square::new(en_passant.file(), pawn_rank); + let pawn_square = Square::from_coords(en_passant.file(), pawn_rank); en_passant.rank() != target_rank || blockers.contains(en_passant) || !pieces.get(!self.turn).get(Role::Pawn).contains(pawn_square) diff --git a/src/uci.rs b/src/uci.rs index 704fba2..c6e9e8f 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -81,8 +81,8 @@ impl UciMove { pub fn from_ascii(s: &[u8]) -> Option { match s { [a, b, c, d, s @ ..] => Some(Self { - from: Square::new(File::from_ascii(*a)?, Rank::from_ascii(*b)?), - to: Square::new(File::from_ascii(*c)?, Rank::from_ascii(*d)?), + from: Square::from_coords(File::from_ascii(*a)?, Rank::from_ascii(*b)?), + to: Square::from_coords(File::from_ascii(*c)?, Rank::from_ascii(*d)?), promotion: match s { [] => None, [c] => Some(Role::from_ascii(*c)?),