[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;
|
||||
mod position;
|
||||
mod sight;
|
||||
mod square;
|
||||
|
||||
pub use piece::{Color, Piece};
|
||||
pub use position::{MoveBuilder as MakeMoveBuilder, Position, PositionBuilder};
|
||||
|
|
|
|||
|
|
@ -1,360 +0,0 @@
|
|||
// Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
use crate::Color;
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
pub enum Direction {
|
||||
North,
|
||||
NorthWest,
|
||||
West,
|
||||
SouthWest,
|
||||
South,
|
||||
SouthEast,
|
||||
East,
|
||||
NorthEast,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseFileError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseSquareError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SquareOutOfBoundsError;
|
||||
|
||||
macro_rules! coordinate_enum {
|
||||
($name: ident, $($variant:ident),*) => {
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum $name {
|
||||
$($variant), *
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
pub fn try_index(index: usize) -> Option<Self> {
|
||||
$(
|
||||
#[allow(non_upper_case_globals)]
|
||||
const $variant: usize = $name::$variant as usize;
|
||||
)*
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
match index {
|
||||
$($variant => Some($name::$variant),)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
coordinate_enum!(Rank,
|
||||
One, Two, Three, Four, Five, Six, Seven, Eight
|
||||
);
|
||||
|
||||
#[rustfmt::skip]
|
||||
coordinate_enum!(File,
|
||||
A, B, C, D, E, F, G, H
|
||||
);
|
||||
|
||||
#[rustfmt::skip]
|
||||
coordinate_enum!(Square,
|
||||
A1, B1, C1, D1, E1, F1, G1, H1,
|
||||
A2, B2, C2, D2, E2, F2, G2, H2,
|
||||
A3, B3, C3, D3, E3, F3, G3, H3,
|
||||
A4, B4, C4, D4, E4, F4, G4, H4,
|
||||
A5, B5, C5, D5, E5, F5, G5, H5,
|
||||
A6, B6, C6, D6, E6, F6, G6, H6,
|
||||
A7, B7, C7, D7, E7, F7, G7, H7,
|
||||
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 {
|
||||
#[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]
|
||||
}
|
||||
|
||||
pub fn from_algebraic_str(s: &str) -> Result<Square, ParseSquareError> {
|
||||
s.parse()
|
||||
}
|
||||
|
||||
pub fn neighbor(self, direction: Direction) -> Option<Square> {
|
||||
match direction {
|
||||
Direction::North => Square::try_index(self as usize + 8),
|
||||
Direction::NorthWest => {
|
||||
if self.rank() != Rank::Eight {
|
||||
Square::try_index(self as usize + 7)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Direction::West => {
|
||||
if self.file() != File::A {
|
||||
Square::try_index(self as usize - 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Direction::SouthWest => {
|
||||
if self.rank() != Rank::One {
|
||||
Square::try_index(self as usize - 9)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Direction::South => {
|
||||
if self.rank() != Rank::One {
|
||||
Square::try_index(self as usize - 8)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Direction::SouthEast => {
|
||||
if self.rank() != Rank::One {
|
||||
Square::try_index(self as usize - 7)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Direction::East => {
|
||||
if self.file() != File::H {
|
||||
Square::try_index(self as usize + 1)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Direction::NorthEast => Square::try_index(self as usize + 9),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Square {
|
||||
type Err = ParseSquareError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut chars = s.chars();
|
||||
|
||||
let file: File = chars
|
||||
.next()
|
||||
.and_then(|c| c.try_into().ok())
|
||||
.ok_or(ParseSquareError)?;
|
||||
|
||||
let rank: Rank = chars
|
||||
.next()
|
||||
.and_then(|c| c.try_into().ok())
|
||||
.ok_or(ParseSquareError)?;
|
||||
|
||||
if !chars.next().is_none() {
|
||||
return Err(ParseSquareError);
|
||||
}
|
||||
|
||||
Ok(Square::from_file_rank(file, rank))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! try_from_integer {
|
||||
($int_type:ident) => {
|
||||
impl TryFrom<$int_type> for Square {
|
||||
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);
|
||||
try_from_integer!(u16);
|
||||
try_from_integer!(u32);
|
||||
try_from_integer!(u64);
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_algebraic_input() {
|
||||
Square::from_algebraic_str("a0").expect_err("Got valid Square for 'a0'");
|
||||
Square::from_algebraic_str("j3").expect_err("Got valid Square for 'j3'");
|
||||
Square::from_algebraic_str("a11").expect_err("Got valid Square for 'a11'");
|
||||
Square::from_algebraic_str("b-1").expect_err("Got valid Square for 'b-1'");
|
||||
Square::from_algebraic_str("a 1").expect_err("Got valid Square for 'a 1'");
|
||||
Square::from_algebraic_str("").expect_err("Got valid Square for ''");
|
||||
}
|
||||
|
||||
#[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_index(28).expect("Unable to get Square from index");
|
||||
assert_eq!(sq.file(), File::E);
|
||||
assert_eq!(sq.rank(), Rank::Four);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_index() {
|
||||
assert_eq!(Square::A1 as usize, 0);
|
||||
assert_eq!(Square::H8 as usize, 63);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_neighbors() {
|
||||
let sq = Square::E4;
|
||||
|
||||
assert_eq!(sq.neighbor(Direction::North), Some(Square::E5));
|
||||
assert_eq!(sq.neighbor(Direction::NorthEast), Some(Square::F5));
|
||||
assert_eq!(sq.neighbor(Direction::East), Some(Square::F4));
|
||||
assert_eq!(sq.neighbor(Direction::SouthEast), Some(Square::F3));
|
||||
assert_eq!(sq.neighbor(Direction::South), Some(Square::E3));
|
||||
assert_eq!(sq.neighbor(Direction::SouthWest), Some(Square::D3));
|
||||
assert_eq!(sq.neighbor(Direction::West), Some(Square::D4));
|
||||
assert_eq!(sq.neighbor(Direction::NorthWest), Some(Square::D5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_neighbors() {
|
||||
let sq = Square::A1;
|
||||
assert!(sq.neighbor(Direction::West).is_none());
|
||||
assert!(sq.neighbor(Direction::SouthWest).is_none());
|
||||
assert!(sq.neighbor(Direction::South).is_none());
|
||||
|
||||
let sq = Square::H1;
|
||||
assert!(sq.neighbor(Direction::East).is_none());
|
||||
assert!(sq.neighbor(Direction::SouthEast).is_none());
|
||||
assert!(sq.neighbor(Direction::South).is_none());
|
||||
|
||||
let sq = Square::A8;
|
||||
assert!(sq.neighbor(Direction::North).is_none());
|
||||
assert!(sq.neighbor(Direction::NorthWest).is_none());
|
||||
assert!(sq.neighbor(Direction::West).is_none());
|
||||
|
||||
let sq = Square::H8;
|
||||
assert!(sq.neighbor(Direction::North).is_none());
|
||||
assert!(sq.neighbor(Direction::NorthEast).is_none());
|
||||
assert!(sq.neighbor(Direction::East).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display() {
|
||||
assert_eq!(format!("{}", Square::C5), "c5");
|
||||
assert_eq!(format!("{}", Square::A1), "a1");
|
||||
assert_eq!(format!("{}", Square::H8), "h8");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue