Refactor events into their own package

Most of the existing actions are game actions (they control the player character)
so they live in actions.game. Eventually, there will be modules for different
kinds of actions that only apply to, e.g. modal UI.
This commit is contained in:
Eryn Wells 2022-05-28 08:52:54 -07:00
parent 46af8863b1
commit ae1c7f5ce2
6 changed files with 114 additions and 90 deletions

View file

@ -0,0 +1,4 @@
# Eryn Wells <eryn@erynwells.me>
from .action import Action
from .result import ActionResult

45
erynrl/actions/action.py Normal file
View file

@ -0,0 +1,45 @@
# Eryn Wells <eryn@erynwells.me>
from typing import TYPE_CHECKING, Optional
from ..object import Actor
from .result import ActionResult
if TYPE_CHECKING:
from ..engine import Engine
class Action:
'''An action that an Entity should perform.'''
def __init__(self, actor: Optional[Actor] = None):
self.actor = actor
def perform(self, engine: 'Engine') -> ActionResult:
'''Perform this action.
Parameters
----------
engine : Engine
The game engine
Returns
-------
ActionResult
A result object reflecting how the action was handled, and what follow-up actions, if any, are needed to
complete the action.
'''
raise NotImplementedError()
def failure(self) -> ActionResult:
'''Create an ActionResult indicating failure with no follow-up'''
return ActionResult(self, success=False)
def success(self) -> ActionResult:
'''Create an ActionResult indicating success with no follow-up'''
return ActionResult(self, success=True)
def __str__(self) -> str:
return f'{self.__class__.__name__} for {self.actor!s}'
def __repr__(self):
return f'{self.__class__.__name__}({self.actor!r})'

View file

@ -18,96 +18,24 @@ Action : Base class of all actions
WaitAction WaitAction
''' '''
from typing import Optional, TYPE_CHECKING from typing import TYPE_CHECKING
from . import items from .. import items
from . import log from .. import log
from .geometry import Direction from ..geometry import Direction
from .object import Actor, Item from ..object import Actor, Item
from .action import Action
from .result import ActionResult
if TYPE_CHECKING: if TYPE_CHECKING:
from .engine import Engine from ..engine import Engine
class ActionResult:
'''The result of an Action.
`Action.perform()` returns an instance of this class to inform the caller of the result
Attributes
----------
action : Action
The Action that was performed
success : bool, optional
True if the action succeeded
done : bool, optional
True if the action is complete, and no follow-up action is needed
alternate : Action, optional
An alternate action to perform if this action failed
'''
def __init__(self, action: 'Action', *,
success: Optional[bool] = None,
done: Optional[bool] = None,
alternate: Optional['Action'] = None):
self.action = action
self.alternate = alternate
if success is not None:
self.success = success
elif alternate:
self.success = False
else:
self.success = True
if done is not None:
self.done = done
elif self.success:
self.done = True
else:
self.done = not alternate
def __repr__(self):
return f'{self.__class__.__name__}({self.action!r}, success={self.success}, done={self.done}, alternate={self.alternate!r})'
class Action:
'''An action that an Entity should perform.'''
def __init__(self, actor: Optional[Actor]):
self.actor = actor
def perform(self, engine: 'Engine') -> ActionResult:
'''Perform this action.
Parameters
----------
engine : Engine
The game engine
Returns
-------
ActionResult
A result object reflecting how the action was handled, and what follow-up actions, if any, are needed to
complete the action.
'''
raise NotImplementedError()
def failure(self) -> ActionResult:
'''Create an ActionResult indicating failure with no follow-up'''
return ActionResult(self, success=False)
def success(self) -> ActionResult:
'''Create an ActionResult indicating success with no follow-up'''
return ActionResult(self, success=True)
def __str__(self) -> str:
return f'{self.__class__.__name__} for {self.actor!s}'
def __repr__(self):
return f'{self.__class__.__name__}({self.actor!r})'
class ExitAction(Action): class ExitAction(Action):
'''Exit the game.''' '''Exit the game.'''
def __init__(self):
super().__init__(None)
def perform(self, engine: 'Engine') -> ActionResult: def perform(self, engine: 'Engine') -> ActionResult:
raise SystemExit() raise SystemExit()

47
erynrl/actions/result.py Normal file
View file

@ -0,0 +1,47 @@
# Eryn Wells <eryn@erynwells.me>
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from .action import Action
class ActionResult:
'''The result of an Action.
`Action.perform()` returns an instance of this class to inform the caller of the result
Attributes
----------
action : Action
The Action that was performed
success : bool, optional
True if the action succeeded
done : bool, optional
True if the action is complete, and no follow-up action is needed
alternate : Action, optional
An alternate action to perform if this action failed
'''
def __init__(self, action: 'Action', *,
success: Optional[bool] = None,
done: Optional[bool] = None,
alternate: Optional['Action'] = None):
self.action = action
self.alternate = alternate
if success is not None:
self.success = success
elif alternate:
self.success = False
else:
self.success = True
if done is not None:
self.done = done
elif self.success:
self.done = True
else:
self.done = not alternate
def __repr__(self):
return f'{self.__class__.__name__}({self.action!r}, success={self.success}, done={self.done}, alternate={self.alternate!r})'

View file

@ -7,7 +7,8 @@ import numpy as np
import tcod import tcod
from . import log from . import log
from .actions import Action, BumpAction, WaitAction from .actions import Action
from .actions.game import BumpAction, WaitAction
from .components import Component from .components import Component
from .geometry import Direction, Point from .geometry import Direction, Point
from .object import Entity from .object import Entity

View file

@ -7,7 +7,8 @@ from typing import Optional, TYPE_CHECKING
import tcod import tcod
from . import log from . import log
from .actions import Action, ExitAction, RegenerateRoomsAction, BumpAction, WaitAction from .actions.action import Action
from .actions.game import ExitAction, RegenerateRoomsAction, BumpAction, WaitAction
from .geometry import Direction, Point from .geometry import Direction, Point
if TYPE_CHECKING: if TYPE_CHECKING:
@ -46,7 +47,7 @@ class EventHandler(tcod.event.EventDispatch[Action]):
self.engine.process_input_action(action) self.engine.process_input_action(action)
def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]:
return ExitAction(self.engine.hero) return ExitAction()
class MainGameEventHandler(EventHandler): class MainGameEventHandler(EventHandler):
''' '''
@ -80,7 +81,7 @@ class MainGameEventHandler(EventHandler):
case tcod.event.KeySym.y: case tcod.event.KeySym.y:
action = BumpAction(hero, Direction.NorthWest) action = BumpAction(hero, Direction.NorthWest)
case tcod.event.KeySym.ESCAPE: case tcod.event.KeySym.ESCAPE:
action = ExitAction(hero) action = ExitAction()
case tcod.event.KeySym.SPACE: case tcod.event.KeySym.SPACE:
action = RegenerateRoomsAction(hero) action = RegenerateRoomsAction(hero)
case tcod.event.KeySym.PERIOD: case tcod.event.KeySym.PERIOD:
@ -100,11 +101,9 @@ class GameOverEventHandler(EventHandler):
def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]: def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]:
action: Optional[Action] = None action: Optional[Action] = None
hero = self.engine.hero
sym = event.sym sym = event.sym
match sym: match sym:
case tcod.event.KeySym.ESCAPE: case tcod.event.KeySym.ESCAPE:
action = ExitAction(hero) action = ExitAction()
return action return action