1use core::ops::Neg;
2
3use super::{self as game, *};
4
5#[derive(Debug, thiserror::Error)]
7pub enum Error {
8 #[error("change tracking is disabled")]
9 Disabled,
10 #[error(transparent)]
11 Insert(#[from] Insert),
12}
13
14#[derive(Debug, thiserror::Error)]
16pub enum Insert {
17 #[error("move slot {0} is unavailable")]
18 Unavailable(MoveSlot),
19}
20
21#[derive(Clone, Deserialize, PartialEq, Serialize)]
24pub enum Patch {
25 Game(Box<GamePublic>),
27 Position { slot: PositionSlot, public: Box<PositionPublic> },
29 Move { slot: MoveSlot, public: Box<MovePublic> },
31 Insert { position: PositionSlot, index: usize, branch: Branch },
33 Remove(MoveSlot),
35 Drag { position: PositionSlot, from: usize, to: usize },
37 Swap { position: PositionSlot, a: usize, b: usize },
39}
40
41#[derive(Clone, Deserialize, PartialEq, Serialize)]
43pub enum Change {
44 Game { before: Box<GamePublic>, after: Box<GamePublic> },
45 Position { slot: PositionSlot, before: Box<PositionPublic>, after: Box<PositionPublic> },
46 Move { slot: MoveSlot, before: Box<MovePublic>, after: Box<MovePublic> },
47 Insert { position: PositionSlot, index: usize, branch: Branch },
48 Remove { position: PositionSlot, index: usize, branch: Branch },
49 Drag { position: PositionSlot, from: usize, to: usize },
50 Swap { position: PositionSlot, a: usize, b: usize },
51}
52
53#[derive(Clone, Deserialize, PartialEq, Serialize)]
55pub struct Branch {
56 pub slot: MoveSlot,
58 pub play: chess::Move,
60 #[serde(default, skip_serializing_if = "macros::is_default")]
62 pub public: BranchPublic,
63 #[serde(default, skip_serializing_if = "macros::is_default")]
65 pub options: Vec<Branch>,
66}
67
68#[derive(Clone, Default, Deserialize, PartialEq, Serialize)]
70pub struct BranchPublic {
71 #[serde(default, skip_serializing_if = "macros::is_default")]
73 pub play: MovePublic,
74 #[serde(default, skip_serializing_if = "macros::is_default")]
76 pub position: PositionPublic,
77}
78
79impl Game {
80 pub fn undo(&mut self) -> game::Result<()> {
81 let Some(mut changes) = self.changes.take() else {
82 return Err(Error::Disabled.into());
83 };
84 let Some(change) = changes.pop() else {
85 self.changes = Some(changes);
86 return Ok(());
87 };
88 let backward = -change;
89 if let Err(error) = self.apply(&backward) {
90 changes.push(-backward);
91 self.changes = Some(changes);
92 return Err(error);
93 }
94 self.changes = Some(changes);
95 Ok(())
96 }
97
98 pub fn restore(&mut self, revision: usize) -> game::Result<()> {
99 if !self.tracks_changes() {
100 return Err(Error::Disabled.into());
101 }
102 while self.revisions().is_some_and(|revisions| revisions > revision) {
103 self.undo()?;
104 }
105 Ok(())
106 }
107
108 pub fn patch(&mut self, patch: Patch) -> game::Result<()> {
109 match patch {
110 Patch::Game(public) => self.update_public(|current| *current = *public),
111 Patch::Position { slot, public } => {
112 self.position_mut(slot)?.update_public(|current| *current = *public);
113 }
114 Patch::Move { slot, public } => {
115 self.play_mut(slot)?.update_public(|current| *current = *public);
116 }
117 Patch::Insert { position, index, branch } => {
118 self.insert_tracked(position, index, branch)?;
119 }
120 Patch::Remove(slot) => {
121 let position = self.play(slot)?.previous().slot();
122 self.position_mut(position)?.remove(slot)?;
123 }
124 Patch::Drag { position, from, to } => {
125 self.position_mut(position)?.drag(from, to)?;
126 }
127 Patch::Swap { position, a, b } => {
128 self.position_mut(position)?.swap(a, b)?;
129 }
130 }
131 Ok(())
132 }
133
134 pub(in crate::game) fn track(&mut self, inverse: Patch) {
135 let Some(mut changes) = self.changes.take() else { return };
136 let change = -inverse.reversible(self).expect("recorded patch applies to the current game");
137 changes.push(change);
138 self.changes = Some(changes);
139 }
140
141 fn apply(&mut self, change: &Change) -> game::Result<()> {
142 match change {
143 Change::Game { after, .. } => self.public = (**after).clone(),
144 Change::Position { slot, after, .. } => {
145 self.position_mut(*slot)?.position_mut().public = (**after).clone();
146 }
147 Change::Move { slot, after, .. } => {
148 self.play_mut(*slot)?.play_mut().public = (**after).clone();
149 }
150 Change::Drag { position, from, to } => {
151 self.position_mut(*position)?.drag_untracked(*from, *to)?;
152 }
153 Change::Swap { position, a, b } => {
154 self.position_mut(*position)?.swap_untracked(*a, *b)?;
155 }
156 Change::Remove { branch, .. } => {
157 let position = self.play(branch.slot)?.previous().slot();
158 self.position_mut(position)?.remove_untracked(branch.slot)?;
159 }
160 Change::Insert { position, index, branch } => {
161 self.insert_untracked(*position, *index, branch)?;
162 }
163 }
164 Ok(())
165 }
166
167 fn insert_tracked(
168 &mut self,
169 position: PositionSlot,
170 index: usize,
171 branch: Branch,
172 ) -> game::Result<()> {
173 self.insert_untracked(position, index, &branch)?;
174 if let Some(changes) = self.changes.as_mut() {
175 changes.push(Change::Insert { position, index, branch });
176 }
177 Ok(())
178 }
179
180 fn insert_untracked(
181 &mut self,
182 position: PositionSlot,
183 index: usize,
184 branch: &Branch,
185 ) -> game::Result<()> {
186 let parent = self.position(position)?;
187 if index > parent.options().len() {
188 let missing = traversal::Missing { position, choice: index.into() };
189 return Err(traversal::Error::from(missing).into());
190 }
191 if let Ok(index) = parent.index(branch.play) {
192 let duplicate = options::Insert::Duplicate(index);
193 return Err(options::Error::from(duplicate).into());
194 }
195
196 let mut trial = parent.into_game();
197 insert_branch(&mut trial, Some(self), START, 0, branch)?;
198 insert_branch(self, None, position, index, branch)
199 }
200}
201
202impl Patch {
203 fn reversible(self, game: &Game) -> traversal::Result<Change> {
204 Ok(match self {
205 Self::Game(after) => Change::Game { before: Box::new(game.public.clone()), after },
206 Self::Position { slot, public: after } => Change::Position {
207 slot,
208 before: Box::new(game.position(slot)?.public.clone()),
209 after,
210 },
211 Self::Move { slot, public: after } => {
212 Change::Move { slot, before: Box::new(game.play(slot)?.public.clone()), after }
213 }
214 Self::Insert { position, index, branch } => Change::Insert { position, index, branch },
215 Self::Remove(slot) => {
216 let position = game.play(slot)?.previous();
217 let index = position.index(slot)?;
218 Change::Remove { position: position.slot(), index, branch: game.tree.branch(slot) }
219 }
220 Self::Drag { position, from, to } => Change::Drag { position, from, to },
221 Self::Swap { position, a, b } => Change::Swap { position, a, b },
222 })
223 }
224}
225
226impl Change {
227 pub fn forward(&self) -> Patch {
228 match self {
229 Self::Game { after, .. } => Patch::Game(after.clone()),
230 Self::Position { slot, after, .. } => {
231 Patch::Position { slot: *slot, public: after.clone() }
232 }
233 Self::Move { slot, after, .. } => Patch::Move { slot: *slot, public: after.clone() },
234 Self::Insert { position, index, branch } => {
235 Patch::Insert { position: *position, index: *index, branch: branch.clone() }
236 }
237 Self::Remove { branch, .. } => Patch::Remove(branch.slot),
238 Self::Drag { position, from, to } => {
239 Patch::Drag { position: *position, from: *from, to: *to }
240 }
241 Self::Swap { position, a, b } => Patch::Swap { position: *position, a: *a, b: *b },
242 }
243 }
244
245 pub fn backward(&self) -> Patch {
246 match self {
247 Self::Game { before, .. } => Patch::Game(before.clone()),
248 Self::Position { slot, before, .. } => {
249 Patch::Position { slot: *slot, public: before.clone() }
250 }
251 Self::Move { slot, before, .. } => Patch::Move { slot: *slot, public: before.clone() },
252 Self::Insert { branch, .. } => Patch::Remove(branch.slot),
253 Self::Remove { position, index, branch } => {
254 Patch::Insert { position: *position, index: *index, branch: branch.clone() }
255 }
256 Self::Drag { position, from, to } => {
257 Patch::Drag { position: *position, from: *to, to: *from }
258 }
259 Self::Swap { position, a, b } => Patch::Swap { position: *position, a: *a, b: *b },
260 }
261 }
262}
263
264impl Neg for Change {
265 type Output = Self;
266
267 fn neg(self) -> Self::Output {
268 match self {
269 Self::Game { before, after } => Self::Game { before: after, after: before },
270 Self::Position { slot, before, after } => {
271 Self::Position { slot, before: after, after: before }
272 }
273 Self::Move { slot, before, after } => Self::Move { slot, before: after, after: before },
274 Self::Insert { position, index, branch } => Self::Remove { position, index, branch },
275 Self::Remove { position, index, branch } => Self::Insert { position, index, branch },
276 Self::Drag { position, from, to } => Self::Drag { position, from: to, to: from },
277 Self::Swap { position, a, b } => Self::Swap { position, a, b },
278 }
279 }
280}
281
282fn insert_branch(
283 game: &mut Game,
284 source: Option<&Game>,
285 position: PositionSlot,
286 index: usize,
287 branch: &Branch,
288) -> game::Result<()> {
289 let unavailable_in_source = source.is_some_and(|source| source.play(branch.slot).is_ok());
290 let unavailable_in_game = game.play(branch.slot).is_ok();
291 if unavailable_in_source || unavailable_in_game {
292 return Err(Error::Insert(Insert::Unavailable(branch.slot)).into());
293 }
294
295 game.position_mut(position)?.insert_at_untracked(Some(branch.slot), index, branch.play)?;
296 let mut play = game.play_mut(branch.slot)?;
297 play.play_mut().public = branch.public.play.clone();
298 play.position_mut().position_mut().public = branch.public.position.clone();
299
300 for (index, option) in branch.options.iter().enumerate() {
301 insert_branch(game, source, branch.slot.into(), index, option)?;
302 }
303 Ok(())
304}
305
306#[cfg(test)]
307mod tests {
308 use arbitrary::{Arbitrary as _, Unstructured};
309
310 use crate::{
311 Game, Player, Position,
312 board::Role::{Knight, Pawn},
313 formats::Pgn,
314 position::{Evaluation, Terminal},
315 square::Square::*,
316 };
317
318 use super::*;
319
320 #[test]
321 fn game_change_tracking() {
322 let mut game = Game::new(Position::start());
323 assert!(!game.tracks_changes());
324 assert_eq!(game.revisions(), None);
325 assert!(matches!(game.undo(), Err(game::Error::Change(Error::Disabled))));
326 assert!(matches!(game.restore(0), Err(game::Error::Change(Error::Disabled))));
327
328 game.track_changes(true);
329 assert!(game.tracks_changes());
330 assert_eq!(game.revisions(), Some(0));
331 game.track_changes(false);
332 assert!(!game.tracks_changes());
333 assert!(game.changes().is_none());
334 }
335
336 #[test]
337 fn game_public_changes() {
338 let mut game = Game::new(Position::start());
339 game.set_orientation(Player::Black);
340 assert!(game.changes().is_none());
341
342 game.track_changes(true);
343 game.set_orientation(Player::Black);
344 game.update_roster(|_| ());
345 game.update_tags(|_| ());
346 assert!(game.changes().unwrap().is_empty());
347
348 game.set_orientation(Player::White);
349 game.set_outcome(Outcome::Draw);
350 game.update_roster(|roster| roster.event = Some("Event".parse().unwrap()));
351 game.update_tags(|tags| {
352 tags.push(Tag { key: "Annotator".parse().unwrap(), value: "Player".to_string() });
353 });
354 assert_eq!(game.changes().unwrap().len(), 4);
355
356 let Change::Game { before, after } = &game.changes().unwrap()[0] else { panic!() };
357 assert_eq!(before.orientation, Player::Black);
358 assert_eq!(after.orientation, Player::White);
359
360 let Change::Game { before, after } = &game.changes().unwrap()[3] else { panic!() };
361 assert!(before.tags.is_empty());
362 assert_eq!(after.tags.len(), 1);
363 }
364
365 #[test]
366 fn position_public_changes() {
367 let mut game = Game::new(Position::start());
368 let comment: Text = "Comment".parse().unwrap();
369 game.start_mut().set_comment(Some(comment.clone()));
370 assert!(game.changes().is_none());
371
372 game.track_changes(true);
373 game.start_mut().set_comment(Some(comment));
374 assert!(game.changes().unwrap().is_empty());
375
376 game.start_mut().set_comment(None);
377 game.start_mut().set_evaluation(Some(Evaluation::Terminal(Terminal::Stalemate)));
378 game.start_mut().set_expanded(true);
379 assert_eq!(game.changes().unwrap().len(), 2);
380 assert_eq!(game.start().evaluation(), Some(&Evaluation::Terminal(Terminal::Stalemate)));
381
382 let Change::Position { slot, before, after } = &game.changes().unwrap()[0] else {
383 panic!()
384 };
385 assert_eq!(*slot, START);
386 assert!(before.comment.is_some());
387 assert!(after.comment.is_none());
388 }
389
390 #[test]
391 fn clear_evaluations_does_not_change_history() {
392 let mut game = Game::new(Position::start());
393 game.track_changes(true);
394 let slot = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
395 let evaluation = Evaluation::Terminal(Terminal::Stalemate);
396 game.start_mut().set_evaluation(Some(evaluation.clone()));
397 game.play_mut(slot).unwrap().position_mut().set_evaluation(Some(evaluation));
398 let revision = game.revisions();
399
400 game.clear_evaluations();
401
402 assert_eq!(game.start().evaluation(), None);
403 assert_eq!(game.play(slot).unwrap().evaluation(), None);
404 assert_eq!(game.revisions(), revision);
405 }
406
407 #[test]
408 fn move_public_changes() {
409 let mut game = Game::new(Position::start());
410 let slot = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
411 let intro: Text = "Intro".parse().unwrap();
412 game.play_mut(slot).unwrap().set_intro(Some(intro.clone()));
413 assert!(game.changes().is_none());
414
415 game.track_changes(true);
416 game.play_mut(slot).unwrap().set_intro(Some(intro));
417 assert!(game.changes().unwrap().is_empty());
418
419 game.play_mut(slot).unwrap().set_intro(None);
420 game.play_mut(slot).unwrap().set_outro(Some("Outro".parse().unwrap()));
421 game.play_mut(slot).unwrap().update_commands(|commands| commands.push(Command::expanded()));
422 game.play_mut(slot).unwrap().update_nags(|nags| nags.push(Nag::Numeric(1)));
423 assert_eq!(game.changes().unwrap().len(), 4);
424
425 let Change::Move { slot: patched, before, after } = &game.changes().unwrap()[0] else {
426 panic!()
427 };
428 assert_eq!(*patched, slot);
429 assert!(before.affixes.intro.is_some());
430 assert!(after.affixes.intro.is_none());
431 }
432
433 #[test]
434 fn option_order_changes() {
435 let mut game = Game::new(Position::start());
436 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
437 let d4 = game.start_mut().push(crate::Move::normal(Pawn, D2, D4)).unwrap().slot();
438 let c4 = game.start_mut().push(crate::Move::normal(Pawn, C2, C4)).unwrap().slot();
439
440 game.track_changes(true);
441 assert!(!game.start_mut().promote(e4).unwrap());
442 assert!(game.changes().unwrap().is_empty());
443
444 assert!(game.start_mut().promote(c4).unwrap());
445 assert!(game.start_mut().swap(c4, d4).unwrap());
446 assert_eq!(game.changes().unwrap().len(), 2);
447
448 let Change::Drag { position, from, to } = game.changes().unwrap()[0] else { panic!() };
449 assert_eq!(position, START);
450 assert_eq!((from, to), (2, 0));
451
452 let Change::Swap { position, a, b } = game.changes().unwrap()[1] else { panic!() };
453 assert_eq!(position, START);
454 assert_eq!((a, b), (0, 2));
455 }
456
457 #[test]
458 fn apply_public_patch() {
459 let mut game = Game::new(Position::start());
460 game.track_changes(true);
461 game.set_outcome(Outcome::Draw);
462
463 let draw = game.public.clone();
464 let change = game.changes().unwrap()[0].clone();
465 game.apply(&-change).unwrap();
466 assert_eq!(game.outcome(), Outcome::Unknown);
467 assert_eq!(game.changes().unwrap().len(), 1);
468
469 let change = Patch::Game(Box::new(draw)).reversible(&game).unwrap();
470 game.apply(&change).unwrap();
471 assert_eq!(game.outcome(), Outcome::Draw);
472 assert_eq!(game.changes().unwrap().len(), 1);
473 }
474
475 #[test]
476 fn patch_records_actual_state() {
477 let mut game = Game::new(Position::start());
478 let mut public = game.public.clone();
479 public.outcome = Outcome::Draw;
480 game.patch(Patch::Game(Box::new(public.clone()))).unwrap();
481 assert!(game.changes().is_none());
482
483 game.track_changes(true);
484 public.outcome = Outcome::White;
485 game.patch(Patch::Game(Box::new(public))).unwrap();
486
487 let Change::Game { before, after } = &game.changes().unwrap()[0] else { panic!() };
488 assert_eq!(before.outcome, Outcome::Draw);
489 assert_eq!(after.outcome, Outcome::White);
490 }
491
492 #[test]
493 fn replay_forward_history() {
494 let mut game = Game::new(Position::start());
495 let base = game.clone();
496 game.track_changes(true);
497 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
498 game.play_mut(e4).unwrap().set_comment(Some("King's pawn".parse().unwrap()));
499 game.play_mut(e4).unwrap().position_mut().push(crate::Move::normal(Pawn, E7, E5)).unwrap();
500 game.set_outcome(Outcome::Draw);
501
502 let patches = game
503 .changes()
504 .unwrap()
505 .iter()
506 .map(|change| {
507 let encoded_change = serde_json::to_string(change).unwrap();
508 let decoded_change: Change = serde_json::from_str(&encoded_change).unwrap();
509 assert!(decoded_change == *change);
510
511 let patch = change.forward();
512 let encoded_patch = serde_json::to_string(&patch).unwrap();
513 let decoded_patch: Patch = serde_json::from_str(&encoded_patch).unwrap();
514 assert!(decoded_patch == patch);
515 decoded_patch
516 })
517 .collect::<Vec<_>>();
518
519 assert_eq!(
520 serde_json::to_value(&patches[0]).unwrap(),
521 serde_json::json!({
522 "Insert": {"position": 0, "index": 0, "branch": {"slot": 1, "play": "e2-e4"}}
523 })
524 );
525 assert_eq!(
526 serde_json::to_value(&game.changes().unwrap()[1]).unwrap(),
527 serde_json::json!({
528 "Position": {"slot": 1, "before": {}, "after": {"comment": "King's pawn"}}
529 })
530 );
531
532 let mut replay = base;
533 replay.track_changes(true);
534 for patch in patches {
535 replay.patch(patch).unwrap();
536 }
537
538 assert!(replay.changes() == game.changes());
539 assert_eq!(Pgn::from(replay), Pgn::from(game));
540 }
541
542 #[test]
543 fn apply_order_patch() {
544 let mut game = Game::new(Position::start());
545 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
546 let d4 = game.start_mut().push(crate::Move::normal(Pawn, D2, D4)).unwrap().slot();
547 let c4 = game.start_mut().push(crate::Move::normal(Pawn, C2, C4)).unwrap().slot();
548 game.track_changes(true);
549 game.start_mut().promote(c4).unwrap();
550
551 let change = game.changes().unwrap()[0].clone();
552 game.apply(&-change).unwrap();
553 assert_eq!(game.start().options(), [e4, d4, c4]);
554 assert_eq!(game.changes().unwrap().len(), 1);
555 }
556
557 #[test]
558 fn apply_remove_patch() {
559 let mut game = Game::new(Position::start());
560 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
561 let e5 = game
562 .play_mut(e4)
563 .unwrap()
564 .position_mut()
565 .push(crate::Move::normal(Pawn, E7, E5))
566 .unwrap()
567 .slot();
568 game.track_changes(true);
569
570 game.patch(Patch::Remove(e4)).unwrap();
571 assert!(game.play(e4).is_err());
572 assert!(game.play(e5).is_err());
573
574 let Change::Remove { position, index, branch } = &game.changes().unwrap()[0] else {
575 panic!()
576 };
577 assert_eq!(*position, START);
578 assert_eq!(*index, 0);
579 assert_eq!(branch.slot, e4);
580 assert_eq!(branch.options[0].slot, e5);
581 }
582
583 #[test]
584 fn branch_capture() {
585 let mut game = Game::new(Position::start());
586 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
587 let e5 = game
588 .play_mut(e4)
589 .unwrap()
590 .position_mut()
591 .push(crate::Move::normal(Pawn, E7, E5))
592 .unwrap()
593 .slot();
594 let c5 = game
595 .play_mut(e4)
596 .unwrap()
597 .position_mut()
598 .push(crate::Move::normal(Pawn, C7, C5))
599 .unwrap()
600 .slot();
601 let nf3 = game
602 .play_mut(e5)
603 .unwrap()
604 .position_mut()
605 .push(crate::Move::normal(Knight, G1, F3))
606 .unwrap()
607 .slot();
608 game.play_mut(e4).unwrap().set_intro(Some("Intro".parse().unwrap()));
609 game.play_mut(e4).unwrap().set_comment(Some("Comment".parse().unwrap()));
610
611 let branch = game.tree.branch(e4);
612 assert_eq!(branch.slot, e4);
613 assert_eq!(branch.play, crate::Move::normal(Pawn, E2, E4));
614 assert!(branch.public.play.affixes.intro.is_some());
615 assert!(branch.public.position.comment.is_some());
616 assert_eq!(branch.options.iter().map(|branch| branch.slot).collect::<Vec<_>>(), [e5, c5]);
617 assert_eq!(branch.options[0].options[0].slot, nf3);
618 }
619
620 #[test]
621 fn remove_patch() {
622 let mut game = Game::new(Position::start());
623 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
624 let e5 = game
625 .play_mut(e4)
626 .unwrap()
627 .position_mut()
628 .push(crate::Move::normal(Pawn, E7, E5))
629 .unwrap()
630 .slot();
631
632 game.track_changes(true);
633 game.start_mut().remove(e4).unwrap();
634 assert!(game.play(e4).is_err());
635 assert!(game.play(e5).is_err());
636 assert_eq!(game.changes().unwrap().len(), 1);
637
638 let Change::Remove { position, index, branch } = &game.changes().unwrap()[0] else {
639 panic!()
640 };
641 assert_eq!(*position, START);
642 assert_eq!(*index, 0);
643 assert_eq!(branch.slot, e4);
644 assert_eq!(branch.options[0].slot, e5);
645
646 assert!(game.start_mut().remove(e4).is_err());
647 assert_eq!(game.changes().unwrap().len(), 1);
648 }
649
650 #[test]
651 fn insert_patch() {
652 let mut game = Game::new(Position::start());
653 let play = crate::Move::normal(Pawn, E2, E4);
654 game.track_changes(true);
655
656 let e4 = game.start_mut().push(play).unwrap().slot();
657 assert_eq!(game.changes().unwrap().len(), 1);
658 let Change::Insert { branch, .. } = &game.changes().unwrap()[0] else { panic!() };
659 assert_eq!(branch.slot, e4);
660 assert_eq!(game.start().options(), [e4]);
661
662 assert!(game.start_mut().push(play).is_err());
663 assert_eq!(game.changes().unwrap().len(), 1);
664 }
665
666 #[test]
667 fn insert_branch_round_trip() {
668 let mut game = Game::new(Position::start());
669 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
670 let e5 = game
671 .play_mut(e4)
672 .unwrap()
673 .position_mut()
674 .push(crate::Move::normal(Pawn, E7, E5))
675 .unwrap()
676 .slot();
677 game.play_mut(e4).unwrap().position_mut().push(crate::Move::normal(Pawn, C7, C5)).unwrap();
678 game.play_mut(e5)
679 .unwrap()
680 .position_mut()
681 .push(crate::Move::normal(Knight, G1, F3))
682 .unwrap();
683 game.play_mut(e4).unwrap().set_intro(Some("Intro".parse().unwrap()));
684 game.play_mut(e5).unwrap().set_comment(Some("Comment".parse().unwrap()));
685
686 let before = game.clone();
687 let branch = game.tree.branch(e4);
688 game.start_mut().remove(e4).unwrap();
689 game.patch(Patch::Insert { position: START, index: 0, branch }).unwrap();
690
691 assert!(game == before);
692 }
693
694 #[test]
695 fn failed_insert_is_atomic() {
696 let mut game = Game::new(Position::start());
697 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
698 game.play_mut(e4).unwrap().position_mut().push(crate::Move::normal(Pawn, E7, E5)).unwrap();
699 let mut branch = game.tree.branch(e4);
700 game.start_mut().remove(e4).unwrap();
701 let before = game.clone();
702 branch.options[0].play = crate::Move::normal(Pawn, E2, E4);
703
704 let error = game.patch(Patch::Insert { position: START, index: 0, branch }).unwrap_err();
705
706 assert!(matches!(
707 error,
708 game::Error::Options(options::Error::Insert(options::Insert::Illegal))
709 ));
710 assert!(game == before);
711 }
712
713 #[test]
714 fn unavailable_insert_is_atomic() {
715 let mut source = Game::new(Position::start());
716 let e4 = source.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
717 let branch = source.tree.branch(e4);
718
719 let mut game = Game::new(Position::start());
720 game.start_mut().push(crate::Move::normal(Pawn, D2, D4)).unwrap();
721 let before = game.clone();
722
723 let error = game.patch(Patch::Insert { position: START, index: 1, branch }).unwrap_err();
724
725 assert!(matches!(
726 error,
727 game::Error::Change(Error::Insert(Insert::Unavailable(slot))) if slot == e4
728 ));
729 assert!(game == before);
730 }
731
732 #[test]
733 fn into_game() {
734 let mut game = Game::new(Position::start());
735 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
736 let d4 = game.start_mut().push(crate::Move::normal(Pawn, D2, D4)).unwrap().slot();
737 let e5 = game
738 .play_mut(e4)
739 .unwrap()
740 .position_mut()
741 .push(crate::Move::normal(Pawn, E7, E5))
742 .unwrap()
743 .slot();
744
745 let mut position = game.play(e4).unwrap().position().into_game();
746 assert!(position.start().options().is_empty());
747 let position_e5 =
748 position.start_mut().push(crate::Move::normal(Pawn, E7, E5)).unwrap().slot();
749 assert_eq!(position_e5, e5.next().unwrap());
750
751 let mut play = game.play(e4).unwrap().into_game();
752 assert_eq!(play.start().options(), [e4]);
753 assert!(play.play(e4).unwrap().position().options().is_empty());
754 assert!(play.play(d4).is_err());
755 assert!(play.play(e5).is_err());
756 let play_e5 = play
757 .play_mut(e4)
758 .unwrap()
759 .position_mut()
760 .push(crate::Move::normal(Pawn, E7, E5))
761 .unwrap()
762 .slot();
763 assert_eq!(play_e5, e5.next().unwrap());
764 }
765
766 #[test]
767 fn arbitrary_insert_round_trip() {
768 let mut tested = 0;
769 for seed in 0_u8..100 {
770 let bytes = (0..1024)
771 .map(|index| (index as u8).wrapping_mul(31).wrapping_add(seed))
772 .collect::<Vec<_>>();
773 let mut input = Unstructured::new(&bytes);
774 let Ok(mut game) = Game::arbitrary(&mut input) else { continue };
775 let Some(slot) = game.start().iter().next().map(|play| play.slot()) else { continue };
776
777 let before = game.clone();
778 let branch = game.tree.branch(slot);
779 game.start_mut().remove(slot).unwrap();
780 game.patch(Patch::Insert { position: START, index: 0, branch }).unwrap();
781 assert!(game == before);
782
783 tested += 1;
784 if tested == 10 {
785 break;
786 }
787 }
788 assert_eq!(tested, 10);
789 }
790
791 #[test]
792 fn insert_branch_at_deep_nonzero_index() {
793 let mut game = Game::new(Position::start());
794 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
795 game.play_mut(e4).unwrap().position_mut().push(crate::Move::normal(Pawn, E7, E5)).unwrap();
796 let c5 = game
797 .play_mut(e4)
798 .unwrap()
799 .position_mut()
800 .push(crate::Move::normal(Pawn, C7, C5))
801 .unwrap()
802 .slot();
803 let before = game.clone();
804 let branch = game.tree.branch(c5);
805
806 game.play_mut(e4).unwrap().position_mut().remove(c5).unwrap();
807 game.patch(Patch::Insert { position: e4.into(), index: 1, branch }).unwrap();
808
809 assert!(game == before);
810 }
811
812 #[test]
813 fn insert_branch_records_one_change() {
814 let mut source = Game::new(Position::start());
815 let e4 = source.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
816 source
817 .play_mut(e4)
818 .unwrap()
819 .position_mut()
820 .push(crate::Move::normal(Pawn, E7, E5))
821 .unwrap();
822 let branch = source.tree.branch(e4);
823
824 let mut game = Game::new(Position::start());
825 game.track_changes(true);
826 game.patch(Patch::Insert { position: START, index: 0, branch }).unwrap();
827
828 assert_eq!(game.changes().unwrap().len(), 1);
829 assert!(matches!(
830 game.changes().unwrap()[0],
831 Change::Insert { ref branch, .. } if branch.slot == e4
832 ));
833 }
834
835 #[test]
836 fn out_of_range_insert_is_atomic() {
837 let mut source = Game::new(Position::start());
838 let e4 = source.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
839 let branch = source.tree.branch(e4);
840
841 let mut game = Game::new(Position::start());
842 let before = game.clone();
843 let error = game.patch(Patch::Insert { position: START, index: 1, branch }).unwrap_err();
844
845 assert!(matches!(
846 error,
847 game::Error::Traversal(traversal::Error::Option(ref missing))
848 if missing.position == START
849 && missing.choice == traversal::Choice::Index(1)
850 ));
851 assert!(game == before);
852 }
853
854 #[test]
855 fn duplicate_branch_slot_is_atomic() {
856 let mut source = Game::new(Position::start());
857 let e4 = source.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
858 source
859 .play_mut(e4)
860 .unwrap()
861 .position_mut()
862 .push(crate::Move::normal(Pawn, E7, E5))
863 .unwrap();
864 let mut branch = source.tree.branch(e4);
865 branch.options[0].slot = e4;
866
867 let mut game = Game::new(Position::start());
868 let before = game.clone();
869 let error = game.patch(Patch::Insert { position: START, index: 0, branch }).unwrap_err();
870
871 assert!(matches!(
872 error,
873 game::Error::Change(Error::Insert(Insert::Unavailable(slot))) if slot == e4
874 ));
875 assert!(game == before);
876 }
877
878 #[test]
879 fn undo_public_mutation() {
880 let mut game = Game::new(Position::start());
881 game.track_changes(true);
882 game.set_outcome(Outcome::Draw);
883
884 game.undo().unwrap();
885 assert_eq!(game.outcome(), Outcome::Unknown);
886 assert!(game.changes().unwrap().is_empty());
887 game.undo().unwrap();
888 }
889
890 #[test]
891 fn reslot_clears_history() {
892 let mut game = Game::new(Position::start());
893 game.track_changes(true);
894 game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap();
895
896 game.reslot(Layout::Pgn);
897
898 assert!(game.changes().unwrap().is_empty());
899 game.set_outcome(Outcome::Draw);
900 assert_eq!(game.changes().unwrap().len(), 1);
901 }
902
903 #[test]
904 fn undo_branch_removal() {
905 let mut game = Game::new(Position::start());
906 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
907 game.play_mut(e4).unwrap().position_mut().push(crate::Move::normal(Pawn, E7, E5)).unwrap();
908 game.track_changes(true);
909 let before = game.clone();
910
911 game.start_mut().remove(e4).unwrap();
912 game.undo().unwrap();
913
914 assert!(game == before);
915 }
916
917 #[test]
918 fn restore_revision() {
919 let mut game = Game::new(Position::start());
920 game.track_changes(true);
921 game.set_orientation(Player::Black);
922 game.set_outcome(Outcome::Draw);
923
924 game.restore(1).unwrap();
925 assert_eq!(game.orientation(), Player::Black);
926 assert_eq!(game.outcome(), Outcome::Unknown);
927 assert_eq!(game.changes().unwrap().len(), 1);
928
929 game.restore(2).unwrap();
930 assert_eq!(game.changes().unwrap().len(), 1);
931
932 game.restore(0).unwrap();
933 assert_eq!(game.orientation(), Player::White);
934 assert!(game.changes().unwrap().is_empty());
935 }
936
937 #[test]
938 fn undo_position_and_move_public() {
939 let mut game = Game::new(Position::start());
940 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
941 game.track_changes(true);
942
943 game.start_mut().set_comment(Some("Position".parse().unwrap()));
944 game.play_mut(e4).unwrap().set_intro(Some("Move".parse().unwrap()));
945 game.restore(0).unwrap();
946
947 assert!(game.start().comment().is_none());
948 assert!(game.play(e4).unwrap().public.affixes.intro.is_none());
949 }
950
951 #[test]
952 fn undo_pushed_move() {
953 let mut game = Game::new(Position::start());
954 game.track_changes(true);
955 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
956
957 game.undo().unwrap();
958 assert!(game.start().options().is_empty());
959 assert!(game.play(e4).is_err());
960 }
961
962 #[test]
963 fn undo_option_order() {
964 let mut game = Game::new(Position::start());
965 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
966 let d4 = game.start_mut().push(crate::Move::normal(Pawn, D2, D4)).unwrap().slot();
967 let c4 = game.start_mut().push(crate::Move::normal(Pawn, C2, C4)).unwrap().slot();
968 game.track_changes(true);
969 let before = game.clone();
970
971 game.start_mut().promote(c4).unwrap();
972 game.start_mut().swap(e4, d4).unwrap();
973 game.restore(0).unwrap();
974
975 assert!(game == before);
976 }
977
978 #[test]
979 fn failed_undo_restores_change() {
980 let mut game = Game::new(Position::start());
981 let e4 = game.start_mut().push(crate::Move::normal(Pawn, E2, E4)).unwrap().slot();
982 let branch = game.tree.branch(e4);
983 game.start_mut().remove(e4).unwrap();
984 game.track_changes(true);
985 game.changes.as_mut().unwrap().push(Change::Insert { position: START, index: 0, branch });
986
987 let error = game.undo().unwrap_err();
988
989 assert!(matches!(
990 error,
991 game::Error::Traversal(traversal::Error::Move(slot)) if slot == e4
992 ));
993 assert!(matches!(
994 game.changes().unwrap(),
995 [Change::Insert { branch, .. }] if branch.slot == e4
996 ));
997 }
998}