//! Chessboard vocabulary. use crate::bitboard::*; macro_rules! container { ($a:ident, $b:ident, $n:literal) => { #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct $b(pub(crate) [T; $n]); #[allow(unused)] impl $b { #[inline] pub fn new(f: F) -> Self where F: FnMut($a) -> T, { Self($a::all().map(f)) } #[inline] pub fn get(&self, k: $a) -> &T { unsafe { self.0.get_unchecked(k as usize) } } #[inline] pub fn get_mut(&mut self, k: $a) -> &mut T { unsafe { self.0.get_unchecked_mut(k as usize) } } pub(crate) const fn get_const(&self, k: $a) -> &T { &self.0[k as usize] } } }; } /// The players. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] pub enum Color { White, Black, } container!(Color, ByColor, 2); impl Color { #[inline] pub fn all() -> [Self; 2] { [Self::White, Self::Black] } #[inline] pub(crate) fn home_rank(self) -> Rank { match self { Self::White => Rank::First, Self::Black => Rank::Eighth, } } #[inline] pub(crate) fn promotion_rank(self) -> Rank { match self { Self::White => Rank::Eighth, Self::Black => Rank::First, } } #[inline] pub(crate) fn forward(self) -> Direction { match self { Self::White => Direction::North, Self::Black => Direction::South, } } } impl std::ops::Not for Color { type Output = Self; #[inline] fn not(self) -> Self::Output { match self { Self::Black => Self::White, Self::White => Self::Black, } } } /// A column of the board. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] pub enum File { A, B, C, D, E, F, G, H, } container!(File, ByFile, 8); impl File { #[inline] pub fn all() -> [Self; 8] { [ Self::A, Self::B, Self::C, Self::D, Self::E, Self::F, Self::G, Self::H, ] } #[inline] pub fn to_char(self) -> char { self.to_ascii() as char } #[inline] pub fn from_char(file: char) -> Option { u8::try_from(file).ok().and_then(Self::from_ascii) } #[inline] pub(crate) fn to_ascii(self) -> u8 { self as u8 + b'a' } #[inline] pub(crate) fn from_ascii(c: u8) -> Option { (c <= b'h') .then(|| c.checked_sub(b'a').map(|i| unsafe { Self::transmute(i) })) .flatten() } #[inline] pub(crate) const fn bitboard(self) -> Bitboard { Bitboard(0x0101010101010101 << (self as u8)) } #[inline] pub(crate) const unsafe fn transmute(value: u8) -> Self { debug_assert!(value < 8); std::mem::transmute(value) } } impl std::fmt::Display for File { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.to_char()) } } /// A row of the board. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] pub enum Rank { First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth, } container!(Rank, ByRank, 8); impl Rank { #[inline] pub fn all() -> [Self; 8] { [ Self::First, Self::Second, Self::Third, Self::Fourth, Self::Fifth, Self::Sixth, Self::Seventh, Self::Eighth, ] } #[inline] pub fn to_char(self) -> char { self.to_ascii() as char } #[inline] pub fn from_char(rank: char) -> Option { u8::try_from(rank).ok().and_then(Self::from_ascii) } #[inline] pub fn mirror(self) -> Self { unsafe { Self::transmute(!(self as u8)) } } #[inline] pub(crate) fn to_ascii(self) -> u8 { self as u8 + b'1' } #[inline] pub(crate) fn from_ascii(c: u8) -> Option { (c <= b'8') .then(|| c.checked_sub(b'1').map(|i| unsafe { Self::transmute(i) })) .flatten() } #[inline] pub(crate) const fn bitboard(self) -> Bitboard { Bitboard(0xFF << ((self as u64) << 3)) } #[inline] pub(crate) const unsafe fn transmute(value: u8) -> Self { debug_assert!(value < 8); std::mem::transmute(value) } } impl std::fmt::Display for Rank { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.to_char()) } } /// A square of the board. #[rustfmt::skip] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] pub 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, } container!(Square, BySquare, 64); impl Square { #[inline] #[rustfmt::skip] pub fn all() -> [Self; 64] { [ Self::A1, Self::B1, Self::C1, Self::D1, Self::E1, Self::F1, Self::G1, Self::H1, Self::A2, Self::B2, Self::C2, Self::D2, Self::E2, Self::F2, Self::G2, Self::H2, Self::A3, Self::B3, Self::C3, Self::D3, Self::E3, Self::F3, Self::G3, Self::H3, Self::A4, Self::B4, Self::C4, Self::D4, Self::E4, Self::F4, Self::G4, Self::H4, Self::A5, Self::B5, Self::C5, Self::D5, Self::E5, Self::F5, Self::G5, Self::H5, Self::A6, Self::B6, Self::C6, Self::D6, Self::E6, Self::F6, Self::G6, Self::H6, Self::A7, Self::B7, Self::C7, Self::D7, Self::E7, Self::F7, Self::G7, Self::H7, Self::A8, Self::B8, Self::C8, Self::D8, Self::E8, Self::F8, Self::G8, Self::H8, ] } pub(crate) const fn from_index(index: u8) -> Option { if index < 64 { Some(unsafe { Self::transmute(index) }) } else { None } } #[inline] pub fn new(file: File, rank: Rank) -> Self { unsafe { Self::transmute(((rank as u8) << 3) | file as u8) } } #[inline] pub const fn file(self) -> File { unsafe { File::transmute((self as u8) & 7) } } #[inline] pub const fn rank(self) -> Rank { unsafe { Rank::transmute((self as u8) >> 3) } } #[inline] pub fn mirror(self) -> Self { let sq = self as u8; unsafe { Self::transmute(sq & 0b000111 | (!sq & 0b111000)) } } #[inline] pub(crate) const fn bitboard(self) -> Bitboard { Bitboard(1 << self as u8) } #[inline] #[rustfmt::skip] pub(crate) fn to_str(self) -> &'static str { match self { Self::A1 => "a1", Self::B1 => "b1", Self::C1 => "c1", Self::D1 => "d1", Self::E1 => "e1", Self::F1 => "f1", Self::G1 => "g1", Self::H1 => "h1", Self::A2 => "a2", Self::B2 => "b2", Self::C2 => "c2", Self::D2 => "d2", Self::E2 => "e2", Self::F2 => "f2", Self::G2 => "g2", Self::H2 => "h2", Self::A3 => "a3", Self::B3 => "b3", Self::C3 => "c3", Self::D3 => "d3", Self::E3 => "e3", Self::F3 => "f3", Self::G3 => "g3", Self::H3 => "h3", Self::A4 => "a4", Self::B4 => "b4", Self::C4 => "c4", Self::D4 => "d4", Self::E4 => "e4", Self::F4 => "f4", Self::G4 => "g4", Self::H4 => "h4", Self::A5 => "a5", Self::B5 => "b5", Self::C5 => "c5", Self::D5 => "d5", Self::E5 => "e5", Self::F5 => "f5", Self::G5 => "g5", Self::H5 => "h5", Self::A6 => "a6", Self::B6 => "b6", Self::C6 => "c6", Self::D6 => "d6", Self::E6 => "e6", Self::F6 => "f6", Self::G6 => "g6", Self::H6 => "h6", Self::A7 => "a7", Self::B7 => "b7", Self::C7 => "c7", Self::D7 => "d7", Self::E7 => "e7", Self::F7 => "f7", Self::G7 => "g7", Self::H7 => "h7", Self::A8 => "a8", Self::B8 => "b8", Self::C8 => "c8", Self::D8 => "d8", Self::E8 => "e8", Self::F8 => "f8", Self::G8 => "g8", Self::H8 => "h8", } } #[inline] pub(crate) fn from_str(s: &str) -> Option { match s.as_bytes() { [f, r] => Self::from_ascii(&[*f, *r]), _ => None, } } #[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)?)) } #[inline] pub(crate) const fn trans(self, direction: Direction) -> Option { if self.check_trans(direction) { Some(unsafe { self.trans_unchecked(direction) }) } else { None } } /// SAFETY: the translation must not move the square outside the board #[inline] pub(crate) const unsafe fn trans_unchecked(self, direction: Direction) -> Self { debug_assert!(self.check_trans(direction)); let i = self as u8; unsafe { Self::transmute(match direction { Direction::East => i.unchecked_add(1), Direction::NorthEast => i.unchecked_add(9), Direction::North => i.unchecked_add(8), Direction::NorthWest => i.unchecked_add(7), Direction::SouthEast => i.unchecked_sub(7), Direction::South => i.unchecked_sub(8), Direction::SouthWest => i.unchecked_sub(9), Direction::West => i.unchecked_sub(1), }) } } /// Returns `false` if the translation would move the square outside the board #[inline] const fn check_trans(self, direction: Direction) -> bool { let file = self.file() as u8; let rank = self.rank() as u8; match direction { Direction::East => file < 7, Direction::NorthEast => file < 7 && rank < 7, Direction::North => rank < 7, Direction::NorthWest => file > 0 && rank < 7, Direction::SouthEast => file < 7 && rank > 0, Direction::South => rank > 0, Direction::SouthWest => file > 0 && rank > 0, Direction::West => file > 0, } } #[inline] pub(crate) const unsafe fn transmute(value: u8) -> Self { debug_assert!(value < 64); std::mem::transmute(value) } } impl std::fmt::Display for Square { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.write_str(self.to_str()) } } /// An error while parsing a [`Square`]. #[derive(Debug)] pub struct ParseSquareError; impl std::fmt::Display for ParseSquareError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("invalid square syntax") } } impl std::error::Error for ParseSquareError {} impl std::str::FromStr for Square { type Err = ParseSquareError; #[inline] fn from_str(s: &str) -> Result { Self::from_str(s).ok_or(ParseSquareError) } } #[rustfmt::skip] #[allow(unused)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] pub(crate) enum OptionSquare { 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, None, } impl OptionSquare { #[inline] pub(crate) fn new(square: Option) -> OptionSquare { match square { Some(square) => Self::from_square(square), None => Self::None, } } #[inline] pub(crate) fn try_into_square(self) -> Option { unsafe { match self { Self::None => None, _ => Some(Square::transmute(self as u8)), } } } #[inline] pub(crate) fn from_square(square: Square) -> Self { unsafe { std::mem::transmute(square) } } } /// A type of piece. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] pub enum Role { Pawn = 1, Knight, Bishop, Rook, Queen, King, } impl Role { #[inline] pub fn all() -> [Self; 6] { [ Self::Pawn, Self::Knight, Self::Bishop, Self::Rook, Self::Queen, Self::King, ] } #[inline] pub(crate) fn to_char_uppercase(self) -> char { match self { Self::Pawn => 'P', Self::Knight => 'N', Self::Bishop => 'B', Self::Rook => 'R', Self::Queen => 'Q', Self::King => 'K', } } #[inline] pub(crate) fn to_char_lowercase(self) -> char { match self { Self::Pawn => 'p', Self::Knight => 'n', Self::Bishop => 'b', Self::Rook => 'r', Self::Queen => 'q', Self::King => 'k', } } #[inline] pub(crate) fn from_ascii(x: u8) -> Option { Some(match x { b'p' | b'P' => Self::Pawn, b'n' | b'N' => Self::Knight, b'b' | b'B' => Self::Bishop, b'r' | b'R' => Self::Rook, b'q' | b'Q' => Self::Queen, b'k' | b'K' => Self::King, _ => return None, }) } #[inline] pub(crate) unsafe fn transmute(i: u8) -> Self { debug_assert!(i > 0 && i <= 6, "got {i}"); unsafe { std::mem::transmute(i) } } } #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub(crate) struct ByRole(pub(crate) [T; 6]); #[allow(unused)] impl ByRole { #[inline] pub fn new(f: F) -> Self where F: FnMut(Role) -> T, { Self(Role::all().map(f)) } #[inline] pub fn get(&self, kind: Role) -> &T { unsafe { self.0.get_unchecked((kind as usize).unchecked_sub(1)) } } #[inline] pub fn get_mut(&mut self, kind: Role) -> &mut T { unsafe { self.0.get_unchecked_mut((kind as usize).unchecked_sub(1)) } } } impl ByRole where T: Copy, { #[inline] pub(crate) fn pawn(&self) -> T { *self.get(Role::Pawn) } #[inline] pub(crate) fn knight(&self) -> T { *self.get(Role::Knight) } #[inline] pub(crate) fn bishop(&self) -> T { *self.get(Role::Bishop) } #[inline] pub(crate) fn rook(&self) -> T { *self.get(Role::Rook) } #[inline] pub(crate) fn queen(&self) -> T { *self.get(Role::Queen) } #[inline] pub(crate) fn king(&self) -> T { *self.get(Role::King) } } /// A chess piece (i.e. its [`Role`] and [`Color`]). #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Piece { pub role: Role, pub color: Color, } #[derive(Clone, Copy)] #[repr(u8)] pub(crate) enum Direction { East, NorthEast, North, NorthWest, SouthEast, South, SouthWest, West, } container!(Direction, ByDirection, 8); impl Direction { #[inline] pub fn all() -> [Self; 8] { [ Self::East, Self::NorthEast, Self::North, Self::NorthWest, Self::SouthEast, Self::South, Self::SouthWest, Self::West, ] } #[inline] pub(crate) const fn from_index(index: u8) -> Option { if index < 8 { Some(unsafe { Self::transmute(index) }) } else { None } } #[inline] const unsafe fn transmute(value: u8) -> Self { debug_assert!(value < 8); std::mem::transmute(value) } } impl std::ops::Not for Direction { type Output = Self; #[inline] fn not(self) -> Self::Output { unsafe { Self::transmute(self as u8 ^ 0b111) } } } /// A side of the board. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] pub enum CastlingSide { /// King's side Short, /// Queen's side Long, } container!(CastlingSide, ByCastlingSide, 2); impl CastlingSide { #[inline] pub fn all() -> [Self; 2] { [Self::Short, Self::Long] } #[inline] pub(crate) fn rook_origin_file(self) -> File { match self { Self::Short => File::H, Self::Long => File::A, } } } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct CastlingRights(u8); impl CastlingRights { #[inline] pub(crate) fn new() -> Self { Self(0) } #[inline] pub(crate) const fn full() -> Self { Self(15) } #[inline] pub(crate) fn get(&self, color: Color, side: CastlingSide) -> bool { (self.0 & Self::mask(color, side)) != 0 } #[inline] pub(crate) fn set(&mut self, color: Color, side: CastlingSide) { self.0 |= Self::mask(color, side); } #[inline] pub(crate) fn unset(&mut self, color: Color, side: CastlingSide) { self.0 &= !Self::mask(color, side); } #[inline] pub(crate) fn mirror(&self) -> Self { Self(((self.0 & 3) << 2) | (self.0 >> 2)) } #[inline] const fn mask(color: Color, side: CastlingSide) -> u8 { match (color, side) { (Color::White, CastlingSide::Short) => 1, (Color::White, CastlingSide::Long) => 2, (Color::Black, CastlingSide::Short) => 4, (Color::Black, CastlingSide::Long) => 8, } } }