From 2560a4dcd933b31a673e9297e7cbfa01e574996f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20N=C3=B6llemeyer?= Date: Sat, 17 Aug 2024 10:40:57 +0200 Subject: [PATCH] module refactoring --- game/state.py | 55 ++++++++++++++++++ game/state_tools.py | 61 -------------------- game/states/{InGame.py => game_screens.py} | 6 +- game/states/{MainMenu.py => menu_screens.py} | 16 ++--- game/{ => states}/menus.py | 5 +- main.py | 7 +-- 6 files changed, 71 insertions(+), 79 deletions(-) delete mode 100644 game/state_tools.py rename game/states/{InGame.py => game_screens.py} (96%) rename game/states/{MainMenu.py => menu_screens.py} (65%) rename game/{ => states}/menus.py (96%) diff --git a/game/state.py b/game/state.py index 4b935c4..4497f0d 100644 --- a/game/state.py +++ b/game/state.py @@ -8,6 +8,8 @@ import attrs import tcod.console import tcod.event +import g + class State(Protocol): """An abstract game state.""" @@ -42,3 +44,56 @@ class Reset: StateResult: TypeAlias = "Push | Pop | Reset | None" """Union of state results.""" + + +def main_draw() -> None: + """Render and present the active state.""" + if not g.states: + return + g.console.clear() + g.states[-1].on_draw(g.console) + g.context.present(g.console) + + +def apply_state_result(result: StateResult) -> None: + """Apply a StateResult to `g.states`.""" + match result: + case Push(state=state): + g.states.append(state) + case Pop(): + g.states.pop() + case Reset(state=state): + while g.states: + apply_state_result(Pop()) + apply_state_result(Push(state)) + case None: + pass + case _: + raise TypeError(result) + + +def main_loop() -> None: + """Run the active state forever.""" + while g.states: + main_draw() + for event in tcod.event.wait(): + tile_event = g.context.convert_event(event) + if g.states: + apply_state_result(g.states[-1].on_event(tile_event)) + + +def get_previous_state(state: State) -> State | None: + """Return the state before `state` in the stack if it exists.""" + current_index = next(index for index, value in enumerate(g.states) if value is state) + return g.states[current_index - 1] if current_index > 0 else None + + +def draw_previous_states(state: State, console: tcod.console.Console, dim: bool = True) -> None: + """Draw previous states, optionally dimming all but the active state.""" + prev_state = get_previous_state(state) + if prev_state is None: + return + prev_state.on_draw(console) + if dim and state is g.states[-1]: + console.rgb["fg"] //= 4 + console.rgb["bg"] //= 4 diff --git a/game/state_tools.py b/game/state_tools.py deleted file mode 100644 index 77d2e22..0000000 --- a/game/state_tools.py +++ /dev/null @@ -1,61 +0,0 @@ -"""State handling functions.""" - -from __future__ import annotations - -import tcod.console - -import g -from game.state import Pop, Push, Reset, State, StateResult - - -def main_draw() -> None: - """Render and present the active state.""" - if not g.states: - return - g.console.clear() - g.states[-1].on_draw(g.console) - g.context.present(g.console) - - -def apply_state_result(result: StateResult) -> None: - """Apply a StateResult to `g.states`.""" - match result: - case Push(state=state): - g.states.append(state) - case Pop(): - g.states.pop() - case Reset(state=state): - while g.states: - apply_state_result(Pop()) - apply_state_result(Push(state)) - case None: - pass - case _: - raise TypeError(result) - - -def main_loop() -> None: - """Run the active state forever.""" - while g.states: - main_draw() - for event in tcod.event.wait(): - tile_event = g.context.convert_event(event) - if g.states: - apply_state_result(g.states[-1].on_event(tile_event)) - - -def get_previous_state(state: State) -> State | None: - """Return the state before `state` in the stack if it exists.""" - current_index = next(index for index, value in enumerate(g.states) if value is state) - return g.states[current_index - 1] if current_index > 0 else None - - -def draw_previous_state(state: State, console: tcod.console.Console, dim: bool = True) -> None: - """Draw previous states, optionally dimming all but the active state.""" - prev_state = get_previous_state(state) - if prev_state is None: - return - prev_state.on_draw(console) - if dim and state is g.states[-1]: - console.rgb["fg"] //= 4 - console.rgb["bg"] //= 4 diff --git a/game/states/InGame.py b/game/states/game_screens.py similarity index 96% rename from game/states/InGame.py rename to game/states/game_screens.py index 106f45c..583e431 100644 --- a/game/states/InGame.py +++ b/game/states/game_screens.py @@ -13,11 +13,11 @@ from game.constants import DIRECTION_KEYS, ACTION_KEYS from game.state import Push, State, StateResult from game.tags import IsItem, IsPlayer, IsDoor, IsActor from game.constants import WALL_CHAR -from game.states import MainMenu +from game.states import menu_screens @attrs.define() -class InGame(State): +class MainScreen(State): """Primary in-game state.""" def on_event(self, event: tcod.event.Event) -> StateResult: @@ -43,7 +43,7 @@ class InGame(State): gold.clear() return None case tcod.event.KeyDown(sym=KeySym.ESCAPE): - return Push(MainMenu()) + return Push(menu_screens()) case _: return None diff --git a/game/states/MainMenu.py b/game/states/menu_screens.py similarity index 65% rename from game/states/MainMenu.py rename to game/states/menu_screens.py index 6854593..320323f 100644 --- a/game/states/MainMenu.py +++ b/game/states/menu_screens.py @@ -3,23 +3,23 @@ from __future__ import annotations import g -import game.menus +import game.states.menus import game.world_tools from game.state import Reset, StateResult -from game.states.InGame import InGame +from game.states.game_screens import MainScreen -class MainMenu(game.menus.ListMenu): +class MainMenu(game.states.menus.ListMenu): """Main/escape menu.""" __slots__ = () def __init__(self) -> None: """Initialize the main menu.""" items = [ - game.menus.SelectItem("New game", self.new_game), - game.menus.SelectItem("Quit", self.quit), + game.states.menus.SelectItem("New game", self.new_game), + game.states.menus.SelectItem("Quit", self.quit), ] if hasattr(g, "world"): - items.insert(0, game.menus.SelectItem("Continue", self.continue_)) + items.insert(0, game.states.menus.SelectItem("Continue", self.continue_)) super().__init__( items=tuple(items), @@ -31,13 +31,13 @@ class MainMenu(game.menus.ListMenu): @staticmethod def continue_() -> StateResult: """Return to the game.""" - return Reset(InGame()) + return Reset(MainScreen()) @staticmethod def new_game() -> StateResult: """Begin a new game.""" g.world = game.world_tools.new_world() - return Reset(InGame()) + return Reset(MainScreen()) @staticmethod def quit() -> StateResult: diff --git a/game/menus.py b/game/states/menus.py similarity index 96% rename from game/menus.py rename to game/states/menus.py index 7c1bb37..c9e6219 100644 --- a/game/menus.py +++ b/game/states/menus.py @@ -10,9 +10,8 @@ import tcod.console import tcod.event from tcod.event import KeySym -import game.state_tools from game.constants import DIRECTION_KEYS -from game.state import Pop, State, StateResult +from game.state import Pop, State, StateResult, draw_previous_states class MenuItem(Protocol): @@ -96,6 +95,6 @@ class ListMenu(State): def on_draw(self, console: tcod.console.Console) -> None: """Render the menu.""" - game.state_tools.draw_previous_state(self, console) + draw_previous_states(self, console) for i, item in enumerate(self.items): item.on_draw(console, x=self.x, y=self.y + i, highlight=i == self.selected) diff --git a/main.py b/main.py index 6703a24..582f885 100755 --- a/main.py +++ b/main.py @@ -8,10 +8,9 @@ import tcod.context import tcod.tileset import g -import game.state_tools -import game.states -from game.states.MainMenu import MainMenu +import game.state +from game.states.menu_screens import MainMenu def main() -> None: """Entry point function.""" @@ -23,7 +22,7 @@ def main() -> None: g.states = [MainMenu()] g.console = tcod.console.Console(80, 50) with tcod.context.new(console=g.console, tileset=tileset) as g.context: - game.state_tools.main_loop() + game.state.main_loop() if __name__ == "__main__":