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 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 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}