1
0

Add basic attacking action

This commit is contained in:
Timothy Warren 2022-01-07 15:52:53 -05:00
parent 12dd66727b
commit 1636f60096
7 changed files with 123 additions and 12 deletions

View File

@ -25,13 +25,29 @@ class EscapeAction(Action):
raise SystemExit() raise SystemExit()
class MovementAction(Action): class ActionWithDirection(Action):
def __init__(self, dx: int, dy: int): def __init__(self, dx: int, dy: int):
super().__init__() super().__init__()
self.dx = dx self.dx = dx
self.dy = dy self.dy = dy
def perform(self, engine: Engine, entity: Entity) -> 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)
if not target:
return # No entity to attack.
print(f"You kick the {target.name}, much to its annoyance!")
class MovementAction(ActionWithDirection):
def perform(self, engine: Engine, entity: Entity) -> None: def perform(self, engine: Engine, entity: Entity) -> None:
dest_x = entity.x + self.dx dest_x = entity.x + self.dx
dest_y = entity.y + self.dy dest_y = entity.y + self.dy
@ -40,5 +56,18 @@ class MovementAction(Action):
return # Destination is out of bounds return # Destination is out of bounds
if not engine.game_map.tiles["walkable"][dest_x, dest_y]: if not engine.game_map.tiles["walkable"][dest_x, dest_y]:
return # Destination is blocked by a tile. return # Destination is blocked by a tile.
if engine.game_map.get_blocking_entity_at_location(dest_x, dest_y):
return # Destination is blocked by an entity.
entity.move(self.dx, self.dy) entity.move(self.dx, self.dy)
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)

View File

@ -1,15 +1,42 @@
from typing import Tuple from __future__ import annotations
import copy
from typing import Tuple, TypeVar, TYPE_CHECKING
if TYPE_CHECKING:
from game_map import GameMap
T = TypeVar("T", bound="Entity")
class Entity: class Entity:
""" """
A generic object to represent players, enemies, items, etc. A generic object to represent players, enemies, items, etc.
""" """
def __init__(self, x: int, y: int, char: str, color: Tuple[int, int, int]):
def __init__(
self,
x: int = 0,
y: int = 0,
char: str = "?",
color: Tuple[int, int, int] = (255, 255, 255),
name: str = "<Unnamed>",
blocks_movement: bool = False,
):
self.x = x self.x = x
self.y = y self.y = y
self.char = char self.char = char
self.color = color self.color = color
self.name = name
self.blocks_movement = blocks_movement
def spawn(self: T, gamemap: GameMap, x: int, y: int) -> T:
"""Spawn a copy of this instance at the given location."""
clone = copy.deepcopy(self)
clone.x = x
clone.y = y
gamemap.entities.add(clone)
return clone
def move(self, dx: int, dy: int): def move(self, dx: int, dy: int):
# Move the entity by a given amount # Move the entity by a given amount

21
entity_factories.py Normal file
View File

@ -0,0 +1,21 @@
from entity import Entity
player = Entity(
char="@",
color=(255, 255, 255),
name="PLayer",
blocks_movement=True
)
orc = Entity(
char="o",
color=(63, 127, 63),
name="Orc",
blocks_movement=True
)
troll = Entity(
char="T",
color=(0, 127, 0),
name="Troll",
blocks_movement=True
)

View File

@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import Iterable, TYPE_CHECKING from typing import Iterable, Optional, TYPE_CHECKING
import numpy as np # type: ignore import numpy as np # type: ignore
from tcod.console import Console from tcod.console import Console
@ -14,12 +14,19 @@ if TYPE_CHECKING:
class GameMap: class GameMap:
def __init__(self, width: int, height: int, entities: Iterable[Entity] = ()): def __init__(self, width: int, height: int, entities: Iterable[Entity] = ()):
self.width, self.height = width, height self.width, self.height = width, height
self.entities = entities self.entities = set(entities)
self.tiles = np.full((width, height), fill_value=tile_types.wall, order="F") self.tiles = np.full((width, height), fill_value=tile_types.wall, order="F")
self.visible = np.full((width, height), fill_value=False, order="F") # Tiles the player can currently see 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 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) -> Optional[Entity]:
for entity in self.entities:
if entity.blocks_movement and entity.x == location_x and entity.y == location_y:
return entity
return None
def in_bounds(self, x: int, y: int) -> bool: def in_bounds(self, x: int, y: int) -> bool:
"""Return True if x and y are inside the bounds of the map.""" """Return True if x and y are inside the bounds of the map."""
return 0 <= x < self.width and 0 <= y < self.height return 0 <= x < self.width and 0 <= y < self.height

