verticopolis_engine/snapshot.rs
1//! The hashed state view: the saved game without its prose, which the
2//! conformance suite hashes at every checkpoint. Hash it with
3//! [`crate::canonical::digest`] to tell whether two games are in the same
4//! state.
5//!
6//! ```
7//! use verticopolis_engine::canonical::digest;
8//! use verticopolis_engine::clock::GameMode;
9//! use verticopolis_engine::sim::Simulation;
10//! use verticopolis_engine::snapshot::state_view;
11//!
12//! let mut a = Simulation::new_game(9, GameMode::Classic);
13//! let mut b = Simulation::new_game(9, GameMode::Classic);
14//! a.tick(120.0);
15//! b.tick(60.0);
16//! b.tick(60.0);
17//! assert_eq!(digest(&state_view(&a)), digest(&state_view(&b)));
18//! ```
19use serde_json::Value;
20
21use crate::sim::Simulation;
22
23/// The saved game ([`Simulation::serialize`]) without the log messages' text
24/// and the pending choice's message, so wording changes never move a hash.
25pub fn state_view(sim: &Simulation) -> Value {
26 let mut v = sim.serialize();
27 if let Some(log) = v.get_mut("log").and_then(Value::as_array_mut) {
28 for e in log {
29 if let Some(o) = e.as_object_mut() {
30 o.remove("text");
31 }
32 }
33 }
34 if let Some(p) = v
35 .get_mut("events")
36 .and_then(|e| e.get_mut("pending"))
37 .and_then(Value::as_object_mut)
38 {
39 p.remove("message");
40 }
41 v
42}