1use core::{fmt, str};
2
3use crate::{Player, Role, Square, square::File};
4
5use super::Side;
6
7use File::*;
8use Role::*;
9
10#[cfg(test)]
11use Player::*;
12
13#[derive(
16 Clone, Copy, Debug, DeserializeFromStr, Eq, Ord, PartialEq, PartialOrd, SerializeDisplay,
17)]
18pub struct Move {
20 pub kind: Kind,
21 pub role: Role,
23 pub from: Square,
24 pub to: Square,
25 pub capture: Option<Role>,
27}
28
29#[repr(u8)]
31#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
32pub enum Kind {
33 #[default]
34 Normal,
35 Special(Special),
36}
37
38#[repr(u8)]
39#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
40pub enum Special {
41 EnPassant,
42 Castle(File),
43 Promote(Role),
44}
45
46impl Kind {
47 #[inline]
48 pub const fn normal() -> Self {
49 Kind::Normal
50 }
51
52 #[inline]
53 pub const fn special(special: Special) -> Self {
54 Kind::Special(special)
55 }
56
57 #[inline]
58 pub const fn en_passant() -> Self {
59 Kind::Special(Special::EnPassant)
60 }
61
62 #[inline]
63 pub const fn castle(file: File) -> Self {
64 Kind::Special(Special::Castle(file))
65 }
66
67 #[inline]
68 pub const fn promote(role: Role) -> Self {
69 Kind::Special(Special::Promote(role))
70 }
71
72 #[inline]
73 pub const fn is_normal(self) -> bool {
74 matches!(self, Kind::Normal)
75 }
76
77 #[inline]
78 pub const fn is_special(self) -> bool {
79 matches!(self, Kind::Normal)
80 }
81
82 #[inline]
83 pub const fn specials(self) -> Option<Special> {
84 if let Kind::Special(special) = self { Some(special) } else { None }
85 }
86
87 #[inline]
88 pub const fn is_en_passant(self) -> bool {
89 if let Kind::Special(special) = self { special.is_en_passant() } else { false }
90 }
91
92 #[inline]
93 pub const fn is_castle(self) -> bool {
94 if let Kind::Special(special) = self { special.is_castle() } else { false }
95 }
96
97 #[inline]
98 pub const fn castle_rook_file(self) -> Option<File> {
99 if let Kind::Special(special) = self { special.castle_rook_file() } else { None }
100 }
101
102 #[inline]
103 pub const fn is_promote(self) -> bool {
104 if let Kind::Special(special) = self { special.is_promote() } else { false }
105 }
106
107 #[inline]
108 pub const fn promotes(self) -> Option<Role> {
109 if let Kind::Special(special) = self { special.promotes() } else { None }
110 }
111
112 #[inline]
113 pub const fn is_promote_role(self, role: Role) -> bool {
114 if let Kind::Special(special) = self { special.is_promote_role(role) } else { false }
115 }
116}
117
118impl Move {
124 #[inline]
125 pub const fn normal(role: Role, from: Square, to: Square) -> Move {
126 Move::capture(role, from, to, None)
127 }
128
129 #[inline]
130 pub const fn capture(role: Role, from: Square, to: Square, capture: Option<Role>) -> Move {
131 Move { kind: Kind::Normal, role, from, to, capture }
132 }
133
134 #[inline]
135 pub const fn chess_castle(player: Player, side: Side) -> Move {
136 Move::castle(player, Square::new(E, player.backrank()), side.chess_rook())
137 }
138
139 #[inline]
140 pub const fn castle(player: Player, from: Square, file: File) -> Move {
141 let side = Side::of_rook(from, file);
142 Move {
143 kind: Kind::Special(Special::Castle(file)),
144 role: King,
145 from,
146 to: player.castle_king_to(side),
147 capture: None,
148 }
149 }
150
151 #[inline]
152 pub const fn en_passant(from: Square, to: Square) -> Move {
153 Move { role: Pawn, from, to, capture: Some(Pawn), kind: Kind::Special(Special::EnPassant) }
154 }
155
156 #[inline]
157 pub const fn promote(from: Square, to: Square, role: Role) -> Move {
158 Move::promote_capture(from, to, role, None)
159 }
160
161 #[inline]
162 pub const fn promote_capture(
163 from: Square,
164 to: Square,
165 role: Role,
166 capture: Option<Role>,
167 ) -> Move {
168 Move { role: Pawn, from, to, capture, kind: Kind::Special(Special::Promote(role)) }
169 }
170
171 pub fn pawn(player: Player, from: Square, to: Square, capture: Option<Role>) -> Vec<Move> {
172 let mut plays = Vec::new();
173
174 if to.rank() as u8 == player.promotion_rank() as u8 {
175 for role in [Queen, Rook, Bishop, Knight] {
176 plays.push(Move::promote_capture(from, to, role, capture));
177 }
178 } else {
179 plays.push(Move::capture(Pawn, from, to, capture));
180 }
181
182 plays
183 }
184}
185
186impl Move {
188 #[inline]
189 pub const fn capturing(mut self, role: Role) -> Move {
190 self.capture = Some(role);
191 self
192 }
193}
194
195impl Move {
197 #[inline]
198 pub const fn is_normal(self) -> bool {
199 self.kind.is_normal()
200 }
201
202 #[inline]
203 pub const fn is_special(self) -> bool {
204 self.kind.is_special()
205 }
206
207 #[inline]
208 pub const fn specials(self) -> Option<Special> {
209 self.kind.specials()
210 }
211}
212
213impl Move {
215 #[inline]
216 pub const fn is_en_passant(self) -> bool {
217 self.kind.is_en_passant()
218 }
219
220 #[inline]
221 pub const fn is_castle(self) -> bool {
222 self.kind.is_castle()
223 }
224
225 #[inline]
226 pub const fn castle_rook_file(self) -> Option<File> {
227 self.kind.castle_rook_file()
228 }
229
230 #[inline]
231 pub const fn is_castle_side(self, side: Side) -> bool {
232 match self.castle_side() {
233 Some(this) => this.eq(side),
234 None => false,
235 }
236 }
237
238 #[inline]
239 pub const fn castle_side(self) -> Option<Side> {
240 match self.castle_rook_file() {
241 Some(file) => Some(Side::of_rook(self.from, file)),
242 None => None,
243 }
244 }
245
246 #[inline]
247 pub const fn is_promote(self) -> bool {
248 self.kind.is_promote()
249 }
250
251 #[inline]
252 pub const fn promotes(self) -> Option<Role> {
253 self.kind.promotes()
254 }
255
256 #[inline]
257 pub const fn is_promote_role(self, role: Role) -> bool {
258 self.kind.is_promote_role(role)
259 }
260}
261
262impl Move {
264 #[inline]
265 pub const fn is_capture(self) -> bool {
266 self.capture.is_some()
267 }
268
269 #[inline]
270 pub const fn captures(self) -> Option<Role> {
271 self.capture
272 }
273
274 #[inline]
275 pub const fn is_capture_role(self, role: Role) -> bool {
276 if let Some(this) = self.capture { this as u8 == role as u8 } else { false }
277 }
278}
279
280impl Move {
282 #[inline]
283 pub const fn code(self) -> u16 {
284 (self.to as u16)
285 | ((self.from as u16) << 6)
286 | (self.promotion_code() << 12)
287 | (self.kind_code() << 14)
288 }
289
290 #[inline]
291 const fn promotion_code(self) -> u16 {
292 match self.promotes() {
293 Some(Knight) => 0,
294 Some(Bishop) => 1,
295 Some(Rook) => 2,
296 Some(Queen) => 3,
297 _ => 0,
298 }
299 }
300
301 #[inline]
302 const fn kind_code(self) -> u16 {
303 match self.specials() {
304 Some(Special::Promote(_)) => 1,
305 Some(Special::EnPassant) => 2,
306 Some(Special::Castle(_)) => 3,
307 None => 0,
308 }
309 }
310}
311
312impl fmt::Display for Move {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 repr::display(self, f)
316 }
317}
318
319impl str::FromStr for Move {
320 type Err = repr::Error;
321
322 fn from_str(s: &str) -> Result<Self, Self::Err> {
323 repr::from_str(s)
324 }
325}
326
327impl Special {
328 #[inline]
329 pub const fn en_passant() -> Self {
330 Special::EnPassant
331 }
332
333 #[inline]
334 pub const fn castle(file: File) -> Self {
335 Special::Castle(file)
336 }
337
338 #[inline]
339 pub const fn promote(role: Role) -> Self {
340 Special::Promote(role)
341 }
342
343 #[inline]
344 pub const fn is_en_passant(self) -> bool {
345 matches!(self, Special::EnPassant)
346 }
347
348 #[inline]
349 pub const fn is_castle(self) -> bool {
350 matches!(self, Special::Castle(_))
351 }
352
353 #[inline]
354 pub const fn castle_rook_file(self) -> Option<File> {
355 if let Special::Castle(file) = self { Some(file) } else { None }
356 }
357
358 #[inline]
359 pub const fn is_promote(self) -> bool {
360 matches!(self, Special::Promote(_))
361 }
362
363 #[inline]
364 pub const fn promotes(self) -> Option<Role> {
365 if let Special::Promote(role) = self { Some(role) } else { None }
366 }
367
368 #[inline]
369 pub const fn is_promote_role(self, role: Role) -> bool {
370 if let Special::Promote(this) = self { this as u8 == role as u8 } else { false }
371 }
372}
373
374pub mod algebraic {
375 pub mod fide {
377 pub const SHORT_CASTLE: &str = "0-0";
378 pub const LONG_CASTLE: &str = "0-0-0";
379 }
380 pub mod pgn {
382 pub const SHORT_CASTLE: &str = "O-O";
383 pub const LONG_CASTLE: &str = "O-O-O";
384 }
385
386 pub const SHORT_CASTLE: &str = pgn::SHORT_CASTLE;
387 pub const LONG_CASTLE: &str = pgn::LONG_CASTLE;
388 pub const MOVE: char = '-';
389 pub const CAPTURE: char = 'x';
390}
391
392pub mod repr {
397 use crate::{
398 formats::{StrInput as Input, prelude::*},
399 square::Rank,
400 };
401
402 use super::*;
403
404 pub fn display(play: &Move, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405 if play.role != Pawn {
406 write!(f, "{}", play.role.upper())?;
407 }
408 write!(f, "{}", play.from)?;
409 match play.capture {
410 Some(Pawn) => write!(f, "x{}", play.to)?,
411 Some(role) => write!(f, "x{}{}", role.upper(), play.to)?,
412 None => write!(f, "-{}", play.to)?,
413 }
414 match play.kind {
415 Kind::Normal => Ok(()),
416 Kind::Special(Special::EnPassant) => write!(f, ".ep"),
417 Kind::Special(Special::Castle(file)) => write!(f, "/{file}"),
418 Kind::Special(Special::Promote(role)) => write!(f, "={}", role.upper()),
419 }
420 }
421
422 pub fn from_str(s: &str) -> Result<Move, Error> {
423 let mut input = s.trim();
424 let play = play(&mut input).map_err(|_| Error)?;
425 input.is_empty().then_some(play).ok_or(Error)
426 }
427
428 #[derive(Debug, thiserror::Error)]
429 #[error("invalid move representation")]
430 pub struct Error;
431
432 pub fn play(input: &mut Input<'_>) -> ModalResult<Move> {
433 let play = seq! {Move {
434 role: role,
435 from: square,
436 capture: capture,
437 to: square,
438 kind: kind,
439 }}
440 .parse_next(input)?;
441
442 let invalid_castle = play.is_castle() && (play.capture.is_some() || play.role != King);
447 let invalid_en_passant =
448 play.is_en_passant() && (play.role != Pawn || play.capture != Some(Pawn));
449 if invalid_castle || invalid_en_passant {
450 return Err(backtrack());
451 }
452
453 Ok(play)
454 }
455
456 fn capture(input: &mut Input<'_>) -> ModalResult<Option<Role>> {
457 alt(('-'.value(None), preceded('x', role).map(Some))).parse_next(input)
458 }
459
460 fn role(input: &mut Input<'_>) -> ModalResult<Role> {
461 opt(explicit_role).map(|role| role.unwrap_or(Pawn)).parse_next(input)
462 }
463
464 fn explicit_role(input: &mut Input<'_>) -> ModalResult<Role> {
465 one_of(|c| "NBRQK".contains(c)).map(Role::panicky_from_char).parse_next(input)
466 }
467
468 fn kind(input: &mut Input<'_>) -> ModalResult<Kind> {
469 opt(special).map(|special| special.map_or(Kind::Normal, Kind::Special)).parse_next(input)
470 }
471
472 fn special(input: &mut Input<'_>) -> ModalResult<Special> {
473 alt((
474 preceded('/', file).map(Special::castle),
475 ".ep".value(Special::en_passant()),
476 preceded('=', promotion).map(Special::promote),
477 ))
478 .parse_next(input)
479 }
480
481 fn promotion(input: &mut Input<'_>) -> ModalResult<Role> {
482 one_of(|c| "NBRQ".contains(c)).map(Role::panicky_from_char).parse_next(input)
483 }
484
485 fn square(input: &mut Input<'_>) -> ModalResult<Square> {
486 (file, rank).map(|(file, rank)| Square::new(file, rank)).parse_next(input)
487 }
488
489 fn file(input: &mut Input<'_>) -> ModalResult<File> {
490 one_of(|c| "abcdefgh".contains(c)).map(File::panicky_from_char).parse_next(input)
491 }
492
493 fn rank(input: &mut Input<'_>) -> ModalResult<Rank> {
494 one_of(|c| "12345678".contains(c)).map(Rank::panicky_from_char).parse_next(input)
495 }
496}
497
498#[test]
499fn display_play() {
500 use Square::*;
501
502 let mut play = Move::promote(A2, H7, Queen);
503
504 assert_eq!(play.to_string(), "a2-h7=Q");
505 assert_eq!(play.to_string().parse::<Move>().unwrap(), play);
506 assert_eq!(play.uci_chess().to_string(), "a2h7q");
507
508 play.role = King;
509 assert_eq!(play.to_string(), "Ka2-h7=Q");
510 assert_eq!(play.to_string().parse::<Move>().unwrap(), play);
511 assert_eq!(play.uci_chess().to_string(), "a2h7q");
512
513 play = play.capturing(Bishop);
514 assert_eq!(play.to_string(), "Ka2xBh7=Q");
515 assert_eq!(play.to_string().parse::<Move>().unwrap(), play);
516 assert_eq!(play.uci_chess().to_string(), "a2h7q");
517
518 let play = Move::chess_castle(White, Side::King);
519 assert_eq!(play.to_string(), "Ke1-g1/h");
520 assert_eq!(play.to_string().parse::<Move>().unwrap(), play);
521 assert_eq!(play.uci_chess().to_string(), "e1g1");
522
523 let play = Move::chess_castle(Black, Side::Queen);
524 assert_eq!(play.to_string(), "Ke8-c8/a");
525 assert_eq!(play.to_string().parse::<Move>().unwrap(), play);
526 assert_eq!(play.uci_chess().to_string(), "e8c8");
527
528 let play = Move::castle(White, G1, F);
529 assert_eq!(play.to_string(), "Kg1-c1/f");
530 assert_eq!(play.to_string().parse::<Move>().unwrap(), play);
531 assert_eq!(play.uci_chess().to_string(), "g1c1");
532 assert_eq!(play.uci_960().to_string(), "g1f1");
533
534 let play = Move::en_passant(E5, D6);
535 assert_eq!(play.capture, Some(Pawn));
536 assert!(play.is_capture());
537 assert_eq!(play.to_string(), "e5xd6.ep");
538 assert_eq!(play.to_string().parse::<Move>().unwrap(), play);
539
540 let play = Move::capture(Pawn, E5, D6, Some(Pawn));
541 assert_eq!(play.to_string(), "e5xd6");
542 assert_eq!(play.to_string().parse::<Move>().unwrap(), play);
543}