View File

@ -2,7 +2,7 @@ from typing import Optional
import tcod.event import tcod.event
from actions import Action, EscapeAction, MovementAction from actions import Action, EscapeAction, BumpAction
class EventHandler(tcod.event.EventDispatch[Action]): class EventHandler(tcod.event.EventDispatch[Action]):
@ -15,13 +15,13 @@ class EventHandler(tcod.event.EventDispatch[Action]):
key = event.sym key = event.sym
if key == tcod.event.K_UP: if key == tcod.event.K_UP:
action = MovementAction(dx=0, dy=-1) action = BumpAction(dx=0, dy=-1)
elif key == tcod.event.K_DOWN: elif key == tcod.event.K_DOWN:
action = MovementAction(dx=0, dy=1) action = BumpAction(dx=0, dy=1)
elif key == tcod.event.K_LEFT: elif key == tcod.event.K_LEFT:
action = MovementAction(dx=-1, dy=0) action = BumpAction(dx=-1, dy=0)
elif key == tcod.event.K_RIGHT: elif key == tcod.event.K_RIGHT:
action = MovementAction(dx=1, dy=0) action = BumpAction(dx=1, dy=0)
elif key == tcod.event.K_ESCAPE: elif key == tcod.event.K_ESCAPE:
action = EscapeAction() action = EscapeAction()

View File

@ -1,8 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import copy
import tcod import tcod
from engine import Engine from engine import Engine
from entity import Entity import entity_factories
from input_handlers import EventHandler from input_handlers import EventHandler
from procgen import generate_dungeon from procgen import generate_dungeon
@ -18,6 +20,8 @@ def main() -> None:
room_min_size = 6 room_min_size = 6
max_rooms = 30 max_rooms = 30
max_monsters_per_room = 2
tileset = tcod.tileset.load_tilesheet( tileset = tcod.tileset.load_tilesheet(
"dejavu10x10_gs_tc.png", "dejavu10x10_gs_tc.png",
32, 32,
@ -27,7 +31,7 @@ def main() -> None:
event_handler = EventHandler() event_handler = EventHandler()
player = Entity(int(screen_width / 2), int(screen_height / 2), "@", (255, 255, 255)) player = copy.deepcopy(entity_factories.player)
game_map = generate_dungeon( game_map = generate_dungeon(
max_rooms, max_rooms,
@ -35,6 +39,7 @@ def main() -> None:
room_max_size, room_max_size,
map_width, map_width,
map_height, map_height,
max_monsters_per_room,
player, player,
) )

View File

@ -5,6 +5,7 @@ from typing import Iterator, List, Tuple, TYPE_CHECKING
import tcod import tcod
import entity_factories
from game_map import GameMap from game_map import GameMap
import tile_types import tile_types
@ -41,6 +42,24 @@ class RectangularRoom:
) )
def place_entities(
room: RectangularRoom,
dungeon: GameMap,
maximum_monsters: int,
) -> None:
number_of_monsters = random.randint(0, maximum_monsters)
for i in range(number_of_monsters):
x = random.randint(room.x1 + 1, room.x2 - 1)
y = random.randint(room.y1 + 1, room.y2 - 1)
if not any(entity.x == x and entity.y == y for entity in dungeon.entities):
if random.random() < 0.8:
entity_factories.orc.spawn(dungeon, x, y)
else:
entity_factories.troll.spawn(dungeon, x, y)
def tunnel_between(start: Tuple[int, int], end: Tuple[int, int]) -> Iterator[Tuple[int, int]]: def tunnel_between(start: Tuple[int, int], end: Tuple[int, int]) -> Iterator[Tuple[int, int]]:
"""Return an L-shaped tunnel between these two points.""" """Return an L-shaped tunnel between these two points."""
x1, y1 = start x1, y1 = start
@ -66,6 +85,7 @@ def generate_dungeon(
room_max_size: int, room_max_size: int,
map_width: int, map_width: int,
map_height: int, map_height: int,
max_monsters_per_room: int,
player: Entity, player: Entity,
) -> GameMap: ) -> GameMap:
"""Generate a new dungeon map.""" """Generate a new dungeon map."""
@ -99,6 +119,8 @@ def generate_dungeon(
for x, y in tunnel_between(rooms[-1].center, new_room.center): for x, y in tunnel_between(rooms[-1].center, new_room.center):
dungeon.tiles[x, y] = tile_types.floor dungeon.tiles[x, y] = tile_types.floor
place_entities(new_room, dungeon, max_monsters_per_room)
# Finally, append the new room to the list. # Finally, append the new room to the list.
rooms.append(new_room) rooms.append(new_room)