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)
|
2023-05-17 15:57:23 +02:00
|
|
|
(C) Alexander Bocken, Viviane Fahrni, Grace Kagho
|
2023-04-26 23:45:14 +02:00
|
|
|
"""
|
|
|
|
|
2023-05-07 15:24:27 +02:00
|
|
|
from sys import dont_write_bytecode
|
2023-04-26 23:45:14 +02:00
|
|
|
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):
|
2023-04-28 19:10:33 +02:00
|
|
|
def __init__(self, fields: list[str], width : int, height : int, torus : bool, scalar_initial_value : float=0) -> None:
|
2023-04-28 14:45:56 +02:00
|
|
|
super().__init__(width=width, height=height, torus=torus)
|
|
|
|
|
|
|
|
self.fields : dict[str, npt.NDArray[np.float_]] = {}
|
|
|
|
|
2023-04-28 19:10:33 +02:00
|
|
|
for key in fields:
|
2023-04-28 14:45:56 +02:00
|
|
|
self.fields[key] = np.ones((width, height)).astype(float) * scalar_initial_value
|
|
|
|
|
|
|
|
def reset_field(self, key : str) -> None:
|
|
|
|
self.fields[key] = np.zeros((self.width, self.height))
|
|
|
|
|
2023-05-07 15:24:27 +02:00
|
|
|
def is_food(self, pos):
|
|
|
|
assert('food' in self.fields.keys())
|
|
|
|
return bool(self.fields['food'][pos])
|
|
|
|
|
|
|
|
def add_food(self, size : int , pos=None):
|
|
|
|
"""
|
|
|
|
Adds food source to grid.
|
|
|
|
Args:
|
|
|
|
pos (optional): if None, selects random place on grid which
|
|
|
|
is not yet occupied by either a nest or another food source
|
|
|
|
size: how much food should be added to field
|
|
|
|
"""
|
|
|
|
assert('food' in self.fields.keys())
|
|
|
|
if pos is None:
|
|
|
|
def select_random_place():
|
|
|
|
i = np.random.randint(0, self.width)
|
|
|
|
j = np.random.randint(0, self.height)
|
|
|
|
return i,j
|
|
|
|
pos = select_random_place()
|
|
|
|
while(self.is_nest(pos) or self.is_food(pos)):
|
|
|
|
pos = select_random_place()
|
|
|
|
|
|
|
|
self.fields['food'][pos] = size
|
|
|
|
|
|
|
|
def is_nest(self, pos : Coordinate) -> bool:
|
|
|
|
assert('nests' in self.fields.keys())
|
|
|
|
return bool(self.fields['nests'][pos])
|
|
|
|
|
|
|
|
def add_nest(self, pos:None|Coordinate=None):
|
|
|
|
"""
|
|
|
|
Adds nest to grid.
|
|
|
|
Args:
|
|
|
|
pos: if None, selects random place on grid which
|
|
|
|
is not yet occupied by either a nest or another food source
|
|
|
|
"""
|
|
|
|
assert('nests' in self.fields.keys())
|
|
|
|
if pos is None:
|
|
|
|
def select_random_place():
|
|
|
|
i = np.random.randint(0, self.width)
|
|
|
|
j = np.random.randint(0, self.height)
|
|
|
|
return i,j
|
|
|
|
pos = select_random_place()
|
|
|
|
while(self.is_nest(pos) or self.is_food(pos)):
|
|
|
|
pos = select_random_place()
|
|
|
|
|
|
|
|
self.fields['nests'][pos] = True
|
|
|
|
|
|
|
|
|
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/>
|
|
|
|
"""
|