carina_libgo/
move.rs

1use core::fmt;
2use std::str::FromStr;
3
4use serde::Serialize;
5
6use crate::ParseError;
7
8const CHAR_LOWER_A: u8 = b'a'; // ASCII value of 'a'
9
10#[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}