"""Package for game screens.""" from __future__ import annotations from typing import Protocol, TypeAlias import attrs import tcod.console import tcod.event import g class Screen(Protocol): """An abstract game screen.""" __slots__ = () def on_event(self, event: tcod.event.Event) -> ScreenResult: """Called on events.""" def on_draw(self, console: tcod.console.Console) -> None: """Called when the screen is being drawn.""" @attrs.define() class Push: """Push a new screen on top of the stack.""" screen: Screen @attrs.define() class Pop: """Remove the current screen from the stack.""" @attrs.define() class Reset: """Replace the entire stack with a new screen.""" screen: Screen ScreenResult: TypeAlias = "Push | Pop | Reset | None" """Union of screen results.""" def main_draw() -> None: """Render and present the active screen.""" if not g.screens: return g.console.clear() g.screens[-1].on_draw(g.console) g.context.present(g.console) def apply_screen_result(result: ScreenResult) -> None: """Apply a ScreenResult to `g.screens`.""" match result: case Push(screen=screen): g.screens.append(screen) case Pop(): g.screens.pop() case Reset(screen=screen): while g.screens: apply_screen_result(Pop()) apply_screen_result(Push(screen)) case None: pass case _: raise TypeError(result) def main_loop() -> None: """Run the active screen forever.""" while g.screens: main_draw() for event in tcod.event.wait(): tile_event = g.context.convert_event(event) if g.screens: apply_screen_result(g.screens[-1].on_event(tile_event)) def get_previous_screen(screen: Screen) -> Screen | None: """Return the screen before `screen` in the stack if it exists.""" current_index = next(index for index, value in enumerate(g.screens) if value is screen) return g.screens[current_index - 1] if current_index > 0 else None def draw_previous_screens(screen: Screen, console: tcod.console.Console, dim: bool = True) -> None: """Draw previous screens, optionally dimming all but the active screen.""" prev_screen = get_previous_screen(screen) if prev_screen is None: return prev_screen.on_draw(console) if dim and screen is g.screens[-1]: console.rgb["fg"] //= 4 console.rgb["bg"] //= 4