Use the Direction enum to calculate the neighbor of a square with bounds checking. Remove the unused rank_index() and file_index methods. Add Square::from_rank_file and Square::from_index_unsafe to support tests. Unify the Square*OutOfBounds error types
246 lines
7.2 KiB
Rust
246 lines
7.2 KiB
Rust
// Eryn Wells <eryn@erynwells.me>
|
|
|
|
use crate::neighbor::Direction;
|
|
use std::str::FromStr;
|
|
|
|
#[derive(Debug)]
|
|
pub struct ParseSquareError;
|
|
|
|
#[derive(Debug)]
|
|
pub struct SquareOutOfBoundsError;
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
pub struct Square {
|
|
pub rank: u8,
|
|
pub file: u8,
|
|
pub index: u8,
|
|
}
|
|
|
|
impl Square {
|
|
pub fn from_index(index: u8) -> Result<Square, SquareOutOfBoundsError> {
|
|
if index >= 64 {
|
|
return Err(SquareOutOfBoundsError);
|
|
}
|
|
|
|
Ok(Square::from_index_unsafe(index))
|
|
}
|
|
|
|
fn from_index_unsafe(index: u8) -> Square {
|
|
Square {
|
|
rank: index / 8,
|
|
file: index % 8,
|
|
index: index,
|
|
}
|
|
}
|
|
|
|
pub fn from_rank_file(rank: u8, file: u8) -> Result<Square, SquareOutOfBoundsError> {
|
|
if rank >= 8 || file >= 8 {
|
|
return Err(SquareOutOfBoundsError);
|
|
}
|
|
|
|
Ok(Square {
|
|
rank,
|
|
file,
|
|
index: rank * 8 + file,
|
|
})
|
|
}
|
|
|
|
pub fn from_algebraic_string(s: &str) -> Result<Square, ParseSquareError> {
|
|
s.parse()
|
|
}
|
|
|
|
pub fn neighbor(&self, direction: Direction) -> Option<Square> {
|
|
match direction {
|
|
Direction::North => Square::from_index(self.index + 8),
|
|
Direction::NorthWest => {
|
|
if self.rank < 7 {
|
|
Square::from_index(self.index + 7)
|
|
} else {
|
|
Err(SquareOutOfBoundsError)
|
|
}
|
|
}
|
|
Direction::West => {
|
|
if self.file > 0 {
|
|
Square::from_index(self.index - 1)
|
|
} else {
|
|
Err(SquareOutOfBoundsError)
|
|
}
|
|
}
|
|
Direction::SouthWest => {
|
|
if self.rank > 0 {
|
|
Square::from_index(self.index - 9)
|
|
} else {
|
|
Err(SquareOutOfBoundsError)
|
|
}
|
|
}
|
|
Direction::South => {
|
|
if self.rank > 0 {
|
|
Square::from_index(self.index - 8)
|
|
} else {
|
|
Err(SquareOutOfBoundsError)
|
|
}
|
|
}
|
|
Direction::SouthEast => {
|
|
if self.rank > 0 {
|
|
Square::from_index(self.index - 7)
|
|
} else {
|
|
Err(SquareOutOfBoundsError)
|
|
}
|
|
}
|
|
Direction::East => {
|
|
if self.file < 7 {
|
|
Square::from_index(self.index + 1)
|
|
} else {
|
|
Err(SquareOutOfBoundsError)
|
|
}
|
|
}
|
|
Direction::NorthEast => Square::from_index(self.index + 9),
|
|
}
|
|
.ok()
|
|
}
|
|
}
|
|
|
|
impl FromStr for Square {
|
|
type Err = ParseSquareError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
if !s.is_ascii() || s.len() != 2 {
|
|
return Err(ParseSquareError);
|
|
}
|
|
|
|
let mut chars = s.chars();
|
|
|
|
let file_char = chars.next().unwrap().to_ascii_lowercase();
|
|
if !file_char.is_ascii_lowercase() {
|
|
return Err(ParseSquareError);
|
|
}
|
|
|
|
let file = (file_char as u8) - ('a' as u8);
|
|
if file >= 8 {
|
|
return Err(ParseSquareError);
|
|
}
|
|
|
|
let converted_rank_digit = chars
|
|
.next()
|
|
.unwrap()
|
|
.to_digit(10)
|
|
.and_then(|x| if x >= 1 && x <= 8 { Some(x) } else { None })
|
|
.ok_or(ParseSquareError)?;
|
|
let rank = u8::try_from(converted_rank_digit).map_err(|_| ParseSquareError)? - 1;
|
|
|
|
Ok(Square {
|
|
rank,
|
|
file,
|
|
index: rank * 8 + file,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn good_algebraic_input() {
|
|
let sq1 = Square::from_algebraic_string("a4").expect("Failed to parse 'a4' square");
|
|
assert_eq!(sq1.file, 0);
|
|
assert_eq!(sq1.rank, 3);
|
|
|
|
let sq2 = Square::from_algebraic_string("B8").expect("Failed to parse 'B8' square");
|
|
assert_eq!(sq2.file, 1);
|
|
assert_eq!(sq2.rank, 7);
|
|
|
|
let sq3 = Square::from_algebraic_string("e4").expect("Failed to parse 'B8' square");
|
|
assert_eq!(sq3.rank, 3, "Expected rank of e4 to be 3");
|
|
assert_eq!(sq3.file, 4, "Expected file of e4 to be 4");
|
|
}
|
|
|
|
#[test]
|
|
fn bad_algebraic_input() {
|
|
Square::from_algebraic_string("a0").expect_err("Got valid Square for 'a0'");
|
|
Square::from_algebraic_string("j3").expect_err("Got valid Square for 'j3'");
|
|
Square::from_algebraic_string("a11").expect_err("Got valid Square for 'a11'");
|
|
Square::from_algebraic_string("b-1").expect_err("Got valid Square for 'b-1'");
|
|
Square::from_algebraic_string("a 1").expect_err("Got valid Square for 'a 1'");
|
|
Square::from_algebraic_string("").expect_err("Got valid Square for ''");
|
|
}
|
|
|
|
#[test]
|
|
fn from_index() {
|
|
let sq1 = Square::from_index(4).expect("Unable to get Square from index");
|
|
assert_eq!(sq1.rank, 0);
|
|
assert_eq!(sq1.file, 4);
|
|
|
|
let sq1 = Square::from_index(28).expect("Unable to get Square from index");
|
|
assert_eq!(sq1.rank, 3);
|
|
assert_eq!(sq1.file, 4);
|
|
}
|
|
|
|
#[test]
|
|
fn valid_neighbors() {
|
|
let sq = Square::from_index_unsafe(28);
|
|
|
|
assert_eq!(
|
|
sq.neighbor(Direction::North),
|
|
Some(Square::from_index_unsafe(36))
|
|
);
|
|
|
|
assert_eq!(
|
|
sq.neighbor(Direction::NorthEast),
|
|
Some(Square::from_index_unsafe(37))
|
|
);
|
|
|
|
assert_eq!(
|
|
sq.neighbor(Direction::East),
|
|
Some(Square::from_index_unsafe(29))
|
|
);
|
|
|
|
assert_eq!(
|
|
sq.neighbor(Direction::SouthEast),
|
|
Some(Square::from_index_unsafe(21))
|
|
);
|
|
|
|
assert_eq!(
|
|
sq.neighbor(Direction::South),
|
|
Some(Square::from_index_unsafe(20))
|
|
);
|
|
|
|
assert_eq!(
|
|
sq.neighbor(Direction::SouthWest),
|
|
Some(Square::from_index_unsafe(19))
|
|
);
|
|
|
|
assert_eq!(
|
|
sq.neighbor(Direction::West),
|
|
Some(Square::from_index_unsafe(27))
|
|
);
|
|
|
|
assert_eq!(
|
|
sq.neighbor(Direction::NorthWest),
|
|
Some(Square::from_index_unsafe(35))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_neighbors() {
|
|
let square0 = Square::from_index_unsafe(0);
|
|
assert!(square0.neighbor(Direction::West).is_none());
|
|
assert!(square0.neighbor(Direction::SouthWest).is_none());
|
|
assert!(square0.neighbor(Direction::South).is_none());
|
|
|
|
let square7 = Square::from_index_unsafe(7);
|
|
assert!(square7.neighbor(Direction::East).is_none());
|
|
assert!(square7.neighbor(Direction::SouthEast).is_none());
|
|
assert!(square7.neighbor(Direction::South).is_none());
|
|
|
|
let square56 = Square::from_index_unsafe(56);
|
|
assert!(square56.neighbor(Direction::North).is_none());
|
|
assert!(square56.neighbor(Direction::NorthWest).is_none());
|
|
assert!(square56.neighbor(Direction::West).is_none());
|
|
|
|
let square63 = Square::from_index_unsafe(63);
|
|
assert!(square63.neighbor(Direction::North).is_none());
|
|
assert!(square63.neighbor(Direction::NorthEast).is_none());
|
|
assert!(square63.neighbor(Direction::East).is_none());
|
|
}
|
|
}
|