1use core::fmt;
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(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
16pub struct Move {
18 pub kind: Kind,
19 pub role: Role,
21 pub from: Square,
22 pub to: Square,
23 pub capture: Option<Role>,
25}
26
27#[repr(u8)]
29#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
30pub enum Kind {
31 #[default]
32 Normal,
33 Special(Special),
34}
35
36#[repr(u8)]
37#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
38pub enum Special {
39 EnPassant,
40 Castle(File),
41 Promote(Role),
42}
43
44impl Kind {
45 #[inline]
46 pub const fn normal() -> Self {
47 Kind::Normal
48 }
49
50 #[inline]
51 pub const fn special(special: Special) -> Self {
52 Kind::Special(special)
53 }
54
55 #[inline]
56 pub const fn en_passant() -> Self {
57 Kind::Special(Special::EnPassant)
58 }
59
60 #[inline]
61 pub const fn castle(file: File) -> Self {
62 Kind::Special(Special::Castle(file))
63 }
64
65 #[inline]
66 pub const fn promote(role: Role) -> Self {
67 Kind::Special(Special::Promote(role))
68 }
69
70 #[inline]
71 pub const fn is_normal(self) -> bool {
72 matches!(self, Kind::Normal)
73 }
74
75 #[inline]
76 pub const fn is_special(self) -> bool {
77 matches!(self, Kind::Normal)
78 }
79
80 #[inline]
81 pub const fn specials(self) -> Option<Special> {
82 if let Kind::Special(special) = self { Some(special) } else { None }
83 }
84
85 #[inline]
86 pub const fn is_en_passant(self) -> bool {
87 if let Kind::Special(special) = self { special.is_en_passant() } else { false }
88 }
89
90 #[inline]
91 pub const fn is_castle(self) -> bool {
92 if let Kind::Special(special) = self { special.is_castle() } else { false }
93 }
94
95 #[inline]
96 pub const fn castle_rook_file(self) -> Option<File> {
97 if let Kind::Special(special) = self { special.castle_rook_file() } else { None }
98 }
99
100 #[inline]
101 pub const fn is_promote(self) -> bool {
102 if let Kind::Special(special) = self { special.is_promote() } else { false }
103 }
104
105 #[inline]
106 pub const fn promotes(self) -> Option<Role> {
107 if let Kind::Special(special) = self { special.promotes() } else { None }
108 }
109
110 #[inline]
111 pub const fn is_promote_role(self, role: Role) -> bool {
112 if let Kind::Special(special) = self { special.is_promote_role(role) } else { false }
113 }
114}
115
116impl Move {
122 #[inline]
123 pub const fn normal(role: Role, from: Square, to: Square) -> Move {
124 Move::capture(role, from, to, None)
125 }
126
127 #[inline]
128 pub const fn capture(role: Role, from: Square, to: Square, capture: Option<Role>) -> Move {
129 Move { kind: Kind::Normal, role, from, to, capture }
130 }
131
132 #[inline]
133 pub const fn chess_castle(player: Player, side: Side) -> Move {
134 Move::castle(player, Square::new(E, player.backrank()), side.chess_rook())
135 }
136
137 #[inline]
138 pub const fn castle(player: Player, from: Square, file: File) -> Move {
139 let side = Side::of_rook(from, file);
140 Move {
141 kind: Kind::Special(Special::Castle(file)),
142 role: King,
143 from,
144 to: player.castle_king_to(side),
145 capture: None,
146 }
147 }
148
149 #[inline]
150 pub const fn en_passant(from: Square, to: Square) -> Move {
151 Move { role: Pawn, from, to, capture: None, kind: Kind::Special(Special::EnPassant) }
152 }
153
154 #[inline]
155 pub const fn promote(from: Square, to: Square, role: Role) -> Move {
156 Move::promote_capture(from, to, role, None)
157 }
158
159 #[inline]
160 pub const fn promote_capture(
161 from: Square,
162 to: Square,
163 role: Role,
164 capture: Option<Role>,
165 ) -> Move {
166 Move { role: Pawn, from, to, capture, kind: Kind::Special(Special::Promote(role)) }
167 }
168
169 pub fn pawn(player: Player, from: Square, to: Square, capture: Option<Role>) -> Vec<Move> {
170 let mut moves = Vec::new();
171
172 if to.rank() as u8 == player.promotion_rank() as u8 {
173 for role in [Queen, Rook, Bishop, Knight] {
174 moves.push(Move::promote_capture(from, to, role, capture));
175 }
176 } else {
177 moves.push(Move::capture(Pawn, from, to, capture));
178 }
179
180 moves
181 }
182}
183
184impl Move {
186 #[inline]
187 pub const fn capturing(mut self, role: Role) -> Move {
188 self.capture = Some(role);
189 self
190 }
191}
192
193impl Move {
195 #[inline]
196 pub const fn is_normal(self) -> bool {
197 self.kind.is_normal()
198 }
199
200 #[inline]
201 pub const fn is_special(self) -> bool {
202 self.kind.is_special()
203 }
204
205 #[inline]
206 pub const fn specials(self) -> Option<Special> {
207 self.kind.specials()
208 }
209}
210
211impl Move {
213 #[inline]
214 pub const fn is_en_passant(self) -> bool {
215 self.kind.is_en_passant()
216 }
217
218 #[inline]
219 pub const fn is_castle(self) -> bool {
220 self.kind.is_castle()
221 }
222
223 #[inline]
224 pub const fn castle_rook_file(self) -> Option<File> {
225 self.kind.castle_rook_file()
226 }
227
228 #[inline]
229 pub const fn is_castle_side(self, side: Side) -> bool {
230 match self.castle_side() {
231 Some(this) => this.eq(side),
232 None => false,
233 }
234 }
235
236 #[inline]
237 pub const fn castle_side(self) -> Option<Side> {
238 match self.castle_rook_file() {
239 Some(file) => Some(Side::of_rook(self.from, file)),
240 None => None,
241 }
242 }
243
244 #[inline]
245 pub const fn is_promote(self) -> bool {
246 self.kind.is_promote()
247 }
248
249 #[inline]
250 pub const fn promotes(self) -> Option<Role> {
251 self.kind.promotes()
252 }
253
254 #[inline]
255 pub const fn is_promote_role(self, role: Role) -> bool {
256 self.kind.is_promote_role(role)
257 }
258}
259
260impl Move {
262 #[inline]
263 pub const fn is_capture(self) -> bool {
264 self.capture.is_some()
265 }
266
267 #[inline]
268 pub const fn captures(self) -> Option<Role> {
269 self.capture
270 }
271
272 #[inline]
273 pub const fn is_capture_role(self, role: Role) -> bool {
274 if let Some(this) = self.capture { this as u8 == role as u8 } else { false }
275 }
276}
277
278impl Move {
280 #[inline]
281 pub const fn code(self) -> u16 {
282 (self.to as u16)
283 | ((self.from as u16) << 6)
284 | (self.promotion_code() << 12)
285 | (self.kind_code() << 14)
286 }
287
288 #[inline]
289 const fn promotion_code(self) -> u16 {
290 match self.promotes() {
291 Some(Knight) => 0,
292 Some(Bishop) => 1,
293 Some(Rook) => 2,
294 Some(Queen) => 3,
295 _ => 0,
296 }
297 }
298
299 #[inline]
300 const fn kind_code(self) -> u16 {
301 match self.specials() {
302 Some(Special::Promote(_)) => 1,
303 Some(Special::EnPassant) => 2,
304 Some(Special::Castle(_)) => 3,
305 None => 0,
306 }
307 }
308
309 fn long_algebraic(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 use algebraic::*;
311 use fmt::Write as _;
312
313 let Move { role, from, to, capture, kind } = *self;
314
315 if let Some(side) = self.castle_side() {
316 f.write_str(if side == Side::King { SHORT_CASTLE } else { LONG_CASTLE })
317 } else {
318 if role != Pawn {
319 f.write_char(role.upper())?;
320 }
321 let does = if capture.is_some() { CAPTURE } else { MOVE };
322 write!(f, "{}{}{}", from, does, to)?;
323
324 if let Some(promoted) = kind.promotes() {
325 write!(f, "={}", promoted.upper())?;
326 }
327 Ok(())
328 }
329 }
330}
331
332impl fmt::Display for Move {
334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335 self.long_algebraic(f)
336 }
337}
338
339impl Special {
340 #[inline]
341 pub const fn en_passant() -> Self {
342 Special::EnPassant
343 }
344
345 #[inline]
346 pub const fn castle(file: File) -> Self {
347 Special::Castle(file)
348 }
349
350 #[inline]
351 pub const fn promote(role: Role) -> Self {
352 Special::Promote(role)
353 }
354
355 #[inline]
356 pub const fn is_en_passant(self) -> bool {
357 matches!(self, Special::EnPassant)
358 }
359
360 #[inline]
361 pub const fn is_castle(self) -> bool {
362 matches!(self, Special::Castle(_))
363 }
364
365 #[inline]
366 pub const fn castle_rook_file(self) -> Option<File> {
367 if let Special::Castle(file) = self { Some(file) } else { None }
368 }
369
370 #[inline]
371 pub const fn is_promote(self) -> bool {
372 matches!(self, Special::Promote(_))
373 }
374
375 #[inline]
376 pub const fn promotes(self) -> Option<Role> {
377 if let Special::Promote(role) = self { Some(role) } else { None }
378 }
379
380 #[inline]
381 pub const fn is_promote_role(self, role: Role) -> bool {
382 if let Special::Promote(this) = self { this as u8 == role as u8 } else { false }
383 }
384}
385
386pub mod algebraic {
387 pub mod fide {
389 pub const SHORT_CASTLE: &str = "0-0";
390 pub const LONG_CASTLE: &str = "0-0-0";
391 }
392 pub mod pgn {
394 pub const SHORT_CASTLE: &str = "O-O";
395 pub const LONG_CASTLE: &str = "O-O-O";
396 }
397
398 pub const SHORT_CASTLE: &str = pgn::SHORT_CASTLE;
399 pub const LONG_CASTLE: &str = pgn::LONG_CASTLE;
400 pub const MOVE: char = '-';
401 pub const CAPTURE: char = 'x';
402}
403
404#[test]
405fn display_move() {
406 use Square::*;
407
408 let mut play = Move::promote(A2, H7, Queen);
409
410 assert_eq!(play.to_string(), "a2-h7=Q");
411 assert_eq!(play.uci_chess().to_string(), "a2h7q");
412
413 play.role = King;
414 assert_eq!(play.to_string(), "Ka2-h7=Q");
415 assert_eq!(play.uci_chess().to_string(), "a2h7q");
416
417 play = play.capturing(Bishop);
418 assert_eq!(play.to_string(), "Ka2xh7=Q");
419 assert_eq!(play.uci_chess().to_string(), "a2h7q");
420
421 let play = Move::chess_castle(White, Side::King);
422 assert_eq!(play.to_string(), algebraic::SHORT_CASTLE);
423 assert_eq!(play.uci_chess().to_string(), "e1g1");
424
425 let play = Move::chess_castle(Black, Side::Queen);
426 assert_eq!(play.to_string(), algebraic::LONG_CASTLE);
427 assert_eq!(play.uci_chess().to_string(), "e8c8");
428
429 let play = Move::castle(White, G1, F);
430 assert_eq!(play.to_string(), algebraic::LONG_CASTLE);
431 assert_eq!(play.uci_chess().to_string(), "g1c1");
432 assert_eq!(play.uci_960().to_string(), "g1f1");
433}