Skip to content

Commit 778e23b

Browse files
committed
major rewrite
1 parent 705c8cb commit 778e23b

File tree

7 files changed

+129
-69
lines changed

7 files changed

+129
-69
lines changed

actions.py

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,85 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING
3+
from typing import Optional, Tuple, TYPE_CHECKING
44

55
if TYPE_CHECKING:
66
from engine import Engine
77
from entity import Entity
88

99

1010
class Action:
11-
def perform(self, engine: Engine, entity: Entity) -> None:
11+
def __init__(self, entity: Entity) -> None:
12+
super().__init__()
13+
self.entity = entity
14+
15+
@property
16+
def engine(self) -> Engine:
17+
"""Return the engine this action belongs to."""
18+
return self.entity.gamemap.engine
19+
20+
def perform(self) -> None:
1221
"""Perform this action with the objects needed to determine its scope.
1322
14-
`engine` is the scope this action is being performed in.
23+
`self.engine` is the scope this action is being performed in.
1524
16-
`entity` is the object performing the action.
25+
`self.entity` is the object performing the action.
1726
1827
This method must be overridden by Action subclasses.
1928
"""
2029
raise NotImplementedError()
2130

2231
class ActionWithDirection(Action):
23-
def __init__(self, dx: int, dy: int):
24-
super().__init__()
32+
def __init__(self, entity: Entity, dx: int, dy: int):
33+
super().__init__(entity)
2534

2635
self.dx = dx
2736
self.dy = dy
2837

2938
def perform(self, engine: Engine, entity: Entity) -> None:
3039
raise NotImplementedError()
40+
41+
@property
42+
def dest_xy(self) -> Tuple[int, int]:
43+
"""Returns this actions destination."""
44+
return self.entity.x + self.dx, self.entity.y + self.dy
45+
46+
@property
47+
def blocking_entity(self) -> Optional[Entity]:
48+
"""Return the blocking entity at this actions destination.."""
49+
return self.engine.game_map.get_blocking_entity_at_location(*self.dest_xy)
3150

3251
class EscapeAction(Action):
33-
def perform(self, engine: Engine, entity: Entity) -> None:
52+
53+
def perform(self) -> None:
3454
raise SystemExit()
3555

3656
class MeleeAction(ActionWithDirection):
37-
def perform(self, engine: Engine, entity: Entity) -> None:
38-
dest_x = entity.x + self.dx
39-
dest_y = entity.y + self.dy
40-
target = engine.game_map.get_blocking_entity_at_location(dest_x, dest_y)
57+
def perform(self) -> None:
58+
target = self.blocking_entity
4159
if not target:
4260
return # No entity to attack.
4361

4462
print(f"You punch the {target.name}, it does not seem to be doing much damage...")
4563

4664
class MovementAction(ActionWithDirection):
47-
def perform(self, engine: Engine, entity: Entity) -> None:
48-
dest_x = entity.x + self.dx
49-
dest_y = entity.y + self.dy
65+
def perform(self) -> None:
66+
dest_x, dest_y = self.dest_xy
5067

51-
if not engine.game_map.in_bounds(dest_x, dest_y):
68+
if not self.engine.game_map.in_bounds(dest_x, dest_y):
5269
return # Destination is out of bounds.
53-
if not engine.game_map.tiles["walkable"][dest_x, dest_y]:
70+
if not self.engine.game_map.tiles["walkable"][dest_x, dest_y]:
5471
return # Destination is blocked by a tile.
55-
if engine.game_map.get_blocking_entity_at_location(dest_x, dest_y):
72+
if self.engine.game_map.get_blocking_entity_at_location(dest_x, dest_y):
5673
return # Destination is blocked by an entity.
5774

5875

59-
entity.move(self.dx, self.dy)
76+
self.entity.move(self.dx, self.dy)
6077

6178

6279
class BumpAction(ActionWithDirection):
63-
def perform(self, engine: Engine, entity: Entity) -> None:
64-
dest_x = entity.x + self.dx
65-
dest_y = entity.y + self.dy
66-
67-
if engine.game_map.get_blocking_entity_at_location(dest_x, dest_y):
68-
return MeleeAction(self.dx, self.dy).perform(engine, entity)
80+
def perform(self) -> None:
81+
if self.blocking_entity is not None:
82+
return MeleeAction(self.entity, self.dx, self.dy).perform()
6983

7084
else:
71-
return MovementAction(self.dx, self.dy).perform(engine, entity)
85+
return MovementAction(self.entity, self.dx, self.dy).perform()

engine.py

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,30 @@
1-
from typing import Iterable, Any
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
24

