Skip to main content

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;