use std::cmp::max;
use std::convert::TryInto;
use std::fmt;
use std::str;
use std::error::Error;
use std::ops::Sub;
macro_rules! from_repr_u8_impl {
($from:ty, $($t:ty)+) => {
$(impl From<$from> for $t {
#[inline]
#[allow(clippy::cast_lossless)]
fn from(value: $from) -> $t {
value as u8 as $t
}
})+
}
}
macro_rules! try_from_number_impl {
($type:ty, $error:ty, $lower:expr, $upper:expr, $($t:ty)+) => {
$(impl std::convert::TryFrom<$t> for $type {
type Error = $error;
#[inline]
#[allow(unused_comparisons)]
#[allow(clippy::cast_lossless)]
fn try_from(value: $t) -> Result<$type, Self::Error> {
if $lower <= value && value < $upper {
Ok(<$type>::new(value as u32))
} else {
Err(<$error>::from(()))
}
}
})+
}
}
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(u8)]
pub enum File {
A = 0, B, C, D, E, F, G, H
}
impl File {
#[inline]
pub fn new(index: u32) -> File {
assert!(index < 8);
unsafe { File::new_unchecked(index) }
}
#[inline]
pub unsafe fn new_unchecked(index: u32) -> File {
debug_assert!(index < 8);
::std::mem::transmute(index as u8)
}
#[inline]
pub fn from_char(ch: char) -> Option<File> {
if 'a' <= ch && ch <= 'h' {
Some(File::new(u32::from(ch as u8 - b'a')))
} else {
None
}
}
#[inline]
pub fn char(self) -> char {
char::from(b'a' + u8::from(self))
}
#[inline]
pub fn flip_diagonal(self) -> Rank {
Rank::new(u32::from(self))
}
#[inline]
pub fn offset(self, delta: i32) -> Option<Rank> {
i32::from(self).checked_add(delta).and_then(|index| index.try_into().ok())
}
#[inline]
pub fn flip_horizontal(self) -> File {
File::new(7 - u32::from(self))
}
}
impl Sub for File {
type Output = i32;
#[inline]
fn sub(self, other: File) -> i32 {
i32::from(self) - i32::from(other)
}
}
impl fmt::Display for File {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.char())
}
}
from_repr_u8_impl! { File, u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize f32 f64 }
try_from_number_impl! { File, crate::errors::TryFromIntError, 0, 8, u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize }
try_from_number_impl! { File, crate::errors::TryFromFloatError, 0.0, 8.0, f32 f64 }
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(u8)]
pub enum Rank {
First = 0, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth
}
impl Rank {
#[inline]
pub fn new(index: u32) -> Rank {
assert!(index < 8);
unsafe { Rank::new_unchecked(index) }
}
#[inline]
pub unsafe fn new_unchecked(index: u32) -> Rank {
debug_assert!(index < 8);
::std::mem::transmute(index as u8)
}
#[inline]
pub fn from_char(ch: char) -> Option<Rank> {
if '1' <= ch && ch <= '8' {
Some(Rank::new(u32::from(ch as u8 - b'1')))
} else {
None
}
}
#[inline]
pub fn char(self) -> char {
char::from(b'1' + u8::from(self))
}
#[inline]
pub fn flip_diagonal(self) -> File {
File::new(u32::from(self))
}
#[inline]
pub fn offset(self, delta: i32) -> Option<Rank> {
i32::from(self).checked_add(delta).and_then(|index| index.try_into().ok())
}
#[inline]
pub fn flip_vertical(self) -> Rank {
Rank::new(7 - u32::from(self))
}
}
impl Sub for Rank {
type Output = i32;
#[inline]
fn sub(self, other: Rank) -> i32 {
i32::from(self) - i32::from(other)
}
}
impl fmt::Display for Rank {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.char())
}
}
from_repr_u8_impl! { Rank, u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize f32 f64 }
try_from_number_impl! { Rank, crate::errors::TryFromIntError, 0, 8, u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize }
try_from_number_impl! { Rank, crate::errors::TryFromFloatError, 0.0, 8.0, f32 f64 }
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParseSquareError;
impl fmt::Display for ParseSquareError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"invalid square name".fmt(f)
}
}
impl Error for ParseSquareError {
fn description(&self) -> &str {
"invalid square name"
}
}
impl From<()> for ParseSquareError {
fn from(_: ()) -> ParseSquareError {
ParseSquareError
}
}
#[allow(missing_docs)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(u8)]
pub enum Square {
A1 = 0, 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 Square {
#[inline]
pub fn new(index: u32) -> Square {
assert!(index < 64);
unsafe { Square::new_unchecked(index) }
}
#[inline]
pub unsafe fn new_unchecked(index: u32) -> Square {
debug_assert!(index < 64);
::std::mem::transmute(index as u8)
}
#[inline]
pub fn from_coords(file: File, rank: Rank) -> Square {
unsafe { Square::new_unchecked(u32::from(file) | (u32::from(rank) << 3)) }
}
#[inline]
pub fn from_ascii(s: &[u8]) -> Result<Square, ParseSquareError> {
if s.len() == 2 {
match (File::from_char(char::from(s[0])), Rank::from_char(char::from(s[1]))) {
(Some(file), Some(rank)) => Ok(Square::from_coords(file, rank)),
_ => Err(ParseSquareError),
}
} else {
Err(ParseSquareError)
}
}
#[inline]
pub fn file(self) -> File {
File::new(u32::from(self) & 7)
}
#[inline]
pub fn rank(self) -> Rank {
Rank::new(u32::from(self) >> 3)
}
#[inline]
pub fn coords(self) -> (File, Rank) {
(self.file(), self.rank())
}
#[inline]
pub fn offset(self, delta: i32) -> Option<Square> {
i32::from(self).checked_add(delta).and_then(|index| index.try_into().ok())
}
#[inline]
pub fn flip_horizontal(self) -> Square {
unsafe { Square::new_unchecked(u32::from(self) ^ 0b000_111) }
}
#[inline]
pub fn flip_vertical(self) -> Square {
unsafe { Square::new_unchecked(u32::from(self) ^ 0b111_000) }
}
pub fn flip_diagonal(self) -> Square {
Square::from_coords(self.rank().flip_diagonal(), self.file().flip_diagonal())
}
#[inline]
pub fn is_light(self) -> bool {
(u32::from(self.rank()) + u32::from(self.file())) % 2 == 1
}
#[inline]
pub fn is_dark(self) -> bool {
(u32::from(self.rank()) + u32::from(self.file())) % 2 == 0
}
pub fn distance(self, other: Square) -> u32 {
max((self.file() - other.file()).abs(),
(self.rank() - other.rank()).abs()) as u32
}
#[inline]
pub fn with_rank_of(self, other: Square) -> Square {
Square::from_coords(self.file(), other.rank())
}
}
from_repr_u8_impl! { Square, u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize }
try_from_number_impl! { Square, crate::errors::TryFromIntError, 0, 64, u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 usize isize }
impl Sub for Square {
type Output = i32;
#[inline]
fn sub(self, other: Square) -> i32 {
i32::from(self) - i32::from(other)
}
}
impl From<(File, Rank)> for Square {
#[inline]
fn from((file, rank): (File, Rank)) -> Square {
Square::from_coords(file, rank)
}
}
impl str::FromStr for Square {
type Err = ParseSquareError;
fn from_str(s: &str) -> Result<Square, ParseSquareError> {
Square::from_ascii(s.as_bytes())
}
}
impl fmt::Display for Square {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.file().char(), self.rank().char())
}
}
impl fmt::Debug for Square {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string().to_uppercase())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_square() {
for file in (0..8).map(File::new) {
for rank in (0..8).map(Rank::new) {
let square = Square::from_coords(file, rank);
assert_eq!(square.file(), file);
assert_eq!(square.rank(), rank);
}
}
}
}