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