35
from tcod.context import Context
46
from tcod.console import Console
57
from tcod.map import compute_fov
68
import tcod.constants
79

8-
from entity import Entity
9-
from game_map import GameMap
1010
from input_handlers import EventHandler
1111

12+
if TYPE_CHECKING:
13+
from entity import Entity
14+
from game_map import GameMap
1215

1316
class Engine:
14-
def __init__(self, event_handler: EventHandler, game_map: GameMap, player: Entity):
15-
self.event_handler = event_handler
16-
self.game_map = game_map
17+
18+
game_map: GameMap
19+
20+
def __init__(self, player: Entity):
21+
self.event_handler: EventHandler = EventHandler(self)
1722
self.player = player
18-
self.update_fov()
1923

2024
def handle_enemy_turns(self) -> None:
2125
for entity in self.game_map.entities - {self.player}:
2226
print(f'The {entity.name} wonders when it will get to take a real turn.')
2327

24-
def handle_events(self, events: Iterable[Any]) -> None:
25-
for event in events:
26-
action = self.event_handler.dispatch(event)
27-
28-
if action is None:
29-
continue
30-
31-
action.perform(self, self.player)
32-
self.handle_enemy_turns()
33-
self.update_fov() # Update the FOV before the players next action.
34-
3528
def update_fov(self) -> None:
3629
"""Recompute the visible area based on the players point of view."""
3730
self.game_map.visible[:] = compute_fov(

entity.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import copy
4-
from typing import Tuple, TypeVar, TYPE_CHECKING
4+
from typing import Optional, Tuple, TypeVar, TYPE_CHECKING
55

66
if TYPE_CHECKING:
77
from game_map import GameMap
@@ -12,8 +12,11 @@ class Entity:
1212
"""
1313
A generic object to represent players, enemies, items, etc.
1414
"""
15+
16+
gamemap: GameMap
1517
def __init__(
1618
self,
19+
gamemap: Optional[GameMap] = None,
1720
x: int = 0,
1821
y: int = 0,
1922
char: str = "?",
@@ -30,15 +33,30 @@ def __init__(
3033
self.color = color
3134
self.name = name
3235
self.blocks_movement = blocks_movement
36+
if gamemap:
37+
# If gamemap isn't provided now then it will be set later.
38+
self.gamemap = gamemap
39+
gamemap.entities.add(self)
3340

3441
def spawn(self: T, gamemap: GameMap, x: int, y: int) -> T:
3542
"""Spawn a copy of this instance at the given location."""
3643
clone = copy.deepcopy(self)
3744
clone.x = x
3845
clone.y = y
46+
clone.gamemap = gamemap
3947
gamemap.entities.add(clone)
4048
return clone
4149

50+
def place(self, x: int, y: int, gamemap: Optional[GameMap] = None) -> None:
51+
"""Place this entity at a new location. Handles moving across GameMaps."""
52+
self.x = x
53+
self.y = y
54+
if gamemap:
55+
if hasattr(self, "gamemap"): # Possibly uninitialized.
56+
self.gamemap.entities.remove(self)
57+
self.gamemap = gamemap
58+
gamemap.entities.add(self)
59+
4260
def move(self, dx: int, dy: int) -> None:
4361
"""
4462
Moves the entity by a given amount

game_map.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,39 @@
88
import tile_types
99

1010
if TYPE_CHECKING:
11+
from engine import Engine
1112
from entity import Entity
1213

1314
class GameMap:
14-
def __init__(self, width: int, height: int, entities: Iterable[Entity] = ()):
15+
def __init__(
16+
self, engine: Engine, width: int, height: int, entities: Iterable[Entity] = ()
17+
):
18+
self.engine = engine
1519
self.entities = set(entities)
1620
self.width, self.height = width, height
1721
self.tiles = np.full((width, height), fill_value=tile_types.wall, order="F")
1822

19-
self.visible = np.full((width, height), fill_value=False, order="F") # Tiles the player can currently see
20-
self.explored = np.full((width, height), fill_value=False, order="F") # Tiles the player has seen before
23+
self.visible = np.full(
24+
(width, height), fill_value=False, order="F"
25+
) # Tiles the player can currently see
26+
self.explored = np.full(
27+
(width, height), fill_value=False, order="F"
28+
) # Tiles the player has seen before
2129

2230
def in_bounds(self, x: int, y: int) -> bool:
2331
"""Return True if x and y are inside of the bounds of this map."""
2432
return 0 <= x < self.width and 0 <= y < self.height
2533

26-
def get_blocking_entity_at_location(self, location_x: int, location_y: int) -> Optional[Entity]:
34+
def get_blocking_entity_at_location(
35+
self, location_x: int, location_y: int,
36+
) -> Optional[Entity]:
2737
"""Return the blocking entity at a given location, if any."""
2838
for entity in self.entities:
29-
if entity.blocks_movement and entity.x == location_x and entity.y == location_y:
39+
if (
40+
entity.blocks_movement
41+
and entity.x == location_x
42+
and entity.y == location_y
43+
):
3044
return entity
3145

3246
return None
@@ -132,10 +146,10 @@ def render(self, console: Console) -> None:
132146
If it isn't, but it's in the "explored" array, then draw it with the "dark" colors.
133147
Otherwise, the default is "SHROUD".
134148
"""
135-
console.tiles_rgb[0:self.width, 0:self.height] = np.select(
149+
console.tiles_rgb[0 : self.width, 0 : self.height] = np.select(
136150
condlist=[self.visible, self.explored],
137151
choicelist=[self.tiles["light"], self.tiles["dark"]],
138-
default=tile_types.SHROUD
152+
default=tile_types.SHROUD,
139153
)
140154

141155
for entity in self.entities:

input_handlers.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,31 @@
1-
from typing import Optional
1+
from __future__ import annotations
2+
3+
from typing import Optional, TYPE_CHECKING
24

35
import tcod.event
46

57
from actions import Action, BumpAction, EscapeAction
68

9+
if TYPE_CHECKING:
10+
from engine import Engine
11+
712

813
class EventHandler(tcod.event.EventDispatch[Action]):
14+
def __init__(self, engine: Engine):
15+
self.engine = engine
16+
17+
def handle_events(self) -> None:
18+
for event in tcod.event.wait():
19+
action = self.dispatch(event)
20+
21+
if action is None:
22+
continue
23+
24+
action.perform()
25+
26+
self.engine.handle_enemy_turns()
27+
self.engine.update_fov() # Update the FOV before the players next action.
28+
929
def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]:
1030
raise SystemExit()
1131

@@ -14,17 +34,19 @@ def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]:
1434

1535
key = event.sym
1636

37+
player = self.engine.player
38+
1739
if key == tcod.event.KeySym.UP:
18-
action = BumpAction(dx=0, dy=-1)
40+
action = BumpAction(player, dx=0, dy=-1)
1941
elif key == tcod.event.KeySym.DOWN:
20-
action = BumpAction(dx=0, dy=1)
42+
action = BumpAction(player, dx=0, dy=1)
2143
elif key == tcod.event.KeySym.LEFT:
22-
action = BumpAction(dx=-1, dy=0)
44+
action = BumpAction(player, dx=-1, dy=0)
2345
elif key == tcod.event.KeySym.RIGHT:
24-
action = BumpAction(dx=1, dy=0)
46+
action = BumpAction(player, dx=1, dy=0)
2547

2648
elif key == tcod.event.KeySym.ESCAPE:
27-
action = EscapeAction()
49+
action = EscapeAction(player)
2850

2951
# No valid key was pressed
3052
return action

main.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from engine import Engine
66
from entity import Entity
77
from procgen import generate_dungeon
8-
from input_handlers import EventHandler
98
import entity_factories
109

1110
def main():
@@ -23,20 +22,19 @@ def main():
2322

2423
tileset = tcod.tileset.load_tilesheet("data/zaratustra_msx.png", 16, 16, tcod.tileset.CHARMAP_CP437)
2524

26-
event_handler = EventHandler()
27-
2825
player = copy.deepcopy(entity_factories.player)
29-
game_map = generate_dungeon(
26+
engine = Engine(player=player)
27+
28+
engine.game_map = generate_dungeon(
3029
max_rooms=max_rooms,
3130
room_min_size=room_min_size,
3231
room_max_size=room_max_size,
3332
map_width=map_width,
3433
map_height=map_height,
3534
max_monsters_per_room=max_monsters_per_room,
36-
player=player
35+
engine=engine,
3736
)
38-
39-
engine = Engine(event_handler=event_handler, game_map=game_map, player=player)
37+
engine.update_fov()
4038

4139
with tcod.context.new_terminal(
4240
screen_width,
@@ -48,8 +46,7 @@ def main():
4846
root_console = tcod.console.Console(screen_width, screen_height, order="F")
4947
while True:
5048
engine.render(console=root_console, context=context)
51-
events = tcod.event.wait()
52-
engine.handle_events(events)
49+
engine.event_handler.handle_events()
5350

5451

5552
if __name__ == "__main__":

0 commit comments

Comments
 (0)