14998034ce7557a883bb14ee478f5c6e67bab647
[koperkapel.git] / koperkapel / scenes / base.py
1 """ Scene utilities. """
2
3 import functools
4
5
6 def apply_events(f):
7     """ Decorator that applies events to an engine. """
8     @functools.wraps(f)
9     def wrap(self, *args, **kw):
10         events = f(self, *args, **kw)
11         self._apply_events(events)
12     return wrap
13
14
15 class Engine:
16     """ A holder for game state & scene management.
17         """
18
19     def __init__(self, app, scene, world):
20         self._app = app
21         self._scene = scene
22         self._world = world
23         self._update_vehicle = False
24
25     def _apply_events(self, events):
26         if not events:
27             return
28         for ev in events:
29             ev.apply(self)
30
31     def change_scene(self, scene):
32         self._apply_events(self._scene.exit(self._world.proxy()))
33         self._scene = scene
34         self._apply_events(self._scene.enter(self._world.proxy()))
35
36     def change_world(self, *args, **kw):
37         self._world.apply_event(*args, **kw)
38
39     def quit_game(self):
40         from pgzero.game import exit
41         exit()
42
43     def move_screen(self, offset):
44         self._scene.move_screen(offset)
45
46     def add_roach(self):
47         self._world.add_new_roach()
48
49     @apply_events
50     def update(self, dt):
51         return self._scene.update(self._world.proxy(), self, dt)
52
53     def draw(self):
54         self._scene.draw(self._app.screen)
55
56     @apply_events
57     def on_mouse_down(self, pos, button):
58         return self._scene.on_mouse_down(pos, button)
59
60     @apply_events
61     def on_mouse_up(self, pos, button):
62         return self._scene.on_mouse_up(pos, button)
63
64     @apply_events
65     def on_key_down(self, key, mod, unicode):
66         return self._scene.on_key_down(key, mod, unicode)
67
68     @apply_events
69     def on_key_up(self, key, mod):
70         return self._scene.on_key_up(key, mod)
71
72     @apply_events
73     def on_music_end(self):
74         return self._scene.on_music_end()
75
76
77 class Event:
78     """ Base class for events. """
79
80     ENGINE_METHOD = "unknown_event"
81
82     def __init__(self, *args, **kw):
83         self._args = args
84         self._kw = kw
85
86     def apply(self, engine):
87         getattr(engine, self.ENGINE_METHOD)(*self._args, **self._kw)
88
89
90 class ChangeSceneEvent(Event):
91     """ Change to a new scene. """
92
93     ENGINE_METHOD = "change_scene"
94
95
96 class WorldEvent(Event):
97     """ Be a hero. Change the world. """
98
99     ENGINE_METHOD = "change_world"
100
101
102 class QuitEvent(Event):
103     """ Quit the game. """
104
105     ENGINE_METHOD = "quit_game"
106
107
108 class MoveViewportEvent(Event):
109     """ Change to a new scene. """
110
111     ENGINE_METHOD = "move_screen"
112
113 class AddRoachEvent(Event):
114     """ Change to a new scene. """
115
116     ENGINE_METHOD = "add_roach"
117
118
119 class Layer:
120     """ A single layer of actors. """
121
122     def __init__(self, name):
123         self.name = name
124         self.actors = []
125
126     def __iter__(self):
127         return iter(self.actors)
128
129     def __getitem__(self, i):
130         return self.actors[i]
131
132     def __len__(self):
133         return len(self.actors)
134
135     def add(self, actor):
136         self.actors.append(actor)
137         return actor
138
139     def clear(self):
140         self.actors.clear()
141
142     def remove(self, actor):
143         self.actors.remove(actor)
144         return actor
145
146
147 class Actors:
148     """ Layers of actors.
149
150     Actors may be rendered in different layers. Layers with lower levels
151     are rendered lower than layers with higher ones.
152     """
153
154     def __init__(self):
155         self._ordered_layers = []
156         self._layers = {}
157         self.add_layer("default", 0)
158
159     def __getattr__(self, name):
160         return self._layers[name]
161
162     def add_layer(self, name, level):
163         layer = self._layers[name] = Layer(name)
164         self._ordered_layers.append((level, name))
165         self._ordered_layers.sort()
166         return layer
167
168     def add(self, actor, layer="default"):
169         return self._layers[layer].add(actor)
170
171     def remove(self, actor, layer="default"):
172         return self._layers[layer].remove(actor)
173
174     def draw(self, screen):
175         for lvl, name in self._ordered_layers:
176             for actor in self._layers[name]:
177                 # actor.draw doesn't allow blitting to anything other than
178                 # the game scene
179                 screen.blit(actor._surf, actor.topleft)
180
181
182 def defer_to_update(f):
183     """ Defers a function until the next update run. """
184     @functools.wraps(f)
185     def wrapper(self, *args, **kw):
186         self._deferred_updates.append((f, args, kw))
187     return wrapper
188
189
190 class Scene:
191     """ Base class for scenes. """
192
193     def __init__(self):
194         self.actors = Actors()
195         self.viewport = (0, 0)
196         self._deferred_updates = []
197
198     def move_screen(self, offset):
199         self.viewport = (self.viewport[0] + offset[0],
200                          self.viewport[1] + offset[1])
201
202     def calc_offset(self, x, y):
203         """ Return a position offset by the viewport. """
204         return x - self.viewport[0], y - self.viewport[1]
205
206     def enter(self, world):
207         pass
208
209     def exit(self, world):
210         pass
211
212     def update(self, world, engine, dt):
213         deferred_updates, self._deferred_updates = self._deferred_updates, []
214         for f, args, kw in deferred_updates:
215             f(self, world, *args, **kw)
216
217     def draw(self, screen):
218         screen.clear()
219         self.actors.draw(screen)
220
221     def on_mouse_down(self, pos, button):
222         pass
223
224     def on_mouse_up(self, pos, button):
225         pass
226
227     def on_key_down(self, key, mod, unicode):
228         pass
229
230     def on_key_up(self, key, mod):
231         pass
232
233     def on_music_end(self):
234         pass