From 406631b617646a43967668ae94125d94e62fcf2b Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 24 Jan 2024 08:25:56 -0800 Subject: [PATCH] [core] Move the contents of board::square to core::coordinates Export Square, Rank, and File from the core crate. --- board/src/lib.rs | 1 - .../src/square.rs => core/src/coordinates.rs | 361 ++++++++++-------- core/src/lib.rs | 3 + 3 files changed, 212 insertions(+), 153 deletions(-) rename board/src/square.rs => core/src/coordinates.rs (55%) diff --git a/board/src/lib.rs b/board/src/lib.rs index 3db0753..8c68fad 100644 --- a/board/src/lib.rs +++ b/board/src/lib.rs @@ -12,7 +12,6 @@ mod move_generator; pub mod piece; mod position; mod sight; -mod square; pub use piece::{Color, Piece}; pub use position::{MoveBuilder as MakeMoveBuilder, Position, PositionBuilder}; diff --git a/board/src/square.rs b/core/src/coordinates.rs similarity index 55% rename from board/src/square.rs rename to core/src/coordinates.rs index 23565cf..fd0d25e 100644 --- a/board/src/square.rs +++ b/core/src/coordinates.rs @@ -1,8 +1,10 @@ // Eryn Wells -use crate::Color; -use std::{fmt, str::FromStr}; +use std::fmt; +use std::str::FromStr; +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] pub enum Direction { North, NorthWest, @@ -14,14 +16,24 @@ pub enum Direction { NorthEast, } -#[derive(Debug)] -pub struct ParseFileError; +impl Direction { + pub fn to_offset(&self) -> i8 { + const OFFSETS: [i8; 8] = [8, 7, -1, -9, -8, -7, 1, 9]; + OFFSETS[*self as usize] + } +} -#[derive(Debug)] -pub struct ParseSquareError; +macro_rules! try_from_integer { + ($type:ident, $int_type:ident) => { + impl TryFrom<$int_type> for $type { + type Error = (); -#[derive(Debug)] -pub struct SquareOutOfBoundsError; + fn try_from(value: $int_type) -> Result { + Square::try_from(value as u8) + } + } + }; +} macro_rules! coordinate_enum { ($name: ident, $($variant:ident),*) => { @@ -34,43 +46,116 @@ macro_rules! coordinate_enum { impl $name { pub const NUM: usize = [$(Self::$variant), *].len(); pub const ALL: [Self; Self::NUM] = [$(Self::$variant), *]; + } - #[inline] - pub(crate) fn from_index(index: usize) -> Self { - assert!( - index < Self::NUM, - "Index {} out of bounds for {}.", - index, - stringify!($name) - ); - Self::try_index(index).unwrap() + impl TryFrom for $name { + type Error = (); + + fn try_from(value: u8) -> Result { + let value_usize = value as usize; + if value_usize < Self::NUM { + Ok($name::ALL[value_usize]) + } else { + Err(()) + } + } + } + + try_from_integer!($name, u16); + try_from_integer!($name, u32); + try_from_integer!($name, u64); + } +} + +macro_rules! range_bound_struct { + ($vis:vis, $type:ident, $repr:ty, $max:expr) => { + #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] + $vis struct $type($repr); + + #[allow(dead_code)] + impl $type { + $vis const FIRST: $type = $type(0); + $vis const LAST: $type = $type($max - 1); + } + + impl $type { + $vis fn new(x: $repr) -> Option { + if x < $max { + Some(Self(x)) + } else { + None + } } - pub fn try_index(index: usize) -> Option { - $( - #[allow(non_upper_case_globals)] - const $variant: usize = $name::$variant as usize; - )* + $vis unsafe fn new_unchecked(x: $repr) -> Self { + Self(x) + } + } - #[allow(non_upper_case_globals)] - match index { - $($variant => Some($name::$variant),)* - _ => None, - } + impl Into<$repr> for $type { + fn into(self) -> $repr { + self.0 + } + } + + impl TryFrom<$repr> for $type { + type Error = (); + + fn try_from(value: $repr) -> Result { + Self::new(value).ok_or(()) } } } } -#[rustfmt::skip] -coordinate_enum!(Rank, - One, Two, Three, Four, Five, Six, Seven, Eight -); +range_bound_struct!(pub, File, u8, 8); -#[rustfmt::skip] -coordinate_enum!(File, - A, B, C, D, E, F, G, H -); +impl File { + pub const A: File = File(0); + pub const B: File = File(1); + pub const C: File = File(2); + pub const D: File = File(3); + pub const E: File = File(4); + pub const F: File = File(5); + pub const G: File = File(6); + pub const H: File = File(7); + + pub const ALL: [File; 8] = [ + File::A, + File::B, + File::C, + File::D, + File::E, + File::F, + File::G, + File::H, + ]; +} + +range_bound_struct!(pub, Rank, u8, 8); + +#[allow(dead_code)] +impl Rank { + pub const ONE: Rank = Rank(0); + pub const TWO: Rank = Rank(1); + pub const THREE: Rank = Rank(2); + pub const FOUR: Rank = Rank(3); + pub const FIVE: Rank = Rank(4); + pub const SIX: Rank = Rank(5); + pub const SEVEN: Rank = Rank(6); + pub const EIGHT: Rank = Rank(7); + + pub const ALL: [Rank; 8] = [ + Rank::ONE, + Rank::TWO, + Rank::THREE, + Rank::FOUR, + Rank::FIVE, + Rank::SIX, + Rank::SEVEN, + Rank::EIGHT, + ]; +} #[rustfmt::skip] coordinate_enum!(Square, @@ -84,138 +169,87 @@ coordinate_enum!(Square, A8, B8, C8, D8, E8, F8, G8, H8 ); -impl Into for File { - fn into(self) -> char { - ('a' as u8 + self as u8) as char - } -} - -impl TryFrom for File { - type Error = ParseFileError; - - fn try_from(value: char) -> Result { - let lowercase_value = value.to_ascii_lowercase(); - for file in File::ALL.iter() { - if lowercase_value == (*file).into() { - return Ok(*file); - } - } - - Err(ParseFileError) - } -} - -impl fmt::Display for File { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", Into::::into(*self).to_uppercase()) - } -} - -impl Into for Rank { - fn into(self) -> char { - ('1' as u8 + self as u8) as char - } -} - -impl TryFrom for Rank { - type Error = ParseFileError; - - fn try_from(value: char) -> Result { - let lowercase_value = value.to_ascii_lowercase(); - for rank in Self::ALL.iter().cloned() { - if lowercase_value == rank.into() { - return Ok(rank); - } - } - - Err(ParseFileError) - } -} - -impl fmt::Display for Rank { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", Into::::into(*self)) - } -} - impl Square { + pub unsafe fn from_index(x: u8) -> Square { + Self::try_from(x).unwrap_unchecked() + } + #[inline] pub fn from_file_rank(file: File, rank: Rank) -> Square { - Self::from_index((rank as usize) << 3 | file as usize) - } - - #[inline] - pub fn file(self) -> File { - File::from_index(self as usize & 0b000111) - } - - #[inline] - pub fn rank(self) -> Rank { - Rank::from_index(self as usize >> 3) - } -} - -impl Square { - const KING_STARTING_SQUARES: [Square; 2] = [Square::E1, Square::E8]; - - pub fn king_starting_square(color: Color) -> Square { - Square::KING_STARTING_SQUARES[color as usize] + let file_int: u8 = file.into(); + let rank_int: u8 = rank.into(); + unsafe { Self::from_index(rank_int << 3 | file_int) } } pub fn from_algebraic_str(s: &str) -> Result { s.parse() } + #[inline] + pub fn file(self) -> File { + unsafe { File::new_unchecked((self as u8) & 0b000111) } + } + + #[inline] + pub fn rank(self) -> Rank { + unsafe { Rank::new_unchecked((self as u8) >> 3) } + } + pub fn neighbor(self, direction: Direction) -> Option { + let index: u8 = self as u8; + let dir: i8 = direction.to_offset(); match direction { - Direction::North => Square::try_index(self as usize + 8), + Direction::North => Square::try_from(index.wrapping_add_signed(dir)).ok(), Direction::NorthWest => { - if self.rank() != Rank::Eight { - Square::try_index(self as usize + 7) + if self.rank() != Rank::EIGHT { + Square::try_from(index.wrapping_add_signed(dir)).ok() } else { None } } Direction::West => { if self.file() != File::A { - Square::try_index(self as usize - 1) + Square::try_from(index.wrapping_add_signed(dir)).ok() } else { None } } Direction::SouthWest => { - if self.rank() != Rank::One { - Square::try_index(self as usize - 9) + if self.rank() != Rank::ONE { + Square::try_from(index.wrapping_add_signed(dir)).ok() } else { None } } Direction::South => { - if self.rank() != Rank::One { - Square::try_index(self as usize - 8) + if self.rank() != Rank::ONE { + Square::try_from(index.wrapping_add_signed(dir)).ok() } else { None } } Direction::SouthEast => { - if self.rank() != Rank::One { - Square::try_index(self as usize - 7) + if self.rank() != Rank::ONE { + Square::try_from(index.wrapping_add_signed(dir)).ok() } else { None } } Direction::East => { if self.file() != File::H { - Square::try_index(self as usize + 1) + Square::try_from(index.wrapping_add_signed(dir)).ok() } else { None } } - Direction::NorthEast => Square::try_index(self as usize + 9), + Direction::NorthEast => Square::try_from(index.wrapping_add_signed(dir)).ok(), } } } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ParseSquareError; + impl FromStr for Square { type Err = ParseSquareError; @@ -240,31 +274,54 @@ impl FromStr for Square { } } -macro_rules! try_from_integer { - ($int_type:ident) => { - impl TryFrom<$int_type> for Square { - type Error = SquareOutOfBoundsError; - - fn try_from(value: $int_type) -> Result { - Square::try_index(value as usize).ok_or(SquareOutOfBoundsError) - } - } - }; +impl fmt::Display for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", Into::::into(*self)) + } } -try_from_integer!(u8); -try_from_integer!(u16); -try_from_integer!(u32); -try_from_integer!(u64); +impl fmt::Display for Rank { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", Into::::into(*self)) + } +} impl fmt::Display for Square { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}{}", - ('a' as u8 + self.file() as u8) as char, - self.rank() as usize + 1 - ) + self.file().fmt(f)?; + self.rank().fmt(f)?; + Ok(()) + } +} + +impl Into for File { + fn into(self) -> char { + let value: u8 = self.into(); + (value + 'a' as u8) as char + } +} + +impl Into for Rank { + fn into(self) -> char { + let value: u8 = self.into(); + (value + '1' as u8) as char + } +} + +impl TryFrom for File { + type Error = (); + + fn try_from(value: char) -> Result { + File::try_from(value.to_ascii_lowercase() as u8 - 'a' as u8) + } +} + +impl TryFrom for Rank { + type Error = (); + + fn try_from(value: char) -> Result { + let result = (value as u8).checked_sub('1' as u8).ok_or(())?; + Self::try_from(result) } } @@ -275,16 +332,16 @@ mod tests { #[test] fn good_algebraic_input() { let sq = Square::from_algebraic_str("a4").expect("Failed to parse 'a4' square"); - assert_eq!(sq.file(), File::A); - assert_eq!(sq.rank(), Rank::Four); + assert_eq!(sq.file(), File(0)); + assert_eq!(sq.rank(), Rank(3)); let sq = Square::from_algebraic_str("B8").expect("Failed to parse 'B8' square"); - assert_eq!(sq.file(), File::B); - assert_eq!(sq.rank(), Rank::Eight); + assert_eq!(sq.file(), File(1)); + assert_eq!(sq.rank(), Rank(7)); let sq = Square::from_algebraic_str("e4").expect("Failed to parse 'B8' square"); - assert_eq!(sq.file(), File::E); - assert_eq!(sq.rank(), Rank::Four); + assert_eq!(sq.file(), File(4)); + assert_eq!(sq.rank(), Rank(3)); } #[test] @@ -299,13 +356,13 @@ mod tests { #[test] fn from_index() { - let sq = Square::try_index(4).expect("Unable to get Square from index"); - assert_eq!(sq.file(), File::E); - assert_eq!(sq.rank(), Rank::One); + let sq = Square::try_from(4u32).expect("Unable to get Square from index"); + assert_eq!(sq.file(), File(4)); + assert_eq!(sq.rank(), Rank(0)); - let sq = Square::try_index(28).expect("Unable to get Square from index"); - assert_eq!(sq.file(), File::E); - assert_eq!(sq.rank(), Rank::Four); + let sq = Square::try_from(28u32).expect("Unable to get Square from index"); + assert_eq!(sq.file(), File(4)); + assert_eq!(sq.rank(), Rank(3)); } #[test] diff --git a/core/src/lib.rs b/core/src/lib.rs index e69de29..2fe191d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -0,0 +1,3 @@ +mod coordinates; + +pub use coordinates::{Direction, File, Rank, Square};