use std::ops::Add;
use std::error;
use std::fmt;
use std::str::FromStr;
use std::num::ParseIntError;
static ALPHABET: &str = "abcdefghjiklmnopqrstuvwxyz";
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
pub struct Coord {
pub x: u8,
pub y: u8,
}
impl Add<Coord> for Coord {
type Output = Coord;
fn add(self, rhs: Coord) -> Self::Output {
Coord {
x: self.x + rhs.x,
y: self.y + rhs.y
}
}
}
impl fmt::Display for Coord {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", ALPHABET.chars().nth(self.x as usize).unwrap(), self.y + 1)
}
}
#[derive(Debug, Clone)]
pub enum ParseCoordError {
InvalidFormat,
InvalidInt(ParseIntError)
}
impl fmt::Display for ParseCoordError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ParseCoordError::InvalidFormat => write!(f, "invalid coordinate string"),
ParseCoordError::InvalidInt(ref e) => e.fmt(f),
}
}
}
impl error::Error for ParseCoordError {
fn description(&self) -> &str {
match *self {
ParseCoordError::InvalidFormat => "invalid coordinate string format",
ParseCoordError::InvalidInt(ref e) => e.description(),
}
}
fn cause(&self) -> Option<&error::Error> {
match *self {
ParseCoordError::InvalidFormat => None,
ParseCoordError::InvalidInt(ref e) => Some(e)
}
}
}
impl From<ParseIntError> for ParseCoordError {
fn from(err: ParseIntError) -> ParseCoordError {
ParseCoordError::InvalidInt(err)
}
}
impl FromStr for Coord {
type Err = ParseCoordError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s_lower = s.to_lowercase();
if s_lower.len() < 2 || !ALPHABET.contains(s_lower.chars().nth(0).unwrap()) {
Err(ParseCoordError::InvalidFormat)
} else {
let x: u8 = ALPHABET.find(|x| x == s_lower.chars().nth(0).unwrap()).unwrap() as u8;
let y: u8 = s_lower.chars().skip(1).collect::<String>().parse()?;
match Coord::new(x, y - 1) {
Some(c) => Ok(c),
None => Err(ParseCoordError::InvalidFormat)
}
}
}
}
impl Coord {
pub fn new(x: u8, y: u8) -> Option<Coord> {
if x > 25 || y > 25 {
Option::None
} else {
Option::Some(Coord{x, y})
}
}
pub fn neighbors(self) -> Vec<Coord> {
if self == Coord::default() {
return vec![
Coord{x: 1, y: 0},
Coord{x: 0, y: 1},
]
}
else if self.x == 0 {
return vec![
Coord{x: 0, y: self.y - 1},
Coord{x: 1, y: self.y - 1},
Coord{x: 1, y: self.y},
Coord{x: 0, y: self.y + 1}
]
}
else if self.y == 0 {
return vec![
Coord{x: self.x + 1, y: 0},
Coord{x: self.x, y: 1},
Coord{x: self.x - 1, y: 1},
Coord{x: self.x - 1, y: 0}
]
}
else {
vec![
Coord{x: self.x, y: self.y - 1},
Coord{x: self.x + 1, y: self.y - 1},
Coord{x: self.x + 1, y: self.y},
Coord{x: self.x, y: self.y + 1},
Coord{x: self.x - 1, y: self.y + 1},
Coord{x: self.x - 1, y: self.y},
]
}
}
pub fn is_neighbor(self, other: Coord) -> bool {
(Coord::abs_sub(self.x, other.x) <= 1 &&
Coord::abs_sub(self.y, other.y) <= 1 &&
Coord::abs_sub(self.x + self.y, other.x + other.y) <= 1)
}
pub fn abs_sub(int1: u8, int2: u8) -> u8 {
if int1 >= int2 {
int1 - int2
} else {
int2 - int1
}
}
pub fn distance(self, other: Coord) -> u8 {
(Coord::abs_sub(self.x, other.x) + Coord::abs_sub(self.y, other.y) +
Coord::abs_sub(self.x + self.y, other.x + other.y)) / 2
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_constructor() {
assert_eq!(Coord::new(14, 15).unwrap(), Coord{x: 14, y: 15});
assert_eq!(Coord::new(14, 26), None);
}
#[test]
fn test_neighbors() {
assert_eq!(Coord{x: 0, y: 0}.neighbors(),
vec![Coord{x: 1, y: 0},
Coord{x: 0, y: 1}]);
assert_eq!(Coord{x: 0, y: 5}.neighbors(),
vec![Coord{x: 0, y: 4},
Coord{x: 1, y: 4},
Coord{x: 1, y: 5},
Coord{x: 0, y: 6}]);
assert_eq!(Coord{x: 4, y: 0}.neighbors(),
vec![Coord{x: 5, y: 0},
Coord{x: 4, y: 1},
Coord{x: 3, y: 1},
Coord{x: 3, y: 0}]);
assert_eq!(Coord{x: 7, y: 5}.neighbors(),
vec![Coord{x: 7, y: 4},
Coord{x: 8, y: 4},
Coord{x: 8, y: 5},
Coord{x: 7, y: 6},
Coord{x: 6, y: 6},
Coord{x: 6, y: 5}]);
}
#[test]
fn test_is_neighbor() {
assert!(Coord{x: 0, y: 0}.is_neighbor(Coord{x: 0, y: 1}));
assert!(Coord{x: 0, y: 0}.is_neighbor(Coord{x: 1, y: 0}));
assert!(Coord{x: 3, y: 0}.is_neighbor(Coord{x: 2, y: 0}));
assert!(Coord{x: 0, y: 5}.is_neighbor(Coord{x: 0, y: 4}));
assert!(Coord{x: 3, y: 0}.is_neighbor(Coord{x: 2, y: 1}));
assert!(Coord{x: 0, y: 5}.is_neighbor(Coord{x: 1, y: 4}));
assert!(Coord{x: 6, y: 7}.is_neighbor(Coord{x: 6, y: 7}));
assert!(!Coord{x: 0, y: 0}.is_neighbor(Coord{x: 1, y: 1}));
assert!(!Coord{x: 0, y: 0}.is_neighbor(Coord{x: 12, y: 10}));
assert!(!Coord{x: 3, y: 0}.is_neighbor(Coord{x: 2, y: 2}));
}
#[test]
fn test_distance() {
assert_eq!(Coord{x: 0, y: 0}.distance(Coord{x: 1, y: 1}), 2);
assert_eq!(Coord{x: 4, y: 3}.distance(Coord{x: 1, y: 1}), 5);
assert_eq!(Coord{x: 4, y: 3}.distance(Coord{x: 4, y: 2}), 1);
assert_eq!(Coord{x: 4, y: 3}.distance(Coord{x: 4, y: 3}), 0);
}
#[test]
fn test_display() {
assert_eq!(&Coord{x: 0, y: 0}.to_string(), "a1");
assert_eq!(&Coord{x: 13, y: 0}.to_string(), "n1");
assert_eq!(&Coord{x: 13, y: 5}.to_string(), "n6");
assert_eq!(&Coord{x: 25, y: 25}.to_string(), "z26");
assert_eq!(&Coord{x: 25, y: 0}.to_string(), "z1");
}
#[test]
fn test_parsing() {
for string in vec!["a1", "a16", "B6", "z6", "z23", "Q25"].iter() {
assert_eq!(string.to_lowercase(), Coord::from_str(string).unwrap().to_string());
}
assert!(Coord::from_str("ZZ").is_err());
assert!(Coord::from_str("Z126").is_err());
}
}