use crate::othello::{
Bitboard,
constants::{FILES, POSITIONS, POSITIONS_AS_NOTATION, RANKS},
};
use std::{error, fmt};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Position(pub(crate) u64);
impl Position {
pub(crate) fn new_unchecked(bitboard: u64) -> Self {
Self(bitboard)
}
#[must_use]
pub fn raw(self) -> u64 {
self.0
}
#[must_use]
pub fn rank(self) -> u8 {
(self.0.leading_zeros() / 8).try_into().unwrap()
}
#[must_use]
pub fn file(self) -> u8 {
(self.0.leading_zeros() % 8).try_into().unwrap()
}
#[must_use]
pub fn to_notation(self) -> String {
POSITIONS_AS_NOTATION[self.0.leading_zeros() as usize].to_string()
}
}
impl From<Position> for Bitboard {
fn from(position: Position) -> Self {
Bitboard::from(position.0)
}
}
impl From<Position> for u64 {
fn from(position: Position) -> Self {
position.0
}
}
impl TryFrom<(u8, u8)> for Position {
type Error = PositionError;
fn try_from(pair: (u8, u8)) -> Result<Self, Self::Error> {
let (rank, file) = pair;
if rank > 7 || file > 7 {
Err(PositionError::OutOfRange)
} else {
let bitboard = RANKS[rank as usize] & FILES[file as usize];
Ok(Position::new_unchecked(bitboard))
}
}
}
impl TryFrom<String> for Position {
type Error = PositionError;
fn try_from(text: String) -> Result<Self, Self::Error> {
Position::try_from(text.as_ref())
}
}
impl TryFrom<&str> for Position {
type Error = PositionError;
fn try_from(text: &str) -> Result<Self, Self::Error> {
let text = text.to_lowercase();
let bitboard = POSITIONS_AS_NOTATION
.iter()
.position(|position| position == &text)
.map(|index| POSITIONS[index])
.ok_or(PositionError::InvalidNotation)?;
Ok(Position::new_unchecked(bitboard))
}
}
impl TryFrom<u64> for Position {
type Error = PositionError;
fn try_from(bitboard: u64) -> Result<Self, Self::Error> {
if bitboard.is_power_of_two() {
Ok(Position::new_unchecked(bitboard))
} else {
Err(PositionError::ExpectedSingleBit)
}
}
}
impl TryFrom<Bitboard> for Position {
type Error = PositionError;
fn try_from(bitboard: Bitboard) -> Result<Self, Self::Error> {
Position::try_from(bitboard.raw())
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum PositionError {
ExpectedSingleBit,
InvalidNotation,
OutOfRange,
}
impl fmt::Display for PositionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ExpectedSingleBit => write!(f, "expected exactly one set bit"),
Self::InvalidNotation => write!(f, "invalid notation"),
Self::OutOfRange => write!(f, "rank or file out of range (expected [0, 8))"),
}
}
}
impl error::Error for PositionError {}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Position {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = u64::deserialize(deserializer)?;
Position::try_from(value)
.map_err(|_| serde::de::Error::custom("Position must have exactly one bit set"))
}
}