Turns out I was doing the starting position really wrong. In an upcoming commit, I will implement FEN output for Positions. While doing that work, I found several issues that were causing the output of the FEN formatter to return garbage. Implement a handful of unit tests to track down the errors. Rename Shape::_ascii_representation() → Shape::to_ascii. Implement to_ascii() on Piece.
320 lines
7.3 KiB
Rust
320 lines
7.3 KiB
Rust
// Eryn Wells <eryn@erynwells.me>
|
|
|
|
use crate::{
|
|
display::{ASCIIDisplay, FENDisplay, UnicodeDisplay},
|
|
Square,
|
|
};
|
|
use std::fmt;
|
|
use std::slice::Iter;
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub enum Color {
|
|
White = 0,
|
|
Black = 1,
|
|
}
|
|
|
|
impl Color {
|
|
pub fn iter() -> impl Iterator<Item = Color> {
|
|
[Color::White, Color::Black].into_iter()
|
|
}
|
|
|
|
pub fn other(&self) -> Color {
|
|
match self {
|
|
Color::White => Color::Black,
|
|
Color::Black => Color::White,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Color {
|
|
fn default() -> Self {
|
|
Color::White
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub enum Shape {
|
|
Pawn = 0,
|
|
Knight = 1,
|
|
Bishop = 2,
|
|
Rook = 3,
|
|
Queen = 4,
|
|
King = 5,
|
|
}
|
|
|
|
impl Shape {
|
|
pub fn iter() -> Iter<'static, Shape> {
|
|
const ALL_SHAPES: [Shape; 6] = [
|
|
Shape::Pawn,
|
|
Shape::Knight,
|
|
Shape::Bishop,
|
|
Shape::Rook,
|
|
Shape::Queen,
|
|
Shape::King,
|
|
];
|
|
|
|
ALL_SHAPES.iter()
|
|
}
|
|
|
|
pub fn promotable() -> Iter<'static, Shape> {
|
|
const PROMOTABLE_SHAPES: [Shape; 4] =
|
|
[Shape::Queen, Shape::Rook, Shape::Bishop, Shape::Knight];
|
|
|
|
PROMOTABLE_SHAPES.iter()
|
|
}
|
|
|
|
fn to_ascii(&self) -> char {
|
|
match self {
|
|
Shape::Pawn => 'P',
|
|
Shape::Knight => 'N',
|
|
Shape::Bishop => 'B',
|
|
Shape::Rook => 'R',
|
|
Shape::Queen => 'Q',
|
|
Shape::King => 'K',
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
pub struct TryFromError;
|
|
|
|
impl TryFrom<char> for Shape {
|
|
type Error = TryFromError;
|
|
|
|
fn try_from(value: char) -> Result<Self, Self::Error> {
|
|
match value {
|
|
'P' | 'p' => Ok(Shape::Pawn),
|
|
'N' | 'n' => Ok(Shape::Knight),
|
|
'B' | 'b' => Ok(Shape::Bishop),
|
|
'R' | 'r' => Ok(Shape::Rook),
|
|
'Q' | 'q' => Ok(Shape::Queen),
|
|
'K' | 'k' => Ok(Shape::King),
|
|
_ => Err(TryFromError),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&str> for Shape {
|
|
type Error = TryFromError;
|
|
|
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
|
let first_char = value.chars().nth(0).ok_or(TryFromError)?;
|
|
Shape::try_from(first_char)
|
|
}
|
|
}
|
|
|
|
impl Into<char> for &Shape {
|
|
fn into(self) -> char {
|
|
self.to_ascii()
|
|
}
|
|
}
|
|
|
|
impl Into<char> for Shape {
|
|
fn into(self) -> char {
|
|
self.to_ascii()
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Shape {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let self_char: char = self.into();
|
|
write!(f, "{}", self_char)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub struct Piece {
|
|
color: Color,
|
|
shape: Shape,
|
|
}
|
|
|
|
macro_rules! piece_constructor {
|
|
($func_name:ident, $type:tt) => {
|
|
pub fn $func_name(color: Color) -> Piece {
|
|
Piece {
|
|
color,
|
|
shape: Shape::$type,
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! is_shape {
|
|
($func_name:ident, $shape:ident) => {
|
|
pub fn $func_name(&self) -> bool {
|
|
self.shape == Shape::$shape
|
|
}
|
|
};
|
|
}
|
|
|
|
impl Piece {
|
|
pub fn new(color: Color, shape: Shape) -> Piece {
|
|
Piece { color, shape }
|
|
}
|
|
|
|
piece_constructor!(pawn, Pawn);
|
|
piece_constructor!(knight, Knight);
|
|
piece_constructor!(bishop, Bishop);
|
|
piece_constructor!(rook, Rook);
|
|
piece_constructor!(queen, Queen);
|
|
piece_constructor!(king, King);
|
|
|
|
pub fn color(&self) -> Color {
|
|
self.color
|
|
}
|
|
|
|
pub fn shape(&self) -> Shape {
|
|
self.shape
|
|
}
|
|
|
|
is_shape!(is_pawn, Pawn);
|
|
is_shape!(is_knight, Knight);
|
|
is_shape!(is_bishop, Bishop);
|
|
is_shape!(is_rook, Rook);
|
|
is_shape!(is_queen, Queen);
|
|
is_shape!(is_king, King);
|
|
|
|
pub fn to_ascii(&self) -> char {
|
|
self.shape.to_ascii()
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Piece {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
UnicodeDisplay::fmt(self, f)
|
|
}
|
|
}
|
|
|
|
impl ASCIIDisplay for Piece {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{}", Into::<char>::into(self.shape()))
|
|
}
|
|
}
|
|
|
|
impl UnicodeDisplay for Piece {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"{}",
|
|
match (self.color, self.shape) {
|
|
(Color::Black, Shape::Pawn) => '♟',
|
|
(Color::Black, Shape::Knight) => '♞',
|
|
(Color::Black, Shape::Bishop) => '♝',
|
|
(Color::Black, Shape::Rook) => '♜',
|
|
(Color::Black, Shape::Queen) => '♛',
|
|
(Color::Black, Shape::King) => '♚',
|
|
(Color::White, Shape::Pawn) => '♙',
|
|
(Color::White, Shape::Knight) => '♘',
|
|
(Color::White, Shape::Bishop) => '♗',
|
|
(Color::White, Shape::Rook) => '♖',
|
|
(Color::White, Shape::Queen) => '♕',
|
|
(Color::White, Shape::King) => '♔',
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
impl FENDisplay for Piece {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let ascii = self.shape().to_ascii();
|
|
write!(
|
|
f,
|
|
"{}",
|
|
match self.color {
|
|
Color::White => ascii.to_ascii_uppercase(),
|
|
Color::Black => ascii.to_ascii_lowercase(),
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub struct PlacedPiece {
|
|
piece: Piece,
|
|
square: Square,
|
|
}
|
|
|
|
macro_rules! is_shape {
|
|
($func_name:ident, $shape:ident) => {
|
|
pub fn $func_name(&self) -> bool {
|
|
self.piece().shape == Shape::$shape
|
|
}
|
|
};
|
|
}
|
|
|
|
impl PlacedPiece {
|
|
pub const fn new(piece: Piece, square: Square) -> PlacedPiece {
|
|
PlacedPiece { piece, square }
|
|
}
|
|
|
|
#[inline]
|
|
pub fn piece(&self) -> &Piece {
|
|
&self.piece
|
|
}
|
|
|
|
#[inline]
|
|
pub fn square(&self) -> Square {
|
|
self.square
|
|
}
|
|
|
|
#[inline]
|
|
pub fn color(&self) -> Color {
|
|
self.piece.color
|
|
}
|
|
|
|
#[inline]
|
|
pub fn shape(&self) -> Shape {
|
|
self.piece.shape
|
|
}
|
|
|
|
is_shape!(is_pawn, Pawn);
|
|
is_shape!(is_knight, Knight);
|
|
is_shape!(is_bishop, Bishop);
|
|
is_shape!(is_rook, Rook);
|
|
is_shape!(is_queen, Queen);
|
|
is_shape!(is_king, King);
|
|
|
|
pub fn is_kingside_rook(&self) -> bool {
|
|
self.is_rook()
|
|
&& match self.color() {
|
|
Color::White => self.square == Square::H1,
|
|
Color::Black => self.square == Square::H8,
|
|
}
|
|
}
|
|
|
|
pub fn is_queenside_rook(&self) -> bool {
|
|
self.is_rook()
|
|
&& match self.color() {
|
|
Color::White => self.square == Square::A1,
|
|
Color::Black => self.square == Square::A8,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for PlacedPiece {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}{}", self.piece, self.square)
|
|
}
|
|
}
|
|
|
|
impl From<(&Square, &Piece)> for PlacedPiece {
|
|
fn from(value: (&Square, &Piece)) -> Self {
|
|
PlacedPiece::new(*value.1, *value.0)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn shape_try_from() {
|
|
assert_eq!(Shape::try_from('p'), Ok(Shape::Pawn));
|
|
assert_eq!(Shape::try_from("p"), Ok(Shape::Pawn));
|
|
}
|
|
|
|
#[test]
|
|
fn shape_into_char() {
|
|
assert_eq!(<Shape as Into<char>>::into(Shape::Pawn) as char, 'P');
|
|
}
|
|
}
|