use std::{fmt::Display, ops};
use crate::errors::{PositionInvalidError, PositionOutOfRangeError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Position {
pub col: u8,
pub row: u8,
}
impl Position {
pub fn new(col: u8, row: u8) -> Result<Position, PositionOutOfRangeError> {
if col >= 8 || row >= 8 {
return Err(PositionOutOfRangeError::new(col, row));
}
Ok(Position { col, row })
}
pub fn from_string(s: &str) -> Result<Position, PositionInvalidError> {
if s.len() != 2 {
return Err(PositionInvalidError::new(s.to_string()));
}
let col = s.chars().nth(0).unwrap() as u8 - 'a' as u8;
let row = s.chars().nth(1).unwrap() as u8 - '1' as u8;
Position::new(col, row).map_err(|_| PositionInvalidError::new(s.to_string()))
}
pub fn to_bitboard(&self) -> u64 {
1 << (self.row * 8 + self.col)
}
pub fn from_bitboard(bitboard: u64) -> Vec<Position> {
let mut positions = Vec::new();
let mut bitboard = bitboard;
while bitboard != 0 {
let pos = bitboard.trailing_zeros() as u8;
positions.push(Position::new(pos % 8, pos / 8).unwrap());
bitboard &= bitboard - 1;
}
positions
}
pub fn direction(&self, other: &Position) -> (i8, i8) {
let mut col = other.col as i8 - self.col as i8;
let mut row = other.row as i8 - self.row as i8;
col = if col == 0 { 0 } else { col / col.abs() };
row = if row == 0 { 0 } else { row / row.abs() };
(col, row)
}
}
impl ops::Add<(i8, i8)> for &Position {
type Output = Position;
fn add(self, other: (i8, i8)) -> Position {
Position::new(
(self.col as i8 + other.0) as u8,
(self.row as i8 + other.1) as u8,
)
.unwrap()
}
}
impl ops::Sub<&Position> for &Position {
type Output = (i8, i8);
fn sub(self, other: &Position) -> (i8, i8) {
(
self.col as i8 - other.col as i8,
self.row as i8 - other.row as i8,
)
}
}
impl ops::Sub<(i8, i8)> for &Position {
type Output = Position;
fn sub(self, other: (i8, i8)) -> Position {
Position {
col: (self.col as i8 - other.0) as u8,
row: (self.row as i8 - other.1) as u8,
}
}
}
impl Display for Position {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}{}",
('a' as u8 + self.col) as char,
('1' as u8 + self.row) as char
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_position() {
let pos = Position::new(0, 0).unwrap();
assert_eq!(pos.to_string(), "a1");
let pos = Position::new(7, 7).unwrap();
assert_eq!(pos.to_string(), "h8");
let pos = Position::from_string("a1").unwrap();
assert_eq!(pos.col, 0);
assert_eq!(pos.row, 0);
let pos = Position::from_string("h8").unwrap();
assert_eq!(pos.col, 7);
assert_eq!(pos.row, 7);
}
#[test]
fn test_position_invalid() {
let pos = Position::new(8, 0);
assert!(pos.is_err());
let pos = Position::new(0, 8);
assert!(pos.is_err());
let pos = Position::from_string("i1");
assert!(pos.is_err());
let pos = Position::from_string("a9");
assert!(pos.is_err());
let pos = Position::from_string("a");
assert!(pos.is_err());
let pos = Position::from_string("a12");
assert!(pos.is_err());
}
#[test]
fn test_position_to_bitboard() {
let pos = Position::new(0, 0).unwrap();
assert_eq!(pos.to_bitboard(), 0x0000000000000001);
let pos = Position::new(7, 7).unwrap();
assert_eq!(pos.to_bitboard(), 0x8000000000000000);
}
#[test]
fn test_position_from_bitboard() {
let positions = Position::from_bitboard(0x0000000000000001);
let pos = positions.first().unwrap();
assert_eq!(pos.to_string(), "a1");
assert_eq!(positions.len(), 1);
let positions = Position::from_bitboard(0x8000000000000000);
let pos = positions.first().unwrap();
assert_eq!(pos.to_string(), "h8");
assert_eq!(positions.len(), 1);
let positions = Position::from_bitboard(0xFFFFFFFFFFFFFFFF);
assert_eq!(positions.len(), 64);
}
#[test]
fn test_position_sub() {
let pos1 = Position::new(1, 1).unwrap();
let pos2 = Position::new(0, 0).unwrap();
let pos3 = &pos1 - &pos2;
assert_eq!(pos3.0, 1);
assert_eq!(pos3.1, 1);
}
#[test]
fn test_position_add_tuple() {
let pos1 = Position::new(0, 0).unwrap();
let pos2 = (1, 1);
let pos3 = &pos1 + pos2;
assert_eq!(pos3.to_string(), "b2");
}
#[test]
fn test_position_sub_tuple() {
let pos1 = Position::new(1, 1).unwrap();
let pos2 = (1, 1);
let pos3 = &pos1 - pos2;
assert_eq!(pos3.to_string(), "a1");
}
#[test]
fn test_direction() {
let pos1 = Position::new(0, 0).unwrap();
let pos2 = Position::new(1, 1).unwrap();
let dir = pos1.direction(&pos2);
assert_eq!(dir, (1, 1));
}
}