// Eryn Wells use std::str::FromStr; pub enum Direction { North, NorthWest, West, SouthWest, South, SouthEast, East, NorthEast, } #[derive(Debug)] pub struct ParseSquareError; #[derive(Debug)] pub struct SquareOutOfBoundsError; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct Square { rank: u8, file: u8, index: u8, } impl Square { pub fn from_rank_file(rank: u8, file: u8) -> Result { if rank >= 8 || file >= 8 { return Err(SquareOutOfBoundsError); } Ok(Square { rank, file, index: rank * 8 + file, }) } pub fn from_algebraic_str(s: &str) -> Result { s.parse() } pub fn rank_file(&self) -> (u8, u8) { (self.rank, self.file) } pub fn neighbor(&self, direction: Direction) -> Option { 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 Square { pub(crate) fn from_index(index: u8) -> Result { if index >= 64 { return Err(SquareOutOfBoundsError); } Ok(Square::from_index_unsafe(index)) } pub(crate) fn from_index_unsafe(index: u8) -> Square { Square { rank: index / 8, file: index % 8, index: index, } } pub(crate) fn index(&self) -> u8 { self.index } } impl FromStr for Square { type Err = ParseSquareError; fn from_str(s: &str) -> Result { 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_str("a4").expect("Failed to parse 'a4' square"); assert_eq!(sq1.file, 0); assert_eq!(sq1.rank, 3); let sq2 = Square::from_algebraic_str("B8").expect("Failed to parse 'B8' square"); assert_eq!(sq2.file, 1); assert_eq!(sq2.rank, 7); let sq3 = Square::from_algebraic_str("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_str("a0").expect_err("Got valid Square for 'a0'"); Square::from_algebraic_str("j3").expect_err("Got valid Square for 'j3'"); Square::from_algebraic_str("a11").expect_err("Got valid Square for 'a11'"); Square::from_algebraic_str("b-1").expect_err("Got valid Square for 'b-1'"); Square::from_algebraic_str("a 1").expect_err("Got valid Square for 'a 1'"); Square::from_algebraic_str("").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()); } }