-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgame_map.py
More file actions
345 lines (287 loc) · 11.6 KB
/
game_map.py
File metadata and controls
345 lines (287 loc) · 11.6 KB
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
from __future__ import annotations
import random
from typing import Iterable, Iterator, Optional, TYPE_CHECKING
import numpy as np # type: ignore
from tcod.console import Console
from color import current_bg
import color
from entity import Actor, Item, NPC, Zone
import tile_types
if TYPE_CHECKING:
from engine import Engine
from entity import Entity
class GameMap:
def __init__(
self, engine: Engine, width: int, height: int, entities: Iterable[Entity] = ()
):
self.engine = engine
self.entities = set(entities)
self.width, self.height = width, height
self.tiles = np.full((width, height), fill_value=tile_types.wall, order="F")
self.downstairs_location = (0, 0)
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
@property
def actors(self) -> Iterator[Actor]:
"""Iterate over this maps living actors."""
yield from (
entity
for entity in self.entities
if isinstance(entity, Actor) and entity.is_alive
)
@property
def NPCs(self) -> Iterator[NPC]:
"""Iterate over this maps living NPCs."""
yield from (
entity
for entity in self.entities
if isinstance(entity, NPC)
)
@property
def zones(self) -> Iterator[Zone]:
"""Iterate over this maps zones."""
yield from (
entity
for entity in self.entities
if isinstance(entity, Zone)
)
@property
def gamemap(self) -> GameMap:
return self
@property
def items(self) -> Iterator[Item]:
yield from (entity for entity in self.entities if isinstance(entity, Item))
def in_bounds(self, x: int, y: int) -> bool:
"""Return True if x and y are inside of the bounds of this map."""
return 0 <= x < self.width and 0 <= y < self.height
def get_blocking_entity_at_location(
self, location_x: int, location_y: int,
) -> Optional[Entity]:
"""Return the blocking entity at a given location, if any."""
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_entity_at_location(
self, location_x: int, location_y: int,
) -> Optional[Entity]:
"""Return the entity at a given location, if any."""
for entity in self.entities:
if entity.x == location_x and entity.y == location_y:
return entity
return None
def is_walkable_tile(
self, location_x: int, location_y: int,
) -> bool:
"""Return if there is a walkable tile at the given location."""
if self.in_bounds(location_x, location_y) and self.tiles[location_x, location_y]["walkable"]:
return self.tiles[location_x, location_y]["walkable"]
return None
def set_tile(self, x: int, y: int, tile: str) -> None:
self.tiles[x, y] = tile
# Update the 8 tiles around the position were the tile was placed
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
self.update_tile_at(x + dx, y + dy, self.tiles[x + dx, y + dy])
def get_actor_at_location(self, x: int, y: int) -> Optional[Actor]:
for actor in self.actors:
if actor.x == x and actor.y == y:
return actor
return None
def get_NPC_at_location(self, x: int, y: int) -> Optional[NPC]:
for npc in self.NPCs:
if npc.x == x and npc.y == y:
return npc
return None
def update_tile_at(self, x: int, y: int, tile: str) -> None:
# Set the "light" color of the tile to the current floor or wall color, if the tile is both solid and blocks vision
if tile["walkable"] and tile["transparent"]:
tile["light"]["fg"] = color.current_floor
tile["dark"]["fg"] = color.gray_scale_color(color.current_floor)
else:
tile["light"]["fg"] = color.current_wall
tile["dark"]["fg"] = color.gray_scale_color(color.current_wall)
tile["light"]["bg"] = color.current_bg
tile["dark"]["bg"] = color.current_bg
# Autotile the tile if it is an autotile
if tile["autotile"]:
# get the neighbors
top_tile = self.tiles[x, y - 1] if y > 0 else None
bottom_tile = self.tiles[x, y + 1] if y < self.height - 1 else None
left_tile = self.tiles[x - 1, y] if x > 0 else None
right_tile = self.tiles[x + 1, y] if x < self.width - 1 else None
top_right_tile = self.tiles[x + 1, y - 1] if x < self.width - 1 and y > 0 else None
top_left_tile = self.tiles[x - 1, y - 1] if x > 0 and y > 0 else None
bottom_right_tile = self.tiles[x + 1, y + 1] if x < self.width - 1 and y < self.height - 1 else None
bottom_left_tile = self.tiles[x - 1, y + 1] if x > 0 and y < self.height - 1 else None
top = False
bottom = False
left = False
right = False
top_right = False
top_left = False
bottom_right = False
bottom_left = False
if top_tile and top_tile["autotile"]:
top = True
if bottom_tile and bottom_tile["autotile"]:
bottom = True
if left_tile and left_tile["autotile"]:
left = True
if right_tile and right_tile["autotile"]:
right = True
if top_right_tile and top_right_tile["autotile"]:
top_right = True
if top_left_tile and top_left_tile["autotile"]:
top_left = True
if bottom_right_tile and bottom_right_tile["autotile"]:
bottom_right = True
if bottom_left_tile and bottom_left_tile["autotile"]:
bottom_left = True
# set the autotile based on the flags
ch = " "
if (top and bottom) and not (left and right):
ch = "║"
elif (left and right) and not (top and bottom):
ch = "═"
elif top and left and not top_left:
ch = "╝"
elif top and right and not top_right:
ch = "╚"
elif bottom and right and not bottom_right:
ch = "╔"
elif bottom and left and not bottom_left:
ch = "╗"
elif top and left:
ch = "╝"
elif top and right:
ch = "╚"
elif bottom and right:
ch = "╔"
elif bottom and left:
ch = "╗"
elif left and right and bottom:
ch = "╦"
elif left and right and top:
ch = "╩"
elif right and top and bottom:
ch = "╠"
elif left and top and bottom:
ch = "╣"
elif left and top and right and bottom:
ch = "╬"
elif left:
ch = "■"
elif right:
ch = "■"
elif top:
ch = "■"
elif bottom:
ch = "■"
# Set the first int if the "dark" nparray with the unicode of the ch
tile["dark"]["ch"] = ord(ch)
tile["light"]["ch"] = ord(ch)
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 draw it with the "dark" colors.
Otherwise, the default is "SHROUD".
"""
console.rgb[0 : self.width, 0 : self.height] = np.select(
condlist=[self.visible, self.explored],
choicelist=[self.tiles["light"], self.tiles["dark"]],
default=tile_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 or are visible.
if self.visible[entity.x, entity.y] and entity.visible == True:
console.print(
x=entity.x, y=entity.y, string=entity.char, fg=entity.color
)
class GameWorld:
"""
Holds the settings for the GameMap, and generates new maps when moving down the stairs.
"""
def __init__(
self,
*,
engine: Engine,
map_width: int,
map_height: int,
max_rooms: int,
room_min_size: int,
room_max_size: int,
current_floor: int = 0,
starting_credits: int = 0
):
self.engine = engine
self.map_width = map_width
self.map_height = map_height
self.max_rooms = max_rooms
self.room_min_size = room_min_size
self.room_max_size = room_max_size
self.current_floor = current_floor
self.credits = starting_credits
self.floors_without_shop = 0
self.godmode = False
self.player_confused_turns = 0
def update_floor_colors(self) -> None:
if self.current_floor < 10:
# Use "The lab" colors for the tiles
color.current_bg = color.bg_lab
color.current_wall = color.wall_lab
color.current_floor = color.floor_lab
tile_types.SHROUD["fg"] = color.gray_scale_color(color.bg_lab)
tile_types.SHROUD["bg"] = color.bg_lab
elif self.current_floor >= 10:
# Use "The grotto" colors for the tiles
color.current_bg = color.bg_grotto
color.current_wall = color.wall_grotto
color.current_floor = color.floor_grotto
tile_types.SHROUD["fg"] = color.gray_scale_color(color.bg_grotto)
tile_types.SHROUD["bg"] = color.bg_grotto
def generate_floor(self) -> None:
from procgen import generate_dungeon, generate_shopkeep_floor
floor_type = "normal"
self.current_floor += 1
self.update_floor_colors()
if self.current_floor == 1:
pass # Guarantee the first floor is normal
else:
roll = random.randint(0, 100)
if roll < 10 + (10 * self.floors_without_shop): # 10% chance to be a shop floor, and increase the chance as you go down until you find a shop floor.
floor_type = "shop"
self.floors_without_shop = 0
else:
self.floors_without_shop += 1
if floor_type == "normal":
self.engine.game_map = generate_dungeon(
max_rooms=self.max_rooms,
room_min_size=self.room_min_size,
room_max_size=self.room_max_size,
map_width=self.map_width,
map_height=self.map_height,
engine=self.engine,
)
elif floor_type == "shop":
self.engine.game_map = generate_shopkeep_floor(
map_width=self.map_width,
map_height=self.map_height,
engine=self.engine,
)
# Tell the player about his lucky find!
self.engine.message_log.add_message("Lucky find! You find a shopkeeper in this floor!", color.health_recovered)
else:
raise Exception("Invalid floor type")