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