1use std::error;
2use std::fmt;
3
4pub type CoordValue = u8;
6
7#[derive(Copy, Clone, Debug, PartialEq, Eq)]
38pub struct Coords {
39 pub row: CoordValue,
41 pub column: CoordValue,
43}
44
45impl Coords {
46 pub fn new(row: CoordValue, column: CoordValue) -> Self {
49 Self { row, column }
50 }
51
52 pub fn is_on_board_with_size(&self, size: CoordValue) -> bool {
54 self.row < size && self.column < size
55 }
56}
57
58impl std::str::FromStr for Coords {
59 type Err = ParseCoordsError;
60
61 fn from_str(string: &str) -> Result<Self, Self::Err> {
63 let column = string.chars().next().and_then(parse_column_char);
64 let row = string
65 .get(1..)
66 .and_then(|s| s.parse::<CoordValue>().ok())
67 .filter(|&row| 0 < row)
68 .map(|row| row - 1);
69
70 match row.zip(column) {
71 Some((row, column)) => Ok(Coords { row, column }),
72 None => Err(ParseCoordsError {
73 description: format!("Invalid coordinates: {}", string),
74 }),
75 }
76 }
77}
78
79pub fn parse_column_char(c: char) -> Option<CoordValue> {
82 if ('a'..='z').contains(&c) {
83 Some(((c as u8) - b'a') as CoordValue)
84 } else {
85 None
86 }
87}
88
89pub fn to_column_char(column: CoordValue) -> char {
91 (b'a' + (column as u8)) as char
92}
93
94impl fmt::Display for Coords {
95 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
96 write!(f, "{}{}", to_column_char(self.column), self.row + 1)
97 }
98}
99
100#[derive(Debug)]
102pub struct ParseCoordsError {
103 description: String,
104}
105
106impl fmt::Display for ParseCoordsError {
107 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
108 write!(f, "{}", self.description)
109 }
110}
111
112impl error::Error for ParseCoordsError {}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use std::str::FromStr;
118
119 #[test]
120 fn test_to_string() {
121 assert_eq!(Coords::new(0, 0).to_string(), "a1");
122 assert_eq!(Coords::new(12, 5).to_string(), "f13");
123 }
124
125 #[test]
126 fn test_from_str() {
127 assert_eq!(Coords::from_str("a1").unwrap(), Coords::new(0, 0));
128 assert_eq!(Coords::from_str("f13").unwrap(), Coords::new(12, 5));
129 }
130
131 #[test]
132 fn test_from_invalid_strings() {
133 assert!(Coords::from_str("").is_err());
134 assert!(Coords::from_str("abc").is_err());
135 assert!(Coords::from_str("A2").is_err());
136 assert!(Coords::from_str("a0").is_err());
137 assert!(Coords::from_str("ä2").is_err());
138 }
139}