use std::fmt;
use crate::color::Color;
use crate::error::ChessAIError;
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Square(u8);
impl Square {
pub(crate) const COUNT: usize = 90;
#[inline]
pub(crate) const fn new_unchecked(raw: u8) -> Square {
debug_assert!(raw < 90);
Square(raw)
}
#[inline]
pub const fn from_rank_file(rank: u8, file: u8) -> Option<Square> {
if rank < 10 && file < 9 { Some(Square(rank * 9 + file)) } else { None }
}
#[inline]
pub(crate) const fn raw(self) -> u8 { self.0 }
#[inline]
pub const fn rank(self) -> u8 { self.0 / 9 }
#[inline]
pub const fn file(self) -> u8 { self.0 % 9 }
#[inline]
pub(crate) const fn mirror_file(self) -> Square { Square(self.rank() * 9 + (8 - self.file())) }
#[inline]
pub(crate) const fn flip_rank(self) -> Square { Square((9 - self.rank()) * 9 + self.file()) }
#[inline]
pub const fn is_in_palace(self, color: Color) -> bool {
let f = self.file();
if f < 3 || f > 5 {
return false;
}
match color {
Color::Red => self.rank() <= 2,
Color::Black => self.rank() >= 7,
}
}
pub fn from_iccs(s: &str) -> Result<Square, ChessAIError> {
let b = s.as_bytes();
if b.len() != 2 {
return Err(ChessAIError::BadIccsSquare(s.to_string()));
}
let file = match b[0] {
c @ b'a'..=b'i' => c - b'a',
c @ b'A'..=b'I' => c - b'A',
_ => return Err(ChessAIError::BadIccsSquare(s.to_string())),
};
let rank = match b[1] {
c @ b'0'..=b'9' => c - b'0',
_ => return Err(ChessAIError::BadIccsSquare(s.to_string())),
};
Square::from_rank_file(rank, file).ok_or_else(|| ChessAIError::BadIccsSquare(s.to_string()))
}
#[inline]
pub fn to_iccs(self) -> String {
let file_ch = (b'a' + self.file()) as char;
let rank_ch = (b'0' + self.rank()) as char;
let mut s = String::with_capacity(2);
s.push(file_ch);
s.push(rank_ch);
s
}
}
impl fmt::Display for Square {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.to_iccs()) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rank_file_roundtrip() {
for r in 0u8..10 {
for f in 0u8..9 {
let sq = Square::from_rank_file(r, f).unwrap();
assert_eq!(sq.rank(), r);
assert_eq!(sq.file(), f);
assert_eq!(sq.raw(), r * 9 + f);
}
}
}
#[test]
fn iccs_roundtrip() {
for raw in 0..90u8 {
let sq = Square::new_unchecked(raw);
let s = sq.to_iccs();
assert_eq!(Square::from_iccs(&s).unwrap(), sq);
}
}
#[test]
fn palace_membership() {
assert!(Square::from_iccs("d0").unwrap().is_in_palace(Color::Red));
assert!(Square::from_iccs("e1").unwrap().is_in_palace(Color::Red));
assert!(!Square::from_iccs("a0").unwrap().is_in_palace(Color::Red));
assert!(Square::from_iccs("e9").unwrap().is_in_palace(Color::Black));
assert!(!Square::from_iccs("e0").unwrap().is_in_palace(Color::Black));
}
#[test]
fn flip_involution() {
for raw in 0..90u8 {
let sq = Square::new_unchecked(raw);
assert_eq!(sq.flip_rank().flip_rank(), sq);
assert_eq!(sq.mirror_file().mirror_file(), sq);
}
}
}