Пример #1
0
 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)
Пример #2
0
 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)
Пример #3
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)
Пример #4
0
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
        ]
Пример #5
0
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,
        }