Coverage for /home/kale/kxgames/libraries/kxg/kxg/theater.py: 100%

# Coverage for /home/kale/kxgames/libraries/kxg/kxg/theater.py : 100%

## 134 statements 134 run 0 missing 1 excluded

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 | #!/usr/bin/env python3 import time, pyglet from .errors import * from .forums import Forum from .multiplayer import ClientForum, ServerActor class Theater: """ Manage whichever stage is currently active. This involves both updating the current stage and handling transitions between stages. """ def __init__(self, initial_stage=None, gui=None): self._gui = gui self._initial_stage = initial_stage self._current_stage = None self._current_update = self._update_before_loop self._previous_time = time.time() @property def gui(self): return self._gui @gui.setter def gui(self, gui): if self._current_stage: raise ApiUsageError("theater already playing; can't set gui.") self._gui = gui @property def initial_stage(self): return self._initial_stage @initial_stage.setter def initial_stage(self, stage): require_stage(stage) if self._current_stage: raise ApiUsageError("theater already playing; can't set initial stage.") self._initial_stage = stage @property def current_stage(self): return self._current_stage @property def is_finished(self): return self._current_update == self._update_after_loop def update(self, dt=None): self._current_update(dt) def exit(self): if self._current_stage: self._current_stage.on_exit_stage() self._current_update = self._update_after_loop def _update_before_loop(self, dt): self._current_stage = self._initial_stage self._current_stage.theater = self self._current_stage.on_enter_stage() self._current_update = self._update_main_loop self._current_update(dt) def _update_main_loop(self, dt): if dt is None: current_time = time.time() dt = current_time - self._previous_time self._previous_time = current_time self._current_stage.on_update_stage(dt) if self._current_stage.is_finished: self._current_stage.on_exit_stage() self._current_stage = self._current_stage.successor if not self._current_stage: self.exit() else: require_stage(self._current_stage) self._current_stage.theater = self self._current_stage.on_enter_stage() def _update_after_loop(self, dt): raise AssertionError("shouldn't update the theater after the game has ended.") class PygletTheater(Theater): def play(self, frames_per_sec=50): pyglet.clock.schedule_interval(self.update, 1/frames_per_sec) pyglet.app.run() def exit(self): super().exit() pyglet.app.exit() class Stage: def __init__(self): self.theater = None self.successor = None self.is_finished = False @property def gui(self): return self.theater.gui def exit_stage(self): """ Stop this stage from executing once the current update ends. """ self.is_finished = True def exit_theater(self): """ Exit the game once the current update ends. """ self.theater.exit() def on_enter_stage(self): """ Give the stage a chance to set itself up before it is updated for the first time. """ pass def on_update_stage(self, dt): """ Give the stage a chance to react to each clock cycle. The amount of time that passed since the last clock cycle is provided as an argument. """ pass def on_exit_stage(self): """ Give the stage a chance to react before it is stopped and the next stage is started. You can define the next stage by setting the Stage.successor attribute. If the successor is static, you can just set it in the constructor. But if it will differ depending on the context, this method may be a good place to calculate it because it is called only once and just before the theater queries for the successor. """ pass class GameStage(Stage): def __init__(self, world, forum, actors): Stage.__init__(self) from .tokens import require_world; require_world(world) from .forums import require_forum; require_forum(forum) from .actors import require_actors; require_actors(actors) self.world = world self.forum = forum self.actors = actors self.successor = None def on_enter_stage(self): """ Prepare the actors, the world, and the messaging system to begin playing the game. This method is guaranteed to be called exactly once upon entering the game stage. """ with self.world._unlock_temporarily(): self.forum.connect_everyone(self.world, self.actors) # 1. Setup the forum. self.forum.on_start_game() # 2. Setup the world. with self.world._unlock_temporarily(): self.world.on_start_game() # 3. Setup the actors. Because this is done after the forum and the # world have been setup, this signals to the actors that they can # send messages and query the game world as usual. num_players = len(self.actors) - 1 for actor in self.actors: actor.on_setup_gui(self.gui) for actor in self.actors: actor.on_start_game(num_players) def on_update_stage(self, dt): """ Sequentially update the actors, the world, and the messaging system. The theater terminates once all of the actors indicate that they are done. """ for actor in self.actors: actor.on_update_game(dt) self.forum.on_update_game() with self.world._unlock_temporarily(): self.world.on_update_game(dt) if self.world.has_game_ended(): self.exit_stage() def on_exit_stage(self): """ Give the actors, the world, and the messaging system a chance to react to the end of the game. """ # 1. Let the forum react to the end of the game. Local forums don't # react to this, but remote forums take the opportunity to stop # trying to extract tokens from messages. self.forum.on_finish_game() # 2. Let the actors react to the end of the game. for actor in self.actors: actor.on_finish_game() # 3. Let the world react to the end of the game. with self.world._unlock_temporarily(): self.world.on_finish_game() class UniplayerGameStage(GameStage): def __init__(self, world, referee, gui_actor, ai_actors=None): forum = Forum() actors = [referee, gui_actor] + list(ai_actors or []) GameStage.__init__(self, world, forum, actors) class MultiplayerClientGameStage(Stage): def __init__(self, world, gui_actor, pipe): super().__init__() self.forum = ClientForum(pipe) self.successor = GameStage(world, self.forum, [gui_actor]) def on_update_stage(self, dt): if self.forum.receive_id_from_server(): self.exit_stage() class MultiplayerServerGameStage(GameStage): def __init__(self, world, referee, ai_actors, pipes): forum = Forum() actors = [referee] + [ServerActor(x) for x in pipes] + ai_actors GameStage.__init__(self, world, forum, actors) @debug_only def require_stage(object): require_instance(Stage(), object)