chess_lab/common/constants/pgn.rs
1use std::{cell::RefCell, fmt::Display, rc::Rc};
2
3use super::{GameStatus, Position};
4
5/// A struct representing a PGN line or variation
6/// Its also a tree node that contains a list of child nodes, the parent node,
7/// the move number and the move itself
8///
9#[derive(Debug, Clone)]
10pub struct PgnLine<T: PartialEq + Clone + Display> {
11 pub lines: Vec<Rc<RefCell<PgnLine<T>>>>,
12 pub parent: Option<Rc<RefCell<PgnLine<T>>>>,
13 pub halfmove_clock: u32,
14 pub fullmove_number: u32,
15 pub en_passant: Option<Position>,
16 pub castling_rights: u8,
17 pub game_status: GameStatus,
18 pub mov: T,
19}
20
21impl<T: PartialEq + Clone + Display> PartialEq for PgnLine<T> {
22 /// Compares two PgnLine structs
23 /// Two PgnLine structs are equal if their moves are equal
24 ///
25 /// # Arguments
26 /// * `other`: The other PgnLine struct
27 ///
28 /// # Returns
29 /// A boolean indicating if the two PgnLine structs are equal
30 ///
31 fn eq(&self, other: &Self) -> bool {
32 self.mov == other.mov
33 }
34}
35
36/// A struct representing a PGN tree
37/// It contains the game metadata and a list of lines
38/// The current line is the move node that is currently being checked
39///
40#[derive(Debug, Clone)]
41pub struct PgnTree<T: PartialEq + Clone + Display> {
42 pub event: Option<String>,
43 pub site: Option<String>,
44 pub date: Option<String>,
45 pub round: Option<String>,
46 pub white: Option<String>,
47 pub black: Option<String>,
48 pub result: Option<String>,
49 pub variant: Option<String>,
50 pub white_elo: Option<u32>,
51 pub black_elo: Option<u32>,
52 pub time_control: Option<String>,
53 pub termination: Option<String>,
54 lines: Vec<Rc<RefCell<PgnLine<T>>>>,
55 current_line: Option<Rc<RefCell<PgnLine<T>>>>,
56}
57
58impl<T: PartialEq + Clone + Display> Default for PgnTree<T> {
59 /// Creates a new PgnTree with no metadata and an empty list of lines
60 ///
61 /// # Returns
62 /// A new PgnTree
63 ///
64 /// # Examples
65 /// ```
66 /// use chess_lab::constants::pgn::PgnTree;
67 /// use chess_lab::constants::Move;
68 ///
69 /// let tree: PgnTree<Move> = PgnTree::default();
70 /// ```
71 ///
72 fn default() -> PgnTree<T> {
73 PgnTree {
74 event: None,
75 site: None,
76 date: None,
77 round: None,
78 white: None,
79 black: None,
80 result: None,
81 variant: None,
82 white_elo: None,
83 black_elo: None,
84 time_control: None,
85 termination: None,
86 lines: Vec::new(),
87 current_line: None,
88 }
89 }
90}
91
92impl<T: PartialEq + Clone + Display> PgnTree<T> {
93 /// Creates a new PgnTree with the provided metadata and an empty list of lines
94 ///
95 /// # Arguments
96 /// * `event`: The event name
97 /// * `site`: The site name
98 /// * `date`: The date of the game
99 /// * `round`: The round number
100 /// * `white`: The white player name
101 /// * `black`: The black player name
102 /// * `result`: The result of the game
103 /// * `variant`: The variant of the game
104 /// * `white_elo`: The white player ELO
105 /// * `black_elo`: The black player ELO
106 /// * `time_control`: The time control of the game
107 ///
108 /// # Returns
109 /// A new PgnTree
110 ///
111 /// # Examples
112 /// ```
113 /// use chess_lab::constants::pgn::PgnTree;
114 /// use chess_lab::constants::Move;
115 ///
116 /// let tree: PgnTree<Move> = PgnTree::new(
117 /// Some("Event".to_string()),
118 /// Some("Site".to_string()),
119 /// Some("Date".to_string()),
120 /// Some("Round".to_string()),
121 /// Some("White".to_string()),
122 /// Some("Black".to_string()),
123 /// Some("Result".to_string()),
124 /// Some("Variant".to_string()),
125 /// Some(1000),
126 /// Some(1000),
127 /// Some("Time Control".to_string()),
128 /// Some("Termination".to_string()),
129 /// );
130 /// ```
131 ///
132 pub fn new(
133 event: Option<String>,
134 site: Option<String>,
135 date: Option<String>,
136 round: Option<String>,
137 white: Option<String>,
138 black: Option<String>,
139 result: Option<String>,
140 variant: Option<String>,
141 white_elo: Option<u32>,
142 black_elo: Option<u32>,
143 time_control: Option<String>,
144 termination: Option<String>,
145 ) -> PgnTree<T> {
146 PgnTree {
147 event,
148 site,
149 date,
150 round,
151 white,
152 black,
153 result,
154 variant,
155 white_elo,
156 black_elo,
157 time_control,
158 termination,
159 lines: Vec::new(),
160 current_line: None,
161 }
162 }
163
164 /// Adds a move to the current line
165 ///
166 /// # Arguments
167 /// * `mov`: The move to add
168 /// * `halfmove_clock`: The halfmove clock
169 /// * `fullmove_number`: The fullmove number
170 /// * `en_passant`: The en passant position
171 /// * `castling_rights`: The castling rights
172 ///
173 /// # Examples
174 /// ```
175 /// use chess_lab::constants::{pgn::PgnTree, Move, MoveType, PieceType, Color, Position, GameStatus};
176 /// use chess_lab::logic::Piece;
177 ///
178 /// let mut pgn_tree: PgnTree<Move> = PgnTree::default();
179 /// let mov = Move::new(
180 /// Piece::new(Color::Black, PieceType::Pawn),
181 /// Position::from_string("e2"),
182 /// Position::from_string("e4"),
183 /// MoveType::Normal {
184 /// capture: false,
185 /// promotion: None,
186 /// },
187 /// None,
188 /// None,
189 /// (false, false),
190 /// false,
191 /// false,
192 /// );
193 /// pgn_tree.add_move(mov.clone(), 0, 0, None, 0, GameStatus::InProgress);
194 ///
195 /// assert_eq!(mov, pgn_tree.get_move().unwrap());
196 /// ```
197 ///
198 pub fn add_move(
199 &mut self,
200 mov: T,
201 halfmove_clock: u32,
202 fullmove_number: u32,
203 en_passant: Option<Position>,
204 castling_rights: u8,
205 game_status: GameStatus,
206 ) {
207 if let Some(current_line) = &self.current_line {
208 let new_line = Rc::new(RefCell::new(PgnLine {
209 lines: Vec::new(),
210 parent: Some(Rc::clone(¤t_line)),
211 halfmove_clock,
212 fullmove_number,
213 en_passant,
214 castling_rights,
215 game_status,
216 mov,
217 }));
218 if !current_line.as_ref().borrow_mut().lines.contains(&new_line) {
219 current_line
220 .as_ref()
221 .borrow_mut()
222 .lines
223 .push(Rc::clone(&new_line));
224 }
225 self.current_line = Some(new_line);
226 } else {
227 let new_line = Rc::new(RefCell::new(PgnLine {
228 lines: Vec::new(),
229 parent: None,
230 halfmove_clock,
231 fullmove_number,
232 en_passant,
233 castling_rights,
234 game_status,
235 mov,
236 }));
237 self.lines.push(Rc::clone(&new_line));
238 self.current_line = Some(new_line);
239 }
240 }
241
242 /// Removes the current line
243 ///
244 /// # Examples
245 /// ```
246 /// use chess_lab::constants::{pgn::PgnTree, Move, PieceType, MoveType, Color, Position, GameStatus};
247 /// use chess_lab::logic::Piece;
248 ///
249 /// let mut tree = PgnTree::default();
250 ///
251 /// tree.add_move(Move::new(
252 /// Piece::new(Color::Black, PieceType::Pawn),
253 /// Position::from_string("e2"),
254 /// Position::from_string("e4"),
255 /// MoveType::Normal {
256 /// capture: false,
257 /// promotion: None,
258 /// },
259 /// None,
260 /// None,
261 /// (false, false),
262 /// false,
263 /// false,
264 /// ), 0, 0, None, 0, GameStatus::InProgress);
265 /// tree.rm_move();
266 /// ```
267 ///
268 pub fn rm_move(&mut self) {
269 if let None = &self.current_line {
270 return;
271 }
272
273 let current_line = self.current_line.take().unwrap();
274 let current_line_borrowed = current_line.borrow();
275
276 if current_line_borrowed.parent.is_none() {
277 return;
278 }
279
280 let parent = Rc::clone(¤t_line_borrowed.parent.as_ref().unwrap());
281 let index = parent
282 .borrow()
283 .lines
284 .iter()
285 .position(|x| Rc::ptr_eq(x, &self.current_line.as_ref().unwrap()))
286 .unwrap();
287
288 parent.borrow_mut().lines.remove(index);
289
290 self.current_line = Some(parent);
291 }
292
293 /// Returns the current move
294 ///
295 /// # Returns
296 /// The current move
297 ///
298 /// # Examples
299 /// ```
300 /// use chess_lab::constants::{pgn::PgnTree, Move, PieceType, MoveType, Color, Position, GameStatus};
301 /// use chess_lab::logic::Piece;
302 ///
303 /// let mut tree = PgnTree::default();
304 /// let mov = Move::new(
305 /// Piece::new(Color::Black, PieceType::Pawn),
306 /// Position::from_string("e2"),
307 /// Position::from_string("e4"),
308 /// MoveType::Normal {
309 /// capture: false,
310 /// promotion: None,
311 /// },
312 /// None,
313 /// None,
314 /// (false, false),
315 /// false,
316 /// false,
317 /// );
318 /// tree.add_move(mov.clone(), 0, 0, None, 0, GameStatus::InProgress);
319 /// assert_eq!(tree.get_move(), Some(mov));
320 /// ```
321 ///
322 pub fn get_move(&self) -> Option<T> {
323 Some(self.current_line.as_ref()?.borrow().mov.clone())
324 }
325
326 /// Returns the move info
327 ///
328 /// # Returns
329 /// A tuple containing the halfmove clock, the fullmove number, the en passant position and the castling rights
330 ///
331 /// # Examples
332 /// ```
333 /// use chess_lab::constants::{pgn::PgnTree, Move, PieceType, MoveType, Color, Position, GameStatus};
334 /// use chess_lab::logic::Piece;
335 ///
336 /// let mut tree = PgnTree::default();
337 /// let mov = Move::new(
338 /// Piece::new(Color::Black, PieceType::Pawn),
339 /// Position::from_string("e2"),
340 /// Position::from_string("e4"),
341 /// MoveType::Normal {
342 /// capture: false,
343 /// promotion: None,
344 /// },
345 /// None,
346 /// None,
347 /// (false, false),
348 /// false,
349 /// false,
350 /// );
351 /// tree.add_move(mov.clone(), 0, 0, None, 0, GameStatus::InProgress);
352 ///
353 /// assert_eq!(tree.get_prev_move_info(), (0, 0, None, 0, GameStatus::InProgress));
354 /// ```
355 ///
356 pub fn get_prev_move_info(&self) -> (u32, u32, Option<Position>, u8, GameStatus) {
357 let current_line = self
358 .current_line
359 .as_ref()
360 .unwrap_or_else(|| {
361 panic!("No current line found. Please add a move before calling this method")
362 })
363 .borrow();
364 (
365 current_line.halfmove_clock,
366 current_line.fullmove_number,
367 current_line.en_passant,
368 current_line.castling_rights,
369 current_line.game_status,
370 )
371 }
372
373 /// Returns the next move
374 ///
375 /// # Returns
376 /// The next move
377 ///
378 /// # Examples
379 /// ```
380 /// use chess_lab::constants::{pgn::PgnTree, Move, PieceType, MoveType, Color, Position, GameStatus};
381 /// use chess_lab::logic::Piece;
382 ///
383 /// let mut pgn_tree = PgnTree::default();
384 /// let mov1 = Move::new(
385 /// Piece::new(Color::Black, PieceType::Pawn),
386 /// Position::from_string("e2"),
387 /// Position::from_string("e4"),
388 /// MoveType::Normal {
389 /// capture: false,
390 /// promotion: None,
391 /// },
392 /// None,
393 /// None,
394 /// (false, false),
395 /// false,
396 /// false,
397 /// );
398 /// let mov2 = Move::new(
399 /// Piece::new(Color::White, PieceType::Pawn),
400 /// Position::from_string("e7"),
401 /// Position::from_string("e5"),
402 /// MoveType::Normal {
403 /// capture: false,
404 /// promotion: None,
405 /// },
406 /// None,
407 /// None,
408 /// (false, false),
409 /// false,
410 /// false,
411 /// );
412 /// pgn_tree.add_move(mov1.clone(), 0, 0, None, 0, GameStatus::InProgress);
413 /// pgn_tree.add_move(mov2.clone(), 0, 0, None, 0, GameStatus::InProgress);
414 ///
415 /// assert_eq!(mov2, pgn_tree.get_move().unwrap());
416 /// assert_eq!(mov1, pgn_tree.prev_move().unwrap());
417 /// assert_eq!(mov2, pgn_tree.next_move().unwrap());
418 /// ```
419 ///
420 pub fn next_move(&mut self) -> Option<T> {
421 self.next_move_variant(0)
422 }
423
424 /// Returns the next move variant
425 ///
426 /// # Arguments
427 /// * `variant`: The variant to get
428 ///
429 /// # Returns
430 /// The next move variant
431 ///
432 /// # Examples
433 /// ```
434 /// use chess_lab::constants::{pgn::PgnTree, Move, PieceType, MoveType, Color, Position, GameStatus};
435 /// use chess_lab::logic::Piece;
436 ///
437 /// let mut pgn_tree = PgnTree::default();
438 /// let mov1 = Move::new(
439 /// Piece::new(Color::White, PieceType::Pawn),
440 /// Position::from_string("e2"),
441 /// Position::from_string("e4"),
442 /// MoveType::Normal {
443 /// capture: false,
444 /// promotion: None,
445 /// },
446 /// None,
447 /// None,
448 /// (false, false),
449 /// false,
450 /// false,
451 /// );
452 /// let mov2 = Move::new(
453 /// Piece::new(Color::White, PieceType::Pawn),
454 /// Position::from_string("d2"),
455 /// Position::from_string("d4"),
456 /// MoveType::Normal {
457 /// capture: false,
458 /// promotion: None,
459 /// },
460 /// None,
461 /// None,
462 /// (false, false),
463 /// false,
464 /// false,
465 /// );
466 /// pgn_tree.add_move(mov1.clone(), 0, 0, None, 0, GameStatus::InProgress);
467 /// pgn_tree.prev_move();
468 /// pgn_tree.add_move(mov2.clone(), 0, 0, None, 0, GameStatus::InProgress);
469 ///
470 /// pgn_tree.prev_move();
471 /// assert_eq!(mov1, pgn_tree.next_move().unwrap());
472 ///
473 /// pgn_tree.prev_move();
474 /// assert_eq!(mov2, pgn_tree.next_move_variant(1).unwrap());
475 /// ```
476 ///
477 pub fn next_move_variant(&mut self, variant: u32) -> Option<T> {
478 if let Some(current_line) = &self.current_line {
479 if current_line.borrow().lines.len() > variant as usize {
480 let next_line = Rc::clone(¤t_line.borrow().lines[variant as usize]);
481 self.current_line = Some(Rc::clone(&next_line));
482 return Some(next_line.borrow().mov.clone());
483 }
484 } else {
485 if self.lines.len() > variant as usize {
486 let next_line = Rc::clone(&self.lines[variant as usize]);
487 self.current_line = Some(Rc::clone(&next_line));
488 return Some(next_line.borrow().mov.clone());
489 }
490 }
491 None
492 }
493
494 /// Returns all the next moves
495 ///
496 /// # Returns
497 /// All the next moves
498 ///
499 /// # Example
500 /// ```
501 /// use chess_lab::constants::{pgn::PgnTree, Move, PieceType, MoveType, Color, Position, GameStatus};
502 /// use chess_lab::logic::Piece;
503 ///
504 /// let mut pgn_tree = PgnTree::default();
505 /// let mov1 = Move::new(
506 /// Piece::new(Color::White, PieceType::Pawn),
507 /// Position::from_string("e4"),
508 /// Position::from_string("e2"),
509 /// MoveType::Normal {
510 /// capture: false,
511 /// promotion: None,
512 /// },
513 /// None,
514 /// None,
515 /// (false, false),
516 /// false,
517 /// false,
518 /// );
519 ///
520 /// let mov2 = Move::new(
521 /// Piece::new(Color::White, PieceType::Pawn),
522 /// Position::from_string("d2"),
523 /// Position::from_string("d4"),
524 /// MoveType::Normal {
525 /// capture: false,
526 /// promotion: None,
527 /// },
528 /// None,
529 /// None,
530 /// (false, false),
531 /// false,
532 /// false,
533 /// );
534 ///
535 /// pgn_tree.add_move(mov1.clone(), 0, 0, None, 0, GameStatus::InProgress);
536 /// pgn_tree.prev_move();
537 /// pgn_tree.add_move(mov2.clone(), 0, 0, None, 0, GameStatus::InProgress);
538 /// pgn_tree.prev_move();
539 ///
540 /// assert_eq!(vec![mov1.clone(), mov2.clone()], pgn_tree.all_next_moves());
541 /// ```
542 ///
543 pub fn all_next_moves(&self) -> Vec<T> {
544 let mut moves = Vec::new();
545 if let Some(current_line) = &self.current_line {
546 for line in current_line.borrow().lines.iter() {
547 moves.push(line.borrow().mov.clone());
548 }
549 } else {
550 for line in self.lines.iter() {
551 moves.push(line.borrow().mov.clone());
552 }
553 }
554 moves
555 }
556
557 /// Returns the previous move
558 ///
559 /// # Returns
560 /// The previous move
561 ///
562 /// # Examples
563 /// ```
564 /// use chess_lab::constants::{pgn::PgnTree, Move, PieceType, MoveType, Color, Position, GameStatus};
565 /// use chess_lab::logic::Piece;
566 ///
567 /// let mut pgn_tree = PgnTree::default();
568 /// let mov1 = Move::new(
569 /// Piece::new(Color::Black, PieceType::Pawn),
570 /// Position::from_string("e2"),
571 /// Position::from_string("e4"),
572 /// MoveType::Normal {
573 /// capture: false,
574 /// promotion: None,
575 /// },
576 /// None,
577 /// None,
578 /// (false, false),
579 /// false,
580 /// false,
581 /// );
582 /// let mov2 = Move::new(
583 /// Piece::new(Color::White, PieceType::Pawn),
584 /// Position::from_string("e7"),
585 /// Position::from_string("e5"),
586 /// MoveType::Normal {
587 /// capture: false,
588 /// promotion: None,
589 /// },
590 /// None,
591 /// None,
592 /// (false, false),
593 /// false,
594 /// false,
595 /// );
596 /// pgn_tree.add_move(mov1.clone(), 0, 0, None, 0, GameStatus::InProgress);
597 /// pgn_tree.add_move(mov2.clone(), 0, 0, None, 0, GameStatus::InProgress);
598 ///
599 /// assert_eq!(mov2, pgn_tree.get_move().unwrap());
600 /// assert_eq!(mov1, pgn_tree.prev_move().unwrap());
601 /// ```
602 ///
603 pub fn prev_move(&mut self) -> Option<T> {
604 if self.current_line.is_none() || self.current_line.as_ref()?.borrow().parent.is_none() {
605 self.current_line = None;
606 return None;
607 }
608
609 let parent = Rc::clone(
610 &self
611 .current_line
612 .as_ref()?
613 .borrow()
614 .parent
615 .as_ref()
616 .unwrap(),
617 );
618 self.current_line = Some(Rc::clone(&parent));
619 Some(self.current_line.as_ref()?.borrow().mov.clone())
620 }
621
622 pub fn pgn(&self) -> String {
623 let mut pgn = String::new();
624 pgn.push_str(&self.pgn_header());
625 pgn.push_str(&self.pgn_moves());
626 pgn
627 }
628
629 /// Returns the PGN header
630 ///
631 /// # Returns
632 /// The PGN header
633 ///
634 fn pgn_header(&self) -> String {
635 let mut header = String::new();
636 if let Some(event) = &self.event {
637 header.push_str(&format!("[Event \"{}\"]\n", event));
638 }
639 if let Some(site) = &self.site {
640 header.push_str(&format!("[Site \"{}\"]\n", site));
641 }
642 if let Some(date) = &self.date {
643 header.push_str(&format!("[Date \"{}\"]\n", date));
644 }
645 if let Some(round) = &self.round {
646 header.push_str(&format!("[Round \"{}\"]\n", round));
647 }
648 if let Some(white) = &self.white {
649 header.push_str(&format!("[White \"{}\"]\n", white));
650 }
651 if let Some(black) = &self.black {
652 header.push_str(&format!("[Black \"{}\"]\n", black));
653 }
654 if let Some(result) = &self.result {
655 header.push_str(&format!("[Result \"{}\"]\n", result));
656 }
657 if let Some(white_elo) = &self.white_elo {
658 header.push_str(&format!("[WhiteElo \"{}\"]\n", white_elo));
659 }
660 if let Some(black_elo) = &self.black_elo {
661 header.push_str(&format!("[BlackElo \"{}\"]\n", black_elo));
662 }
663 if let Some(time_control) = &self.time_control {
664 header.push_str(&format!("[TimeControl \"{}\"]\n", time_control));
665 }
666 if let Some(variant) = &self.variant {
667 header.push_str(&format!("[Variant \"{}\"]\n", variant));
668 }
669 header
670 }
671
672 fn pgn_moves(&self) -> String {
673 let mut pgn = String::new();
674
675 if self.lines.is_empty() {
676 return pgn;
677 }
678
679 let line = self.lines[0].as_ref().borrow();
680 pgn.push_str(&format!("1. {}", line.mov));
681
682 for next in self.lines.iter().skip(1) {
683 pgn.push_str(&format!(
684 " {}",
685 self.pgn_line_moves(Rc::clone(next), 1, true)
686 ));
687 }
688
689 pgn.push_str(&format!(
690 " {}",
691 self.pgn_line_moves(Rc::clone(&self.lines[0]), 2, false)
692 ));
693
694 pgn
695 }
696
697 fn pgn_line_moves(
698 &self,
699 line: Rc<RefCell<PgnLine<T>>>,
700 move_number: u32,
701 secondary: bool,
702 ) -> String {
703 let mut pgn = String::new();
704
705 let mut tmp_move_number = move_number;
706
707 if secondary {
708 if tmp_move_number % 2 == 0 {
709 pgn.push_str(&format!("{}... ", tmp_move_number / 2))
710 } else {
711 pgn.push_str(&format!("{}. ", tmp_move_number / 2 + 1));
712 };
713 pgn.push_str(&format!("{} ", line.as_ref().borrow().mov));
714
715 tmp_move_number += 1;
716 }
717
718 let mut stack = vec![line];
719
720 while let Some(current) = stack.pop() {
721 let line = current.as_ref().borrow();
722
723 if line.lines.is_empty() {
724 pgn.pop();
725 continue;
726 } else {
727 if tmp_move_number % 2 != 0 {
728 pgn.push_str(&format!("{}. ", tmp_move_number / 2 + 1));
729 };
730 tmp_move_number += 1;
731
732 let next = Rc::clone(&line.lines[0]);
733 pgn.push_str(&format!("{} ", next.as_ref().borrow().mov));
734 stack.push(Rc::clone(&next));
735
736 if line.lines.len() != 1 {
737 for next in line.lines.iter().skip(1) {
738 pgn.push_str(&format!(
739 "{} ",
740 self.pgn_line_moves(Rc::clone(next), tmp_move_number - 1, true)
741 ));
742 }
743 }
744 }
745 }
746
747 if secondary {
748 format!("({})", pgn)
749 } else {
750 pgn
751 }
752 }
753}
754
755impl<T: PartialEq + Clone + Display> Iterator for PgnTree<T> {
756 type Item = T;
757
758 /// Returns the next move
759 ///
760 /// # Returns
761 /// The next move
762 ///
763 /// # Examples
764 /// ```
765 /// use chess_lab::constants::{pgn::PgnTree, Move, PieceType, MoveType, Color, Position, GameStatus};
766 /// use chess_lab::logic::Piece;
767 ///
768 /// let mut pgn_tree = PgnTree::default();
769 /// let mov1 = Move::new(
770 /// Piece::new(Color::Black, PieceType::Pawn),
771 /// Position::from_string("e2"),
772 /// Position::from_string("e4"),
773 /// MoveType::Normal {
774 /// capture: false,
775 /// promotion: None,
776 /// },
777 /// None,
778 /// None,
779 /// (false, false),
780 /// false,
781 /// false,
782 /// );
783 /// let mov2 = Move::new(
784 /// Piece::new(Color::White, PieceType::Pawn),
785 /// Position::from_string("e7"),
786 /// Position::from_string("e5"),
787 /// MoveType::Normal {
788 /// capture: false,
789 /// promotion: None,
790 /// },
791 /// None,
792 /// None,
793 /// (false, false),
794 /// false,
795 /// false,
796 /// );
797 /// pgn_tree.add_move(mov1.clone(), 0, 0, None, 0, GameStatus::InProgress);
798 /// pgn_tree.add_move(mov2.clone(), 0, 0, None, 0, GameStatus::InProgress);
799 ///
800 /// assert_eq!(mov2, pgn_tree.get_move().unwrap());
801 /// assert_eq!(mov1, pgn_tree.next_back().unwrap());
802 /// assert_eq!(mov2, pgn_tree.next().unwrap());
803 /// ```
804 ///
805 fn next(&mut self) -> Option<Self::Item> {
806 self.next_move()
807 }
808}
809
810impl<T: PartialEq + Clone + Display> DoubleEndedIterator for PgnTree<T> {
811 /// Returns the previous move
812 ///
813 /// # Returns
814 /// The previous move
815 ///
816 /// # Examples
817 /// ```
818 /// use chess_lab::constants::{pgn::PgnTree, Move, PieceType, MoveType, Color, Position, GameStatus};
819 /// use chess_lab::logic::Piece;
820 ///
821 /// let mut pgn_tree = PgnTree::default();
822 /// let mov1 = Move::new(
823 /// Piece::new(Color::Black, PieceType::Pawn),
824 /// Position::from_string("e2"),
825 /// Position::from_string("e4"),
826 /// MoveType::Normal {
827 /// capture: false,
828 /// promotion: None,
829 /// },
830 /// None,
831 /// None,
832 /// (false, false),
833 /// false,
834 /// false,
835 /// );
836 /// let mov2 = Move::new(
837 /// Piece::new(Color::White, PieceType::Pawn),
838 /// Position::from_string("e7"),
839 /// Position::from_string("e5"),
840 /// MoveType::Normal {
841 /// capture: false,
842 /// promotion: None,
843 /// },
844 /// None,
845 /// None,
846 /// (false, false),
847 /// false,
848 /// false,
849 /// );
850 /// pgn_tree.add_move(mov1.clone(), 0, 0, None, 0, GameStatus::InProgress);
851 /// pgn_tree.add_move(mov2.clone(), 0, 0, None, 0, GameStatus::InProgress);
852 ///
853 /// assert_eq!(mov2, pgn_tree.get_move().unwrap());
854 /// assert_eq!(mov1, pgn_tree.next_back().unwrap());
855 /// ```
856 ///
857 fn next_back(&mut self) -> Option<Self::Item> {
858 self.prev_move()
859 }
860}
861
862#[cfg(test)]
863mod tests {
864 use crate::constants::pgn::PgnTree;
865 use crate::constants::{Color, GameStatus, Move, MoveType, PieceType, Position};
866 use crate::logic::Piece;
867
868 #[test]
869 fn test_add_move() {
870 let mut pgn_tree = PgnTree::default();
871 let mov = Move::new(
872 Piece::new(Color::Black, PieceType::Pawn),
873 Position::from_string("e2"),
874 Position::from_string("e4"),
875 MoveType::Normal {
876 capture: false,
877 promotion: None,
878 },
879 None,
880 None,
881 (false, false),
882 false,
883 false,
884 );
885 pgn_tree.add_move(mov.clone(), 0, 0, None, 0, GameStatus::InProgress);
886
887 assert_eq!(mov, pgn_tree.get_move().unwrap());
888 }
889
890 #[test]
891 fn test_rm_move() {
892 let mut pgn_tree = PgnTree::default();
893 let mov = Move::new(
894 Piece::new(Color::Black, PieceType::Pawn),
895 Position::from_string("e2"),
896 Position::from_string("e4"),
897 MoveType::Normal {
898 capture: false,
899 promotion: None,
900 },
901 None,
902 None,
903 (false, false),
904 false,
905 false,
906 );
907 pgn_tree.add_move(mov.clone(), 0, 0, None, 0, GameStatus::InProgress);
908 pgn_tree.rm_move();
909
910 assert!(pgn_tree.get_move().is_none());
911 }
912
913 #[test]
914 fn test_prev_move() {
915 let mut pgn_tree = PgnTree::default();
916 let mov1 = Move::new(
917 Piece::new(Color::Black, PieceType::Pawn),
918 Position::from_string("e2"),
919 Position::from_string("e4"),
920 MoveType::Normal {
921 capture: false,
922 promotion: None,
923 },
924 None,
925 None,
926 (false, false),
927 false,
928 false,
929 );
930 let mov2 = Move::new(
931 Piece::new(Color::White, PieceType::Pawn),
932 Position::from_string("e7"),
933 Position::from_string("e5"),
934 MoveType::Normal {
935 capture: false,
936 promotion: None,
937 },
938 None,
939 None,
940 (false, false),
941 false,
942 false,
943 );
944 pgn_tree.add_move(mov1.clone(), 0, 0, None, 0, GameStatus::InProgress);
945 pgn_tree.add_move(mov2.clone(), 0, 0, None, 0, GameStatus::InProgress);
946
947 assert_eq!(mov2, pgn_tree.get_move().unwrap());
948 assert_eq!(mov1, pgn_tree.prev_move().unwrap());
949 assert!(pgn_tree.prev_move().is_none());
950 }
951
952 #[test]
953 fn test_next_move() {
954 let mut pgn_tree = PgnTree::default();
955 let mov1 = Move::new(
956 Piece::new(Color::Black, PieceType::Pawn),
957 Position::from_string("e2"),
958 Position::from_string("e4"),
959 MoveType::Normal {
960 capture: false,
961 promotion: None,
962 },
963 None,
964 None,
965 (false, false),
966 false,
967 false,
968 );
969 let mov2 = Move::new(
970 Piece::new(Color::White, PieceType::Pawn),
971 Position::from_string("e7"),
972 Position::from_string("e5"),
973 MoveType::Normal {
974 capture: false,
975 promotion: None,
976 },
977 None,
978 None,
979 (false, false),
980 false,
981 false,
982 );
983 pgn_tree.add_move(mov1.clone(), 0, 0, None, 0, GameStatus::InProgress);
984 pgn_tree.add_move(mov2.clone(), 0, 0, None, 0, GameStatus::InProgress);
985
986 assert_eq!(mov2, pgn_tree.get_move().unwrap());
987 assert_eq!(mov1, pgn_tree.prev_move().unwrap());
988 assert_eq!(mov2, pgn_tree.next_move().unwrap());
989 }
990
991 #[test]
992 fn test_all_next_moves() {
993 let mut pgn_tree = PgnTree::default();
994 let mov1 = Move::new(
995 Piece::new(Color::White, PieceType::Pawn),
996 Position::from_string("e4"),
997 Position::from_string("e2"),
998 MoveType::Normal {
999 capture: false,
1000 promotion: None,
1001 },
1002 None,
1003 None,
1004 (false, false),
1005 false,
1006 false,
1007 );
1008
1009 let mov2 = Move::new(
1010 Piece::new(Color::White, PieceType::Pawn),
1011 Position::from_string("d2"),
1012 Position::from_string("d4"),
1013 MoveType::Normal {
1014 capture: false,
1015 promotion: None,
1016 },
1017 None,
1018 None,
1019 (false, false),
1020 false,
1021 false,
1022 );
1023
1024 pgn_tree.add_move(mov1.clone(), 0, 0, None, 0, GameStatus::InProgress);
1025 pgn_tree.prev_move();
1026 pgn_tree.add_move(mov2.clone(), 0, 0, None, 0, GameStatus::InProgress);
1027 pgn_tree.prev_move();
1028
1029 assert_eq!(vec![mov1.clone(), mov2.clone()], pgn_tree.all_next_moves());
1030 }
1031
1032 #[test]
1033 fn test_pgn_header() {
1034 let mut tree: PgnTree<Move> = PgnTree::default();
1035 tree.event = Some("Event".to_string());
1036 tree.site = Some("Site".to_string());
1037 tree.date = Some("Date".to_string());
1038 tree.round = Some("Round".to_string());
1039 tree.white = Some("White".to_string());
1040 tree.black = Some("Black".to_string());
1041 tree.result = Some("Result".to_string());
1042 tree.white_elo = Some(1000);
1043 tree.black_elo = Some(1000);
1044 tree.time_control = Some("TimeControl".to_string());
1045 tree.variant = Some("Variant".to_string());
1046
1047 assert_eq!(tree.pgn_header(), "[Event \"Event\"]\n[Site \"Site\"]\n[Date \"Date\"]\n[Round \"Round\"]\n[White \"White\"]\n[Black \"Black\"]\n[Result \"Result\"]\n[WhiteElo \"1000\"]\n[BlackElo \"1000\"]\n[TimeControl \"TimeControl\"]\n[Variant \"Variant\"]\n");
1048 }
1049
1050 #[test]
1051 fn test_next_variant() {
1052 let mut pgn_tree = PgnTree::default();
1053 let mov1 = Move::new(
1054 Piece::new(Color::White, PieceType::Pawn),
1055 Position::from_string("e2"),
1056 Position::from_string("e4"),
1057 MoveType::Normal {
1058 capture: false,
1059 promotion: None,
1060 },
1061 None,
1062 None,
1063 (false, false),
1064 false,
1065 false,
1066 );
1067 let mov2 = Move::new(
1068 Piece::new(Color::White, PieceType::Pawn),
1069 Position::from_string("d2"),
1070 Position::from_string("d4"),
1071 MoveType::Normal {
1072 capture: false,
1073 promotion: None,
1074 },
1075 None,
1076 None,
1077 (false, false),
1078 false,
1079 false,
1080 );
1081 pgn_tree.add_move(mov1.clone(), 0, 0, None, 0, GameStatus::InProgress);
1082 pgn_tree.prev_move();
1083 pgn_tree.add_move(mov2.clone(), 0, 0, None, 0, GameStatus::InProgress);
1084
1085 pgn_tree.prev_move();
1086 assert_eq!(mov1, pgn_tree.next_move().unwrap());
1087
1088 pgn_tree.prev_move();
1089 assert_eq!(mov2, pgn_tree.next_move_variant(1).unwrap());
1090 }
1091
1092 #[test]
1093 fn test_pgn() {
1094 let mut pgn_tree = PgnTree::default();
1095 let mov1 = Move::new(
1096 Piece::new(Color::White, PieceType::Pawn),
1097 Position::from_string("e2"),
1098 Position::from_string("e4"),
1099 MoveType::Normal {
1100 capture: false,
1101 promotion: None,
1102 },
1103 None,
1104 None,
1105 (false, false),
1106 false,
1107 false,
1108 );
1109 let mov2 = Move::new(
1110 Piece::new(Color::Black, PieceType::Pawn),
1111 Position::from_string("e7"),
1112 Position::from_string("e5"),
1113 MoveType::Normal {
1114 capture: false,
1115 promotion: None,
1116 },
1117 None,
1118 None,
1119 (false, false),
1120 false,
1121 false,
1122 );
1123 pgn_tree.add_move(mov1.clone(), 0, 0, None, 0, GameStatus::InProgress);
1124 pgn_tree.add_move(mov2.clone(), 0, 0, None, 0, GameStatus::InProgress);
1125
1126 assert_eq!(pgn_tree.pgn(), "1. e4 e5");
1127 }
1128}