Poppable.
[koperkapel.git] / koperkapel / world.py
1 """ World and player state. """
2
3 from .scenes.base import WorldEvent
4 from .roaches import build_roach
5
6
7 class World:
8     """ World and player state. """
9
10     def __init__(self):
11         self._state = self._build_initial_state()
12
13     @property
14     def level(self):
15         return self._state["level"]
16
17     @property
18     def roaches(self):
19         return self._state["roaches"]
20
21     def _build_initial_state(self):
22         state = {}
23         state["roaches"] = [
24             build_roach(self, "roupert"),
25         ]
26         state["serums"] = []
27         state["vehicles"] = {
28             "current": "walking",
29             "walking": {
30                 "seating": [
31                     "roachel", None, "roeginald",
32                     None, None, None,
33                 ]
34             },
35             "robot": {"seating": []},
36             "roomba": {"seating": []},
37             "quadcopter": {"seating": []},
38         }
39         state["weapons"] = {
40             "current": "spit",
41         }
42         state["level"] = {
43             "name": "level1",
44         }
45         return state
46
47     def _get_obj(self, name):
48         parts = name.split(".")
49         obj = self._state
50         for p in parts[:-1]:
51             if isinstance(obj, dict):
52                 obj = obj[p]
53             elif isinstance(obj, list):
54                 obj = obj[int(p)]
55             else:
56                 raise KeyError("%r not found in world" % (name,))
57         return obj, parts[-1]
58
59     def _apply_set(self, action, updates):
60         for name, value in updates.items():
61             obj, key = self._get_obj(name)
62             obj[key] = value
63
64     def _apply_append(self, action, updates):
65         for name, value in updates.items():
66             obj, key = self._get_obj(name)
67             obj.append(value)
68
69     def _apply_pop(self, action, updates):
70         for name, pos in updates.items():
71             obj, key = self._get_obj(name)
72             obj.pop(pos)
73
74     def _apply_reset(self, action):
75         self._state = self._build_initial_state()
76
77     def _apply_unknown(self, action, *args, **kw):
78         raise ValueError("Unknown world event action: %r" % (action,))
79
80     def proxy(self):
81         return WorldDictProxy(self._state)
82
83     def apply_event(self, action, *args, **kw):
84         handler = getattr(self, "_apply_%s" % (action,))
85         return handler(action, *args, **kw)
86
87
88 def _maybe_subproxy(proxy, name, value):
89     """ Return a sub world proxy if appropriate. """
90     if isinstance(value, dict):
91         prefix = "%s%s." % (proxy._prefix, name)
92         return WorldDictProxy(value, _prefix=prefix, _top=proxy._top)
93     elif isinstance(value, list):
94         prefix = "%s%s." % (proxy._prefix, name)
95         return WorldListProxy(value, _prefix=prefix, _top=proxy._top)
96     return value
97
98
99 class WorldBaseProxy:
100     """ Base for world proxies. """
101
102     def __init__(self, state, _prefix='', _top=None):
103         if _top is None:
104             _top = self
105             _events = []
106         else:
107             _events = None
108         self.__dict__.update({
109             "_state": state,
110             "_prefix": _prefix,
111             "_top": _top,
112             "_events": _events,
113         })
114
115     def _record_change(self, fullname, value, action="set"):
116         self._events.append(WorldEvent(action, {
117             fullname: value
118         }))
119
120     def pop_events(self):
121         events, self._events = self._events, []
122         return events
123
124
125 class WorldDictProxy(WorldBaseProxy):
126     """ World dictionary proxy that records changes and produces events. """
127
128     def __setattr__(self, name, value):
129         self._top._record_change("%s%s" % (self._prefix, name), value)
130
131     def __getattr__(self, name):
132         # return None for attributes that don't exist
133         value = self._state.get(name)
134         return _maybe_subproxy(self, name, value)
135
136     def __setitem__(self, name, value):
137         return self.__setattr__(name, value)
138
139     def __getitem__(self, name):
140         return self.__getattr__(name)
141
142     def items(self):
143         return (
144             (k, _maybe_subproxy(self, k, v)) for k, v in self._state.items())
145
146
147 class WorldListProxy(WorldBaseProxy):
148     """ World list proxy that records changes and produces events. """
149
150     def __setitem__(self, index, value):
151         self._top._record_change("%s%s" % (self._prefix, index), value)
152
153     def __getitem__(self, index):
154         return _maybe_subproxy(self, index, self._state[index])
155
156     def __len__(self):
157         return len(self._state)
158
159     def __bool__(self):
160         return bool(self._state)
161
162     def append(self, value):
163         self._top._record_change(self._prefix, value, action="append")
164
165     def pop(self, pos=0):
166         self._top._record_change(self._prefix, pos, action="pop")