5a22b04fdbefd21de586e0f8e8fc03cbd01091ef
[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             "smart", "strong", "fast",
28         ]
29         state["vehicles"] = {
30             "current": "walking",
31             "walking": {
32                 "seating": [
33                     "roachel", None, "roeginald",
34                     None, None, None,
35                 ]
36             },
37             "robot": {"seating": []},
38             "roomba": {"seating": []},
39             "quadcopter": {"seating": []},
40         }
41         state["level"] = {
42             "name": "level1",
43         }
44         return state
45
46     def _get_obj(self, name):
47         parts = name.split(".")
48         obj = self._state
49         for p in parts[:-1]:
50             if isinstance(obj, dict):
51                 obj = obj[p]
52             elif isinstance(obj, list):
53                 obj = obj[int(p)]
54             else:
55                 raise KeyError("%r not found in world" % (name,))
56         return obj, parts[-1]
57
58     def _apply_set(self, action, updates):
59         for name, value in updates.items():
60             obj, key = self._get_obj(name)
61             obj[key] = value
62
63     def _apply_append(self, action, updates):
64         for name, value in updates.items():
65             obj, key = self._get_obj(name)
66             obj.append(value)
67
68     def _apply_reset(self, action):
69         self._state = self._build_initial_state()
70
71     def _apply_unknown(self, action, *args, **kw):
72         raise ValueError("Unknown world event action: %r" % (action,))
73
74     def proxy(self):
75         return WorldDictProxy(self._state)
76
77     def apply_event(self, action, *args, **kw):
78         handler = getattr(self, "_apply_%s" % (action,))
79         return handler(action, *args, **kw)
80
81
82 def _maybe_subproxy(proxy, name, value):
83     """ Return a sub world proxy if appropriate. """
84     if isinstance(value, dict):
85         prefix = "%s%s." % (proxy._prefix, name)
86         return WorldDictProxy(value, _prefix=prefix, _top=proxy._top)
87     elif isinstance(value, list):
88         prefix = "%s%s." % (proxy._prefix, name)
89         return WorldListProxy(value, _prefix=prefix, _top=proxy._top)
90     return value
91
92
93 class WorldBaseProxy:
94     """ Base for world proxies. """
95
96     def __init__(self, state, _prefix='', _top=None):
97         if _top is None:
98             _top = self
99             _events = []
100         else:
101             _events = None
102         self.__dict__.update({
103             "_state": state,
104             "_prefix": _prefix,
105             "_top": _top,
106             "_events": _events,
107         })
108
109     def _record_change(self, fullname, value, action="set"):
110         self._events.append(WorldEvent(action, {
111             fullname: value
112         }))
113
114     def pop_events(self):
115         events, self._events = self._events, []
116         return events
117
118
119 class WorldDictProxy(WorldBaseProxy):
120     """ World dictionary proxy that records changes and produces events. """
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     def items(self):
137         return (
138             (k, _maybe_subproxy(self, k, v)) for k, v in self._state.items())
139
140
141 class WorldListProxy(WorldBaseProxy):
142     """ World list proxy that records changes and produces events. """
143
144     def __setitem__(self, index, value):
145         self._top._record_change("%s%s" % (self._prefix, index), value)
146
147     def __getitem__(self, index):
148         return _maybe_subproxy(self, index, self._state[index])
149
150     def __len__(self):
151         return len(self._state)
152
153     def __bool__(self):
154         return bool(self._state)
155
156     def append(self, value):
157         self._top._record_change(self._prefix, value, action="append")