1use core::fmt;
2use std::str::FromStr;
3
4use serde::Serialize;
5
6use crate::ParseError;
7
8const CHAR_LOWER_A: u8 = b'a'; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum Move {
12 Pass,
13 Coordinate { x: u8, y: u8 },
14}
15
16impl Move {
17 #[inline]
18 pub fn new(x: u8, y: u8) -> Self {
19 Move::Coordinate { x, y }
20 }
21
22 #[inline]
23 pub fn pass() -> Self {
24 Move::Pass
25 }
26
27 #[inline]
28 pub fn from_index(index: usize, board_size: usize) -> Self {
29 Move::Coordinate {
30 x: (index % board_size) as u8,
31 y: (index / board_size) as u8,
32 }
33 }
34
35 #[inline]
36 pub fn index(&self, board_size: usize) -> usize {
37 match self {
38 Move::Pass => usize::MAX,
39 Move::Coordinate { x, y } => (*y as usize) * board_size + (*x as usize),
40 }
41 }
42}
43
44impl FromStr for Move {
45 type Err = ParseError;
46
47 fn from_str(s: &str) -> Result<Self, Self::Err> {
48 if s == "pass" {
49 return Ok(Move::Pass);
50 }
51 if s.len() != 2 {
52 return Err(ParseError::InvalidCoordinate);
53 }
54
55 match (s.chars().nth(0), s.chars().nth(1)) {
56 (Some(x), Some(y)) if x.is_ascii_lowercase() && y.is_ascii_lowercase() => {
57 let x = x as u8 - CHAR_LOWER_A;
58 let y = y as u8 - CHAR_LOWER_A;
59 Ok(Move::Coordinate { x, y })
60 }
61 _ => Err(ParseError::InvalidCoordinate),
62 }
63 }
64}
65
66impl From<Move> for String {
67 fn from(pos: Move) -> Self {
68 match pos {
69 Move::Pass => "pass".to_string(),
70 Move::Coordinate { x, y } => format!(
71 "{}{}",
72 (x + CHAR_LOWER_A) as char,
73 (y + CHAR_LOWER_A) as char
74 ),
75 }
76 }
77}
78
79impl fmt::Display for Move {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 Move::Pass => write!(f, "pass"),
83 Move::Coordinate { x, y } => write!(
84 f,
85 "{}{}",
86 (x + CHAR_LOWER_A) as char,
87 (y + CHAR_LOWER_A) as char
88 ),
89 }
90 }
91}
92
93impl Serialize for Move {
94 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
95 where
96 S: serde::Serializer,
97 {
98 serializer.serialize_str(&self.to_string())
99 }
100}
101
102impl<'de> serde::Deserialize<'de> for Move {
103 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
104 where
105 D: serde::Deserializer<'de>,
106 {
107 let s = String::deserialize(deserializer)?;
108 s.parse().map_err(serde::de::Error::custom)
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_move_from_str() {
118 assert_eq!("ab".parse::<Move>().unwrap(), Move::new(0, 1));
119 assert_eq!("pass".parse::<Move>().unwrap(), Move::Pass);
120 assert!("invalid".parse::<Move>().is_err());
121 assert_eq!("ss".parse::<Move>().unwrap(), Move::new(18, 18));
122 }
123
124 #[test]
125 fn test_move_to_string() {
126 assert_eq!(Move::new(0, 1).to_string(), "ab");
127 assert_eq!(Move::Pass.to_string(), "pass");
128 }
129
130 #[test]
131 fn test_move_index() {
132 let m = Move::new(2, 3);
133 assert_eq!(m.index(19), 3 * 19 + 2);
134 }
135}