module refactoring

This commit is contained in:
Lukas Nöllemeyer 2024-08-17 10:40:57 +02:00
parent 72ec02dbae
commit 2560a4dcd9
6 changed files with 71 additions and 79 deletions

View file

@ -8,6 +8,8 @@ import attrs
import tcod.console import tcod.console
import tcod.event import tcod.event
import g
class State(Protocol): class State(Protocol):
"""An abstract game state.""" """An abstract game state."""
@ -42,3 +44,56 @@ class Reset:
StateResult: TypeAlias = "Push | Pop | Reset | None" StateResult: TypeAlias = "Push | Pop | Reset | None"
"""Union of state results.""" """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

View file

@ -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

View file

@ -13,11 +13,11 @@ from game.constants import DIRECTION_KEYS, ACTION_KEYS
from game.state import Push, State, StateResult from game.state import Push, State, StateResult
from game.tags import IsItem, IsPlayer, IsDoor, IsActor from game.tags import IsItem, IsPlayer, IsDoor, IsActor
from game.constants import WALL_CHAR from game.constants import WALL_CHAR
from game.states import MainMenu from game.states import menu_screens
@attrs.define() @attrs.define()
class InGame(State): class MainScreen(State):
"""Primary in-game state.""" """Primary in-game state."""
def on_event(self, event: tcod.event.Event) -> StateResult: def on_event(self, event: tcod.event.Event) -> StateResult:
@ -43,7 +43,7 @@ class InGame(State):
gold.clear() gold.clear()
return None return None
case tcod.event.KeyDown(sym=KeySym.ESCAPE): case tcod.event.KeyDown(sym=KeySym.ESCAPE):
return Push(MainMenu()) return Push(menu_screens())
case _: case _:
return None return None

View file

@ -3,23 +3,23 @@
from __future__ import annotations from __future__ import annotations
import g import g
import game.menus import game.states.menus
import game.world_tools import game.world_tools
from game.state import Reset, StateResult 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.""" """Main/escape menu."""
__slots__ = () __slots__ = ()
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize the main menu.""" """Initialize the main menu."""
items = [ items = [
game.menus.SelectItem("New game", self.new_game), game.states.menus.SelectItem("New game", self.new_game),
game.menus.SelectItem("Quit", self.quit), game.states.menus.SelectItem("Quit", self.quit),
] ]
if hasattr(g, "world"): 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__( super().__init__(
items=tuple(items), items=tuple(items),
@ -31,13 +31,13 @@ class MainMenu(game.menus.ListMenu):
@staticmethod @staticmethod
def continue_() -> StateResult: def continue_() -> StateResult:
"""Return to the game.""" """Return to the game."""
return Reset(InGame()) return Reset(MainScreen())
@staticmethod @staticmethod
def new_game() -> StateResult: def new_game() -> StateResult:
"""Begin a new game.""" """Begin a new game."""
g.world = game.world_tools.new_world() g.world = game.world_tools.new_world()
return Reset(InGame()) return Reset(MainScreen())
@staticmethod @staticmethod
def quit() -> StateResult: def quit() -> StateResult:

View file

@ -10,9 +10,8 @@ import tcod.console
import tcod.event import tcod.event
from tcod.event import KeySym from tcod.event import KeySym
import game.state_tools
from game.constants import DIRECTION_KEYS 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): class MenuItem(Protocol):
@ -96,6 +95,6 @@ class ListMenu(State):
def on_draw(self, console: tcod.console.Console) -> None: def on_draw(self, console: tcod.console.Console) -> None:
"""Render the menu.""" """Render the menu."""
game.state_tools.draw_previous_state(self, console) draw_previous_states(self, console)
for i, item in enumerate(self.items): for i, item in enumerate(self.items):
item.on_draw(console, x=self.x, y=self.y + i, highlight=i == self.selected) item.on_draw(console, x=self.x, y=self.y + i, highlight=i == self.selected)

View file

@ -8,10 +8,9 @@ import tcod.context
import tcod.tileset import tcod.tileset
import g 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: def main() -> None:
"""Entry point function.""" """Entry point function."""
@ -23,7 +22,7 @@ def main() -> None:
g.states = [MainMenu()] g.states = [MainMenu()]
g.console = tcod.console.Console(80, 50) g.console = tcod.console.Console(80, 50)
with tcod.context.new(console=g.console, tileset=tileset) as g.context: with tcod.context.new(console=g.console, tileset=tileset) as g.context:
game.state_tools.main_loop() game.state.main_loop()
if __name__ == "__main__": if __name__ == "__main__":