def __init__(self, grid, settings): """ :param grid: All types of cells to be inserted into the map. :param settings: Constant values provided when generating a level/map. """ self.grid = grid self.settings = settings self._spawn_location_finder = SpawnLocationFinder(self)
def __init__(self): self._spawn_location_finder = SpawnLocationFinder(self) self._cell_cache = {} [self.get_cell(Location(x, y)) for x in range(5) for y in range(5)] self.updates = 0 self.num_avatars = None self.settings = defaultdict(lambda: 0)
def test_potential_spawns(self): spawnable1 = MockCell() spawnable2 = MockCell() score_cell = MockCell(generates_score=True) unhabitable = MockCell(habitable=False) filled = MockCell(avatar='avatar') grid = self._grid_from_list([[spawnable1, score_cell, unhabitable], [unhabitable, spawnable2, filled]]) world_map = WorldMap(grid, self.settings) spawn_location_finder = SpawnLocationFinder(world_map) cells = list(spawn_location_finder.potential_spawn_locations()) self.assertIn(spawnable1, cells) self.assertIn(spawnable2, cells) self.assertNotIn(score_cell, cells, "Score cells should not be spawns") self.assertNotIn(unhabitable, cells, "Unhabitable cells should not be spawns") self.assertNotIn(filled, cells, "Cells with avatars should not be spawns") self.assertEqual(len(cells), 2)
class WorldMap(object): """ The non-player world state. """ def __init__(self, grid, settings): """ :param grid: All types of cells to be inserted into the map. :param settings: Constant values provided when generating a level/map. """ self.grid = grid self.settings = settings self._spawn_location_finder = SpawnLocationFinder(self) @classmethod def _min_max_from_dimensions(cls, height, width): """ The value provided by the user will be an integer both for the width and height components. We calculate the maximum and minimum dimensions in all directions. """ max_x = int(math.floor(width / 2)) min_x = -(width - max_x - 1) max_y = int(math.floor(height / 2)) min_y = -(height - max_y - 1) return min_x, max_x, min_y, max_y @classmethod def generate_empty_map(cls, height, width, settings): new_settings = DEFAULT_LEVEL_SETTINGS.copy() new_settings.update(settings) (min_x, max_x, min_y, max_y) = WorldMap._min_max_from_dimensions(height, width) grid = {} for x in range(min_x, max_x + 1): for y in range(min_y, max_y + 1): location = Location(x, y) grid[location] = Cell(location) return cls(grid, new_settings) def all_cells(self): return self.grid.values() def interactable_cells(self): return (cell for cell in self.all_cells() if cell.interactable) def score_cells(self): return ( cell for cell in self.all_cells() if isinstance(cell.interactable, ScoreLocation) ) def pickup_cells(self): return ( cell for cell in self.all_cells() if isinstance(cell.interactable, ALL_PICKUPS) ) def is_on_map(self, location): try: self.grid[location] except KeyError: return False return True def get_cell(self, location) -> Cell: try: return self.grid[location] except KeyError: # For backwards-compatibility, this throws ValueError raise ValueError("Location %s is not on the map" % location) def get_cell_by_coords(self, x, y): return self.get_cell(Location(x, y)) def clear_cell_actions(self, location): try: cell = self.get_cell(location) cell.actions = [] except ValueError: return def max_y(self): return max(self.grid.keys(), key=lambda c: c.y).y def min_y(self): return min(self.grid.keys(), key=lambda c: c.y).y def max_x(self): return max(self.grid.keys(), key=lambda c: c.x).x def min_x(self): return min(self.grid.keys(), key=lambda c: c.x).x @property def num_rows(self): return self.max_y() - self.min_y() + 1 @property def num_cols(self): return self.max_x() - self.min_x() + 1 @property def num_cells(self): return self.num_rows * self.num_cols def can_move_to(self, target_location): if not self.is_on_map(target_location): return False cell = self.get_cell(target_location) return ( cell.habitable and (not cell.is_occupied or cell.avatar.is_moving) and len(cell.moves) <= 1 ) def attackable_avatar(self, target_location): """ Return a boolean if the avatar is attackable at the given location (or will be after next move), else return None. """ try: cell = self.get_cell(target_location) except ValueError: return None if cell.avatar: return cell.avatar if len(cell.moves) == 1: return cell.moves[0].avatar return None def get_no_fog_distance(self): return self.settings["NO_FOG_OF_WAR_DISTANCE"] def get_partial_fog_distance(self): return self.settings["PARTIAL_FOG_OF_WAR_DISTANCE"] def get_random_spawn_location(self): return self._spawn_location_finder.get_random_spawn_location() def __repr__(self): return repr(self.grid) def __iter__(self): return ( ( self.get_cell(Location(x, y)) for y in range(self.min_y(), self.max_y() + 1) ) for x in range(self.min_x(), self.max_x() + 1) ) # Serialisation Utilities def get_serialized_south_west_corner(self): """ Used in serialising the map size when sent to the front end. Very lightweight as it consists of two integers. :return: A dictionary with two values, x and y coordinates for the bottom left (south-west) corner of the map. """ return {"x": self.min_x(), "y": self.min_y()} def get_serialized_north_east_corner(self): """ Used in serialising the map size when sent to the front end. Very lightweight as it consists of two integers. :return: A dictionary with two values, x and y coordinates for the top right (north-west) corner of the map. """ return {"x": self.max_x(), "y": self.max_y()} def serialize_score_location(self): """ Used to serialize the score locations on every update. :return: A single list that contains all score locations. Within the list there are x and y coordinates. """ def get_coords(cell): return {"location": {"x": cell.location.x, "y": cell.location.y}} return [ get_coords(cell) for cell in self.all_cells() if isinstance(cell.interactable, ScoreLocation) ] def serialize_obstacles(self): """ Used to serialize the obstacle locations on every update. :return: A list that contains all the obstacle information generated by inner method. """ def serialize_obstacle(cell): return { "location": {"x": cell.location.x, "y": cell.location.y}, "width": 1, "height": 1, "type": "wall", "orientation": "north", } return [ serialize_obstacle(cell) for cell in self.all_cells() if not cell.habitable ]
class WorldMap(object): """ The non-player world state. """ def __init__(self, grid, settings): """ :param grid: All types of cells to be inserted into the map. :param settings: Constant values provided when generating a level/map. """ self.grid = grid self.settings = settings self._spawn_location_finder = SpawnLocationFinder(self) @classmethod def _min_max_from_dimensions(cls, height, width): """ The value provided by the user will be an integer both for the width and height components. We calculate the maximum and minimum dimensions in all directions. """ max_x = int(math.floor(width / 2)) min_x = -(width - max_x - 1) max_y = int(math.floor(height / 2)) min_y = -(height - max_y - 1) return min_x, max_x, min_y, max_y @classmethod def generate_empty_map(cls, height, width, settings): new_settings = DEFAULT_LEVEL_SETTINGS.copy() new_settings.update(settings) (min_x, max_x, min_y, max_y) = WorldMap._min_max_from_dimensions(height, width) grid = {} for x in range(min_x, max_x + 1): for y in range(min_y, max_y + 1): location = Location(x, y) grid[location] = Cell(location) return cls(grid, new_settings) def all_cells(self): return self.grid.values() def score_cells(self): return (c for c in self.all_cells() if c.generates_score) def pickup_cells(self): return (c for c in self.all_cells() if c.pickup) def is_on_map(self, location): try: self.grid[location] except KeyError: return False return True def get_cell(self, location): try: return self.grid[location] except KeyError: # For backwards-compatibility, this throws ValueError raise ValueError('Location %s is not on the map' % location) def get_cell_by_coords(self, x, y): return self.get_cell(Location(x, y)) def clear_cell_actions(self, location): try: cell = self.get_cell(location) cell.actions = [] except ValueError: return def max_y(self): return max(self.grid.keys(), key=lambda c: c.y).y def min_y(self): return min(self.grid.keys(), key=lambda c: c.y).y def max_x(self): return max(self.grid.keys(), key=lambda c: c.x).x def min_x(self): return min(self.grid.keys(), key=lambda c: c.x).x @property def num_rows(self): return self.max_y() - self.min_y() + 1 @property def num_cols(self): return self.max_x() - self.min_x() + 1 @property def num_cells(self): return self.num_rows * self.num_cols def update(self, num_avatars): self._update_avatars() self._update_map(num_avatars) def _update_avatars(self): self._apply_score() self._apply_pickups() def _apply_pickups(self): for cell in self.pickup_cells(): if cell.avatar is not None: cell.pickup.apply(cell.avatar) def _apply_score(self): for cell in self.score_cells(): try: cell.avatar.score += 1 except AttributeError: pass def _update_map(self, num_avatars): context = MapContext(num_avatars=num_avatars) MapExpander().update(self, context=context) ScoreLocationUpdater().update(self, context=context) PickupUpdater().update(self, context=context) def can_move_to(self, target_location): if not self.is_on_map(target_location): return False cell = self.get_cell(target_location) return (cell.habitable and (not cell.is_occupied or cell.avatar.is_moving) and len(cell.moves) <= 1) def attackable_avatar(self, target_location): """ Return a boolean if the avatar is attackable at the given location (or will be after next move), else return None. """ try: cell = self.get_cell(target_location) except ValueError: return None if cell.avatar: return cell.avatar if len(cell.moves) == 1: return cell.moves[0].avatar return None def get_no_fog_distance(self): return self.settings['NO_FOG_OF_WAR_DISTANCE'] def get_partial_fog_distance(self): return self.settings['PARTIAL_FOG_OF_WAR_DISTANCE'] def get_random_spawn_location(self): return self._spawn_location_finder.get_random_spawn_location() def __repr__(self): return repr(self.grid) def __iter__(self): return ((self.get_cell(Location(x, y)) for y in range(self.min_y(), self.max_y() + 1)) for x in range(self.min_x(), self.max_x() + 1)) # Serialisation Utilities def get_serialised_south_west_corner(self): """ Used in serialising the map size when sent to the front end. Very lightweight as it consists of two integers. :return: A dictionary with two values, x and y coordinates for the bottom left (south-west) corner of the map. """ return { "x": self.min_x(), "y": self.min_y(), } def get_serialised_north_east_corner(self): """ Used in serialising the map size when sent to the front end. Very lightweight as it consists of two integers. :return: A dictionary with two values, x and y coordinates for the top right (north-west) corner of the map. """ return { "x": self.max_x(), "y": self.max_y(), } def score_location_update(self): """ Used to serialise the score locations on every update. :return: A dictionary with a single list that contains all score locations. Within the list there are x and y coordinates. """ def _generate_score_locations_list(): """ Generates a list of all individual score locations to be further placed into an outer wrapper dictionary for serialisation. """ serialised_list = [] score_cells = (c for c in self.all_cells() if c.generates_score) for score_cell in score_cells: serialised_list.append({ 'location': { 'x': score_cell.location.x, 'y': score_cell.location.y, }, }) return serialised_list score_locations = _generate_score_locations_list() return { 'scoreLocations': score_locations, } def obstacles_update(self): """ Used to serialise the obstacle locations on every update. :return: A dictionary with a single list that contains all the obstacle information generated by inner method. """ def _generate_obstacle_list(): """ Generates a list of all obstacle locations into a list that can be then further wrapped in a dictionary for serialisation. """ serialised_list = [] obstacle_cells = (cell for cell in self.all_cells() if not cell.habitable) for obstacle_cell in obstacle_cells: serialised_list.append({ 'location': { 'x': obstacle_cell.location.x, 'y': obstacle_cell.location.y, }, 'width': 1, 'height': 1, 'type': "wall", 'orientation': "north", }) return serialised_list obstacle_locations = _generate_obstacle_list() return { 'obstacles': obstacle_locations, }