[core] Move the contents of board::square to core::coordinates
Export Square, Rank, and File from the core crate.
This commit is contained in:
parent
7e08a9adc4
commit
406631b617
3 changed files with 212 additions and 153 deletions
|
@ -12,7 +12,6 @@ mod move_generator;
|
||||||
pub mod piece;
|
pub mod piece;
|
||||||
mod position;
|
mod position;
|
||||||
mod sight;
|
mod sight;
|
||||||
mod square;
|
|
||||||
|
|
||||||
pub use piece::{Color, Piece};
|
pub use piece::{Color, Piece};
|
||||||
pub use position::{MoveBuilder as MakeMoveBuilder, Position, PositionBuilder};
|
pub use position::{MoveBuilder as MakeMoveBuilder, Position, PositionBuilder};
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
// Eryn Wells <eryn@erynwells.me>
|
// Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
use crate::Color;
|
use std::fmt;
|
||||||
use std::{fmt, str::FromStr};
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
#[repr(u8)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
North,
|
North,
|
||||||
NorthWest,
|
NorthWest,
|
||||||
|
@ -14,14 +16,24 @@ pub enum Direction {
|
||||||
NorthEast,
|
NorthEast,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl Direction {
|
||||||
pub struct ParseFileError;
|
pub fn to_offset(&self) -> i8 {
|
||||||
|
const OFFSETS: [i8; 8] = [8, 7, -1, -9, -8, -7, 1, 9];
|
||||||
|
OFFSETS[*self as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
macro_rules! try_from_integer {
|
||||||
pub struct ParseSquareError;
|
($type:ident, $int_type:ident) => {
|
||||||
|
impl TryFrom<$int_type> for $type {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
#[derive(Debug)]
|
fn try_from(value: $int_type) -> Result<Self, Self::Error> {
|
||||||
pub struct SquareOutOfBoundsError;
|
Square::try_from(value as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! coordinate_enum {
|
macro_rules! coordinate_enum {
|
||||||
($name: ident, $($variant:ident),*) => {
|
($name: ident, $($variant:ident),*) => {
|
||||||
|
@ -34,43 +46,116 @@ macro_rules! coordinate_enum {
|
||||||
impl $name {
|
impl $name {
|
||||||
pub const NUM: usize = [$(Self::$variant), *].len();
|
pub const NUM: usize = [$(Self::$variant), *].len();
|
||||||
pub const ALL: [Self; Self::NUM] = [$(Self::$variant), *];
|
pub const ALL: [Self; Self::NUM] = [$(Self::$variant), *];
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
impl TryFrom<u8> for $name {
|
||||||
pub(crate) fn from_index(index: usize) -> Self {
|
type Error = ();
|
||||||
assert!(
|
|
||||||
index < Self::NUM,
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
"Index {} out of bounds for {}.",
|
let value_usize = value as usize;
|
||||||
index,
|
if value_usize < Self::NUM {
|
||||||
stringify!($name)
|
Ok($name::ALL[value_usize])
|
||||||
);
|
} else {
|
||||||
Self::try_index(index).unwrap()
|
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<Self> {
|
||||||
|
if x < $max {
|
||||||
|
Some(Self(x))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_index(index: usize) -> Option<Self> {
|
$vis unsafe fn new_unchecked(x: $repr) -> Self {
|
||||||
$(
|
Self(x)
|
||||||
#[allow(non_upper_case_globals)]
|
}
|
||||||
const $variant: usize = $name::$variant as usize;
|
}
|
||||||
)*
|
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
impl Into<$repr> for $type {
|
||||||
match index {
|
fn into(self) -> $repr {
|
||||||
$($variant => Some($name::$variant),)*
|
self.0
|
||||||
_ => None,
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<$repr> for $type {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: $repr) -> Result<Self, Self::Error> {
|
||||||
|
Self::new(value).ok_or(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
range_bound_struct!(pub, File, u8, 8);
|
||||||
coordinate_enum!(Rank,
|
|
||||||
One, Two, Three, Four, Five, Six, Seven, Eight
|
|
||||||
);
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
impl File {
|
||||||
coordinate_enum!(File,
|
pub const A: File = File(0);
|
||||||
A, B, C, D, E, F, G, H
|
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]
|
#[rustfmt::skip]
|
||||||
coordinate_enum!(Square,
|
coordinate_enum!(Square,
|
||||||
|
@ -84,138 +169,87 @@ coordinate_enum!(Square,
|
||||||
A8, B8, C8, D8, E8, F8, G8, H8
|
A8, B8, C8, D8, E8, F8, G8, H8
|
||||||
);
|
);
|
||||||
|
|
||||||
impl Into<char> for File {
|
|
||||||
fn into(self) -> char {
|
|
||||||
('a' as u8 + self as u8) as char
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<char> for File {
|
|
||||||
type Error = ParseFileError;
|
|
||||||
|
|
||||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
|
||||||
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::<char>::into(*self).to_uppercase())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<char> for Rank {
|
|
||||||
fn into(self) -> char {
|
|
||||||
('1' as u8 + self as u8) as char
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<char> for Rank {
|
|
||||||
type Error = ParseFileError;
|
|
||||||
|
|
||||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
|
||||||
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::<char>::into(*self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Square {
|
impl Square {
|
||||||
|
pub unsafe fn from_index(x: u8) -> Square {
|
||||||
|
Self::try_from(x).unwrap_unchecked()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_file_rank(file: File, rank: Rank) -> Square {
|
pub fn from_file_rank(file: File, rank: Rank) -> Square {
|
||||||
Self::from_index((rank as usize) << 3 | file as usize)
|
let file_int: u8 = file.into();
|
||||||
}
|
let rank_int: u8 = rank.into();
|
||||||
|
unsafe { Self::from_index(rank_int << 3 | file_int) }
|
||||||
#[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]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_algebraic_str(s: &str) -> Result<Square, ParseSquareError> {
|
pub fn from_algebraic_str(s: &str) -> Result<Square, ParseSquareError> {
|
||||||
s.parse()
|
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<Square> {
|
pub fn neighbor(self, direction: Direction) -> Option<Square> {
|
||||||
|
let index: u8 = self as u8;
|
||||||
|
let dir: i8 = direction.to_offset();
|
||||||
match direction {
|
match direction {
|
||||||
Direction::North => Square::try_index(self as usize + 8),
|
Direction::North => Square::try_from(index.wrapping_add_signed(dir)).ok(),
|
||||||
Direction::NorthWest => {
|
Direction::NorthWest => {
|
||||||
if self.rank() != Rank::Eight {
|
if self.rank() != Rank::EIGHT {
|
||||||
Square::try_index(self as usize + 7)
|
Square::try_from(index.wrapping_add_signed(dir)).ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Direction::West => {
|
Direction::West => {
|
||||||
if self.file() != File::A {
|
if self.file() != File::A {
|
||||||
Square::try_index(self as usize - 1)
|
Square::try_from(index.wrapping_add_signed(dir)).ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Direction::SouthWest => {
|
Direction::SouthWest => {
|
||||||
if self.rank() != Rank::One {
|
if self.rank() != Rank::ONE {
|
||||||
Square::try_index(self as usize - 9)
|
Square::try_from(index.wrapping_add_signed(dir)).ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Direction::South => {
|
Direction::South => {
|
||||||
if self.rank() != Rank::One {
|
if self.rank() != Rank::ONE {
|
||||||
Square::try_index(self as usize - 8)
|
Square::try_from(index.wrapping_add_signed(dir)).ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Direction::SouthEast => {
|
Direction::SouthEast => {
|
||||||
if self.rank() != Rank::One {
|
if self.rank() != Rank::ONE {
|
||||||
Square::try_index(self as usize - 7)
|
Square::try_from(index.wrapping_add_signed(dir)).ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Direction::East => {
|
Direction::East => {
|
||||||
if self.file() != File::H {
|
if self.file() != File::H {
|
||||||
Square::try_index(self as usize + 1)
|
Square::try_from(index.wrapping_add_signed(dir)).ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
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 {
|
impl FromStr for Square {
|
||||||
type Err = ParseSquareError;
|
type Err = ParseSquareError;
|
||||||
|
|
||||||
|
@ -240,31 +274,54 @@ impl FromStr for Square {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! try_from_integer {
|
impl fmt::Display for File {
|
||||||
($int_type:ident) => {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
impl TryFrom<$int_type> for Square {
|
write!(f, "{}", Into::<char>::into(*self))
|
||||||
type Error = SquareOutOfBoundsError;
|
}
|
||||||
|
|
||||||
fn try_from(value: $int_type) -> Result<Self, Self::Error> {
|
|
||||||
Square::try_index(value as usize).ok_or(SquareOutOfBoundsError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try_from_integer!(u8);
|
impl fmt::Display for Rank {
|
||||||
try_from_integer!(u16);
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
try_from_integer!(u32);
|
write!(f, "{}", Into::<char>::into(*self))
|
||||||
try_from_integer!(u64);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for Square {
|
impl fmt::Display for Square {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
self.file().fmt(f)?;
|
||||||
f,
|
self.rank().fmt(f)?;
|
||||||
"{}{}",
|
Ok(())
|
||||||
('a' as u8 + self.file() as u8) as char,
|
}
|
||||||
self.rank() as usize + 1
|
}
|
||||||
)
|
|
||||||
|
impl Into<char> for File {
|
||||||
|
fn into(self) -> char {
|
||||||
|
let value: u8 = self.into();
|
||||||
|
(value + 'a' as u8) as char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<char> for Rank {
|
||||||
|
fn into(self) -> char {
|
||||||
|
let value: u8 = self.into();
|
||||||
|
(value + '1' as u8) as char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<char> for File {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||||
|
File::try_from(value.to_ascii_lowercase() as u8 - 'a' as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<char> for Rank {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||||
|
let result = (value as u8).checked_sub('1' as u8).ok_or(())?;
|
||||||
|
Self::try_from(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,16 +332,16 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn good_algebraic_input() {
|
fn good_algebraic_input() {
|
||||||
let sq = Square::from_algebraic_str("a4").expect("Failed to parse 'a4' square");
|
let sq = Square::from_algebraic_str("a4").expect("Failed to parse 'a4' square");
|
||||||
assert_eq!(sq.file(), File::A);
|
assert_eq!(sq.file(), File(0));
|
||||||
assert_eq!(sq.rank(), Rank::Four);
|
assert_eq!(sq.rank(), Rank(3));
|
||||||
|
|
||||||
let sq = Square::from_algebraic_str("B8").expect("Failed to parse 'B8' square");
|
let sq = Square::from_algebraic_str("B8").expect("Failed to parse 'B8' square");
|
||||||
assert_eq!(sq.file(), File::B);
|
assert_eq!(sq.file(), File(1));
|
||||||
assert_eq!(sq.rank(), Rank::Eight);
|
assert_eq!(sq.rank(), Rank(7));
|
||||||
|
|
||||||
let sq = Square::from_algebraic_str("e4").expect("Failed to parse 'B8' square");
|
let sq = Square::from_algebraic_str("e4").expect("Failed to parse 'B8' square");
|
||||||
assert_eq!(sq.file(), File::E);
|
assert_eq!(sq.file(), File(4));
|
||||||
assert_eq!(sq.rank(), Rank::Four);
|
assert_eq!(sq.rank(), Rank(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -299,13 +356,13 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from_index() {
|
fn from_index() {
|
||||||
let sq = Square::try_index(4).expect("Unable to get Square from index");
|
let sq = Square::try_from(4u32).expect("Unable to get Square from index");
|
||||||
assert_eq!(sq.file(), File::E);
|
assert_eq!(sq.file(), File(4));
|
||||||
assert_eq!(sq.rank(), Rank::One);
|
assert_eq!(sq.rank(), Rank(0));
|
||||||
|
|
||||||
let sq = Square::try_index(28).expect("Unable to get Square from index");
|
let sq = Square::try_from(28u32).expect("Unable to get Square from index");
|
||||||
assert_eq!(sq.file(), File::E);
|
assert_eq!(sq.file(), File(4));
|
||||||
assert_eq!(sq.rank(), Rank::Four);
|
assert_eq!(sq.rank(), Rank(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
|
@ -0,0 +1,3 @@
|
||||||
|
mod coordinates;
|
||||||
|
|
||||||
|
pub use coordinates::{Direction, File, Rank, Square};
|
Loading…
Add table
Add a link
Reference in a new issue