myopic_board/
mv.rs

1use std::fmt::{Display, Formatter};
2use std::str::FromStr;
3
4use anyhow::Result;
5
6use myopic_core::{Reflectable, Side};
7
8use crate::{CastleZone, Piece, Square};
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
11pub enum Move {
12    Standard {
13        source: u64,
14        moving: Piece,
15        from: Square,
16        dest: Square,
17        capture: Option<Piece>,
18    },
19    Enpassant {
20        source: u64,
21        side: Side,
22        from: Square,
23        dest: Square,
24        capture: Square,
25    },
26    Promotion {
27        source: u64,
28        from: Square,
29        dest: Square,
30        promoted: Piece,
31        capture: Option<Piece>,
32    },
33    Castle {
34        source: u64,
35        zone: CastleZone,
36    },
37}
38
39impl Display for Move {
40    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
41        match self {
42            Move::Standard {
43                moving,
44                from,
45                dest,
46                capture,
47                ..
48            } => write!(
49                f,
50                "s{}{}{}{}",
51                moving,
52                from,
53                dest,
54                match capture {
55                    None => "-".to_string(),
56                    Some(p) => p.to_string(),
57                }
58            ),
59            Move::Promotion {
60                from,
61                dest,
62                promoted,
63                capture,
64                ..
65            } => write!(
66                f,
67                "p{}{}{}{}",
68                from,
69                dest,
70                promoted,
71                match capture {
72                    None => "-".to_string(),
73                    Some(p) => p.to_string(),
74                }
75            ),
76            Move::Enpassant {
77                side,
78                from,
79                dest,
80                capture,
81                ..
82            } => write!(f, "e{}{}{}{}", side, from, dest, capture),
83            Move::Castle { zone, .. } => write!(f, "c{}", zone),
84        }
85    }
86}
87
88#[cfg(test)]
89impl Move {
90    pub fn from(s: &str, source: u64) -> Result<Move> {
91        match s.chars().next() {
92            None => Err(anyhow::anyhow!("Cannot parse move from empty string!")),
93            Some(t) => match t {
94                's' => Ok(Move::Standard {
95                    source,
96                    moving: slice(s, 1, 2).parse()?,
97                    from: slice(s, 3, 2).parse()?,
98                    dest: slice(s, 5, 2).parse()?,
99                    capture: parse_op(slice(s, 7, 2).as_str())?,
100                }),
101                'e' => Ok(Move::Enpassant {
102                    source,
103                    side: slice(s, 1, 1).parse()?,
104                    from: slice(s, 2, 2).parse()?,
105                    dest: slice(s, 4, 2).parse()?,
106                    capture: slice(s, 6, 2).parse()?,
107                }),
108                'p' => Ok(Move::Promotion {
109                    source,
110                    from: slice(s, 1, 2).parse()?,
111                    dest: slice(s, 3, 2).parse()?,
112                    promoted: slice(s, 5, 2).parse()?,
113                    capture: parse_op(slice(s, 7, 2).as_str())?,
114                }),
115                'c' => Ok(Move::Castle {
116                    source,
117                    zone: slice(s, 1, 2).parse()?,
118                }),
119                _ => Err(anyhow::anyhow!("Cannot parse {} as a move", s)),
120            },
121        }
122    }
123}
124
125#[cfg(test)]
126fn slice(s: &str, skip: usize, take: usize) -> String {
127    s.chars().skip(skip).take(take).collect()
128}
129
130pub fn parse_op<F>(s: &str) -> Result<Option<F>>
131where
132    F: FromStr<Err = anyhow::Error>,
133{
134    match s {
135        "-" => Ok(None),
136        _ => Ok(Some(FromStr::from_str(s)?)),
137    }
138}
139
140#[cfg(test)]
141mod test {
142    use anyhow::Result;
143
144    use myopic_core::Side;
145
146    use crate::{CastleZone, Piece, Square};
147    use crate::mv::Move;
148
149    #[test]
150    fn standard() -> Result<()> {
151        assert_eq!(
152            Move::Standard {
153                source: 0u64,
154                moving: Piece::WP,
155                from: Square::E2,
156                dest: Square::E4,
157                capture: None,
158            },
159            Move::from("swpe2e4-", 0u64)?
160        );
161        assert_eq!(
162            Move::Standard {
163                source: 1u64,
164                moving: Piece::BR,
165                from: Square::C4,
166                dest: Square::C2,
167                capture: Some(Piece::WP),
168            },
169            Move::from("sbrc4c2wp", 1u64)?
170        );
171        Ok(())
172    }
173
174    #[test]
175    fn promotion() -> Result<()> {
176        assert_eq!(
177            Move::Promotion {
178                source: 0u64,
179                from: Square::E7,
180                dest: Square::E8,
181                promoted: Piece::WQ,
182                capture: None,
183            },
184            Move::from("pe7e8wq-", 0u64)?
185        );
186        assert_eq!(
187            Move::Promotion {
188                source: 1u64,
189                from: Square::E7,
190                dest: Square::D8,
191                promoted: Piece::WQ,
192                capture: Some(Piece::BB),
193            },
194            Move::from("pe7d8wqbb", 1u64)?
195        );
196        Ok(())
197    }
198
199    #[test]
200    fn enpassant() -> Result<()> {
201        assert_eq!(
202            Move::Enpassant {
203                source: 0,
204                side: Side::Black,
205                from: Square::D4,
206                dest: Square::C3,
207                capture: Square::C4,
208            },
209            Move::from("ebd4c3c4", 0u64)?
210        );
211        Ok(())
212    }
213
214    #[test]
215    fn castle() -> Result<()> {
216        assert_eq!(
217            Move::Castle {
218                source: 0,
219                zone: CastleZone::BK
220            },
221            Move::from("cbk", 0u64)?
222        );
223        Ok(())
224    }
225}
226
227impl Move {
228    pub fn source(&self) -> u64 {
229        *match self {
230            Move::Standard { source, .. } => source,
231            Move::Enpassant { source, .. } => source,
232            Move::Promotion { source, .. } => source,
233            Move::Castle { source, .. } => source,
234        }
235    }
236
237    /// Convert this move into a human readable uci long format string.
238    pub fn uci_format(&self) -> String {
239        match self {
240            Move::Standard { from, dest, .. } => format!("{}{}", from, dest),
241            Move::Enpassant { from, dest, .. } => format!("{}{}", from, dest),
242            Move::Castle { zone, .. } => {
243                let (_, src, dest) = zone.king_data();
244                format!("{}{}", src, dest)
245            }
246            Move::Promotion {
247                from,
248                dest,
249                promoted,
250                ..
251            } => format!(
252                "{}{}{}",
253                from,
254                dest,
255                match promoted {
256                    Piece::WQ | Piece::BQ => "q",
257                    Piece::WR | Piece::BR => "r",
258                    Piece::WB | Piece::BB => "b",
259                    Piece::WN | Piece::BN => "n",
260                    _ => "",
261                }
262            ),
263        }
264    }
265
266    // TODO move me somewhere better
267    pub(crate) fn reflect_for(&self, new_source: u64) -> Move {
268        match self {
269            &Move::Standard {
270                moving,
271                dest,
272                from,
273                capture,
274                ..
275            } => Move::Standard {
276                source: new_source,
277                moving: moving.reflect(),
278                dest: dest.reflect(),
279                from: from.reflect(),
280                capture: capture.reflect(),
281            },
282            &Move::Promotion {
283                from,
284                dest,
285                promoted,
286                capture,
287                ..
288            } => Move::Promotion {
289                source: new_source,
290                from: from.reflect(),
291                dest: dest.reflect(),
292                promoted: promoted.reflect(),
293                capture: capture.reflect(),
294            },
295            &Move::Enpassant {
296                side,
297                from,
298                dest,
299                capture,
300                ..
301            } => Move::Enpassant {
302                source: new_source,
303                side: side.reflect(),
304                from: from.reflect(),
305                dest: dest.reflect(),
306                capture: capture.reflect(),
307            },
308            &Move::Castle { zone, .. } => Move::Castle {
309                source: new_source,
310                zone: zone.reflect(),
311            },
312        }
313    }
314}