1use std::fmt;
4
5#[derive(Debug, Hash, PartialOrd, Ord, PartialEq, Copy, Clone, Eq)]
31#[rustfmt::skip]
32pub enum Square {
33 A8, B8, C8, D8, E8, F8, G8, H8,
34 A7, B7, C7, D7, E7, F7, G7, H7,
35 A6, B6, C6, D6, E6, F6, G6, H6,
36 A5, B5, C5, D5, E5, F5, G5, H5,
37 A4, B4, C4, D4, E4, F4, G4, H4,
38 A3, B3, C3, D3, E3, F3, G3, H3,
39 A2, B2, C2, D2, E2, F2, G2, H2,
40 A1, B1, C1, D1, E1, F1, G1, H1,
41}
42
43const RANK_1: &[&str] = &["a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1"];
44const RANK_2: &[&str] = &["a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2"];
45const RANK_3: &[&str] = &["a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3"];
46const RANK_4: &[&str] = &["a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4"];
47const RANK_5: &[&str] = &["a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5"];
48const RANK_6: &[&str] = &["a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6"];
49const RANK_7: &[&str] = &["a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7"];
50const RANK_8: &[&str] = &["a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8"];
51
52const FILE_A: &[&str] = &["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8"];
53const FILE_B: &[&str] = &["b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8"];
54const FILE_C: &[&str] = &["c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8"];
55const FILE_D: &[&str] = &["d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8"];
56const FILE_E: &[&str] = &["e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8"];
57const FILE_F: &[&str] = &["f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8"];
58const FILE_G: &[&str] = &["g1", "g2", "g3", "g4", "g5", "g6", "g7", "g8"];
59const FILE_H: &[&str] = &["h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8"];
60
61impl Square {
62 pub fn get_file(file: char) -> Result<Vec<Square>, &'static str> {
82 let file = match file {
83 'a' => FILE_A,
84 'b' => FILE_B,
85 'c' => FILE_C,
86 'd' => FILE_D,
87 'e' => FILE_E,
88 'f' => FILE_F,
89 'g' => FILE_G,
90 'h' => FILE_H,
91 _ => return Err("Invalid 'file' character received"),
92 };
93
94 Ok(file
95 .iter() .map(|square| Square::try_from(*square).unwrap())
97 .collect::<Vec<Square>>())
98 }
99
100 pub fn get_rank(rank: char) -> Result<Vec<Square>, &'static str> {
120 let rank = match rank {
121 '8' => RANK_8,
122 '7' => RANK_7,
123 '5' => RANK_5,
124 '6' => RANK_6,
125 '4' => RANK_4,
126 '3' => RANK_3,
127 '2' => RANK_2,
128 '1' => RANK_1,
129 _ => return Err("Invalid 'rank' character received"),
130 };
131
132 Ok(rank
133 .iter() .map(|square| Square::try_from(*square).unwrap())
135 .collect::<Vec<Square>>())
136 }
137
138 pub fn get_relative_neighbor(
153 &self,
154 x: i8,
155 y: i8,
156 ) -> Result<Square, &'static str> {
157 let dst = *self as i8;
158
159 let dst_mod = dst % 8;
160 let l_border = match dst_mod {
161 0 => 0,
162 dst_mod => -dst_mod,
163 };
164 let r_border = 8 - dst_mod;
165 if x < l_border || x >= r_border {
166 return Err("Square out of range");
167 }
168
169 let y = dst - y * 8;
170
171 let result = x + y;
172 if result < 0 || result > Square::H1 as i8 {
173 return Err("Square out of range");
174 }
175
176 use std::mem::transmute;
177 let dst: Self = unsafe { transmute(result as i8) };
178 Ok(dst)
179 }
180
181 pub fn get_file_char(&self) -> char {
183 use Square::*;
184 match self {
185 A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 => 'a',
186 B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8 => 'b',
187 C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 => 'c',
188 D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 => 'd',
189 E1 | E2 | E3 | E4 | E5 | E6 | E7 | E8 => 'e',
190 F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 => 'f',
191 G1 | G2 | G3 | G4 | G5 | G6 | G7 | G8 => 'g',
192 H1 | H2 | H3 | H4 | H5 | H6 | H7 | H8 => 'h',
193 }
194 }
195
196 pub fn get_rank_char(&self) -> char {
198 use Square::*;
199 match self {
200 A8 | B8 | C8 | D8 | E8 | F8 | G8 | H8 => '8',
201 A7 | B7 | C7 | D7 | E7 | F7 | G7 | H7 => '7',
202 A5 | B5 | C5 | D5 | E5 | F5 | G5 | H5 => '5',
203 A6 | B6 | C6 | D6 | E6 | F6 | G6 | H6 => '6',
204 A4 | B4 | C4 | D4 | E4 | F4 | G4 | H4 => '4',
205 A3 | B3 | C3 | D3 | E3 | F3 | G3 | H3 => '3',
206 A2 | B2 | C2 | D2 | E2 | F2 | G2 | H2 => '2',
207 A1 | B1 | C1 | D1 | E1 | F1 | G1 | H1 => '1',
208 }
209 }
210}
211
212impl From<u8> for Square {
213 fn from(square: u8) -> Self {
214 #[rustfmt::skip]
216 const SQUARES: [Square; 64] = [
217 Square::A8, Square::B8, Square::C8, Square::D8,
218 Square::E8, Square::F8, Square::G8, Square::H8,
219 Square::A7, Square::B7, Square::C7, Square::D7,
220 Square::E7, Square::F7, Square::G7, Square::H7,
221 Square::A6, Square::B6, Square::C6, Square::D6,
222 Square::E6, Square::F6, Square::G6, Square::H6,
223 Square::A5, Square::B5, Square::C5, Square::D5,
224 Square::E5, Square::F5, Square::G5, Square::H5,
225 Square::A4, Square::B4, Square::C4, Square::D4,
226 Square::E4, Square::F4, Square::G4, Square::H4,
227 Square::A3, Square::B3, Square::C3, Square::D3,
228 Square::E3, Square::F3, Square::G3, Square::H3,
229 Square::A2, Square::B2, Square::C2, Square::D2,
230 Square::E2, Square::F2, Square::G2, Square::H2,
231 Square::A1, Square::B1, Square::C1, Square::D1,
232 Square::E1, Square::F1, Square::G1, Square::H1,
233 ];
234
235 let i = square as usize;
236 assert!(i < SQUARES.len(), "Square index={} is out of range", i);
237 SQUARES[i]
238 }
239}
240
241impl TryFrom<&str> for Square {
242 type Error = &'static str;
243
244 fn try_from(square: &str) -> Result<Self, Self::Error> {
245 if square.len() != 2 {
246 return Err("Invalid square length");
247 }
248
249 match square {
251 "a1" => Ok(Self::A1),
252 "a2" => Ok(Self::A2),
253 "a3" => Ok(Self::A3),
254 "a4" => Ok(Self::A4),
255 "a5" => Ok(Self::A5),
256 "a6" => Ok(Self::A6),
257 "a7" => Ok(Self::A7),
258 "a8" => Ok(Self::A8),
259
260 "b1" => Ok(Self::B1),
261 "b2" => Ok(Self::B2),
262 "b3" => Ok(Self::B3),
263 "b4" => Ok(Self::B4),
264 "b5" => Ok(Self::B5),
265 "b6" => Ok(Self::B6),
266 "b7" => Ok(Self::B7),
267 "b8" => Ok(Self::B8),
268
269 "c1" => Ok(Self::C1),
270 "c2" => Ok(Self::C2),
271 "c3" => Ok(Self::C3),
272 "c4" => Ok(Self::C4),
273 "c5" => Ok(Self::C5),
274 "c6" => Ok(Self::C6),
275 "c7" => Ok(Self::C7),
276 "c8" => Ok(Self::C8),
277
278 "d1" => Ok(Self::D1),
279 "d2" => Ok(Self::D2),
280 "d3" => Ok(Self::D3),
281 "d4" => Ok(Self::D4),
282 "d5" => Ok(Self::D5),
283 "d6" => Ok(Self::D6),
284 "d7" => Ok(Self::D7),
285 "d8" => Ok(Self::D8),
286
287 "e1" => Ok(Self::E1),
288 "e2" => Ok(Self::E2),
289 "e3" => Ok(Self::E3),
290 "e4" => Ok(Self::E4),
291 "e5" => Ok(Self::E5),
292 "e6" => Ok(Self::E6),
293 "e7" => Ok(Self::E7),
294 "e8" => Ok(Self::E8),
295
296 "f1" => Ok(Self::F1),
297 "f2" => Ok(Self::F2),
298 "f3" => Ok(Self::F3),
299 "f4" => Ok(Self::F4),
300 "f5" => Ok(Self::F5),
301 "f6" => Ok(Self::F6),
302 "f7" => Ok(Self::F7),
303 "f8" => Ok(Self::F8),
304
305 "g1" => Ok(Self::G1),
306 "g2" => Ok(Self::G2),
307 "g3" => Ok(Self::G3),
308 "g4" => Ok(Self::G4),
309 "g5" => Ok(Self::G5),
310 "g6" => Ok(Self::G6),
311 "g7" => Ok(Self::G7),
312 "g8" => Ok(Self::G8),
313
314 "h1" => Ok(Self::H1),
315 "h2" => Ok(Self::H2),
316 "h3" => Ok(Self::H3),
317 "h4" => Ok(Self::H4),
318 "h5" => Ok(Self::H5),
319 "h6" => Ok(Self::H6),
320 "h7" => Ok(Self::H7),
321 "h8" => Ok(Self::H8),
322
323 _ => Err("Invalid square input"),
324 }
325 }
326}
327
328impl fmt::Display for Square {
329 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
330 write!(f, "{}{}", self.get_file_char(), self.get_rank_char())
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[test]
339 fn try_from_invalid_length() {
340 let err = Err("Invalid square length");
341
342 assert_eq!(err, Square::try_from("h"));
343 assert_eq!(err, Square::try_from("a10"));
344 }
345
346 #[test]
347 fn get_rank_with_wrong_char_failure() {
348 let err = Err("Invalid 'rank' character received");
349
350 assert_eq!(err, Square::get_rank('9'));
352
353 assert_eq!(err, Square::get_rank('z'));
355 }
356
357 #[test]
358 fn get_file_with_wrong_char_failure() {
359 let err = Err("Invalid 'file' character received");
360
361 assert_eq!(err, Square::get_file('A'));
363
364 assert_eq!(err, Square::get_file('z'));
366 }
367
368 #[test]
369 #[should_panic]
370 fn from_u8() {
371 let square = Square::A8;
373 assert_eq!(square as u8, 0);
374 assert_eq!(square, Square::from(square as u8));
375
376 let square = Square::H1;
378 assert_eq!(square as u8, 63);
379 assert_eq!(square, Square::from(square as u8));
380
381 let _panic = Square::from(Square::H1 as u8 + 1);
384 }
385
386 #[test]
387 fn get_rank_success() {
388 let rank_str_char_pairs = [
389 (RANK_1, '1'),
390 (RANK_2, '2'),
391 (RANK_3, '3'),
392 (RANK_4, '4'),
393 (RANK_5, '5'),
394 (RANK_6, '6'),
395 (RANK_7, '7'),
396 (RANK_8, '8'),
397 ];
398
399 let c_test_rank_pair = |rank_strings: &[&str], rank_char| {
400 let rank: Vec<Square> = rank_strings
401 .iter()
402 .map(|square| Square::try_from(*square).unwrap())
403 .collect();
404 assert_eq!(rank, Square::get_rank(rank_char).unwrap());
405 };
406
407 rank_str_char_pairs
408 .iter()
409 .for_each(|(rank_strings, rank_char)| {
410 c_test_rank_pair(rank_strings, *rank_char);
411 });
412 }
413
414 #[test]
415 fn get_file_success() {
416 let file_str_char_pairs = [
417 (FILE_A, 'a'),
418 (FILE_B, 'b'),
419 (FILE_C, 'c'),
420 (FILE_D, 'd'),
421 (FILE_E, 'e'),
422 (FILE_F, 'f'),
423 (FILE_G, 'g'),
424 (FILE_H, 'h'),
425 ];
426
427 let c_test_file_pair = |file_strings: &[&str], file_char| {
428 let file: Vec<Square> = file_strings
429 .iter()
430 .map(|square| Square::try_from(*square).unwrap())
431 .collect();
432 assert_eq!(file, Square::get_file(file_char).unwrap());
433 };
434
435 file_str_char_pairs
436 .iter()
437 .for_each(|(file_strings, file_char)| {
438 c_test_file_pair(file_strings, *file_char);
439 });
440 }
441
442 #[test]
443 fn get_relative_neighbor_suceess() {
444 let square = Square::A1;
445 assert_eq!(square.get_relative_neighbor(0, 0), Ok(Square::A1));
446 assert_eq!(square.get_relative_neighbor(1, 0), Ok(Square::B1));
447 assert_eq!(square.get_relative_neighbor(0, 1), Ok(Square::A2));
448 assert_eq!(square.get_relative_neighbor(1, 1), Ok(Square::B2));
449 assert_eq!(square.get_relative_neighbor(2, 2), Ok(Square::C3));
450 assert_eq!(square.get_relative_neighbor(4, 4), Ok(Square::E5));
451 assert_eq!(square.get_relative_neighbor(7, 7), Ok(Square::H8));
452
453 let square1 = Square::D4;
454 let square2 = Square::E5;
455 assert_eq!(
456 square1.get_relative_neighbor(1, 0),
457 square2.get_relative_neighbor(0, -1)
458 );
459
460 let square = Square::H8;
461 assert_eq!(square.get_relative_neighbor(-1, 0), Ok(Square::G8));
462 assert_eq!(square.get_relative_neighbor(0, -1), Ok(Square::H7));
463 assert_eq!(square.get_relative_neighbor(-1, -1), Ok(Square::G7));
464 assert_eq!(square.get_relative_neighbor(-3, -7), Ok(Square::E1));
465 }
466
467 #[test]
468 fn get_relative_neighbor_failure() {
469 for square in Square::get_rank('1').unwrap().into_iter() {
470 assert_eq!(
471 square.get_relative_neighbor(0, -1),
472 Err("Square out of range")
473 );
474 }
475 for square in Square::get_rank('8').unwrap().into_iter() {
476 assert_eq!(
477 square.get_relative_neighbor(0, 1),
478 Err("Square out of range")
479 );
480 }
481 for square in Square::get_rank('7').unwrap().into_iter() {
482 assert_eq!(
483 square.get_relative_neighbor(0, 3),
484 Err("Square out of range")
485 );
486 }
487
488 for square in Square::get_file('a').unwrap().into_iter() {
489 assert_eq!(
490 square.get_relative_neighbor(-1, 0),
491 Err("Square out of range")
492 );
493 }
494 for square in Square::get_file('h').unwrap().into_iter() {
495 assert_eq!(
496 square.get_relative_neighbor(1, 0),
497 Err("Square out of range")
498 );
499 }
500 for square in Square::get_file('g').unwrap().into_iter() {
501 assert_eq!(
502 square.get_relative_neighbor(2, 0),
503 Err("Square out of range")
504 );
505 }
506 }
507}