From 494de58804c8d5e470b9eb0fbff597e9ab2a723e Mon Sep 17 00:00:00 2001 From: Paul-Nicolas Madelaine Date: Sun, 19 Oct 2025 14:28:36 +0200 Subject: [PATCH] new constructors --- src/bitboard.rs | 8 ++--- src/board.rs | 84 +++++++++++++++++++++++++++++++------------------ src/position.rs | 30 ++++++++++-------- src/san.rs | 2 +- src/setup.rs | 20 ++++++++---- src/uci.rs | 4 +-- 6 files changed, 92 insertions(+), 56 deletions(-) diff --git a/src/bitboard.rs b/src/bitboard.rs index c74e435..746e225 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,7 +31,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 @@ -134,7 +134,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); } } @@ -148,7 +148,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 e90eda3..5edaed9 100644 --- a/src/board.rs +++ b/src/board.rs @@ -110,6 +110,17 @@ impl File { ] } + #[inline] + pub fn new(index: u8) -> Option { + (index < 8).then(|| unsafe { Self::new_unchecked(index) }) + } + + #[inline] + pub unsafe fn new_unchecked(index: u8) -> Self { + debug_assert!(index < 8); + std::mem::transmute(index) + } + #[inline] pub fn to_char(self) -> char { self.to_ascii() as char @@ -128,7 +139,10 @@ impl File { #[inline] pub(crate) fn from_ascii(c: u8) -> Option { (c <= b'h') - .then(|| c.checked_sub(b'a').map(|i| unsafe { Self::transmute(i) })) + .then(|| { + c.checked_sub(b'a') + .map(|i| unsafe { Self::new_unchecked(i) }) + }) .flatten() } @@ -136,12 +150,6 @@ impl File { pub(crate) fn bitboard(self) -> Bitboard { Bitboard(0x0101010101010101 << (self as u8)) } - - #[inline] - pub(crate) unsafe fn transmute(value: u8) -> Self { - debug_assert!(value < 8); - std::mem::transmute(value) - } } impl std::fmt::Display for File { @@ -182,6 +190,17 @@ impl Rank { ] } + #[inline] + pub fn new(index: u8) -> Option { + (index < 8).then(|| unsafe { Self::new_unchecked(index) }) + } + + #[inline] + pub unsafe fn new_unchecked(index: u8) -> Self { + debug_assert!(index < 8); + std::mem::transmute(index) + } + #[inline] pub fn to_char(self) -> char { self.to_ascii() as char @@ -194,7 +213,7 @@ impl Rank { #[inline] pub fn mirror(self) -> Self { - unsafe { Self::transmute(7_u8.unchecked_sub(self as u8)) } + unsafe { Self::new_unchecked(7_u8.unchecked_sub(self as u8)) } } #[inline] @@ -205,19 +224,16 @@ impl Rank { #[inline] pub(crate) fn from_ascii(c: u8) -> Option { (c <= b'8') - .then(|| c.checked_sub(b'1').map(|i| unsafe { Self::transmute(i) })) + .then(|| { + c.checked_sub(b'1') + .map(|i| unsafe { Self::new_unchecked(i) }) + }) .flatten() } #[inline] pub(crate) fn bitboard(self) -> Bitboard { - Bitboard(0xFF << ((self as u64) << 3)) - } - - #[inline] - pub(crate) unsafe fn transmute(value: u8) -> Self { - debug_assert!(value < 8); - std::mem::transmute(value) + Bitboard(0xFF << ((self as u8) << 3)) } } @@ -262,23 +278,34 @@ impl Square { } #[inline] - pub fn new(file: File, rank: Rank) -> Self { - unsafe { Self::transmute(((rank as u8) << 3) | file as u8) } + pub fn new(index: u8) -> Option { + (index < 64).then(|| unsafe { Self::new_unchecked(index) }) + } + + #[inline] + pub 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] pub fn file(self) -> File { - unsafe { File::transmute((self as u8) & 7) } + unsafe { File::new_unchecked((self as u8) & 7) } } #[inline] pub fn rank(self) -> Rank { - unsafe { Rank::transmute((self as u8) >> 3) } + unsafe { Rank::new_unchecked((self as u8) >> 3) } } #[inline] pub fn mirror(self) -> Self { - Self::new(self.file(), self.rank().mirror()) + Self::from_coords(self.file(), self.rank().mirror()) } #[inline] @@ -312,7 +339,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] @@ -329,7 +359,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), @@ -356,12 +386,6 @@ impl Square { Direction::West => self.file() > File::A, } } - - #[inline] - pub(crate) unsafe fn transmute(value: u8) -> Self { - debug_assert!(value < 64); - std::mem::transmute(value) - } } impl std::fmt::Display for Square { @@ -419,7 +443,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/position.rs b/src/position.rs index 35df0ba..e822fd5 100644 --- a/src/position.rs +++ b/src/position.rs @@ -426,14 +426,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, @@ -1275,7 +1275,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, @@ -1305,12 +1305,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 { @@ -1333,10 +1333,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); } } @@ -1383,12 +1383,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 83fe4f1..bd3497f 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -76,7 +76,12 @@ impl Setup { }; (file < 8).then_some(())?; setup.set( - unsafe { Square::new(File::transmute(file), Rank::transmute(rank)) }, + unsafe { + Square::from_coords( + File::new_unchecked(file), + Rank::new_unchecked(rank), + ) + }, Some(Piece { role, color }), ); file += 1; @@ -118,7 +123,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()?)?, ))), @@ -319,11 +324,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); @@ -334,7 +342,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) @@ -430,7 +438,7 @@ impl std::fmt::Display for 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 { f.write_char(char::from_u32('0' as u32 + count).unwrap())?; 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)?),