1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use std::borrow::Cow;
use crate::{
Action, Game, GameError, SimEnd, View,
core::{GameContext, PageHandle, PageId, PageStack, game_state::PageKey},
view::{Line, Object, Span},
};
#[derive(Debug, Clone, Copy)]
pub enum Interactable<'a> {
Choice(&'a PageKey, &'a Vec<(u8, Line)>, u8), // parent, index of the choice
Span(&'a Object, &'a Span), // parent, the span
}
impl<'a> Interactable<'a> {
pub fn content(&self) -> Cow<'a, str> {
match self {
Interactable::Choice(_, lines, idx) => lines
.iter()
.find_map(|(i, x)| if i == idx { Some(x) } else { None })
.unwrap()
.content()
.into(),
Interactable::Span(_, s) => Cow::Borrowed(&s.content),
}
}
}
impl View {
/// Nested interactables: each Object maps to a vector of interactables.
/// Choice objects expand to:
/// [Interactable::Choice(parent, idx),
/// Interactable::Span(parent, span…),
/// Interactable::Span(parent, span…),
/// ...]
///
/// Choices which contain an interactable element are ignored!
pub fn interactables(&self) -> Vec<Vec<Interactable<'_>>> {
let mut out = Vec::new();
for obj in &self.inner {
let mut bucket = Vec::new();
match obj {
Object::Text(line, _)
| Object::Paragraph(line)
| Object::Note(line, _)
| Object::Quote(line, _) => {
for span in &line.spans {
if span.action.is_some() {
bucket.push(Interactable::Span(obj, span));
}
}
}
Object::Heading(span, _level) => {
if span.action.is_some() {
bucket.push(Interactable::Span(obj, span));
}
}
Object::Choice(key, choices) => {
for (i, line) in choices {
let ignore = {
if true {
line.spans.iter().any(|span| span.action.is_some())
|| !line.spans.is_empty()
} else {
// all spans have actions
line.spans.iter().all(|span| span.action.is_some())
}
};
if !ignore {
bucket.push(Interactable::Choice(&key, &choices, *i));
}
for span in &line.spans {
if span.action.is_some() {
bucket.push(Interactable::Span(obj, span));
}
}
}
}
Object::Image(_) | Object::Break | Object::Empty(_) | Object::Custom(_) => {
// no interactables
}
}
out.push(bucket);
}
out
}
/// Flatten all nested interactables into one linear Vec
/// Filters out None Actions
pub fn interactables_sim(&self) -> Vec<Interactable<'_>> {
self.interactables()
.into_iter()
.flat_map(|v| {
v.into_iter().filter(|e| {
if let Interactable::Span(_, span) = e
&& (span.no_sim || matches!(span.action, Some(Action::None)))
{
false
} else {
true
}
})
})
.collect()
}
}
impl<C: GameContext> Game<C> {
pub fn interact(&mut self, e: Interactable<'_>, pageid: &PageId) -> Result<(), GameError> {
match e {
Interactable::Choice(key, _, index) => {
self.handle_choice((pageid.clone(), *key), index);
Ok(())
}
Interactable::Span(_, s) => {
let action = s.action.as_ref().unwrap();
self.handle_action(action.clone()).map(|_| {})
}
}
}
pub fn interact_all<F>(&self, view: View) -> Vec<Result<Self, GameError>> {
view.interactables_sim()
.into_iter()
.map(|e| {
let mut g = self.clone();
g.interact(e, &view.pageid).map(|_| g)
})
.collect()
}
}