diff --git a/game/sim/audio.py b/game/sim/audio.py new file mode 100644 index 0000000..6c0dc67 --- /dev/null +++ b/game/sim/audio.py @@ -0,0 +1,36 @@ +class SoundEvent: + __slots__ = ("name", "x", "frame") + + def __init__(self, name, x, frame): + self.name = name + self.x = x + self.frame = frame + + +class AudioBus: + def __init__(self, sink=None, historyCap=64): + self.sink = sink + self.events = [] + self.historyCap = historyCap + self.frame = 0 + self.totalEmitted = 0 + + def setSink(self, sink): + self.sink = sink + + def tick(self): + self.frame += 1 + + def play(self, name, x=None): + event = SoundEvent(name, x, self.frame) + self.events.append(event) + if len(self.events) > self.historyCap: + self.events.pop(0) + self.totalEmitted += 1 + if self.sink is not None: + self.sink(event) + + def drain(self): + drained = self.events + self.events = [] + return drained diff --git a/game/sim/gameState.py b/game/sim/gameState.py new file mode 100644 index 0000000..2746d07 --- /dev/null +++ b/game/sim/gameState.py @@ -0,0 +1,78 @@ +from .world import World + +MENU = "menu" +PLAYING = "playing" +PAUSED = "paused" +GAMEOVER = "gameover" +COMPLETE = "complete" +VICTORY = "victory" + + +class Game: + def __init__(self, levels, rng=None): + self.levels = list(levels) + self.levelIndex = 0 + self.mode = MENU + self.world = None + self.rng = rng + self.frame = 0 + self.transitions = [] + + def startLevel(self, index): + self.levelIndex = index + self.world = World(self.levels[index], rng=self.rng) + self.setMode(PLAYING) + + def start(self): + self.startLevel(0) + + def setMode(self, mode): + if mode != self.mode: + self.transitions.append((self.mode, mode, self.frame)) + self.mode = mode + + def update(self, command): + self.frame += 1 + handler = { + MENU: self.updateMenu, + PLAYING: self.updatePlaying, + PAUSED: self.updatePaused, + GAMEOVER: self.updateGameover, + COMPLETE: self.updateComplete, + VICTORY: self.updateVictory, + }[self.mode] + handler(command) + + def updateMenu(self, command): + if command.confirm or command.jump: + self.start() + + def updatePlaying(self, command): + if command.pause: + self.setMode(PAUSED) + return + self.world.step(command) + if self.world.gameOver: + self.setMode(GAMEOVER) + elif self.world.completed: + if self.levelIndex + 1 < len(self.levels): + self.setMode(COMPLETE) + else: + self.setMode(VICTORY) + + def updatePaused(self, command): + if command.pause: + self.setMode(PLAYING) + + def updateGameover(self, command): + if command.confirm: + self.setMode(MENU) + + def updateComplete(self, command): + if command.confirm: + self.startLevel(self.levelIndex + 1) + + def updateVictory(self, command): + if command.confirm: + self.levelIndex = 0 + self.setMode(MENU) diff --git a/game/sim/stateMachine.py b/game/sim/stateMachine.py new file mode 100644 index 0000000..e4d04a4 --- /dev/null +++ b/game/sim/stateMachine.py @@ -0,0 +1,50 @@ +class State: + name = "state" + + def onEnter(self, owner, world): + pass + + def onExit(self, owner, world): + pass + + def update(self, owner, world): + return None + + +class StateMachine: + def __init__(self, owner): + self.owner = owner + self.states = {} + self.current = None + self.previous = None + self.timeInState = 0 + + def addState(self, state): + self.states[state.name] = state + return self + + def setInitial(self, name, world=None): + self.current = self.states[name] + self.timeInState = 0 + if world is not None: + self.current.onEnter(self.owner, world) + return self + + def transition(self, name, world): + if name == self.current.name: + return + self.previous = self.current + self.current.onExit(self.owner, world) + self.current = self.states[name] + self.timeInState = 0 + self.current.onEnter(self.owner, world) + + def update(self, world): + self.timeInState += 1 + nextState = self.current.update(self.owner, world) + if nextState is not None and nextState != self.current.name: + self.transition(nextState, world) + + @property + def stateName(self): + return self.current.name if self.current else None