shuyi_core/history.rs
1//! A game's history: the state it started in and every command applied
2//! since. Its rules are deterministic ([`TurnRules`]), so any earlier point
3//! is rebuilt by replaying, which is what replays, rewinds and saving a
4//! game in progress need. What a rewind costs, if anything, is the game's.
5//!
6//! # Rules
7//!
8//! - **Points.** Point `n` is the game after its first `n` commands
9//! ([`History::state_at`]); point 0 is the state it started in.
10//! Rewinding to point `n` ([`History::rewind_to`]) forgets command `n` and
11//! every one after it.
12//! - **A refused command replays as nothing**, as it changed nothing when
13//! it was sent; a history that recorded one by mistake replays as if it
14//! had not.
15//! - **Saving.** A history is written as its first state and its
16//! commands: `(initial: …, commands: [ … ])`.
17
18use alloc::vec::Vec;
19use core::fmt;
20
21use serde::{Deserialize, Serialize};
22
23use crate::turn::TurnRules;
24
25/// A game's first state and the commands applied since, oldest first.
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(bound(
28 serialize = "R: Serialize, R::Command: Serialize",
29 deserialize = "R: Deserialize<'de>, R::Command: Deserialize<'de>"
30))]
31pub struct History<R: TurnRules> {
32 initial: R,
33 commands: Vec<R::Command>,
34}
35
36/// One past command, replayed ([`History::replay`]).
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct Replayed<R: TurnRules> {
39 /// The game just before the command (the point a rewind to it goes
40 /// back to).
41 pub before: R,
42 /// The command.
43 pub command: R::Command,
44 /// What it did (nothing, if the rules refused it).
45 pub events: Vec<R::Event>,
46}
47
48/// Why a rewind was refused: there is no earlier point `n` (it must be
49/// below the number of commands). Nothing changed.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct NoSuchPoint(pub usize);
52
53impl fmt::Display for NoSuchPoint {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 write!(f, "no earlier point {} to rewind to", self.0)
56 }
57}
58
59impl core::error::Error for NoSuchPoint {}
60
61impl<R: TurnRules> History<R> {
62 /// A history starting at `initial`, with no command yet.
63 pub fn new(initial: R) -> Self {
64 Self::from_parts(initial, Vec::new())
65 }
66
67 /// A history from its first state and the commands applied since,
68 /// oldest first (as a save holds them).
69 pub fn from_parts(initial: R, commands: Vec<R::Command>) -> Self {
70 Self { initial, commands }
71 }
72
73 /// Its first state and its commands, oldest first.
74 pub fn into_parts(self) -> (R, Vec<R::Command>) {
75 (self.initial, self.commands)
76 }
77
78 /// The state the game started in (point 0).
79 pub fn initial(&self) -> &R {
80 &self.initial
81 }
82
83 /// The state the game started in, to change: for putting back what a
84 /// save leaves out of it. A change to how its commands apply changes
85 /// every later point.
86 pub fn initial_mut(&mut self) -> &mut R {
87 &mut self.initial
88 }
89
90 /// Records `command`, which was just applied to the game successfully.
91 pub fn push(&mut self, command: R::Command) {
92 self.commands.push(command);
93 }
94
95 /// The commands applied so far, oldest first.
96 pub fn commands(&self) -> &[R::Command] {
97 &self.commands
98 }
99
100 /// How many commands were applied (the latest point).
101 pub fn len(&self) -> usize {
102 self.commands.len()
103 }
104
105 /// Whether no command was applied yet.
106 pub fn is_empty(&self) -> bool {
107 self.commands.is_empty()
108 }
109}
110
111impl<R: TurnRules + Clone> History<R> {
112 /// The game after its first `n` commands (all of them if `n` is past
113 /// the end).
114 pub fn state_at(&self, n: usize) -> R {
115 let mut state = self.initial.clone();
116 for command in self.commands.iter().take(n) {
117 // A refused command changes nothing, as when it was first sent.
118 let _ = state.apply(command);
119 }
120 state
121 }
122
123 /// Every command replayed in order, with the game just before it and
124 /// its events: one replay for the whole list.
125 pub fn replay(&self) -> Vec<Replayed<R>>
126 where
127 R::Command: Clone,
128 {
129 let mut state = self.initial.clone();
130 self.commands
131 .iter()
132 .map(|command| {
133 let before = state.clone();
134 let events = state.apply(command).unwrap_or_default();
135 Replayed {
136 before,
137 command: command.clone(),
138 events,
139 }
140 })
141 .collect()
142 }
143
144 /// Goes back to point `n` (before command `n`), forgetting that command
145 /// and every later one, and returns the game at that point.
146 pub fn rewind_to(&mut self, n: usize) -> Result<R, NoSuchPoint> {
147 if n >= self.commands.len() {
148 return Err(NoSuchPoint(n));
149 }
150 self.commands.truncate(n);
151 Ok(self.state_at(n))
152 }
153}
154
155#[cfg(test)]
156mod tests;