-
Notifications
You must be signed in to change notification settings - Fork 0
/
game_map.py
184 lines (156 loc) · 6.16 KB
/
game_map.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
from __future__ import annotations
from typing import Iterable, Iterator, Optional, TYPE_CHECKING, Set, Any
import numpy as np # type: ignore
from tcod.console import Console
import components.base_components
from components.consumable import Consumable, Supplies
from entity import Actor, Item
import tile_room_types
if TYPE_CHECKING:
from engine import Engine
from entity import Entity
class GameMap:
"""
Has additional variables of:
self.tiles
self.visible
self.explored
"""
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_room_types.wall, 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 & is different from never seen
# 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 & is different from never seen
self.downstairs_location = (0, 0)
@property
def gamemap(self) -> GameMap:
return self
@property
def actors(self) -> Iterator[Actor]:
"""Iterate over this mapping living actors."""
yield from (
entity
for entity in self.entities
if isinstance(entity, Actor) and entity.is_alive
)
@property
def items(self) -> Iterator[Item]:
yield from (entity for entity in self.entities if isinstance(entity, Item))
@property
def consumable_items(self) -> Iterator[Item]:
"""
Return items with the consumable object attached
:return:
"""
yield from (item for item in self.items if item.consumable is not None)
def get_blocking_entity_at_location(
self, location_x: int, location_y: int
) -> Optional[Entity]:
"""
Simply checks the block_movement flag for entities in the given spot. The number is
Only the first blocking entity will be returned. This cannot be used to get the important entities here
:param location_x:
:param location_y:
:return: None or a single entity at the location given
"""
for entity in self.entities:
if (
entity.blocks_movement
and entity.x == location_x
and entity.y == location_y
):
return entity
return None
def get_blocking_entity_at_location_set(
self, location_x: int, location_y: int
) -> Set:
e_set = set()
for entity in self.entities:
if (
entity.blocks_movement
and entity.x == location_x
and entity.y == location_y
):
e_set.add(entity)
return e_set
def get_actor_at_location(self, x: int, y: int) -> Optional[Actor]:
"""
Returns the first actor at the location. Note if there are multiple actors here the one returned is unreliable
:return: An actor entity. Could be a player or enemy
"""
for actor in self.actors:
if actor.x == x and actor.y == y:
return actor
return None
@staticmethod
def get_distance(entity1: Entity, entity2: Entity) -> int:
"""
Get distance using Chebyshev distance.
https://en.wikipedia.org/wiki/Chebyshev_distance
:param entity1:
:param entity2:
:return:
"""
dx = entity1.x - entity2.x
dy = entity1.y - entity2.y
distance = max(abs(dx), abs(dy)) # Chebyshev distance
return distance
def get_closest_consumable(self, source: Entity, target_class: type[Consumable]) -> Optional[Consumable]:
"""
Get the closest consumable that matches the target_class. The given coordinates are the
starting location to calculate distance from.
"""
# must be careful we match class and not the instance
# for the every item in the list:
# check it is the target_class
# calculate distance
# if the distance is min keep that as the minimum
minimum_dist = -1
minimum_obj = None
for item in self.consumable_items:
if isinstance(item.consumable, target_class):
# This is not running
dist = self.get_distance(entity1=source, entity2=item)
if dist <= minimum_dist or minimum_dist < 0:
# new contender for closest consumable
minimum_obj = item
minimum_dist = dist
return minimum_obj
def in_bounds(self, x: int, y: int) -> bool:
"""Return True if x and y are inside the bounds of this map."""
return 0 <= x < self.width and 0 <= y < self.height
def render(self, console: Console) -> None:
"""
Renders the map.
If a tile is in the "visible" array, then draw it with the light colors.
If it isn't, but it's in the explored array then use dark colors.
Otherwise, the default is SHROUD
"""
console.tiles_rgb[0: self.width, 0: self.height] = np.select(
condlist=[self.visible, self.explored],
choicelist=[self.tiles["light"], self.tiles["dark"]],
default=tile_room_types.SHROUD,
)
entities_sorted_for_rendering = sorted(
self.entities, key=lambda x: x.render_order.value
)
for entity in entities_sorted_for_rendering:
# Only print entities that are in the FOV
if self.visible[entity.x, entity.y]:
console.print(
x=entity.x, y=entity.y, string=entity.char, fg=entity.color
)