1
0
Fork 0
eschac/src/board.rs

700 lines
17 KiB
Rust

//! Chessboard vocabulary.
use crate::bitboard::*;
macro_rules! container {
($v:vis, $a:ident, $b:ident, $n:literal) => {
#[doc = "Container with values for each [`"]
#[doc = stringify!($a)]
#[doc = "`]."]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
$v struct $b<T>(pub(crate) [T; $n]);
#[allow(unused)]
impl<T> $b<T> {
#[inline]
pub const fn new(values: [T; $n]) -> Self {
Self(values)
}
#[inline]
pub const fn get(&self, k: $a) -> &T {
&self.0[k as usize]
}
#[inline]
pub const fn get_mut(&mut self, k: $a) -> &mut T {
&mut self.0[k as usize]
}
#[inline]
pub fn with<F>(f: F) -> Self
where
F: FnMut($a) -> T,
{
Self($a::all().map(f))
}
}
};
}
/// The players.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum Color {
White,
Black,
}
container!(pub, Color, ByColor, 2);
impl Color {
#[inline]
pub const 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,
}
}
#[inline]
pub(crate) unsafe fn new_unchecked(color: u8) -> Self {
debug_assert!(color < 2);
unsafe { core::mem::transmute(color) }
}
}
impl core::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!(pub(crate), File, ByFile, 8);
impl File {
#[inline]
pub const fn all() -> [Self; 8] {
[
Self::A,
Self::B,
Self::C,
Self::D,
Self::E,
Self::F,
Self::G,
Self::H,
]
}
#[inline]
pub const fn new(index: u8) -> Option<Self> {
if index < 8 {
Some(unsafe { Self::new_unchecked(index) })
} else {
None
}
}
/// **Safety:** The caller must ensure that `index < 8`.
#[inline]
pub const unsafe fn new_unchecked(index: u8) -> Self {
debug_assert!(index < 8);
unsafe { core::mem::transmute(index) }
}
#[inline]
pub const fn to_ascii(self) -> u8 {
self as u8 + b'a'
}
#[inline]
pub const fn from_ascii(file: u8) -> Option<Self> {
if file <= b'h' {
match file.checked_sub(b'a') {
Some(i) => Some(unsafe { Self::new_unchecked(i) }),
None => None,
}
} else {
None
}
}
#[inline]
pub const fn to_char(self) -> char {
self.to_ascii() as char
}
#[inline]
pub const fn from_char(file: char) -> Option<Self> {
if file as u32 <= 255 {
Self::from_ascii(file as u8)
} else {
None
}
}
#[inline]
pub const fn bitboard(self) -> Bitboard {
Bitboard(0x0101010101010101 << (self as u8))
}
}
impl core::fmt::Display for File {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::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!(pub(crate), Rank, ByRank, 8);
impl Rank {
#[inline]
pub const fn all() -> [Self; 8] {
[
Self::First,
Self::Second,
Self::Third,
Self::Fourth,
Self::Fifth,
Self::Sixth,
Self::Seventh,
Self::Eighth,
]
}
#[inline]
pub const fn new(index: u8) -> Option<Self> {
if index < 8 {
Some(unsafe { Self::new_unchecked(index) })
} else {
None
}
}
/// **Safety:** The caller must ensure that `index < 8`.
#[inline]
pub const unsafe fn new_unchecked(index: u8) -> Self {
debug_assert!(index < 8);
unsafe { core::mem::transmute(index) }
}
#[inline]
pub const fn to_ascii(self) -> u8 {
self as u8 + b'1'
}
#[inline]
pub const fn from_ascii(rank: u8) -> Option<Self> {
if rank <= b'8' {
match rank.checked_sub(b'1') {
Some(i) => Some(unsafe { Self::new_unchecked(i) }),
None => None,
}
} else {
None
}
}
#[inline]
pub const fn to_char(self) -> char {
self.to_ascii() as char
}
#[inline]
pub const fn from_char(rank: char) -> Option<Self> {
if rank as u32 <= 255 {
Self::from_ascii(rank as u8)
} else {
None
}
}
#[inline]
pub const fn bitboard(self) -> Bitboard {
Bitboard(0xFF << ((self as u64) << 3))
}
#[inline]
pub const fn mirror(self) -> Self {
unsafe { Self::new_unchecked(!(self as u8)) }
}
}
impl core::fmt::Display for Rank {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::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!(pub, Square, BySquare, 64);
impl Square {
#[inline]
#[rustfmt::skip]
pub const 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 const fn new(index: u8) -> Option<Self> {
if index < 64 {
Some(unsafe { Self::new_unchecked(index) })
} else {
None
}
}
/// **Safety:** The caller must ensure that `index < 64`.
#[inline]
pub const unsafe fn new_unchecked(index: u8) -> Self {
debug_assert!(index < 64);
unsafe { core::mem::transmute(index) }
}
#[inline]
pub const fn from_coords(file: File, rank: Rank) -> Self {
unsafe { Self::new_unchecked(((rank as u8) << 3) | file as u8) }
}
#[inline]
pub const fn file(self) -> File {
unsafe { File::new_unchecked((self as u8) & 7) }
}
#[inline]
pub const fn rank(self) -> Rank {
unsafe { Rank::new_unchecked((self as u8) >> 3) }
}
#[inline]
pub const fn bitboard(self) -> Bitboard {
Bitboard(1 << self as u8)
}
#[inline]
pub const fn mirror(self) -> Self {
let sq = self as u8;
unsafe { Self::new_unchecked(sq & 0b000111 | (!sq & 0b111000)) }
}
#[inline]
pub(crate) const fn trans(self, direction: Direction) -> Option<Self> {
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::new_unchecked(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,
}
}
}
impl core::fmt::Display for Square {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use core::fmt::Write;
f.write_char(self.file().to_char())?;
f.write_char(self.rank().to_char())?;
Ok(())
}
}
/// An error while parsing a [`Square`].
#[derive(Debug)]
pub struct ParseSquareError;
impl core::fmt::Display for ParseSquareError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("invalid square syntax")
}
}
impl core::error::Error for ParseSquareError {}
impl core::str::FromStr for Square {
type Err = ParseSquareError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
(|| match s.as_bytes() {
[f, r] => Some(Self::from_coords(
File::from_ascii(*f)?,
Rank::from_ascii(*r)?,
)),
_ => None,
})()
.ok_or(ParseSquareError)
}
}
/// 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 const 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<Self> {
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 >= 1 && i < 7);
unsafe { core::mem::transmute(i) }
}
}
/// Container with values for each [`Role`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ByRole<T>(pub(crate) [T; 6]);
#[allow(unused)]
impl<T> ByRole<T> {
#[inline]
pub const fn new(values: [T; 6]) -> Self {
Self(values)
}
#[inline]
pub const fn get(&self, k: Role) -> &T {
let i = unsafe { (k as usize).unchecked_sub(1) };
unsafe { core::hint::assert_unchecked(i < 6) };
&self.0[i]
}
#[inline]
pub const fn get_mut(&mut self, k: Role) -> &mut T {
let i = unsafe { (k as usize).unchecked_sub(1) };
unsafe { core::hint::assert_unchecked(i < 6) };
&mut self.0[i]
}
#[inline]
pub fn with<F>(f: F) -> Self
where
F: FnMut(Role) -> T,
{
Self(Role::all().map(f))
}
}
impl<T> ByRole<T>
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, PartialOrd, Ord, 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!(pub(crate), 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<Self> {
if index < 8 {
Some(unsafe { Self::transmute(index) })
} else {
None
}
}
#[inline]
const unsafe fn transmute(value: u8) -> Self {
debug_assert!(value < 8);
unsafe { core::mem::transmute(value) }
}
}
impl core::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!(pub, 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,
}
}
}