use std::{fmt::Display, str::FromStr};
use crate::Error;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum CoordState {
Empty,
Black,
White,
}
impl CoordState {
#[inline(always)]
pub fn is_empty(&self) -> bool {
*self == CoordState::Empty
}
#[inline(always)]
pub fn is_stone(&self) -> bool {
!self.is_empty()
}
#[inline(always)]
pub fn is_black(&self) -> bool {
*self == CoordState::Black
}
#[inline(always)]
pub fn is_white(&self) -> bool {
*self == CoordState::White
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Coord<const SZ: usize> {
x: u8,
y: u8,
}
pub(crate) const COORD_NULL_VAL: u8 = 0xff;
pub type Coord15 = Coord<15>;
pub type Coord20 = Coord<20>;
impl<const SZ: usize> Coord<SZ> {
const SIZE: u8 = if SZ >= 5 && SZ <= 26 {
SZ as u8
} else {
panic!()
};
const COORD_NULL: Self = Self {
x: COORD_NULL_VAL,
y: COORD_NULL_VAL,
};
#[inline(always)]
pub fn new() -> Self {
Self::COORD_NULL
}
#[inline(always)]
pub fn from(x: u8, y: u8) -> Self {
if x < Self::SIZE && y < Self::SIZE {
Self { x, y }
} else {
Self::COORD_NULL
}
}
#[inline(always)]
pub(crate) unsafe fn build_unchecked(x: u8, y: u8) -> Self {
Self { x, y }
}
#[inline(always)]
pub fn is_null(&self) -> bool {
*self == Self::COORD_NULL
}
#[inline(always)]
pub fn is_real(&self) -> bool {
!self.is_null()
}
#[inline(always)]
pub fn get(&self) -> Option<(u8, u8)> {
if self.is_real() {
Some((self.x, self.y))
} else {
None
}
}
#[inline(always)]
pub fn unwrap(&self) -> (u8, u8) {
self.get().unwrap()
}
#[inline(always)]
pub unsafe fn get_unchecked(&self) -> (u8, u8) {
(self.x, self.y)
}
#[inline(always)]
pub fn set(&mut self, x: u8, y: u8) -> Result<(), Error> {
if x < Self::SIZE && y < Self::SIZE {
self.x = x;
self.y = y;
Ok(())
} else {
Err(Error::InvalidCoord)
}
}
#[inline(always)]
pub fn set_null(&mut self) {
*self = Self::COORD_NULL;
}
#[inline(always)]
#[allow(dead_code)]
pub(crate) unsafe fn set_unchecked(&mut self, x: u8, y: u8) {
self.x = x;
self.y = y;
}
}
impl<const SZ: usize> Default for Coord<SZ> {
fn default() -> Self {
Self::new()
}
}
impl<const SZ: usize> FromStr for Coord<SZ> {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::parse_str(s).ok_or(Error::ParseError)?.0)
}
}
impl<const SZ: usize> Display for Coord<SZ> {
#[inline(always)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_real() {
write!(f, "{}{}", coord_x_letter(self.x), self.y + 1)
} else {
write!(f, "-")
}
}
}
impl<const SZ: usize> Coord<SZ> {
#[inline]
pub(crate) fn parse_str(str_coords: &str) -> Option<(Self, usize)> {
const ALPHABET: &str = "abcdefghijklmnopqrstuvwxyz";
let alphabet = &ALPHABET[..SZ];
let mut len_checked: usize = 0;
loop {
let str_rem = &str_coords[len_checked..];
let mut itr = str_rem.chars().enumerate();
let loc_alpha = itr.find_map(|(i, c)| alphabet.find(c).map(|x| (i, x)));
if let Some((i, x)) = loc_alpha {
len_checked += i + 1;
let mut num: Option<u32> = None;
for (_, ch) in itr {
if !ch.is_ascii_digit() {
break;
}
if num.is_none() {
num = ch.to_digit(10);
} else {
num.replace(10 * num.unwrap() + ch.to_digit(10).unwrap());
}
len_checked += 1;
}
if let Some(n) = num {
if n == 0 || n > SZ as u32 {
continue;
}
return Some((
Self {
x: x as u8,
y: (n - 1) as u8,
},
len_checked,
));
}
} else {
return None;
}
}
}
}
#[inline(always)]
pub(crate) fn coord_x_letter(x: u8) -> char {
if x < 26 {
unsafe { char::from_u32_unchecked(('a' as u32) + x as u32) }
} else {
'?'
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum Rotation {
Original, Clockwise, CentralSymmetric, Counterclockwise, FlipHorizontal, FlipLeftDiagonal, FlipVertical, FlipRightDiagonal, }
impl<const SZ: usize> Coord<SZ> {
#[inline(always)]
pub fn rotate(&self, rotation: Rotation) -> Coord<SZ> {
let (mut x, y) = if let Some(coord) = self.get() {
coord
} else {
return *self;
};
let bnd = SZ as u8 - 1u8;
let (fl, ro) = rotation.fl_ro();
if fl == 1_u8 {
x = bnd - x;
}
let (x, y) = match ro {
0b01_u8 => (y, bnd - x),
0b10_u8 => (bnd - x, bnd - y),
0b11_u8 => (bnd - y, x),
_ => (x, y),
};
unsafe { Coord::<SZ>::build_unchecked(x, y) }
}
#[inline(always)]
pub fn offset(&self, offset_x: i8, offset_y: i8) -> Option<Coord<SZ>> {
let (x, y) = if let Some(coord) = self.get() {
coord
} else {
return Some(*self);
};
let (x_new, y_new) = (
(x as i8).checked_add(offset_x)?,
(y as i8).checked_add(offset_y)?,
);
if x_new < 0i8 || y_new < 0i8 {
return None;
}
let (x_new, y_new) = (x_new as u8, y_new as u8);
if x_new < Self::SIZE && y_new < Self::SIZE {
Some(Coord { x: x_new, y: y_new })
} else {
None
}
}
}
use Rotation::*;
impl Rotation {
#[inline]
pub fn add(&self, later: Self) -> Self {
let (fl1, ro1) = self.fl_ro();
let (fl2, ro2) = later.fl_ro();
let fl = (fl1 + fl2) & 0b1_u8;
let ro = if fl2 == 1 {
0b100_u8 - ro1 + ro2
} else {
ro1 + ro2
};
let ro = ro & 0b11_u8;
Self::from_fl_ro(fl, ro)
}
#[inline]
pub fn reverse(&self) -> Self {
let (fl, mut ro) = self.fl_ro();
if fl == 0_u8 {
ro = (0b100_u8 - ro) & 0b11_u8;
}
Self::from_fl_ro(fl, ro)
}
#[inline(always)]
fn fl_ro(&self) -> (u8, u8) {
let fl = match *self {
FlipHorizontal | FlipLeftDiagonal | FlipVertical | FlipRightDiagonal => 0b1_u8,
_ => 0b0_u8,
};
let ro = match *self {
Clockwise | FlipLeftDiagonal => 0b01_u8,
CentralSymmetric | FlipVertical => 0b10_u8,
Counterclockwise | FlipRightDiagonal => 0b11_u8,
_ => 0b00_u8,
};
(fl, ro)
}
#[inline(always)]
fn from_fl_ro(fl: u8, ro: u8) -> Self {
match (fl << 2_u8) | ro {
0b001_u8 => Clockwise,
0b010_u8 => CentralSymmetric,
0b011_u8 => Counterclockwise,
0b100_u8 => FlipHorizontal,
0b101_u8 => FlipLeftDiagonal,
0b110_u8 => FlipVertical,
0b111_u8 => FlipRightDiagonal,
_ => Original,
}
}
}