8097eb2ccf993b2a15fd91231717c042d5fe8c68
[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             "robot": {"seating": []},
40             "roomba": {"seating": []},
41             "quadcopter": {"seating": []},
42         }
43         state["level"] = {
44             "name": "level1",
45         }
46         return state
47
48     def _build_roach(self, name, health=5, **kw):
49         roach = {
50             "name": name,
51             "health": health,
52         }
53         roach.update(kw)
54         return roach
55
56     def _apply_set(self, updates):
57         for name, value in updates.items():
58             parts = name.split(".")
59             obj = self._state
60             for p in parts[:-1]:
61                 if isinstance(obj, dict):
62                     obj = obj[p]
63                 elif isinstance(obj, list):
64                     obj = obj[int(p)]
65                 else:
66                     raise KeyError("%r not found in world" % (name,))
67             obj[parts[-1]] = value
68
69     def proxy(self):
70         return WorldDictProxy(self._state)
71
72     def apply_event(self, action, *args, **kw):
73         if action == "set":
74             return self._apply_set(*args, **kw)
75         raise ValueError("Unknown world event action: %r" % (action,))
76
77
78 def _maybe_subproxy(proxy, name, value):
79     """ Return a sub world proxy if appropriate. """
80     if isinstance(value, dict):
81         prefix = "%s%s." % (proxy._prefix, name)
82         return WorldDictProxy(value, _prefix=prefix, _top=proxy._top)
83     elif isinstance(value, list):
84         prefix = "%s%s." % (proxy._prefix, name)
85         return WorldListProxy(value, _prefix=prefix, _top=proxy._top)
86     return value
87
88
89 class WorldBaseProxy:
90     """ Base for world proxies. """
91
92     def __init__(self, state, _prefix='', _top=None):
93         if _top is None:
94             _top = self
95             _events = []
96         else:
97             _events = None
98         self.__dict__.update({
99             "_state": state,
100             "_prefix": _prefix,
101             "_top": _top,
102             "_events": _events,
103         })
104
105     def _record_change(self, fullname, value):
106         self._events.append(WorldEvent("set", {
107             fullname: value
108         }))
109
110     def pop_events(self):
111         events, self._events = self._events, []
112         return events
113
114
115 class WorldDictProxy(WorldBaseProxy):
116     """ World dictionary proxy that records changes and produces events. """
117
118     def items(self):
119         return (
120             (k, _maybe_subproxy(self, k, v)) for k, v in self._state.items())
121
122     def __setattr__(self, name, value):
123         self._top._record_change("%s%s" % (self._prefix, name), value)
124
125     def __getattr__(self, name):
126         # return None for attributes that don't exist
127         value = self._state.get(name)
128         return _maybe_subproxy(self, name, value)
129
130     def __setitem__(self, name, value):
131         return self.__setattr__(name, value)
132
133     def __getitem__(self, name):
134         return self.__getattr__(name)
135
136
137 class WorldListProxy(WorldBaseProxy):
138     """ World list proxy that records changes and produces events. """
139
140     def __setitem__(self, index, value):
141         self._top._record_change("%s%s" % (self._prefix, index), value)
142
143     def __getitem__(self, index):
144         return _maybe_subproxy(self, index, self._state[index])
145
146     def __len__(self):
147         return len(self._state)
148
149     def __bool__(self):
150         return bool(self._state)