diff --git a/actions.py b/actions.py index 72f35e8..9fa6837 100644 --- a/actions.py +++ b/actions.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import Optional, Tuple, TYPE_CHECKING if TYPE_CHECKING: from engine import Engine @@ -7,12 +7,20 @@ if TYPE_CHECKING: class Action: - def perform(self, engine: Engine, entity: Entity) -> None: + def __init__(self, entity: Entity) -> None: + super().__init__() + self.entity = entity + + @property + def engine(self) -> Engine: + return self.entity.gamemap.engine + + def perform(self) -> None: """Perform this action with the objects needed to determine its scope. - `engine` is the scope this action is being performed in. + `self.engine` is the scope this action is being performed in. - `entity` is the object performing the action. + `self.entity` is the object performing the action. This method must be overridden by Action subclasses. """ @@ -20,48 +28,50 @@ class Action: class EscapeAction(Action): - def perform(self, engine: Engine, entity: Entity) -> None: + def perform(self) -> None: raise SystemExit() class ActionWithDirection(Action): - def __init__(self, dx: int, dy: int): - super().__init__() + def __init__(self, entity: Entity, dx: int, dy: int): + super().__init__(entity) self.dx = dx self.dy = dy - def perform(self, engine: Engine, entity: Entity) -> None: + @property + def dest_xy(self) -> Tuple[int,int]: + return self.entity.x + self.dx, self.entity.y + self.dy + + @property + def blocking_entity(self) -> Optional[Entity]: + return self.engine.game_map.get_blocking_entity_at_location(*self.dest_xy) + + def perform(self) -> None: raise NotImplementedError() class MeleeAction(ActionWithDirection): - def perform(self, engine: Engine, entity: Entity) -> None: - dest_x = entity.x + self.dx - dest_y = entity.y + self.dy - target = engine.game_map.get_blocking_entity_at_location(dest_x, dest_y) + def perform(self) -> None: + target = self.blocking_entity if not target: return # no entity to blockj print(f"You kick {target.name}, much to its annoyance") -class BumpAction(ActionWithDirection): - def perform(self, engine: Engine, entity: Entity) -> None: - dest_x = entity.x + self.dx - dest_y = entity.y + self.dy - if engine.game_map.get_blocking_entity_at_location(dest_x, dest_y): - return MeleeAction(self.dx, self.dy).perform(engine, entity) - else: - return MovementAction(self.dx, self.dy).perform(engine, entity) - - class MovementAction(ActionWithDirection): - def perform(self, engine: Engine, entity: Entity) -> None: - dest_x = entity.x + self.dx - dest_y = entity.y + self.dy + def perform(self) -> None: + dest_x, dest_y = self.dest_xy - if not engine.game_map.in_bounds(dest_x, dest_y): + if not self.engine.game_map.in_bounds(dest_x, dest_y): return # OOB - if not engine.game_map.tiles["walkable"][dest_x, dest_y]: + if not self.engine.game_map.tiles["walkable"][dest_x, dest_y]: return # can't walk - if engine.game_map.get_blocking_entity_at_location(dest_x, dest_y): + if self.engine.game_map.get_blocking_entity_at_location(dest_x, dest_y): return # Blocked - entity.move(self.dx, self.dy) + self.entity.move(self.dx, self.dy) + +class BumpAction(ActionWithDirection): + def perform(self) -> None: + if self.blocking_entity: + return MeleeAction(self.entity, self.dx, self.dy).perform() + else: + return MovementAction(self.entity, self.dx, self.dy).perform() diff --git a/engine.py b/engine.py index 971d17d..a7abaaa 100644 --- a/engine.py +++ b/engine.py @@ -1,34 +1,28 @@ -from typing import Iterable, Any +from __future__ import annotations +from typing import TYPE_CHECKING from tcod.context import Context from tcod.console import Console from tcod.map import compute_fov -from entity import Entity -from game_map import GameMap from input_handlers import EventHandler +if TYPE_CHECKING: + from entity import Entity + from game_map import GameMap + class Engine: - def __init__(self, event_handler: EventHandler, game_map: GameMap, player: Entity): - self.event_handler = event_handler + game_map: GameMap + + def __init__(self, player: Entity): + self.event_handler: EventHandler = EventHandler(self) self.player = player - self.game_map = game_map - self.update_fov() def handle_enemy_turns(self) -> None: for entity in self.game_map.entities - {self.player}: print(f'the {entity.name} wonders when it will move') - def handle_events(self, events: Iterable[Any]) -> None: - for event in events: - action = self.event_handler.dispatch(event) - if action is None: - continue - action.perform(self, self.player) - self.handle_enemy_turns() - self.update_fov() - def update_fov(self) -> None: self.game_map.visible[:] = compute_fov( self.game_map.tiles["transparent"], diff --git a/entity.py b/entity.py index 079cb1a..04f9394 100644 --- a/entity.py +++ b/entity.py @@ -1,7 +1,7 @@ from __future__ import annotations import copy -from typing import Tuple, TypeVar, TYPE_CHECKING +from typing import Tuple, TypeVar, TYPE_CHECKING, Optional if TYPE_CHECKING: from game_map import GameMap @@ -14,7 +14,10 @@ class Entity: A generic object to represent players, enemies, items, etc. """ + gamemap: GameMap + def __init__(self, + gamemap: Optional[GameMap] = None, x: int = 0, y: int = 0, char: str = "?", @@ -28,15 +31,28 @@ class Entity: self.color = color self.name = name self.blocks_movement = blocks_movement + if gamemap: + self.gamemap = gamemap + gamemap.entities.add(self) def spawn(self: T, gamemap: GameMap, x: int, y: int) -> T: """Spawns a copy of this instance at the given location""" clone = copy.deepcopy(self) clone.x = x clone.y = y + clone.gamemap = gamemap gamemap.entities.add(clone) return clone + def place(self, x: int, y: int, gamemap: Optional[GameMap] = None) -> None: + self.x = x + self.y = y + if gamemap: + if hasattr(self, "gamemap"): + self.gamemap.entities.remove(self) + self.gamemap = gamemap + gamemap.entities.add(self) + def move(self, dx: int, dy: int) -> None: self.x += dx self.y += dy diff --git a/game_map.py b/game_map.py index 2ec7823..e3bc94e 100644 --- a/game_map.py +++ b/game_map.py @@ -7,21 +7,38 @@ from tcod.console import Console import tile_types if TYPE_CHECKING: + from engine import Engine from entity import Entity class GameMap: - def __init__(self, width: int, height: int, entities: Iterable[Entity] = ()): + def __init__(self, + engine: Engine, + width: int, + height: int, + entities: Iterable[Entity] = () + ): + self.engine = engine self.width, self.height = width, height self.entities = set(entities) self.tiles = np.full((width, height), fill_value=tile_types.wall, order="F") - self.visible = np.full((width, height), fill_value=False, order="F") - self.explored = np.full((width, height), fill_value=False, order="F") + self.visible = np.full( + (width, height), fill_value=False, order="F" + ) # Tiles the player can currently see + self.explored = np.full( + (width, height), fill_value=False, order="F" + ) # Tiles the player has seen before - def get_blocking_entity_at_location(self, location_x: int, location_y: int) -> Optiona9[Entity]: + def get_blocking_entity_at_location( + self, location_x: int, location_y: int + ) -> Optional[Entity]: for entity in self.entities: - if entity.blocks_movement and entity.x == location_x and entity.y == location_y: + if ( + entity.blocks_movement + and entity.x == location_x + and entity.y == location_y + ): return entity return None diff --git a/input_handlers.py b/input_handlers.py index ab194f8..2125cdc 100644 --- a/input_handlers.py +++ b/input_handlers.py @@ -1,11 +1,28 @@ -from typing import Optional +from __future__ import annotations +from typing import Optional, TYPE_CHECKING import tcod.event from actions import Action, EscapeAction, BumpAction +if TYPE_CHECKING: + from engine import Engine class EventHandler(tcod.event.EventDispatch[Action]): + def __init__(self, engine: Engine): + self.engine = engine + + def handle_events(self) -> None: + for event in tcod.event.wait(): + action = self.dispatch(event) + + if action is None: + continue + + action.perform() + self.engine.handle_enemy_turns() + self.engine.update_fov() + def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: raise SystemExit() @@ -13,17 +30,18 @@ class EventHandler(tcod.event.EventDispatch[Action]): action: Optional[Action] = None key = event.sym + player = self.engine.player if key == tcod.event.K_UP: - action = BumpAction(dx=0, dy=-1) + action = BumpAction(player, dx=0, dy=-1) elif key == tcod.event.K_DOWN: - action = BumpAction(dx=0, dy=1) + action = BumpAction(player, dx=0, dy=1) elif key == tcod.event.K_LEFT: - action = BumpAction(dx=-1, dy=0) + action = BumpAction(player, dx=-1, dy=0) elif key == tcod.event.K_RIGHT: - action = BumpAction(dx=1, dy=0) + action = BumpAction(player, dx=1, dy=0) elif key == tcod.event.K_ESCAPE: - action = EscapeAction() + action = EscapeAction(player) return action diff --git a/main.py b/main.py index af3c98e..8280d20 100644 --- a/main.py +++ b/main.py @@ -25,21 +25,20 @@ def main(): "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD ) - event_handler = EventHandler() - player = copy.deepcopy(entity_factories.player) + engine = Engine(player=player) - game_map = generate_dungeon( + engine.game_map = generate_dungeon( max_rooms=max_rooms, room_min_size=room_min_size, room_max_size=room_max_size, map_width=map_width, map_height=map_height, max_monsters_per_room=max_monsters_per_room, - player=player, + engine=engine, ) + engine.update_fov() - engine = Engine(event_handler=event_handler, game_map=game_map, player=player) with tcod.context.new_terminal( screen_width, @@ -51,8 +50,7 @@ def main(): root_console = tcod.Console(screen_width, screen_height, order="F") while True: engine.render(console=root_console, context=context) - events = tcod.event.wait() - engine.handle_events(events) + engine.event_handler.handle_events() if __name__ == "__main__": diff --git a/procgen.py b/procgen.py index 9f953f7..09d3b8c 100644 --- a/procgen.py +++ b/procgen.py @@ -10,7 +10,7 @@ import entity_factories from game_map import GameMap if TYPE_CHECKING: - from entity import Entity + from engine import Engine class RectangularRoom: @@ -76,9 +76,10 @@ def generate_dungeon( map_width: int, map_height: int, max_monsters_per_room: int, - player: Entity, + engine: Engine, ) -> GameMap: - dungeon = GameMap(map_width, map_height, entities=[player]) + player = engine.player + dungeon = GameMap(engine, map_width, map_height, entities=[player]) rooms: List[RectangularRoom] = [] for room in range(max_rooms): @@ -96,7 +97,7 @@ def generate_dungeon( if len(rooms) == 0: # first room - player.x, player.y = new_room.center + player.place(*new_room.center, dungeon) else: for x, y in tunnel_between(rooms[-1].center, new_room.center): dungeon.tiles[x, y] = tile_types.floor diff --git a/tags b/tags index 78af940..017e8a4 100644 --- a/tags +++ b/tags @@ -10369,10 +10369,12 @@ __index__ venv/lib/python3.10/site-packages/numpy/random/tests/test_random.py /^ __index__ venv/lib/python3.10/site-packages/numpy/typing/tests/data/pass/array_constructors.py /^ def __index__(self) -> int:$/;" m class:Index typeref:typename:int __index__ venv/lib/python3.10/site-packages/numpy/typing/tests/data/pass/scalars.py /^ def __index__(self) -> int:$/;" m class:D typeref:typename:int __index__ venv/lib/python3.10/site-packages/typing_extensions.py /^ def __index__(self) -> int:$/;" m class:SupportsIndex typeref:typename:int -__init__ actions.py /^ def __init__(self, dx: int, dy: int):$/;" m class:ActionWithDirection -__init__ engine.py /^ def __init__(self, event_handler: EventHandler, game_map: GameMap, player: Entity):$/;" m class:Engine +__init__ actions.py /^ def __init__(self, entity: Entity) -> None:$/;" m class:Action typeref:typename:None +__init__ actions.py /^ def __init__(self, entity: Entity, dx: int, dy: int):$/;" m class:ActionWithDirection +__init__ engine.py /^ def __init__(self, player: Entity):$/;" m class:Engine __init__ entity.py /^ def __init__(self,$/;" m class:Entity -__init__ game_map.py /^ def __init__(self, width: int, height: int, entities: Iterable[Entity] = ()):$/;" m class:GameMap +__init__ game_map.py /^ def __init__(self,$/;" m class:GameMap +__init__ input_handlers.py /^ def __init__(self, engine: Engine):$/;" m class:EventHandler __init__ procgen.py /^ def __init__(self, x: int, y: int, width: int, height: int):$/;" m class:RectangularRoom __init__ venv/lib/python3.10/site-packages/black/handle_ipynb_magics.py /^ def __init__(self) -> None:$/;" m class:MagicFinder typeref:typename:None __init__ venv/lib/python3.10/site-packages/black/handle_ipynb_magics.py /^ def __init__(self, cell_magic: Optional[CellMagic] = None) -> None:$/;" m class:CellMagicFinder typeref:typename:None @@ -17991,6 +17993,7 @@ blit_2x venv/lib/python3.10/site-packages/tcod/image.py /^ def blit_2x($/;" m blit_rect venv/lib/python3.10/site-packages/tcod/image.py /^ def blit_rect($/;" m class:Image typeref:typename:None block venv/lib/python3.10/site-packages/numpy/core/shape_base.py /^def block(arrays):$/;" f block venv/lib/python3.10/site-packages/numpy/core/tests/test_shape_base.py /^ def block(self, request):$/;" m class:TestBlock +blocking_entity actions.py /^ def blocking_entity(self) -> Optional[Entity]:$/;" m class:ActionWithDirection typeref:typename:Optional[Entity] blocksize venv/lib/python3.10/site-packages/pip/_vendor/distlib/_backport/tarfile.py /^ blocksize = 1024$/;" v class:ExFileObject blocksize venv/lib/python3.10/site-packages/pip/_vendor/distlib/_backport/tarfile.py /^ blocksize = 16 * 1024$/;" v class:_BZ2Proxy bltn_open venv/lib/python3.10/site-packages/pip/_vendor/distlib/_backport/tarfile.py /^bltn_open = open$/;" v @@ -20233,6 +20236,7 @@ description venv/lib/python3.10/site-packages/setuptools/command/upload_docs.py description_of venv/lib/python3.10/site-packages/pip/_vendor/chardet/cli/chardetect.py /^def description_of(lines, name='stdin'):$/;" f descriptors venv/lib/python3.10/site-packages/numpy/core/include/numpy/experimental_dtype_api.h /^ PyArray_Descr **descriptors;$/;" m struct:__anoncd93f9d90408 typeref:typename:PyArray_Descr ** dest_path venv/lib/python3.10/site-packages/pip/_internal/operations/install/wheel.py /^ dest_path = None # type: str$/;" v class:File +dest_xy actions.py /^ def dest_xy(self) -> Tuple[int,int]:$/;" m class:ActionWithDirection typeref:typename:Tuple[int,int] det venv/lib/python3.10/site-packages/numpy/array_api/linalg.py /^def det(x: Array, \/) -> Array:$/;" f typeref:typename:Array det venv/lib/python3.10/site-packages/numpy/dual.py /^det = linpkg.det$/;" v det venv/lib/python3.10/site-packages/numpy/linalg/linalg.py /^def det(a):$/;" f @@ -20854,6 +20858,7 @@ endpoint venv/lib/python3.10/site-packages/numpy/random/tests/test_generator_mt1 endprogs venv/lib/python3.10/site-packages/blib2to3/pgen2/tokenize.py /^endprogs: Final = {$/;" v typeref:typename:Final endswith venv/lib/python3.10/site-packages/numpy/core/defchararray.py /^ def endswith(self, suffix, start=0, end=None):$/;" m class:chararray endswith venv/lib/python3.10/site-packages/numpy/core/defchararray.py /^def endswith(a, suffix, start=0, end=None):$/;" f +engine actions.py /^ def engine(self) -> Engine:$/;" m class:Action typeref:typename:Engine english_capitalize venv/lib/python3.10/site-packages/numpy/core/_string_helpers.py /^def english_capitalize(s):$/;" f english_lower venv/lib/python3.10/site-packages/numpy/core/_string_helpers.py /^def english_lower(s):$/;" f english_upper venv/lib/python3.10/site-packages/numpy/core/_string_helpers.py /^def english_upper(s):$/;" f @@ -22352,7 +22357,7 @@ get_binary_stdin venv/lib/python3.10/site-packages/click/_compat.py /^def get_bi get_binary_stdout venv/lib/python3.10/site-packages/click/_compat.py /^def get_binary_stdout() -> t.BinaryIO:$/;" f typeref:typename:t.BinaryIO get_binary_stream venv/lib/python3.10/site-packages/click/__init__.py /^from .utils import get_binary_stream as get_binary_stream$/;" x nameref:unknown:get_binary_stream get_binary_stream venv/lib/python3.10/site-packages/click/utils.py /^def get_binary_stream(name: "te.Literal['stdin', 'stdout', 'stderr']") -> t.BinaryIO:$/;" f typeref:typename:t.BinaryIO -get_blocking_entity_at_location game_map.py /^ def get_blocking_entity_at_location(self, location_x: int, location_y: int) -> Optiona9[Enti/;" m class:GameMap typeref:typename:Optiona9[Entity] +get_blocking_entity_at_location game_map.py /^ def get_blocking_entity_at_location($/;" m class:GameMap typeref:typename:Optional[Entity] get_buffer venv/lib/python3.10/site-packages/click/_winconsole.py /^ def get_buffer(obj, writable=False):$/;" f get_buffer venv/lib/python3.10/site-packages/click/_winconsole.py /^ get_buffer = None$/;" v get_build_architecture venv/lib/python3.10/site-packages/numpy/distutils/misc_util.py /^def get_build_architecture():$/;" f @@ -23381,7 +23386,7 @@ handle_PermissionError venv/lib/python3.10/site-packages/black_primer/lib.py /^d handle_display_options venv/lib/python3.10/site-packages/setuptools/_distutils/dist.py /^ def handle_display_options(self, option_order):$/;" m class:Distribution handle_display_options venv/lib/python3.10/site-packages/setuptools/dist.py /^ def handle_display_options(self, option_order):$/;" m class:Distribution handle_enemy_turns engine.py /^ def handle_enemy_turns(self) -> None:$/;" m class:Engine typeref:typename:None -handle_events engine.py /^ def handle_events(self, events: Iterable[Any]) -> None:$/;" m class:Engine typeref:typename:None +handle_events input_handlers.py /^ def handle_events(self) -> None:$/;" m class:EventHandler typeref:typename:None handle_extra_path venv/lib/python3.10/site-packages/setuptools/_distutils/command/install.py /^ def handle_extra_path(self):$/;" m class:install handle_extra_path venv/lib/python3.10/site-packages/setuptools/command/install.py /^ def handle_extra_path(self):$/;" m class:install handle_line venv/lib/python3.10/site-packages/pip/_internal/req/req_file.py /^def handle_line($/;" f typeref:typename:Optional[ParsedRequirement] @@ -28063,12 +28068,12 @@ pending_deprecate venv/lib/python3.10/site-packages/tcod/_internal.py /^def pend percent venv/lib/python3.10/site-packages/pip/_vendor/progress/__init__.py /^ def percent(self):$/;" m class:Progress percentage venv/lib/python3.10/site-packages/pip/_vendor/distlib/util.py /^ def percentage(self):$/;" m class:Progress percentile venv/lib/python3.10/site-packages/numpy/lib/function_base.py /^def percentile(a,$/;" f -perform actions.py /^ def perform(self, engine: Engine, entity: Entity) -> None:$/;" m class:Action typeref:typename:None -perform actions.py /^ def perform(self, engine: Engine, entity: Entity) -> None:$/;" m class:ActionWithDirection typeref:typename:None -perform actions.py /^ def perform(self, engine: Engine, entity: Entity) -> None:$/;" m class:BumpAction typeref:typename:None -perform actions.py /^ def perform(self, engine: Engine, entity: Entity) -> None:$/;" m class:EscapeAction typeref:typename:None -perform actions.py /^ def perform(self, engine: Engine, entity: Entity) -> None:$/;" m class:MeleeAction typeref:typename:None -perform actions.py /^ def perform(self, engine: Engine, entity: Entity) -> None:$/;" m class:MovementAction typeref:typename:None +perform actions.py /^ def perform(self) -> None:$/;" m class:Action typeref:typename:None +perform actions.py /^ def perform(self) -> None:$/;" m class:ActionWithDirection typeref:typename:None +perform actions.py /^ def perform(self) -> None:$/;" m class:BumpAction typeref:typename:None +perform actions.py /^ def perform(self) -> None:$/;" m class:EscapeAction typeref:typename:None +perform actions.py /^ def perform(self) -> None:$/;" m class:MeleeAction typeref:typename:None +perform actions.py /^ def perform(self) -> None:$/;" m class:MovementAction typeref:typename:None perm venv/lib/python3.10/site-packages/numpy/core/include/numpy/ndarraytypes.h /^ npy_intp perm, stride;$/;" m struct:__anonbeb738b31808 typeref:typename:npy_intp permutation_index venv/lib/python3.10/site-packages/setuptools/_vendor/more_itertools/more.py /^def permutation_index(element, iterable):$/;" f permute_dims venv/lib/python3.10/site-packages/numpy/array_api/_manipulation_functions.py /^def permute_dims(x: Array, \/, axes: Tuple[int, ...]) -> Array:$/;" f typeref:typename:Array @@ -28134,6 +28139,7 @@ pkg_name venv/lib/python3.10/site-packages/numpy/distutils/npy_pkg_config.py /^ pkg_resources_distribution_for_wheel venv/lib/python3.10/site-packages/pip/_internal/utils/wheel.py /^def pkg_resources_distribution_for_wheel(wheel_zip, name, location):$/;" f pkg_to_filename venv/lib/python3.10/site-packages/numpy/distutils/npy_pkg_config.py /^def pkg_to_filename(pkg_name):$/;" f pkgname venv/lib/python3.10/site-packages/numpy/core/lib/npy-pkg-config/npymath.ini /^pkgname=numpy.core$/;" k section:variables +place entity.py /^ def place(self, x: int, y: int, gamemap: Optional[GameMap] = None) -> None:$/;" m class:Entity typeref:typename:None place venv/lib/python3.10/site-packages/numpy/lib/function_base.py /^def place(arr, mask, vals):$/;" f place_entities procgen.py /^def place_entities($/;" f typeref:typename:None plainrep venv/lib/python3.10/site-packages/numpy/distutils/conv_template.py /^plainrep = re.compile(r"([^*]+)\\*(\\d+)")$/;" v