-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstate.py
More file actions
149 lines (125 loc) · 5.21 KB
/
Copy pathstate.py
File metadata and controls
149 lines (125 loc) · 5.21 KB
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
141
142
143
144
145
146
147
148
149
"""Game state: health, inventory, flags, journal.
Scene conditions and effects are described with dicts (see scenes.py) so the
content can be edited without touching a single line of code.
Condition ("if"): {"item": "flashlight", "no_flag": "power", "hp>=": 2}
Effect ("do"): {"item+": "medkit", "flag+": "truth", "hp": -1}
Items, flags and journal entries are stored as ids; the player-visible names
come from i18n, so a saved game survives a change of language.
"""
import json
from dataclasses import dataclass, field
import i18n
MAX_HP = 4
def _as_list(v):
return v if isinstance(v, (list, tuple)) else [v]
# Every supported condition key, as a predicate over (state, value). Adding a
# new kind of condition means adding one line here — and check.py will report
# any scene that uses a key this table does not know.
CONDITIONS = {
"item": lambda st, v: all(st.has(i) for i in _as_list(v)),
"no_item": lambda st, v: not any(st.has(i) for i in _as_list(v)),
"flag": lambda st, v: all(f in st.flags for f in _as_list(v)),
"no_flag": lambda st, v: not any(f in st.flags for f in _as_list(v)),
"any_flag": lambda st, v: any(f in st.flags for f in _as_list(v)),
"hp>=": lambda st, v: st.hp >= v,
"hp<=": lambda st, v: st.hp <= v,
"visited": lambda st, v: all(s in st.visited for s in _as_list(v)),
"no_visited": lambda st, v: not any(s in st.visited for s in _as_list(v)),
}
@dataclass
class GameState:
"""Everything a save file has to remember about a play-through."""
scene: str = "start"
hp: int = MAX_HP
items: list = field(default_factory=list)
flags: set = field(default_factory=set)
log: list = field(default_factory=list) # discovered journal entries
visited: set = field(default_factory=set)
turns: int = 0
# ---- queries ------------------------------------------------------
def has(self, item):
"""Whether the item is in the inventory."""
return item in self.items
def check(self, cond):
"""Whether the condition holds. An empty condition is always true."""
for key, val in (cond or {}).items():
test = CONDITIONS.get(key)
if test is None:
raise KeyError(f"unknown condition: {key}")
if not test(self, val):
return False
return True
# ---- mutations ----------------------------------------------------
# One handler per effect key, each returning the messages to show the
# player. Mirrors CONDITIONS above.
def _give_items(self, val):
msgs = []
for i in _as_list(val):
if i not in self.items:
self.items.append(i)
msgs.append(f"+ {i18n.item(i)}")
return msgs
def _take_items(self, val):
msgs = []
for i in _as_list(val):
if i in self.items:
self.items.remove(i)
msgs.append(f"− {i18n.item(i)}")
return msgs
def _set_flags(self, val):
self.flags.update(_as_list(val))
return []
def _clear_flags(self, val):
self.flags.difference_update(_as_list(val))
return []
def _change_hp(self, val):
before = self.hp
self.hp = max(0, min(MAX_HP, self.hp + val))
if self.hp < before:
return [i18n.ui("integrity_down", n=before - self.hp)]
if self.hp > before:
return [i18n.ui("integrity_up", n=self.hp - before)]
return []
def _add_log(self, val):
msgs = []
for entry in _as_list(val):
if entry not in self.log:
self.log.append(entry)
msgs.append(i18n.ui("log_added"))
return msgs
def apply(self, eff):
"""Applies an effect and returns the list of messages for the player."""
handlers = {
"item+": self._give_items,
"item-": self._take_items,
"flag+": self._set_flags,
"flag-": self._clear_flags,
"hp": self._change_hp,
"log+": self._add_log,
}
msgs = []
for key, val in (eff or {}).items():
handler = handlers.get(key)
if handler is None:
raise KeyError(f"unknown effect: {key}")
msgs += handler(val)
return msgs
# ---- persistence --------------------------------------------------
# The save also carries the language, so loading restores the game in the
# language it was played in.
def to_json(self):
"""Serialises the state, plus the language, into a save file."""
return json.dumps({
"lang": i18n.current(),
"scene": self.scene, "hp": self.hp, "items": self.items,
"flags": sorted(self.flags), "log": self.log,
"visited": sorted(self.visited), "turns": self.turns,
}, ensure_ascii=False, indent=1)
@classmethod
def from_json(cls, raw):
"""Restores a state from a save file and switches to its language."""
d = json.loads(raw)
i18n.set_lang(d.get("lang", i18n.DEFAULT))
return cls(scene=d["scene"], hp=d["hp"], items=list(d["items"]),
flags=set(d["flags"]), log=list(d["log"]),
visited=set(d["visited"]), turns=d.get("turns", 0))