2023-04-26 23:45:14 +02:00
|
|
|
"""
|
|
|
|
multihex.py - Part of ants project
|
|
|
|
|
|
|
|
This file impements a Mesa HexGrid while allowing for multiple agents to be
|
|
|
|
at the same location. The base for this code comes from the MultiGrid class
|
|
|
|
in mesa/space.py
|
|
|
|
|
|
|
|
License: AGPL 3 (see end of file)
|
|
|
|
(C) Alexander Bocken, Viviane Fahrni, Grace Kragho
|
|
|
|
"""
|
|
|
|
|
|
|
|
from mesa.space import HexGrid
|
|
|
|
from mesa.agent import Agent
|
|
|
|
import numpy as np
|
2023-04-28 14:45:56 +02:00
|
|
|
import numpy.typing as npt
|
2023-04-26 23:45:14 +02:00
|
|
|
from mesa.space import Coordinate, accept_tuple_argument
|
|
|
|
import itertools
|
|
|
|
from typing import (
|
|
|
|
Any,
|
|
|
|
Callable,
|
|
|
|
Iterable,
|
|
|
|
Iterator,
|
|
|
|
List,
|
|
|
|
Sequence,
|
|
|
|
Tuple,
|
|
|
|
TypeVar,
|
|
|
|
Union,
|
|
|
|
cast,
|
|
|
|
overload,
|
|
|
|
)
|
|
|
|
|
|
|
|
MultiGridContent = list[Agent]
|
|
|
|
|
|
|
|
class MultiHexGrid(HexGrid):
|
|
|
|
"""Hexagonal grid where each cell can contain more than one agent.
|
|
|
|
Mostly based of mesa's HexGrid
|
|
|
|
Functions according to odd-q rules.
|
|
|
|
See http://www.redblobgames.com/grids/hexagons/#coordinates for more.
|
|
|
|
|
|
|
|
Properties:
|
|
|
|
width, height: The grid's width and height.
|
|
|
|
torus: Boolean which determines whether to treat the grid as a torus.
|
|
|
|
|
|
|
|
Methods:
|
|
|
|
get_neighbors: Returns the objects surrounding a given cell.
|
|
|
|
get_neighborhood: Returns the cells surrounding a given cell.
|
|
|
|
iter_neighbors: Iterates over position neighbors.
|
|
|
|
iter_neighborhood: Returns an iterator over cell coordinates that are
|
|
|
|
in the neighborhood of a certain point.
|
|
|
|
"""
|
|
|
|
grid: list[list[MultiGridContent]]
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def default_val() -> MultiGridContent:
|
|
|
|
"""Default value for new cell elements."""
|
|
|
|
return []
|
|
|
|
|
|
|
|
def place_agent(self, agent: Agent, pos: Coordinate) -> None:
|
|
|
|
"""Place the agent at the specified location, and set its pos variable."""
|
|
|
|
x, y = pos
|
|
|
|
if agent.pos is None or agent not in self._grid[x][y]:
|
|
|
|
self._grid[x][y].append(agent)
|
|
|
|
agent.pos = pos
|
|
|
|
if self._empties_built:
|
|
|
|
self._empties.discard(pos)
|
|
|
|
|
|
|
|
def remove_agent(self, agent: Agent) -> None:
|
|
|
|
"""Remove the agent from the given location and set its pos attribute to None."""
|
|
|
|
pos = agent.pos
|
|
|
|
x, y = pos
|
|
|
|
self._grid[x][y].remove(agent)
|
|
|
|
if self._empties_built and self.is_cell_empty(pos):
|
|
|
|
self._empties.add(pos)
|
|
|
|
agent.pos = None
|
|
|
|
|
|
|
|
@accept_tuple_argument
|
|
|
|
def iter_cell_list_contents(
|
|
|
|
self, cell_list: Iterable[Coordinate]
|
|
|
|
) -> Iterator[Agent]:
|
|
|
|
"""Returns an iterator of the agents contained in the cells identified
|
|
|
|
in `cell_list`; cells with empty content are excluded.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
cell_list: Array-like of (x, y) tuples, or single tuple.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
An iterator of the agents contained in the cells identified in `cell_list`.
|
|
|
|
"""
|
|
|
|
return itertools.chain.from_iterable(
|
|
|
|
self._grid[x][y]
|
|
|
|
for x, y in itertools.filterfalse(self.is_cell_empty, cell_list)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-04-28 14:45:56 +02:00
|
|
|
class MultiHexGridScalarFields(MultiHexGrid):
|
|
|
|
def __init__(self, fields: dict[str, bool], width : int, height : int, torus : bool, scalar_initial_value : float=0) -> None:
|
|
|
|
super().__init__(width=width, height=height, torus=torus)
|
|
|
|
self._field_props = fields
|
|
|
|
|
|
|
|
self.fields : dict[str, npt.NDArray[np.float_]] = {}
|
|
|
|
|
|
|
|
for key, is_step_field in fields.items():
|
|
|
|
self.fields[key] = np.ones((width, height)).astype(float) * scalar_initial_value
|
|
|
|
if is_step_field:
|
|
|
|
self.fields[f"_next_{key}"] = np.zeros((width, height)).astype(float)
|
|
|
|
|
|
|
|
def reset_field(self, key : str) -> None:
|
|
|
|
self.fields[key] = np.zeros((self.width, self.height))
|
|
|
|
|
|
|
|
def add_to_field(self, field_key : str, value : float, pos : Coordinate) -> None:
|
|
|
|
if self._field_props[field_key]:
|
|
|
|
self.fields[f"_next_{field_key}"][pos] += value
|
|
|
|
else:
|
|
|
|
self.fields[field_key][pos] += value
|
|
|
|
|
|
|
|
def step(self) -> None:
|
|
|
|
for key, is_step_field in self._field_props.items():
|
|
|
|
if is_step_field:
|
|
|
|
self.fields[key] += self.fields[f"_next_{key}"]
|
|
|
|
self.reset_field(f"_next_{key}")
|
2023-04-26 23:45:14 +02:00
|
|
|
|
|
|
|
"""
|
|
|
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
|
|
"""
|