text_adventure/
text_adventure.rs1#![allow(clippy::indexing_slicing, clippy::panic)]
16
17use std::{convert::Infallible, vec::IntoIter};
18
19use event_simulation::{OwnedSimulation, Simulation, SimulationInfo, SimulationState};
20
21#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
23struct Choice(usize);
24
25struct Passage {
27 text: String,
28 from: usize,
29 to: usize,
30}
31
32struct Adventure {
34 rooms: Vec<String>,
35 passages: Vec<Passage>,
36}
37
38impl Adventure {
39 fn choices_from(&self, room: usize) -> Vec<Choice> {
40 self.matching(|passage| passage.from == room)
41 }
42
43 fn choices_into(&self, room: usize) -> Vec<Choice> {
44 self.matching(|passage| passage.to == room)
45 }
46
47 fn matching(&self, predicate: impl Fn(&Passage) -> bool) -> Vec<Choice> {
48 self.passages
49 .iter()
50 .enumerate()
51 .filter(|(_, passage)| predicate(passage))
52 .map(|(index, _)| Choice(index))
53 .collect()
54 }
55}
56
57#[derive(Clone)]
61struct Position {
62 room: usize,
63 callables: Vec<Choice>,
64 revertables: Vec<Choice>,
65}
66
67impl Position {
68 fn at(adventure: &Adventure, room: usize) -> Self {
69 Self {
70 room,
71 callables: adventure.choices_from(room),
72 revertables: adventure.choices_into(room),
73 }
74 }
75}
76
77impl SimulationInfo for Adventure {
78 type State = Position;
79 type StateLoadingError = Infallible;
80 type AccessData = usize;
81 type LoadData = usize;
82 type Event = Choice;
83 type EventContainer<'a> = IntoIter<Choice>;
84
85 fn default_state(&self) -> Position {
86 Position::at(self, 0)
87 }
88
89 fn load_state(&self, room: usize) -> Result<Position, Infallible> {
90 Ok(Position::at(self, room.min(self.rooms.len() - 1)))
91 }
92
93 unsafe fn clone_state(&self, state: &Position) -> Position {
94 state.clone()
95 }
96
97 unsafe fn data<'a>(&self, state: &'a Position) -> &'a usize {
98 &state.room
99 }
100
101 fn callables(state: &Position) -> IntoIter<Choice> {
102 state.callables.clone().into_iter()
103 }
104
105 fn revertables(state: &Position) -> IntoIter<Choice> {
106 state.revertables.clone().into_iter()
107 }
108
109 fn callable(state: &Position, event: Choice) -> bool {
110 state.callables.contains(&event)
111 }
112
113 fn revertable(state: &Position, event: Choice) -> bool {
114 state.revertables.contains(&event)
115 }
116
117 unsafe fn call(&self, state: &mut Position, Choice(index): Choice) {
118 *state = Position::at(self, self.passages[index].to);
119 }
120
121 unsafe fn revert(&self, state: &mut Position, Choice(index): Choice) {
122 *state = Position::at(self, self.passages[index].from);
123 }
124}
125
126fn passage(text: &str, from: usize, to: usize) -> Passage {
127 Passage {
128 text: text.into(),
129 from,
130 to,
131 }
132}
133
134fn main() {
135 let adventure = Adventure {
136 rooms: vec![
137 "You stand at a crossroads.".into(),
138 "A dark cave yawns before you.".into(),
139 "Sunlight breaks over a quiet meadow.".into(),
140 ],
141 passages: vec![
142 passage("Enter the cave", 0, 1),
143 passage("Walk to the meadow", 0, 2),
144 passage("Retreat from the cave", 1, 0),
145 ],
146 };
147
148 let mut simulation = OwnedSimulation::<Adventure>::new(adventure);
149
150 let describe =
151 |simulation: &OwnedSimulation<Adventure>| simulation.rooms[*simulation.data()].clone();
152
153 println!("{}", describe(&simulation));
154 for choice in simulation.callables() {
155 println!(" → {}", simulation.passages[choice.0].text);
156 }
157
158 let Some(first) = simulation.callables().next() else {
159 panic!("crossroads has choices");
160 };
161 assert!(simulation.try_call(first));
162 assert_eq!(simulation.state.room, 1);
163 println!("\n{}", describe(&simulation));
164
165 let Some(back) = simulation.revertables().next() else {
166 panic!("cave can be left");
167 };
168 assert!(simulation.try_revert(back));
169 assert_eq!(simulation.state.room, 0);
170 println!("\nBack: {}", describe(&simulation));
171}