from __future__ import annotations
import json
import logging
import random
from typing import Any, Dict, List, Optional
import numpy as np
from pygame.constants import BLEND_RGBA_MULT
from scripts.engine.core import dungen
from scripts.engine.internal import library
from scripts.engine.internal.constant import Height, MAP_BORDER_SIZE, TILE_SIZE, TileCategory
from scripts.engine.internal.definition import ActorData
from scripts.engine.world_objects import lighting
from scripts.engine.world_objects.lighting import LightBox
from scripts.engine.world_objects.tile import Tile
__all__ = ["GameMap"]
[docs]class GameMap:
"""
Holds tiles for a map. Handles generation of the map and placement of the entities. Fills map with floors on
init.
"""
[docs] def __init__(self, map_name: str, seed: Any):
self.is_dirty = True
self.name: str = map_name
self.seed = seed
self.rng = random.Random()
self.rng.seed(seed)
_map_data = library.MAPS[map_name]
self.width = _map_data.width + (MAP_BORDER_SIZE * 2)
self.height = _map_data.height + (MAP_BORDER_SIZE * 2)
map_size = (self.width, self.height)
self.light_map: np.ndarray = np.zeros(map_size, dtype=bool, order="F") # what positions are lit
self.tile_map: List[List[Tile]] = [] # array of all Tiles
window = library.VIDEO_CONFIG.base_window
self.light_box: LightBox = lighting.LightBox((window.width, window.height), BLEND_RGBA_MULT) # lighting that
# needs processing
self._block_movement_map: np.ndarray = np.zeros(map_size, dtype=bool, order="F") # array for move blocked
self._block_sight_map: np.ndarray = np.zeros(map_size, dtype=bool, order="F") # array for sight blocked
self._air_tile_positions: List[List[int]] = [] # what positions dont block sight
self.generation_info: str = ""
self._init_tile_map()
################ INIT ##################################################
def _init_tile_map(self):
"""
Only called during init to populate tile map with walls
"""
from scripts.engine.internal import library
_map_data = library.MAPS[self.name]
# get details for a wall tile
from scripts.engine.core import utility
wall_sprite_path = _map_data.sprite_paths[TileCategory.WALL]
wall_sprite = utility.get_image(wall_sprite_path)
height = Height.MIN
blocks_movement = True
# populate tile_map with wall tiles
for x in range(self.width):
self.tile_map.append([]) # create new list for every col
for y in range(self.height):
self.tile_map[x].append(Tile(x, y, wall_sprite, wall_sprite_path, blocks_movement, height))
################## MAP GEN #############################################
[docs] def generate_new_map(self, player_data: Optional[ActorData] = None):
"""
Generate the map for the current game map. Creates tiles. Saves the values directly to the GameMap.
"""
self.tile_map, self.generation_info = dungen.generate(self.name, self.rng, player_data)
self.is_dirty = True
[docs] def dump(self, path: str):
"""
Dumps the dungeon tree into a file
"""
with open(path, "w") as fp:
fp.write(json.dumps(self.generation_info, indent=4))
######################## UTIL ##########################################
[docs] def get_open_space(self):
"""
Returns a random open space from the tile map.
"""
return random.choice(self.air_tile_positions)
################### DATA MANAGEMENT ####################################
def _refresh_internals(self):
"""
Refresh the data in the self._block_movement_map and self._block_sight_map
"""
block_movement_map = [[not tile.blocks_movement for tile in columns] for columns in self.tile_map]
self._block_movement_map = np.asarray(block_movement_map, dtype=np.int8)
# assumes tiles only have min or max height
block_sight_map = [[not tile.height == Height.MAX for tile in columns] for columns in self.tile_map]
self._block_sight_map = np.asarray(block_sight_map, dtype=np.int8)
# get all the non-blocking, or "air", tiles.
self._air_tile_positions = np.argwhere(self._block_sight_map == 1).tolist()
# self.block_sight_map == 1 does the if not block_sight_map part of the loop.
# np.argwhere gets the indexes of all nonzero elements.
# tolist converts this back into a nested list.
# update the walls in the light box
lighting.generate_walls(self.light_box, self._air_tile_positions, TILE_SIZE)
################### SERIALISATION #####################################
[docs] def serialise(self) -> Dict[str, Any]:
"""
Serialise the game map to dict.
"""
tiles: List[List[Dict]] = []
# build list of lists
for x in range(self.width):
# give each new row an empty list
tiles.append([])
for y in range(self.height):
# add to the column
tiles[x].append(self.tile_map[x][y].serialise())
_dict = {"width": self.width, "height": self.height, "tiles": tiles, "seed": self.seed, "map_name": self.name}
return _dict
[docs] @classmethod
def deserialise(cls, serialised: Dict[str, Any]):
"""
Loads the details from the serialised data back into the GameMap.
"""
try:
seed = serialised["seed"]
map_name = serialised["map_name"]
width = serialised["width"]
height = serialised["height"]
tiles: List[List[Tile]] = []
for x in range(width):
tiles.append([])
for y in range(height):
tiles[x].append(Tile.deserialise(serialised["tiles"][x][y]))
game_map = GameMap(map_name, seed)
game_map.tile_map = tiles
return game_map
except KeyError as e:
logging.warning(f"GameMap.Deserialise: Incorrect key ({e.args[0]}) given. Data not loaded correctly.")
raise KeyError # throw exception to hit outer error handler and exit
################### PROPERTIES ########################################
@property
def block_movement_map(self) -> np.ndarray:
"""
Return a copy of an array containing ints, 0 for blocked and 1 for open
"""
if self.is_dirty:
self._refresh_internals()
self.is_dirty = False
return self._block_movement_map.copy("F")
@property
def block_sight_map(self) -> np.ndarray:
"""
Return a copy of an array containing ints, 0 for blocked and 1 for open
"""
if self.is_dirty:
self._refresh_internals()
self.is_dirty = False
return self._block_sight_map.copy("F")
@property
def air_tile_positions(self) -> List[List[int]]:
"""
Return a copy of an array containing ints, 0 for blocked and 1 for open
"""
if self.is_dirty:
self._refresh_internals()
self.is_dirty = False
return self._air_tile_positions.copy()