1use std::{collections::HashMap, fmt};
2
3use crate::errors::MoveInfoError;
4
5use super::{GameStatus, Piece, PieceType, Position};
6
7#[derive(Debug, Clone, PartialEq)]
10pub enum MoveType {
11 Normal {
13 capture: bool,
15 promotion: Option<PieceType>,
17 },
18 Castle {
20 side: CastleType,
22 },
23 EnPassant,
25}
26
27#[derive(Debug, Clone, PartialEq)]
30pub enum CastleType {
31 KingSide,
33 QueenSide,
35}
36
37#[derive(Debug, Clone, PartialEq)]
40pub struct Move {
41 pub piece: Piece,
43 pub from: Position,
45 pub to: Position,
47 pub move_type: MoveType,
49 pub captured_piece: Option<PieceType>,
51 pub rook_from: Option<Position>,
53 pub ambiguity: (bool, bool),
55 pub check: bool,
57 pub checkmate: bool,
59}
60
61impl Move {
62 pub fn new(
110 piece: Piece,
111 from: Position,
112 to: Position,
113 move_type: MoveType,
114 captured_piece: Option<PieceType>,
115 rook_from: Option<Position>,
116 ambiguity: (bool, bool),
117 check: bool,
118 checkmate: bool,
119 ) -> Result<Move, MoveInfoError> {
120 let mov = Move {
121 piece,
122 from,
123 to,
124 move_type: move_type.clone(),
125 captured_piece,
126 rook_from,
127 ambiguity,
128 check,
129 checkmate,
130 };
131 match &move_type {
132 MoveType::Normal {
133 capture: _,
134 promotion,
135 } => {
136 if promotion.is_some() {
137 if piece.piece_type != PieceType::Pawn {
138 return Err(MoveInfoError::new(
139 String::from("The move is a promotion, but the piece is not a pawn"),
140 mov,
141 ));
142 }
143 }
144 }
145 MoveType::Castle { side: _ } => {
146 if piece.piece_type != PieceType::King {
147 return Err(MoveInfoError::new(
148 String::from("The move is a castle, but the piece is not a king"),
149 mov,
150 ));
151 }
152 if rook_from.is_none() {
153 return Err(MoveInfoError::new(
154 String::from("The move is a castle, but no rook position is provided"),
155 mov,
156 ));
157 }
158 }
159 MoveType::EnPassant => {
160 if piece.piece_type != PieceType::Pawn {
161 return Err(MoveInfoError::new(
162 String::from("The move is an en passant, but the piece is not a pawn"),
163 mov,
164 ));
165 }
166 }
167 }
168 Ok(mov)
169 }
170}
171
172impl fmt::Display for Move {
173 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
174 let mut result = String::new();
175 if self.piece.piece_type != PieceType::Pawn {
176 result.push(self.piece.piece_type.to_char());
177 }
178 match &self.move_type {
179 MoveType::Castle { side } => {
180 result = match side {
181 CastleType::KingSide => "O-O".to_string(),
182 CastleType::QueenSide => "O-O-O".to_string(),
183 };
184 }
185 MoveType::Normal { capture, promotion } => {
186 let from_string = self.from.to_string();
187 if self.ambiguity.0 || (PieceType::Pawn == self.piece.piece_type && *capture) {
188 result.push(from_string.chars().nth(0).unwrap());
189 }
190 if self.ambiguity.1 {
191 result.push(from_string.chars().nth(1).unwrap());
192 }
193 if *capture {
194 result.push('x');
195 }
196 result.push_str(&self.to.to_string());
197 if let Some(promotion) = promotion {
198 result.push('=');
199 result.push(promotion.to_char());
200 }
201 }
202 MoveType::EnPassant => {
203 result.push_str(&self.from.to_string());
204 result.push('x');
205 result.push_str(&self.to.to_string());
206 }
207 }
208 if self.checkmate {
209 result.push('#');
210 } else if self.check {
211 result.push('+');
212 }
213
214 write!(f, "{}", result)
215 }
216}
217
218#[derive(Debug, PartialEq, Eq, Clone)]
221pub struct MoveInfo {
222 pub halfmove_clock: u32,
224 pub fullmove_number: u32,
226 pub en_passant: Option<Position>,
228 pub castling_rights: u8,
230 pub game_status: GameStatus,
232 pub prev_positions: HashMap<String, u32>,
234}
235
236impl MoveInfo {
237 pub fn new(
262 halfmove_clock: u32,
263 fullmove_number: u32,
264 en_passant: Option<Position>,
265 castling_rights: u8,
266 game_status: GameStatus,
267 prev_positions: HashMap<String, u32>,
268 ) -> MoveInfo {
269 MoveInfo {
270 halfmove_clock,
271 fullmove_number,
272 en_passant,
273 castling_rights,
274 game_status,
275 prev_positions,
276 }
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use crate::core::{Color, PieceType};
284
285 #[test]
286 fn test_move_display_normal() {
287 let piece = Piece::new(Color::White, PieceType::Knight);
288 let from = Position::new(1, 0).unwrap(); let to = Position::new(2, 2).unwrap(); let move_type = MoveType::Normal {
291 capture: false,
292 promotion: None,
293 };
294 let mv = Move::new(
295 piece,
296 from,
297 to,
298 move_type,
299 None,
300 None,
301 (false, false),
302 false,
303 false,
304 )
305 .unwrap();
306 assert_eq!(mv.to_string(), "Nc3");
307 }
308
309 #[test]
310 fn test_move_display_capture() {
311 let piece = Piece::new(Color::Black, PieceType::Bishop);
312 let from = Position::new(2, 7).unwrap(); let to = Position::new(5, 4).unwrap(); let move_type = MoveType::Normal {
315 capture: true,
316 promotion: None,
317 };
318 let mv = Move::new(
319 piece,
320 from,
321 to,
322 move_type,
323 Some(PieceType::Pawn),
324 None,
325 (false, false),
326 true,
327 false,
328 )
329 .unwrap();
330 assert_eq!(mv.to_string(), "Bxf5+");
331 }
332
333 #[test]
334 fn test_move_display_kingside_castle() {
335 let piece = Piece::new(Color::White, PieceType::King);
336 let from = Position::new(4, 0).unwrap(); let to = Position::new(6, 0).unwrap(); let move_type = MoveType::Castle {
339 side: CastleType::KingSide,
340 };
341 let mv = Move::new(
342 piece,
343 from,
344 to,
345 move_type,
346 None,
347 Some(Position::new(7, 0).unwrap()), (false, false),
349 false,
350 false,
351 )
352 .unwrap();
353 assert_eq!(mv.to_string(), "O-O");
354 }
355
356 #[test]
357 fn test_move_display_queenside_castle() {
358 let piece = Piece::new(Color::Black, PieceType::King);
359 let from = Position::new(4, 7).unwrap(); let to = Position::new(2, 7).unwrap(); let move_type = MoveType::Castle {
362 side: CastleType::QueenSide,
363 };
364 let mv = Move::new(
365 piece,
366 from,
367 to,
368 move_type,
369 None,
370 Some(Position::new(0, 7).unwrap()), (false, false),
372 false,
373 false,
374 )
375 .unwrap();
376 assert_eq!(mv.to_string(), "O-O-O");
377 }
378
379 #[test]
380 fn test_promoting_non_pawn_error() {
381 let piece = Piece::new(Color::White, PieceType::Knight);
382 let from = Position::new(6, 7).unwrap(); let to = Position::new(7, 7).unwrap(); let move_type = MoveType::Normal {
385 capture: false,
386 promotion: Some(PieceType::Queen),
387 };
388 let mv_result = Move::new(
389 piece,
390 from,
391 to,
392 move_type,
393 None,
394 None,
395 (false, false),
396 false,
397 false,
398 );
399 assert!(mv_result.is_err());
400 }
401
402 #[test]
403 fn test_castling_non_king_error() {
404 let piece = Piece::new(Color::Black, PieceType::Queen);
405 let from = Position::new(4, 7).unwrap(); let to = Position::new(6, 7).unwrap(); let move_type = MoveType::Castle {
408 side: CastleType::KingSide,
409 };
410 let mv_result = Move::new(
411 piece,
412 from,
413 to,
414 move_type,
415 None,
416 Some(Position::new(7, 7).unwrap()), (false, false),
418 false,
419 false,
420 );
421 assert!(mv_result.is_err());
422 }
423
424 #[test]
425 fn test_castle_with_no_rook_error() {
426 let piece = Piece::new(Color::White, PieceType::King);
427 let from = Position::new(4, 0).unwrap(); let to = Position::new(2, 0).unwrap(); let move_type = MoveType::Castle {
430 side: CastleType::QueenSide,
431 };
432 let mv_result = Move::new(
433 piece,
434 from,
435 to,
436 move_type,
437 None,
438 None, (false, false),
440 false,
441 false,
442 );
443 assert!(mv_result.is_err());
444 }
445
446 #[test]
447 fn test_en_passant_non_pawn_error() {
448 let piece = Piece::new(Color::Black, PieceType::Bishop);
449 let from = Position::new(3, 4).unwrap(); let to = Position::new(4, 3).unwrap(); let move_type = MoveType::EnPassant;
452
453 let mv_result = Move::new(
454 piece,
455 from,
456 to,
457 move_type,
458 None,
459 None,
460 (false, false),
461 false,
462 false,
463 );
464 assert!(mv_result.is_err());
465 }
466}