use anyhow::anyhow;
use std::fmt;
use std::str::FromStr;
use super::Direction;
use super::bits::{for_each_bit_idx, single_bit_index};
use super::constants::{ASCII_LETTER_A, BOARD_HEIGHT, BOARD_SIZE, BOARD_WIDTH};
#[derive(Hash, Eq, PartialEq, Clone, Copy, Default, Ord, PartialOrd)]
pub struct Square(u8);
impl Square {
pub fn new(column: char, row: usize) -> Self {
let index = (column as u8 - ASCII_LETTER_A) + (BOARD_HEIGHT - row) as u8 * 8;
Square(index)
}
pub fn from_index(index: u8) -> Self {
Square(index)
}
pub fn from_bit_board(board: u64) -> Self {
Square(single_bit_index(board as u128) as u8)
}
pub fn from_multiple_bits(board: u64) -> Vec<Self> {
let mut squares = Vec::with_capacity(board.count_ones() as usize);
for_each_bit_idx(board, |idx| squares.push(Square::from_index(idx as u8)));
squares
}
pub fn as_bit_board(&self) -> u64 {
1 << self.0
}
pub fn get_index(&self) -> usize {
self.0 as usize
}
pub fn invert(&self) -> Self {
Square((BOARD_HEIGHT * BOARD_WIDTH) as u8 - 1 - self.0)
}
pub fn vertical_symmetry(&self) -> Self {
Square(self.0 ^ 7)
}
pub fn column_char(&self) -> char {
let index = self.0 as usize;
let column = index % BOARD_WIDTH;
(ASCII_LETTER_A + column as u8) as char
}
pub fn row(&self) -> u8 {
let index = self.0 as usize;
let row = BOARD_HEIGHT - (index / BOARD_WIDTH);
row as u8
}
pub fn shift_in_direction(&self, direction: Direction) -> Self {
let new_index = match direction {
Direction::Up => self.0 - BOARD_WIDTH as u8,
Direction::Right => self.0 + 1,
Direction::Down => self.0 + BOARD_WIDTH as u8,
Direction::Left => self.0 - 1,
};
Self::from_index(new_index)
}
pub fn can_shift_in_direction(&self, direction: Direction) -> bool {
match direction {
Direction::Up => self.0 >= BOARD_WIDTH as u8,
Direction::Right => self.0 % BOARD_WIDTH as u8 != 7,
Direction::Down => (self.0 + BOARD_WIDTH as u8) < (BOARD_SIZE as u8),
Direction::Left => !self.0.is_multiple_of(BOARD_WIDTH as u8),
}
}
pub fn is_adjacent(&self, square: &Self) -> bool {
let column_diff = isize::abs(self.column_char() as isize - square.column_char() as isize);
let row_diff = i8::abs(self.row() as i8 - square.row() as i8);
column_diff == 1 && row_diff == 1
}
}
impl fmt::Display for Square {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let column = self.column_char();
let row = self.row();
write!(f, "{column}{row}", column = column, row = row)
}
}
impl fmt::Debug for Square {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl FromStr for Square {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let chars: Vec<char> = s.chars().collect();
if chars.len() == 2 {
let column = chars[0];
let row = chars[1];
if let Ok(row) = row.to_string().parse() {
let column_as_num = column as u8 - ASCII_LETTER_A + 1;
if column_as_num >= 1 && column_as_num <= BOARD_WIDTH as u8 && (1..=BOARD_HEIGHT).contains(&row) {
return Ok(Square::new(column, row));
}
}
}
Err(anyhow!("Invalid value for square"))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_can_shift_in_direction(invalid_squares: &[&str], direction: Direction) {
for idx in 0..64 {
let square = Square::from_index(idx);
if invalid_squares.contains(&square.to_string().as_str()) {
assert!(!square.can_shift_in_direction(direction));
} else {
assert!(square.can_shift_in_direction(direction));
}
}
}
#[test]
fn test_can_shift_in_direction_up() {
let invalid_squares = ["a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8"];
let direction = Direction::Up;
assert_can_shift_in_direction(&invalid_squares, direction);
}
#[test]
fn test_can_shift_in_direction_right() {
let invalid_squares = ["h8", "h7", "h6", "h5", "h4", "h3", "h2", "h1"];
let direction = Direction::Right;
assert_can_shift_in_direction(&invalid_squares, direction);
}
#[test]
fn test_can_shift_in_direction_down() {
let invalid_squares = ["a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1"];
let direction = Direction::Down;
assert_can_shift_in_direction(&invalid_squares, direction);
}
#[test]
fn test_can_shift_in_direction_left() {
let invalid_squares = ["a8", "a7", "a6", "a5", "a4", "a3", "a2", "a1"];
let direction = Direction::Left;
assert_can_shift_in_direction(&invalid_squares, direction);
}
}