From 836495a884df9da2a337f10ae80014efe5360a47 Mon Sep 17 00:00:00 2001 From: Steffen Illium Date: Thu, 6 Jul 2023 12:01:25 +0200 Subject: [PATCH] Machines --- marl_factory_grid/__init__.py | 8 +- .../algorithms/static/TSP_base_agent.py | 38 +---- marl_factory_grid/algorithms/static/utils.py | 39 +++++ marl_factory_grid/default_config.yaml | 141 ++++++++++-------- marl_factory_grid/environment/actions.py | 2 + marl_factory_grid/environment/constants.py | 8 +- marl_factory_grid/environment/entity/agent.py | 5 +- .../environment/entity/entity.py | 16 +- .../environment/entity/object.py | 20 +-- marl_factory_grid/environment/entity/util.py | 6 +- .../environment/entity/wall_floor.py | 22 +-- marl_factory_grid/environment/factory.py | 16 +- .../environment/groups/agents.py | 2 +- .../environment/groups/env_objects.py | 10 +- .../environment/groups/mixins.py | 18 ++- .../environment/groups/objects.py | 12 +- marl_factory_grid/environment/groups/utils.py | 11 +- .../environment/groups/wall_n_floors.py | 4 +- marl_factory_grid/environment/rules.py | 18 ++- marl_factory_grid/modules/__init__.py | 7 + .../modules/_template/constants.py | 2 +- marl_factory_grid/modules/_template/rules.py | 2 +- .../modules/batteries/__init__.py | 4 + marl_factory_grid/modules/batteries/groups.py | 7 +- marl_factory_grid/modules/batteries/rules.py | 4 +- .../modules/clean_up/__init__.py | 6 + .../modules/clean_up/entitites.py | 16 ++ marl_factory_grid/modules/clean_up/groups.py | 4 +- .../modules/clean_up/rule_respawn.py | 2 +- .../modules/clean_up/rule_smear_on_move.py | 2 +- .../modules/destinations/__init__.py | 4 + .../modules/destinations/rules.py | 2 +- marl_factory_grid/modules/doors/__init__.py | 4 + marl_factory_grid/modules/doors/actions.py | 3 +- marl_factory_grid/modules/doors/entitites.py | 22 +-- .../assets => modules/factory}/__init__.py | 0 marl_factory_grid/modules/factory/rules.py | 32 ++++ marl_factory_grid/modules/items/__init__.py | 4 + marl_factory_grid/modules/items/entitites.py | 18 +++ marl_factory_grid/modules/items/groups.py | 17 +-- marl_factory_grid/modules/items/rules.py | 6 +- .../modules/machines/__init__.py | 3 + marl_factory_grid/modules/machines/actions.py | 25 ++++ .../modules/machines/constants.py | 2 + .../modules/machines/entitites.py | 38 +++-- marl_factory_grid/modules/machines/groups.py | 3 +- .../modules/machines/machine.png | Bin 0 -> 8729 bytes marl_factory_grid/modules/machines/rules.py | 8 +- .../modules/maintenance/__init__.py | 2 + .../modules/maintenance/constants.py | 3 + .../modules/maintenance/entities.py | 102 +++++++++++++ .../modules/maintenance/groups.py | 27 ++++ .../modules/maintenance/maintainer.png | Bin 0 -> 22988 bytes .../modules/maintenance/rewards.py | 1 + .../modules/maintenance/rules.py | 39 +++++ marl_factory_grid/modules/zones/__init__.py | 3 + marl_factory_grid/modules/zones/constants.py | 4 + marl_factory_grid/modules/zones/entitites.py | 21 +++ marl_factory_grid/modules/zones/groups.py | 12 ++ marl_factory_grid/modules/zones/rules.py | 33 ++++ marl_factory_grid/quickstart.py | 4 +- marl_factory_grid/utils/config_parser.py | 33 ++-- marl_factory_grid/utils/helpers.py | 19 +-- marl_factory_grid/utils/level_parser.py | 35 +++-- .../utils/observation_builder.py | 15 +- marl_factory_grid/utils/renderer.py | 6 +- marl_factory_grid/utils/results.py | 4 +- marl_factory_grid/utils/states.py | 11 +- marl_factory_grid/utils/tools.py | 2 +- marl_factory_grid/utils/utility_classes.py | 15 -- reload_agent.py | 4 +- setup.py | 2 +- 72 files changed, 742 insertions(+), 298 deletions(-) create mode 100644 marl_factory_grid/algorithms/static/utils.py rename marl_factory_grid/{environment/assets => modules/factory}/__init__.py (100%) create mode 100644 marl_factory_grid/modules/factory/rules.py create mode 100644 marl_factory_grid/modules/machines/actions.py create mode 100644 marl_factory_grid/modules/machines/machine.png create mode 100644 marl_factory_grid/modules/maintenance/__init__.py create mode 100644 marl_factory_grid/modules/maintenance/constants.py create mode 100644 marl_factory_grid/modules/maintenance/entities.py create mode 100644 marl_factory_grid/modules/maintenance/groups.py create mode 100644 marl_factory_grid/modules/maintenance/maintainer.png create mode 100644 marl_factory_grid/modules/maintenance/rewards.py create mode 100644 marl_factory_grid/modules/maintenance/rules.py create mode 100644 marl_factory_grid/modules/zones/__init__.py create mode 100644 marl_factory_grid/modules/zones/constants.py create mode 100644 marl_factory_grid/modules/zones/entitites.py create mode 100644 marl_factory_grid/modules/zones/groups.py create mode 100644 marl_factory_grid/modules/zones/rules.py diff --git a/marl_factory_grid/__init__.py b/marl_factory_grid/__init__.py index 180169b..b2bbfa3 100644 --- a/marl_factory_grid/__init__.py +++ b/marl_factory_grid/__init__.py @@ -1,6 +1,6 @@ -from .environment.factory import BaseFactory -from .environment.factory import OBSBuilder - -from .utils.tools import ConfigExplainer +from .environment import * +from .modules import * +from .utils import * from .quickstart import init + diff --git a/marl_factory_grid/algorithms/static/TSP_base_agent.py b/marl_factory_grid/algorithms/static/TSP_base_agent.py index 5cfdb30..ec4cc81 100644 --- a/marl_factory_grid/algorithms/static/TSP_base_agent.py +++ b/marl_factory_grid/algorithms/static/TSP_base_agent.py @@ -1,11 +1,10 @@ -import itertools from random import choice import numpy as np -import networkx as nx from networkx.algorithms.approximation import traveling_salesman as tsp +from marl_factory_grid.algorithms.static.utils import points_to_graph from marl_factory_grid.modules.doors import constants as do from marl_factory_grid.environment import constants as c from marl_factory_grid.utils.helpers import MOVEMAP @@ -15,41 +14,6 @@ from abc import abstractmethod, ABC future_planning = 7 -def points_to_graph(coordiniates_or_tiles, allow_euclidean_connections=True, allow_manhattan_connections=True): - """ - Given a set of coordinates, this function contructs a non-directed graph, by conncting adjected points. - There are three combinations of settings: - Allow all neigbors: Distance(a, b) <= sqrt(2) - Allow only manhattan: Distance(a, b) == 1 - Allow only euclidean: Distance(a, b) == sqrt(2) - - - :param coordiniates_or_tiles: A set of coordinates. - :type coordiniates_or_tiles: Tiles - :param allow_euclidean_connections: Whether to regard diagonal adjected cells as neighbors - :type: bool - :param allow_manhattan_connections: Whether to regard directly adjected cells as neighbors - :type: bool - - :return: A graph with nodes that are conneceted as specified by the parameters. - :rtype: nx.Graph - """ - assert allow_euclidean_connections or allow_manhattan_connections - if hasattr(coordiniates_or_tiles, 'positions'): - coordiniates_or_tiles = coordiniates_or_tiles.positions - possible_connections = itertools.combinations(coordiniates_or_tiles, 2) - graph = nx.Graph() - for a, b in possible_connections: - diff = np.linalg.norm(np.asarray(a)-np.asarray(b)) - if allow_manhattan_connections and allow_euclidean_connections and diff <= np.sqrt(2): - graph.add_edge(a, b) - elif not allow_manhattan_connections and allow_euclidean_connections and diff == np.sqrt(2): - graph.add_edge(a, b) - elif allow_manhattan_connections and not allow_euclidean_connections and diff == 1: - graph.add_edge(a, b) - return graph - - class TSPBaseAgent(ABC): def __init__(self, state, agent_i, static_problem: bool = True): diff --git a/marl_factory_grid/algorithms/static/utils.py b/marl_factory_grid/algorithms/static/utils.py new file mode 100644 index 0000000..2543152 --- /dev/null +++ b/marl_factory_grid/algorithms/static/utils.py @@ -0,0 +1,39 @@ +import itertools + +import networkx as nx +import numpy as np + + +def points_to_graph(coordiniates_or_tiles, allow_euclidean_connections=True, allow_manhattan_connections=True): + """ + Given a set of coordinates, this function contructs a non-directed graph, by conncting adjected points. + There are three combinations of settings: + Allow all neigbors: Distance(a, b) <= sqrt(2) + Allow only manhattan: Distance(a, b) == 1 + Allow only euclidean: Distance(a, b) == sqrt(2) + + + :param coordiniates_or_tiles: A set of coordinates. + :type coordiniates_or_tiles: Tiles + :param allow_euclidean_connections: Whether to regard diagonal adjected cells as neighbors + :type: bool + :param allow_manhattan_connections: Whether to regard directly adjected cells as neighbors + :type: bool + + :return: A graph with nodes that are conneceted as specified by the parameters. + :rtype: nx.Graph + """ + assert allow_euclidean_connections or allow_manhattan_connections + if hasattr(coordiniates_or_tiles, 'positions'): + coordiniates_or_tiles = coordiniates_or_tiles.positions + possible_connections = itertools.combinations(coordiniates_or_tiles, 2) + graph = nx.Graph() + for a, b in possible_connections: + diff = np.linalg.norm(np.asarray(a)-np.asarray(b)) + if allow_manhattan_connections and allow_euclidean_connections and diff <= np.sqrt(2): + graph.add_edge(a, b) + elif not allow_manhattan_connections and allow_euclidean_connections and diff == np.sqrt(2): + graph.add_edge(a, b) + elif allow_manhattan_connections and not allow_euclidean_connections and diff == 1: + graph.add_edge(a, b) + return graph diff --git a/marl_factory_grid/default_config.yaml b/marl_factory_grid/default_config.yaml index 43e00a3..6bc6f6c 100644 --- a/marl_factory_grid/default_config.yaml +++ b/marl_factory_grid/default_config.yaml @@ -1,68 +1,89 @@ ---- -General: - level_name: rooms - env_seed: 69 - verbose: !!bool False - pomdp_r: 5 - individual_rewards: !!bool True - -Entities: - Defaults: {} - DirtPiles: - initial_dirt_ratio: 0.3 # On INIT, on max how many tiles does the dirt spawn in percent. - dirt_spawn_r_var: 0.05 # How much does the dirt spawn amount vary? - initial_amount: 3 - max_local_amount: 5 # Max dirt amount per tile. - max_global_amount: 20 # Max dirt amount in the whole environment. - Doors: - closed_on_init: True - auto_close_interval: 10 - indicate_area: False Agents: Wolfgang: Actions: - - Move8 - - Noop - - DoorUse - - CleanUp + - Noop + - BtryCharge + - CleanUp + - DestAction + - DoorUse + - ItemAction + - Move8 Observations: - - Self - - Placeholder + - Combined: + - Other - Walls - - DirtPiles - - Placeholder - - Doors - - Doors - Björn: - Actions: - # Move4, Noop - - Move4 - - DoorUse - - CleanUp - Observations: - - Defaults - - Combined - Jürgen: - Actions: - # Move4, Noop - - Defaults - - DoorUse - - CleanUp - Observations: - - Walls - - Placeholder - - Agent[Björn] + - GlobalPosition + - Battery + - ChargePods + - DirtPiles + - Destinations + - Doors + - Items + - Inventory + - DropOffLocations + - Machines + - Maintainers +Entities: + Batteries: {} + ChargePods: {} + Destinations: {} + DirtPiles: + clean_amount: 1 + dirt_spawn_r_var: 0.1 + initial_amount: 2 + initial_dirt_ratio: 0.05 + max_global_amount: 20 + max_local_amount: 5 + Doors: {} + DropOffLocations: {} + GlobalPositions: {} + Inventories: {} + Items: {} + Machines: {} + Maintainers: {} + Zones: {} + ReachedDestinations: {} + +General: + env_seed: 69 + individual_rewards: true + level_name: large + pomdp_r: 3 + verbose: false + Rules: - Defaults: {} + Btry: + initial_charge: 0.8 + per_action_costs: 0.02 + BtryDoneAtDischarge: {} Collision: - done_at_collisions: !!bool False - DirtRespawnRule: - spawn_freq: 5 - DirtSmearOnMove: - smear_amount: 0.12 - DoorAutoClose: {} + done_at_collisions: false + AssignGlobalPositions: {} + DestinationDone: {} + DestinationReach: + n_dests: 1 + tiles: null + DestinationSpawn: + n_dests: 1 + spawn_frequency: 5 + spawn_mode: GROUPED DirtAllCleanDone: {} -Assets: - - Defaults - - Dirt - - Doors + DirtRespawnRule: + spawn_freq: 15 + DirtSmearOnMove: + smear_amount: 0.2 + DoorAutoClose: + close_frequency: 10 + ItemRules: + max_dropoff_storage_size: 0 + n_items: 5 + n_locations: 5 + spawn_frequency: 15 + MachineRule: + n_machines: 2 + MaintenanceRule: + n_maintainer: 1 + MaxStepsReached: + max_steps: 500 +# AgentSingleZonePlacement: +# n_zones: 4 diff --git a/marl_factory_grid/environment/actions.py b/marl_factory_grid/environment/actions.py index 35b5419..21ea463 100644 --- a/marl_factory_grid/environment/actions.py +++ b/marl_factory_grid/environment/actions.py @@ -98,3 +98,5 @@ class NorthWest(Move): Move4 = [North, East, South, West] # noinspection PyTypeChecker Move8 = Move4 + [NorthEast, SouthEast, SouthWest, NorthWest] + +ALL_BASEACTIONS = Move8 + [Noop] diff --git a/marl_factory_grid/environment/constants.py b/marl_factory_grid/environment/constants.py index 5891fbd..cc494f4 100644 --- a/marl_factory_grid/environment/constants.py +++ b/marl_factory_grid/environment/constants.py @@ -9,15 +9,13 @@ WALL = 'Wall' # Identifier of Wall-objects and WALLS = 'Walls' # Identifier of Wall-objects and groups (groups). LEVEL = 'Level' # Identifier of Level-objects and groups (groups). AGENT = 'Agent' # Identifier of Agent-objects and groups (groups). -AGENTS = 'Agents' # Identifier of Agent-objects and groups (groups). OTHERS = 'Other' COMBINED = 'Combined' -GLOBAL_POSITION = 'GLOBAL_POSITION' # Identifier of the global position slice - +GLOBALPOSITIONS = 'GlobalPositions' # Identifier of the global position slice # Attributes -IS_BLOCKING_LIGHT = 'is_blocking_light' -HAS_POSITION = 'has_position' +IS_BLOCKING_LIGHT = 'var_is_blocking_light' +HAS_POSITION = 'var_has_position' HAS_NO_POSITION = 'has_no_position' ALL = 'All' diff --git a/marl_factory_grid/environment/entity/agent.py b/marl_factory_grid/environment/entity/agent.py index aa27395..73b9feb 100644 --- a/marl_factory_grid/environment/entity/agent.py +++ b/marl_factory_grid/environment/entity/agent.py @@ -1,6 +1,5 @@ from typing import List, Union -from marl_factory_grid.environment import constants as c from marl_factory_grid.environment.actions import Action from marl_factory_grid.environment.entity.entity import Entity from marl_factory_grid.utils.render import RenderEntity @@ -8,6 +7,8 @@ from marl_factory_grid.utils import renderer from marl_factory_grid.utils.helpers import is_move from marl_factory_grid.utils.results import ActionResult, Result +from marl_factory_grid.environment import constants as c + class Agent(Entity): @@ -24,7 +25,7 @@ class Agent(Entity): return self._observations @property - def can_collide(self): + def var_can_collide(self): return True def step_result(self): diff --git a/marl_factory_grid/environment/entity/entity.py b/marl_factory_grid/environment/entity/entity.py index 00944e1..bce803a 100644 --- a/marl_factory_grid/environment/entity/entity.py +++ b/marl_factory_grid/environment/entity/entity.py @@ -1,15 +1,20 @@ import abc -from marl_factory_grid.environment import constants as c -from marl_factory_grid.environment.entity.object import EnvObject -from marl_factory_grid.utils.render import RenderEntity +from .. import constants as c +from .object import EnvObject +from ...utils.render import RenderEntity +from ...utils.results import ActionResult class Entity(EnvObject, abc.ABC): """Full Env Entity that lives on the environment Grid. Doors, Items, DirtPile etc...""" @property - def has_position(self): + def state(self): + return self._status or ActionResult(entity=self, identifier=c.NOOP, validity=c.VALID, reward=0) + + @property + def var_has_position(self): return self.pos != c.VALUE_NO_POS @property @@ -64,12 +69,13 @@ class Entity(EnvObject, abc.ABC): def __init__(self, tile, **kwargs): super().__init__(**kwargs) + self._status = None self._tile = tile tile.enter(self) def summarize_state(self) -> dict: return dict(name=str(self.name), x=int(self.x), y=int(self.y), - tile=str(self.tile.name), can_collide=bool(self.can_collide)) + tile=str(self.tile.name), can_collide=bool(self.var_can_collide)) @abc.abstractmethod def render(self): diff --git a/marl_factory_grid/environment/entity/object.py b/marl_factory_grid/environment/entity/object.py index dd98539..0c65552 100644 --- a/marl_factory_grid/environment/entity/object.py +++ b/marl_factory_grid/environment/entity/object.py @@ -78,37 +78,37 @@ class EnvObject(Object): return self.name @property - def is_blocking_light(self): + def var_is_blocking_light(self): try: - return self._collection.is_blocking_light or False + return self._collection.var_is_blocking_light or False except AttributeError: return False @property - def can_move(self): + def var_can_move(self): try: - return self._collection.can_move or False + return self._collection.var_can_move or False except AttributeError: return False @property - def is_blocking_pos(self): + def var_is_blocking_pos(self): try: - return self._collection.is_blocking_pos or False + return self._collection.var_is_blocking_pos or False except AttributeError: return False @property - def has_position(self): + def var_has_position(self): try: - return self._collection.has_position or False + return self._collection.var_has_position or False except AttributeError: return False @property - def can_collide(self): + def var_can_collide(self): try: - return self._collection.can_collide or False + return self._collection.var_can_collide or False except AttributeError: return False diff --git a/marl_factory_grid/environment/entity/util.py b/marl_factory_grid/environment/entity/util.py index 7a43664..fbf0c4a 100644 --- a/marl_factory_grid/environment/entity/util.py +++ b/marl_factory_grid/environment/entity/util.py @@ -35,11 +35,11 @@ class GlobalPosition(BoundEntityMixin, EnvObject): @property def encoding(self): if self._normalized: - return tuple(np.divide(self._bound_entity.pos, self._level_shape)) + return tuple(np.divide(self._bound_entity.pos, self._shape)) else: return self.bound_entity.pos - def __init__(self, *args, normalized: bool = True, **kwargs): + def __init__(self, level_shape, *args, normalized: bool = True, **kwargs): super(GlobalPosition, self).__init__(*args, **kwargs) - self._level_shape = math.sqrt(self.size) self._normalized = normalized + self._shape = level_shape diff --git a/marl_factory_grid/environment/entity/wall_floor.py b/marl_factory_grid/environment/entity/wall_floor.py index 1c920f9..7ad8ed4 100644 --- a/marl_factory_grid/environment/entity/wall_floor.py +++ b/marl_factory_grid/environment/entity/wall_floor.py @@ -11,23 +11,23 @@ from marl_factory_grid.utils import helpers as h class Floor(EnvObject): @property - def has_position(self): + def var_has_position(self): return True @property - def can_collide(self): + def var_can_collide(self): return False @property - def can_move(self): + def var_can_move(self): return False @property - def is_blocking_pos(self): + def var_is_blocking_pos(self): return False @property - def is_blocking_light(self): + def var_is_blocking_light(self): return False @property @@ -51,7 +51,7 @@ class Floor(EnvObject): @property def guests_that_can_collide(self): - return [x for x in self.guests if x.can_collide] + return [x for x in self.guests if x.var_can_collide] @property def guests(self): @@ -67,7 +67,7 @@ class Floor(EnvObject): @property def is_blocked(self): - return any([x.is_blocking_pos for x in self.guests]) + return any([x.var_is_blocking_pos for x in self.guests]) def __init__(self, pos, **kwargs): super(Floor, self).__init__(**kwargs) @@ -86,7 +86,7 @@ class Floor(EnvObject): return bool(len(self._guests)) def enter(self, guest): - if (guest.name not in self._guests and not self.is_blocked) and not (guest.is_blocking_pos and self.is_occupied()): + if (guest.name not in self._guests and not self.is_blocked) and not (guest.var_is_blocking_pos and self.is_occupied()): self._guests.update({guest.name: guest}) return c.VALID else: @@ -112,7 +112,7 @@ class Floor(EnvObject): class Wall(Floor): @property - def can_collide(self): + def var_can_collide(self): return True @property @@ -123,9 +123,9 @@ class Wall(Floor): return RenderEntity(c.WALL, self.pos) @property - def is_blocking_pos(self): + def var_is_blocking_pos(self): return True @property - def is_blocking_light(self): + def var_is_blocking_light(self): return True diff --git a/marl_factory_grid/environment/factory.py b/marl_factory_grid/environment/factory.py index 99571ab..c09fd55 100644 --- a/marl_factory_grid/environment/factory.py +++ b/marl_factory_grid/environment/factory.py @@ -19,7 +19,7 @@ from marl_factory_grid.utils.states import Gamestate REC_TAC = 'rec_' -class BaseFactory(gym.Env): +class Factory(gym.Env): @property def action_space(self): @@ -52,11 +52,15 @@ class BaseFactory(gym.Env): def __exit__(self, exc_type, exc_val, exc_tb): self.close() - def __init__(self, config_file: Union[str, PathLike]): + def __init__(self, config_file: Union[str, PathLike], custom_modules_path: Union[None, PathLike] = None, + custom_level_path: Union[None, PathLike] = None): self._config_file = config_file - self.conf = FactoryConfigParser(self._config_file) + self.conf = FactoryConfigParser(self._config_file, custom_modules_path) # Attribute Assignment - self.level_filepath = Path(__file__).parent.parent / h.LEVELS_DIR / f'{self.conf.level_name}.txt' + if custom_level_path is not None: + self.level_filepath = Path(custom_level_path) + else: + self.level_filepath = Path(__file__).parent.parent / h.LEVELS_DIR / f'{self.conf.level_name}.txt' self._renderer = None # expensive - don't use; unless required ! parsed_entities = self.conf.load_entities() @@ -90,7 +94,7 @@ class BaseFactory(gym.Env): self.state.entities.add_item({c.AGENT: agents}) # All is set up, trigger additional init (after agent entity spawn etc) - self.state.rules.do_all_init(self.state) + self.state.rules.do_all_init(self.state, self.map) # Observations # noinspection PyAttributeOutsideInit @@ -144,7 +148,7 @@ class BaseFactory(gym.Env): try: done_reason = next(x for x in done_check_results if x.validity) done = True - self.state.print(f'Env done, Reason: {done_reason.name}.') + self.state.print(f'Env done, Reason: {done_reason.identifier}.') except StopIteration: done = False diff --git a/marl_factory_grid/environment/groups/agents.py b/marl_factory_grid/environment/groups/agents.py index 14f4a0d..f7839ba 100644 --- a/marl_factory_grid/environment/groups/agents.py +++ b/marl_factory_grid/environment/groups/agents.py @@ -1,6 +1,6 @@ +from marl_factory_grid.environment.entity.agent import Agent from marl_factory_grid.environment.groups.env_objects import EnvObjects from marl_factory_grid.environment.groups.mixins import PositionMixin -from marl_factory_grid.environment.entity.agent import Agent class Agents(PositionMixin, EnvObjects): diff --git a/marl_factory_grid/environment/groups/env_objects.py b/marl_factory_grid/environment/groups/env_objects.py index 64d5d25..b3efb2b 100644 --- a/marl_factory_grid/environment/groups/env_objects.py +++ b/marl_factory_grid/environment/groups/env_objects.py @@ -5,10 +5,10 @@ from marl_factory_grid.environment.entity.object import EnvObject class EnvObjects(Objects): _entity = EnvObject - is_blocking_light: bool = False - can_collide: bool = False - has_position: bool = False - can_move: bool = False + var_is_blocking_light: bool = False + var_can_collide: bool = False + var_has_position: bool = False + var_can_move: bool = False @property def encodings(self): @@ -19,7 +19,7 @@ class EnvObjects(Objects): self.size = size def add_item(self, item: EnvObject): - assert self.has_position or (len(self) <= self.size) + assert self.var_has_position or (len(self) <= self.size) super(EnvObjects, self).add_item(item) return self diff --git a/marl_factory_grid/environment/groups/mixins.py b/marl_factory_grid/environment/groups/mixins.py index 4c1f934..44e4ab9 100644 --- a/marl_factory_grid/environment/groups/mixins.py +++ b/marl_factory_grid/environment/groups/mixins.py @@ -1,15 +1,19 @@ +from typing import List + from marl_factory_grid.environment import constants as c - from marl_factory_grid.environment.entity.entity import Entity +from marl_factory_grid.environment.entity.wall_floor import Floor -# noinspection PyUnresolvedReferences,PyTypeChecker,PyArgumentList class PositionMixin: _entity = Entity - is_blocking_light: bool = True - can_collide: bool = True - has_position: bool = True + var_is_blocking_light: bool = True + var_can_collide: bool = True + var_has_position: bool = True + + def spawn(self, tiles: List[Floor]): + self.add_items([self._entity(tile) for tile in tiles]) def render(self): return [y for y in [x.render() for x in self] if y is not None] @@ -81,8 +85,8 @@ class IsBoundMixin: class HasBoundedMixin: @property - def obs_names(self): - return [x.name for x in self] + def obs_pairs(self): + return [(x.name, x) for x in self] def by_entity(self, entity): try: diff --git a/marl_factory_grid/environment/groups/objects.py b/marl_factory_grid/environment/groups/objects.py index 4113ee3..5182d18 100644 --- a/marl_factory_grid/environment/groups/objects.py +++ b/marl_factory_grid/environment/groups/objects.py @@ -4,6 +4,7 @@ from typing import List import numpy as np from marl_factory_grid.environment.entity.object import Object +import marl_factory_grid.environment.constants as c class Objects: @@ -116,12 +117,21 @@ class Objects: def __repr__(self): return f'{self.__class__.__name__}[{dict(self._data)}]' + def spawn(self, n: int): + self.add_items([self._entity() for _ in range(n)]) + return c.VALID + + def despawn(self, items: List[Object]): + items = [items] if isinstance(items, Object) else items + for item in items: + del self[item] + def notify_change_pos(self, entity: object): try: self.pos_dict[entity.last_pos].remove(entity) except (ValueError, AttributeError): pass - if entity.has_position: + if entity.var_has_position: try: self.pos_dict[entity.pos].append(entity) except (ValueError, AttributeError): diff --git a/marl_factory_grid/environment/groups/utils.py b/marl_factory_grid/environment/groups/utils.py index 19293a9..fd1d8e8 100644 --- a/marl_factory_grid/environment/groups/utils.py +++ b/marl_factory_grid/environment/groups/utils.py @@ -2,10 +2,11 @@ from typing import List, Union import numpy as np -from marl_factory_grid.environment.groups.env_objects import EnvObjects -from marl_factory_grid.environment.groups.objects import Objects -from marl_factory_grid.environment.groups.mixins import HasBoundedMixin, PositionMixin from marl_factory_grid.environment.entity.util import GlobalPosition +from marl_factory_grid.environment.groups.env_objects import EnvObjects +from marl_factory_grid.environment.groups.mixins import PositionMixin, HasBoundedMixin +from marl_factory_grid.environment.groups.objects import Objects +from marl_factory_grid.modules.zones import Zone from marl_factory_grid.utils import helpers as h from marl_factory_grid.environment import constants as c @@ -44,7 +45,9 @@ class GlobalPositions(HasBoundedMixin, EnvObjects): super(GlobalPositions, self).__init__(*args, **kwargs) -class Zones(Objects): +class ZonesOLD(Objects): + + _entity = Zone @property def accounting_zones(self): diff --git a/marl_factory_grid/environment/groups/wall_n_floors.py b/marl_factory_grid/environment/groups/wall_n_floors.py index c43a9e9..6acf898 100644 --- a/marl_factory_grid/environment/groups/wall_n_floors.py +++ b/marl_factory_grid/environment/groups/wall_n_floors.py @@ -30,8 +30,8 @@ class Walls(PositionMixin, EnvObjects): class Floors(Walls): _entity = Floor symbol = c.SYMBOL_FLOOR - is_blocking_light: bool = False - can_collide: bool = False + var_is_blocking_light: bool = False + var_can_collide: bool = False def __init__(self, *args, **kwargs): super(Floors, self).__init__(*args, **kwargs) diff --git a/marl_factory_grid/environment/rules.py b/marl_factory_grid/environment/rules.py index 6a329e4..9385841 100644 --- a/marl_factory_grid/environment/rules.py +++ b/marl_factory_grid/environment/rules.py @@ -17,7 +17,7 @@ class Rule(abc.ABC): def __repr__(self): return f'{self.name}' - def on_init(self, state): + def on_init(self, state, lvl_map): return [] def on_reset(self): @@ -42,7 +42,7 @@ class MaxStepsReached(Rule): super().__init__() self.max_steps = max_steps - def on_init(self, state): + def on_init(self, state, lvl_map): pass def on_check_done(self, state): @@ -51,6 +51,20 @@ class MaxStepsReached(Rule): return [DoneResult(validity=c.NOT_VALID, identifier=self.name, reward=0)] +class AssignGlobalPositions(Rule): + + def __init__(self): + super().__init__() + + def on_init(self, state, lvl_map): + from marl_factory_grid.environment.entity.util import GlobalPosition + for agent in state[c.AGENT]: + gp = GlobalPosition(lvl_map.level_shape) + gp.bind_to(agent) + state[c.GLOBALPOSITIONS].add_item(gp) + return [] + + class Collision(Rule): def __init__(self, done_at_collisions: bool = False): diff --git a/marl_factory_grid/modules/__init__.py b/marl_factory_grid/modules/__init__.py index e69de29..e802c8c 100644 --- a/marl_factory_grid/modules/__init__.py +++ b/marl_factory_grid/modules/__init__.py @@ -0,0 +1,7 @@ +from .batteries import * +from .clean_up import * +from .destinations import * +from .doors import * +from .items import * +from .machines import * +from .maintenance import * diff --git a/marl_factory_grid/modules/_template/constants.py b/marl_factory_grid/modules/_template/constants.py index 1aa157d..aace680 100644 --- a/marl_factory_grid/modules/_template/constants.py +++ b/marl_factory_grid/modules/_template/constants.py @@ -8,4 +8,4 @@ WEST = 'west' NORTHEAST = 'north_east' SOUTHEAST = 'south_east' SOUTHWEST = 'south_west' -NORTHWEST = 'north_west' \ No newline at end of file +NORTHWEST = 'north_west' diff --git a/marl_factory_grid/modules/_template/rules.py b/marl_factory_grid/modules/_template/rules.py index 2f0b65c..6ed2f2d 100644 --- a/marl_factory_grid/modules/_template/rules.py +++ b/marl_factory_grid/modules/_template/rules.py @@ -8,7 +8,7 @@ class TemplateRule(Rule): def __init__(self, *args, **kwargs): super(TemplateRule, self).__init__(*args, **kwargs) - def on_init(self, state): + def on_init(self, state, lvl_map): pass def tick_pre_step(self, state) -> List[TickResult]: diff --git a/marl_factory_grid/modules/batteries/__init__.py b/marl_factory_grid/modules/batteries/__init__.py index e69de29..52e82d5 100644 --- a/marl_factory_grid/modules/batteries/__init__.py +++ b/marl_factory_grid/modules/batteries/__init__.py @@ -0,0 +1,4 @@ +from .actions import BtryCharge +from .entitites import ChargePod, Battery +from .groups import ChargePods, Batteries +from .rules import BtryDoneAtDischarge, Btry diff --git a/marl_factory_grid/modules/batteries/groups.py b/marl_factory_grid/modules/batteries/groups.py index 61935a4..ad24eab 100644 --- a/marl_factory_grid/modules/batteries/groups.py +++ b/marl_factory_grid/modules/batteries/groups.py @@ -13,18 +13,13 @@ class Batteries(HasBoundedMixin, EnvObjects): def obs_tag(self): return self.__class__.__name__ - @property - def obs_pairs(self): - return [(x.name, x) for x in self] - def __init__(self, *args, **kwargs): super(Batteries, self).__init__(*args, **kwargs) - def spawn_batteries(self, agents, initial_charge_level): + def spawn(self, agents, initial_charge_level): batteries = [self._entity(initial_charge_level, agent) for _, agent in enumerate(agents)] self.add_items(batteries) - class ChargePods(PositionMixin, EnvObjects): _entity = ChargePod diff --git a/marl_factory_grid/modules/batteries/rules.py b/marl_factory_grid/modules/batteries/rules.py index 16e685a..e8e7816 100644 --- a/marl_factory_grid/modules/batteries/rules.py +++ b/marl_factory_grid/modules/batteries/rules.py @@ -13,8 +13,8 @@ class Btry(Rule): self.per_action_costs = per_action_costs self.initial_charge = initial_charge - def on_init(self, state): - state[b.BATTERIES].spawn_batteries(state[c.AGENT], self.initial_charge) + def on_init(self, state, lvl_map): + state[b.BATTERIES].spawn(state[c.AGENT], self.initial_charge) def tick_pre_step(self, state) -> List[TickResult]: pass diff --git a/marl_factory_grid/modules/clean_up/__init__.py b/marl_factory_grid/modules/clean_up/__init__.py index e69de29..59ce25d 100644 --- a/marl_factory_grid/modules/clean_up/__init__.py +++ b/marl_factory_grid/modules/clean_up/__init__.py @@ -0,0 +1,6 @@ +from .actions import CleanUp +from .entitites import DirtPile +from .groups import DirtPiles +from .rule_respawn import DirtRespawnRule +from .rule_smear_on_move import DirtSmearOnMove +from .rule_done_on_all_clean import DirtAllCleanDone diff --git a/marl_factory_grid/modules/clean_up/entitites.py b/marl_factory_grid/modules/clean_up/entitites.py index 984e644..8ff50b1 100644 --- a/marl_factory_grid/modules/clean_up/entitites.py +++ b/marl_factory_grid/modules/clean_up/entitites.py @@ -7,6 +7,22 @@ from marl_factory_grid.modules.clean_up import constants as d class DirtPile(Entity): + @property + def var_can_collide(self): + return False + + @property + def var_can_move(self): + return False + + @property + def var_is_blocking_light(self): + return False + + @property + def var_has_position(self): + return True + @property def amount(self): return self._amount diff --git a/marl_factory_grid/modules/clean_up/groups.py b/marl_factory_grid/modules/clean_up/groups.py index f6c532c..e51c382 100644 --- a/marl_factory_grid/modules/clean_up/groups.py +++ b/marl_factory_grid/modules/clean_up/groups.py @@ -31,7 +31,7 @@ class DirtPiles(PositionMixin, EnvObjects): self.max_global_amount = max_global_amount self.max_local_amount = max_local_amount - def spawn_dirt(self, then_dirty_tiles, amount) -> bool: + def spawn(self, then_dirty_tiles, amount) -> bool: if isinstance(then_dirty_tiles, Floor): then_dirty_tiles = [then_dirty_tiles] for tile in then_dirty_tiles: @@ -57,7 +57,7 @@ class DirtPiles(PositionMixin, EnvObjects): var = self.dirt_spawn_r_var new_spawn = abs(self.initial_dirt_ratio + (state.rng.uniform(-var, var) if initial_spawn else 0)) n_dirt_tiles = max(0, int(new_spawn * len(free_for_dirt))) - return self.spawn_dirt(free_for_dirt[:n_dirt_tiles], self.initial_amount) + return self.spawn(free_for_dirt[:n_dirt_tiles], self.initial_amount) def __repr__(self): s = super(DirtPiles, self).__repr__() diff --git a/marl_factory_grid/modules/clean_up/rule_respawn.py b/marl_factory_grid/modules/clean_up/rule_respawn.py index 5cdad95..5a2cb2e 100644 --- a/marl_factory_grid/modules/clean_up/rule_respawn.py +++ b/marl_factory_grid/modules/clean_up/rule_respawn.py @@ -11,7 +11,7 @@ class DirtRespawnRule(Rule): self.spawn_freq = spawn_freq self._next_dirt_spawn = spawn_freq - def on_init(self, state) -> str: + def on_init(self, state, lvl_map) -> str: state[d.DIRT].trigger_dirt_spawn(state, initial_spawn=True) return f'Initial Dirt was spawned on: {[x.pos for x in state[d.DIRT]]}' diff --git a/marl_factory_grid/modules/clean_up/rule_smear_on_move.py b/marl_factory_grid/modules/clean_up/rule_smear_on_move.py index 6e29f01..e6d2822 100644 --- a/marl_factory_grid/modules/clean_up/rule_smear_on_move.py +++ b/marl_factory_grid/modules/clean_up/rule_smear_on_move.py @@ -18,7 +18,7 @@ class DirtSmearOnMove(Rule): if is_move(entity.state.identifier) and entity.state.validity == c.VALID: if old_pos_dirt := state[d.DIRT].by_pos(entity.last_pos): if smeared_dirt := round(old_pos_dirt.amount * self.smear_amount, 2): - if state[d.DIRT].spawn_dirt(entity.tile, amount=smeared_dirt): + if state[d.DIRT].spawn(entity.tile, amount=smeared_dirt): results.append(TickResult(identifier=self.name, entity=entity, reward=0, validity=c.VALID)) return results diff --git a/marl_factory_grid/modules/destinations/__init__.py b/marl_factory_grid/modules/destinations/__init__.py index e69de29..31d3c27 100644 --- a/marl_factory_grid/modules/destinations/__init__.py +++ b/marl_factory_grid/modules/destinations/__init__.py @@ -0,0 +1,4 @@ +from .actions import DestAction +from .entitites import Destination +from .groups import ReachedDestinations, Destinations +from .rules import DestinationDone, DestinationReach, DestinationSpawn diff --git a/marl_factory_grid/modules/destinations/rules.py b/marl_factory_grid/modules/destinations/rules.py index b718801..55c66a9 100644 --- a/marl_factory_grid/modules/destinations/rules.py +++ b/marl_factory_grid/modules/destinations/rules.py @@ -62,7 +62,7 @@ class DestinationSpawn(Rule): self.n_dests = n_dests self.spawn_mode = spawn_mode - def on_init(self, state): + def on_init(self, state, lvl_map): # noinspection PyAttributeOutsideInit self._dest_spawn_timer = self.spawn_frequency self.trigger_destination_spawn(self.n_dests, state) diff --git a/marl_factory_grid/modules/doors/__init__.py b/marl_factory_grid/modules/doors/__init__.py index e69de29..e5dc1cf 100644 --- a/marl_factory_grid/modules/doors/__init__.py +++ b/marl_factory_grid/modules/doors/__init__.py @@ -0,0 +1,4 @@ +from .actions import DoorUse +from .entitites import Door, DoorIndicator +from .groups import Doors +from .rule_door_auto_close import DoorAutoClose diff --git a/marl_factory_grid/modules/doors/actions.py b/marl_factory_grid/modules/doors/actions.py index 80b7288..31a5bf8 100644 --- a/marl_factory_grid/modules/doors/actions.py +++ b/marl_factory_grid/modules/doors/actions.py @@ -1,10 +1,9 @@ from typing import Union from marl_factory_grid.environment.actions import Action -from marl_factory_grid.utils.results import ActionResult - from marl_factory_grid.modules.doors import constants as d, rewards as r from marl_factory_grid.environment import constants as c +from marl_factory_grid.utils.results import ActionResult class DoorUse(Action): diff --git a/marl_factory_grid/modules/doors/entitites.py b/marl_factory_grid/modules/doors/entitites.py index 4825726..36933ae 100644 --- a/marl_factory_grid/modules/doors/entitites.py +++ b/marl_factory_grid/modules/doors/entitites.py @@ -22,15 +22,15 @@ class DoorIndicator(Entity): class Door(Entity): @property - def is_blocking_pos(self): + def var_is_blocking_pos(self): return False if self.is_open else True @property - def is_blocking_light(self): + def var_is_blocking_light(self): return False if self.is_open else True @property - def can_collide(self): + def var_can_collide(self): return False if self.is_open else True @property @@ -42,12 +42,14 @@ class Door(Entity): return 'open' if self.is_open else 'closed' def __init__(self, *args, closed_on_init=True, auto_close_interval=10, indicate_area=False, **kwargs): - self._state = d.STATE_CLOSED + self._status = d.STATE_CLOSED super(Door, self).__init__(*args, **kwargs) self.auto_close_interval = auto_close_interval self.time_to_close = 0 if not closed_on_init: self._open() + else: + self._close() if indicate_area: self._collection.add_items([DoorIndicator(x) for x in self.tile.neighboring_floor]) @@ -58,22 +60,22 @@ class Door(Entity): @property def is_closed(self): - return self._state == d.STATE_CLOSED + return self._status == d.STATE_CLOSED @property def is_open(self): - return self._state == d.STATE_OPEN + return self._status == d.STATE_OPEN @property def status(self): - return self._state + return self._status def render(self): name, state = 'door_open' if self.is_open else 'door_closed', 'blank' return RenderEntity(name, self.pos, 1, 'none', state, self.identifier_int + 1) def use(self): - if self._state == d.STATE_OPEN: + if self._status == d.STATE_OPEN: self._close() else: self._open() @@ -90,8 +92,8 @@ class Door(Entity): return c.NOT_VALID def _open(self): - self._state = d.STATE_OPEN + self._status = d.STATE_OPEN self.time_to_close = self.auto_close_interval def _close(self): - self._state = d.STATE_CLOSED + self._status = d.STATE_CLOSED diff --git a/marl_factory_grid/environment/assets/__init__.py b/marl_factory_grid/modules/factory/__init__.py similarity index 100% rename from marl_factory_grid/environment/assets/__init__.py rename to marl_factory_grid/modules/factory/__init__.py diff --git a/marl_factory_grid/modules/factory/rules.py b/marl_factory_grid/modules/factory/rules.py new file mode 100644 index 0000000..82d3b38 --- /dev/null +++ b/marl_factory_grid/modules/factory/rules.py @@ -0,0 +1,32 @@ +import random +from typing import List, Union + +from marl_factory_grid.environment.rules import Rule +from marl_factory_grid.environment import constants as c +from marl_factory_grid.utils.results import TickResult + + +class AgentSingleZonePlacementBeta(Rule): + + def __init__(self): + super().__init__() + + def on_init(self, state, lvl_map): + zones = state[c.ZONES] + n_zones = state[c.ZONES] + agents = state[c.AGENT] + if len(self.coordinates) == len(agents): + coordinates = self.coordinates + elif len(self.coordinates) > len(agents): + coordinates = random.choices(self.coordinates, k=len(agents)) + else: + raise ValueError + tiles = [state[c.FLOOR].by_pos(pos) for pos in coordinates] + for agent, tile in zip(agents, tiles): + agent.move(tile) + + def tick_step(self, state): + return [] + + def tick_post_step(self, state) -> List[TickResult]: + return [] \ No newline at end of file diff --git a/marl_factory_grid/modules/items/__init__.py b/marl_factory_grid/modules/items/__init__.py index e69de29..157c385 100644 --- a/marl_factory_grid/modules/items/__init__.py +++ b/marl_factory_grid/modules/items/__init__.py @@ -0,0 +1,4 @@ +from .actions import ItemAction +from .entitites import Item, DropOffLocation +from .groups import DropOffLocations, Items, Inventory, Inventories +from .rules import ItemRules diff --git a/marl_factory_grid/modules/items/entitites.py b/marl_factory_grid/modules/items/entitites.py index b283b0b..94ddad4 100644 --- a/marl_factory_grid/modules/items/entitites.py +++ b/marl_factory_grid/modules/items/entitites.py @@ -8,6 +8,8 @@ from marl_factory_grid.modules.items import constants as i class Item(Entity): + var_can_collide = False + def render(self): return RenderEntity(i.ITEM, self.tile.pos) if self.pos != c.VALUE_NO_POS else None @@ -38,6 +40,22 @@ class Item(Entity): class DropOffLocation(Entity): + @property + def var_can_collide(self): + return False + + @property + def var_can_move(self): + return False + + @property + def var_is_blocking_light(self): + return False + + @property + def var_has_position(self): + return True + def render(self): return RenderEntity(i.DROP_OFF, self.tile.pos) diff --git a/marl_factory_grid/modules/items/groups.py b/marl_factory_grid/modules/items/groups.py index 3ed3d23..0812b47 100644 --- a/marl_factory_grid/modules/items/groups.py +++ b/marl_factory_grid/modules/items/groups.py @@ -17,15 +17,6 @@ class Items(PositionMixin, EnvObjects): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - def spawn_items(self, tiles: List[Floor]): - items = [self._entity(tile) for tile in tiles] - self.add_items(items) - - def despawn_items(self, items: List[Item]): - items = [items] if isinstance(items, Item) else items - for item in items: - del self[item] - class Inventory(IsBoundMixin, EnvObjects): @@ -58,11 +49,7 @@ class Inventory(IsBoundMixin, EnvObjects): class Inventories(HasBoundedMixin, Objects): _entity = Inventory - can_move = False - - @property - def obs_pairs(self): - return [(x.name, x) for x in self] + var_can_move = False def __init__(self, size, *args, **kwargs): super(Inventories, self).__init__(*args, **kwargs) @@ -70,7 +57,7 @@ class Inventories(HasBoundedMixin, Objects): self._obs = None self._lazy_eval_transforms = [] - def spawn_inventories(self, agents): + def spawn(self, agents): inventories = [self._entity(agent, self.size,) for _, agent in enumerate(agents)] self.add_items(inventories) diff --git a/marl_factory_grid/modules/items/rules.py b/marl_factory_grid/modules/items/rules.py index 706677f..9340dc8 100644 --- a/marl_factory_grid/modules/items/rules.py +++ b/marl_factory_grid/modules/items/rules.py @@ -18,7 +18,7 @@ class ItemRules(Rule): self.max_dropoff_storage_size = max_dropoff_storage_size self.n_locations = n_locations - def on_init(self, state): + def on_init(self, state, lvl_map): self.trigger_drop_off_location_spawn(state) self._next_item_spawn = self.spawn_frequency self.trigger_inventory_spawn(state) @@ -42,7 +42,7 @@ class ItemRules(Rule): def trigger_item_spawn(self, state): if item_to_spawns := max(0, (self.n_items - len(state[i.ITEM]))): empty_tiles = state[c.FLOOR].empty_tiles[:item_to_spawns] - state[i.ITEM].spawn_items(empty_tiles) + state[i.ITEM].spawn(empty_tiles) self._next_item_spawn = self.spawn_frequency state.print(f'{item_to_spawns} new items have been spawned; next spawn in {self._next_item_spawn}') return len(empty_tiles) @@ -52,7 +52,7 @@ class ItemRules(Rule): @staticmethod def trigger_inventory_spawn(state): - state[i.INVENTORY].spawn_inventories(state[c.AGENT]) + state[i.INVENTORY].spawn(state[c.AGENT]) def tick_post_step(self, state) -> List[TickResult]: for item in list(state[i.ITEM].values()): diff --git a/marl_factory_grid/modules/machines/__init__.py b/marl_factory_grid/modules/machines/__init__.py index e69de29..36ba51d 100644 --- a/marl_factory_grid/modules/machines/__init__.py +++ b/marl_factory_grid/modules/machines/__init__.py @@ -0,0 +1,3 @@ +from .entitites import Machine +from .groups import Machines +from .rules import MachineRule diff --git a/marl_factory_grid/modules/machines/actions.py b/marl_factory_grid/modules/machines/actions.py new file mode 100644 index 0000000..8f4eaaa --- /dev/null +++ b/marl_factory_grid/modules/machines/actions.py @@ -0,0 +1,25 @@ +from typing import Union + +from marl_factory_grid.environment.actions import Action +from marl_factory_grid.utils.results import ActionResult + +from marl_factory_grid.modules.machines import constants as m, rewards as r +from marl_factory_grid.environment import constants as c + + +class MachineAction(Action): + + def __init__(self): + super().__init__(m.MACHINE_ACTION) + + def do(self, entity, state) -> Union[None, ActionResult]: + if machine := state[m.MACHINES].by_pos(entity.pos): + if valid := machine.maintain(): + return ActionResult(entity=entity, identifier=self._identifier, validity=valid, reward=r.MAINTAIN_VALID) + else: + return ActionResult(entity=entity, identifier=self._identifier, validity=valid, reward=r.MAINTAIN_FAIL) + else: + return ActionResult(entity=entity, identifier=self._identifier, validity=c.NOT_VALID, reward=r.MAINTAIN_FAIL) + + + diff --git a/marl_factory_grid/modules/machines/constants.py b/marl_factory_grid/modules/machines/constants.py index f26a88c..29ce3bc 100644 --- a/marl_factory_grid/modules/machines/constants.py +++ b/marl_factory_grid/modules/machines/constants.py @@ -2,6 +2,8 @@ MACHINES = 'Machines' MACHINE = 'Machine' +MACHINE_ACTION = 'Maintain' + STATE_WORK = 'working' STATE_IDLE = 'idling' STATE_MAINTAIN = 'maintenance' diff --git a/marl_factory_grid/modules/machines/entitites.py b/marl_factory_grid/modules/machines/entitites.py index f53fa48..0bb42fb 100644 --- a/marl_factory_grid/modules/machines/entitites.py +++ b/marl_factory_grid/modules/machines/entitites.py @@ -2,27 +2,43 @@ from marl_factory_grid.environment.entity.entity import Entity from marl_factory_grid.utils.render import RenderEntity from marl_factory_grid.environment import constants as c from marl_factory_grid.utils.results import TickResult -from marl_factory_grid.modules.machines import constants as m, rewards as r + +from . import constants as m class Machine(Entity): + @property + def var_can_collide(self): + return False + + @property + def var_can_move(self): + return False + + @property + def var_is_blocking_light(self): + return False + + @property + def var_has_position(self): + return True + @property def encoding(self): - return self._encodings[self.state] + return self._encodings[self.status] def __init__(self, *args, work_interval: int = 10, pause_interval: int = 15, **kwargs): super(Machine, self).__init__(*args, **kwargs) self._intervals = dict({m.STATE_IDLE: pause_interval, m.STATE_WORK: work_interval}) self._encodings = dict({m.STATE_IDLE: pause_interval, m.STATE_WORK: work_interval}) - self.state = m.STATE_IDLE + self.status = m.STATE_IDLE self.health = 100 self._counter = 0 - self.__delattr__('move') def maintain(self): - if self.state == m.STATE_WORK: + if self.status == m.STATE_WORK: return c.NOT_VALID if self.health <= 98: self.health = 100 @@ -31,10 +47,10 @@ class Machine(Entity): return c.NOT_VALID def tick(self): - if self.state == m.STATE_MAINTAIN and any([c.AGENT in x.name for x in self.tile.guests]): - return TickResult(self.name, c.VALID, r.NONE, self) - elif self.state == m.STATE_MAINTAIN and not any([c.AGENT in x.name for x in self.tile.guests]): - self.state = m.STATE_WORK + if self.status == m.STATE_MAINTAIN and any([c.AGENT in x.name for x in self.tile.guests]): + return TickResult(identifier=self.name, validity=c.VALID, reward=0, entity=self) + elif self.status == m.STATE_MAINTAIN and not any([c.AGENT in x.name for x in self.tile.guests]): + self.status = m.STATE_WORK self.reset_counter() return None elif self._counter: @@ -42,12 +58,12 @@ class Machine(Entity): self.health -= 1 return None else: - self.state = m.STATE_WORK if self.state == m.STATE_IDLE else m.STATE_IDLE + self.status = m.STATE_WORK if self.status == m.STATE_IDLE else m.STATE_IDLE self.reset_counter() return None def reset_counter(self): - self._counter = self._intervals[self.state] + self._counter = self._intervals[self.status] def render(self): return RenderEntity(m.MACHINE, self.pos) diff --git a/marl_factory_grid/modules/machines/groups.py b/marl_factory_grid/modules/machines/groups.py index b4ee633..f8a27e7 100644 --- a/marl_factory_grid/modules/machines/groups.py +++ b/marl_factory_grid/modules/machines/groups.py @@ -1,6 +1,7 @@ from marl_factory_grid.environment.groups.env_objects import EnvObjects from marl_factory_grid.environment.groups.mixins import PositionMixin -from marl_factory_grid.modules.machines.entitites import Machine + +from .entitites import Machine class Machines(PositionMixin, EnvObjects): diff --git a/marl_factory_grid/modules/machines/machine.png b/marl_factory_grid/modules/machines/machine.png new file mode 100644 index 0000000000000000000000000000000000000000..ba014588d87b367c00d7a5ee14588982418b6f61 GIT binary patch literal 8729 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 literal 0 HcmV?d00001 diff --git a/marl_factory_grid/modules/machines/rules.py b/marl_factory_grid/modules/machines/rules.py index 573eda8..04502b8 100644 --- a/marl_factory_grid/modules/machines/rules.py +++ b/marl_factory_grid/modules/machines/rules.py @@ -12,7 +12,7 @@ class MachineRule(Rule): super(MachineRule, self).__init__() self.n_machines = n_machines - def on_init(self, state): + def on_init(self, state, lvl_map): empty_tiles = state[c.FLOOR].empty_tiles[:self.n_machines] state[m.MACHINES].add_items(Machine(tile) for tile in empty_tiles) @@ -27,3 +27,9 @@ class MachineRule(Rule): def on_check_done(self, state) -> List[DoneResult]: pass + + +class DoneOnBreakRule(Rule): + + def on_check_done(self, state) -> List[DoneResult]: + pass \ No newline at end of file diff --git a/marl_factory_grid/modules/maintenance/__init__.py b/marl_factory_grid/modules/maintenance/__init__.py new file mode 100644 index 0000000..84da0db --- /dev/null +++ b/marl_factory_grid/modules/maintenance/__init__.py @@ -0,0 +1,2 @@ +from .entities import Maintainer +from .groups import Maintainers diff --git a/marl_factory_grid/modules/maintenance/constants.py b/marl_factory_grid/modules/maintenance/constants.py new file mode 100644 index 0000000..e0ab12c --- /dev/null +++ b/marl_factory_grid/modules/maintenance/constants.py @@ -0,0 +1,3 @@ +MAINTAINER = 'Maintainer' # TEMPLATE _identifier. Define your own! +MAINTAINERS = 'Maintainers' # TEMPLATE _identifier. Define your own! + diff --git a/marl_factory_grid/modules/maintenance/entities.py b/marl_factory_grid/modules/maintenance/entities.py new file mode 100644 index 0000000..546f8a5 --- /dev/null +++ b/marl_factory_grid/modules/maintenance/entities.py @@ -0,0 +1,102 @@ +import networkx as nx +import numpy as np + +from ...algorithms.static.utils import points_to_graph +from ...environment import constants as c +from ...environment.actions import Action, ALL_BASEACTIONS +from ...environment.entity.entity import Entity +from ..doors import constants as do +from ..maintenance import constants as mi +from ...utils.helpers import MOVEMAP +from ...utils.render import RenderEntity +from ...utils.states import Gamestate + + +class Maintainer(Entity): + + @property + def var_can_collide(self): + return True + + @property + def var_can_move(self): + return False + + @property + def var_is_blocking_light(self): + return False + + @property + def var_has_position(self): + return True + + def __init__(self, state: Gamestate, objective: str, action: Action, *args, **kwargs): + super().__init__(*args, **kwargs) + self.action = action + self.actions = [x() for x in ALL_BASEACTIONS] + self.objective = objective + self._path = None + self._next = [] + self._last = [] + self._last_serviced = 'None' + self._floortile_graph = points_to_graph(state[c.FLOOR].positions) + + def tick(self, state): + if found_objective := state[self.objective].by_pos(self.pos): + if found_objective.name != self._last_serviced: + self.action.do(self, state) + self._last_serviced = found_objective.name + else: + action = self.get_move_action(state) + return action.do(self, state) + else: + action = self.get_move_action(state) + return action.do(self, state) + + def get_move_action(self, state) -> Action: + if self._path is None or not self._path: + if not self._next: + self._next = list(state[self.objective].values()) + self._last = [] + self._last.append(self._next.pop()) + self._path = self.calculate_route(self._last[-1]) + + if door := self._door_is_close(): + if door.is_closed: + # Translate the action_object to an integer to have the same output as any other model + action = do.ACTION_DOOR_USE + else: + action = self._predict_move(state) + else: + action = self._predict_move(state) + # Translate the action_object to an integer to have the same output as any other model + try: + action_obj = next(x for x in self.actions if x.name == action) + except (StopIteration, UnboundLocalError): + print('Will not happen') + raise EnvironmentError + return action_obj + + def calculate_route(self, entity): + route = nx.shortest_path(self._floortile_graph, self.pos, entity.pos) + return route[1:] + + def _door_is_close(self): + try: + return next(y for x in self.tile.neighboring_floor for y in x.guests if do.DOOR in y.name) + except StopIteration: + return None + + def _predict_move(self, state): + next_pos = self._path[0] + if len(state[c.FLOOR].by_pos(next_pos).guests_that_can_collide) > 0: + action = c.NOOP + else: + next_pos = self._path.pop(0) + diff = np.subtract(next_pos, self.pos) + # Retrieve action based on the pos dif (like in: What do I have to do to get there?) + action = next(action for action, pos_diff in MOVEMAP.items() if np.all(diff == pos_diff)) + return action + + def render(self): + return RenderEntity(mi.MAINTAINER, self.pos) diff --git a/marl_factory_grid/modules/maintenance/groups.py b/marl_factory_grid/modules/maintenance/groups.py new file mode 100644 index 0000000..8f9b8b8 --- /dev/null +++ b/marl_factory_grid/modules/maintenance/groups.py @@ -0,0 +1,27 @@ +from typing import List + +from .entities import Maintainer +from marl_factory_grid.environment.entity.wall_floor import Floor +from marl_factory_grid.environment.groups.env_objects import EnvObjects +from marl_factory_grid.environment.groups.mixins import PositionMixin +from ..machines.actions import MachineAction +from ...utils.render import RenderEntity +from ...utils.states import Gamestate + +from ..machines import constants as mc +from . import constants as mi + + +class Maintainers(PositionMixin, EnvObjects): + + _entity = Maintainer + var_can_collide = True + var_can_move = True + var_is_blocking_light = False + var_has_position = True + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def spawn(self, tiles: List[Floor], state: Gamestate): + self.add_items([self._entity(state, mc.MACHINES, MachineAction(), tile) for tile in tiles]) diff --git a/marl_factory_grid/modules/maintenance/maintainer.png b/marl_factory_grid/modules/maintenance/maintainer.png new file mode 100644 index 0000000000000000000000000000000000000000..af4a37d40cfd03cfefc3371a2a0355fa9eef99a4 GIT binary patch literal 22988 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 VFqj3es~G42a?--qoMh^8<6rG}7CZm| literal 0 HcmV?d00001 diff --git a/marl_factory_grid/modules/maintenance/rewards.py b/marl_factory_grid/modules/maintenance/rewards.py new file mode 100644 index 0000000..425ac3b --- /dev/null +++ b/marl_factory_grid/modules/maintenance/rewards.py @@ -0,0 +1 @@ +MAINTAINER_COLLISION_REWARD = -5 \ No newline at end of file diff --git a/marl_factory_grid/modules/maintenance/rules.py b/marl_factory_grid/modules/maintenance/rules.py new file mode 100644 index 0000000..7cb2178 --- /dev/null +++ b/marl_factory_grid/modules/maintenance/rules.py @@ -0,0 +1,39 @@ +from typing import List +from marl_factory_grid.environment.rules import Rule +from marl_factory_grid.utils.results import TickResult, DoneResult +from marl_factory_grid.environment import constants as c +from . import rewards as r +from . import constants as M +from marl_factory_grid.utils.states import Gamestate + + +class MaintenanceRule(Rule): + + def __init__(self, n_maintainer: int = 1, *args, **kwargs): + super(MaintenanceRule, self).__init__(*args, **kwargs) + self.n_maintainer = n_maintainer + + def on_init(self, state: Gamestate, lvl_map): + state[M.MAINTAINERS].spawn(state[c.FLOOR].empty_tiles[:self.n_maintainer], state) + pass + + def tick_pre_step(self, state) -> List[TickResult]: + pass + + def tick_step(self, state) -> List[TickResult]: + for maintainer in state[M.MAINTAINERS]: + maintainer.tick(state) + return [] + + def tick_post_step(self, state) -> List[TickResult]: + pass + + def on_check_done(self, state) -> List[DoneResult]: + agents = list(state[c.AGENT].values()) + m_pos = state[M.MAINTAINERS].positions + done_results = [] + for agent in agents: + if agent.pos in m_pos: + done_results.append(DoneResult(entity=agent, validity=c.VALID, identifier=self.name, + reward=r.MAINTAINER_COLLISION_REWARD)) + return done_results diff --git a/marl_factory_grid/modules/zones/__init__.py b/marl_factory_grid/modules/zones/__init__.py new file mode 100644 index 0000000..7ae5f3c --- /dev/null +++ b/marl_factory_grid/modules/zones/__init__.py @@ -0,0 +1,3 @@ +from .entitites import Zone +from .groups import Zones +from .rules import AgentSingleZonePlacement diff --git a/marl_factory_grid/modules/zones/constants.py b/marl_factory_grid/modules/zones/constants.py new file mode 100644 index 0000000..135a471 --- /dev/null +++ b/marl_factory_grid/modules/zones/constants.py @@ -0,0 +1,4 @@ +# Names / Identifiers + +ZONES = 'Zones' # Identifier of Zone-objects and groups (groups). +ZONE = 'Zone' # -||- diff --git a/marl_factory_grid/modules/zones/entitites.py b/marl_factory_grid/modules/zones/entitites.py new file mode 100644 index 0000000..cd5aa21 --- /dev/null +++ b/marl_factory_grid/modules/zones/entitites.py @@ -0,0 +1,21 @@ +import random +from typing import List + +from marl_factory_grid.environment.entity.entity import Entity +from marl_factory_grid.environment.entity.object import Object +from marl_factory_grid.environment.entity.wall_floor import Floor +from marl_factory_grid.utils.render import RenderEntity +from marl_factory_grid.environment import constants as c + +from marl_factory_grid.modules.doors import constants as d + + +class Zone(Object): + + def __init__(self, tiles: List[Floor], *args, **kwargs): + super(Zone, self).__init__(*args, **kwargs) + self.tiles = tiles + + @property + def random_tile(self): + return random.choice(self.tiles) diff --git a/marl_factory_grid/modules/zones/groups.py b/marl_factory_grid/modules/zones/groups.py new file mode 100644 index 0000000..a26b3a4 --- /dev/null +++ b/marl_factory_grid/modules/zones/groups.py @@ -0,0 +1,12 @@ +from marl_factory_grid.environment.groups.objects import Objects +from marl_factory_grid.modules.zones import Zone + + +class Zones(Objects): + + symbol = None + _entity = Zone + var_can_move = False + + def __init__(self, *args, **kwargs): + super(Zones, self).__init__(*args, can_collide=True, **kwargs) diff --git a/marl_factory_grid/modules/zones/rules.py b/marl_factory_grid/modules/zones/rules.py new file mode 100644 index 0000000..8df8be2 --- /dev/null +++ b/marl_factory_grid/modules/zones/rules.py @@ -0,0 +1,33 @@ +from random import choices + +from marl_factory_grid.environment.rules import Rule +from marl_factory_grid.environment import constants as c +from marl_factory_grid.modules.zones import Zone +from . import constants as z + + +class AgentSingleZonePlacement(Rule): + + def __init__(self, n_zones=3): + super().__init__() + self.n_zones = n_zones + + def on_init(self, state, lvl_map): + zones = [] + + for z_idx in range(1, self.n_zones): + zone_positions = lvl_map.get_coordinates_for_symbol(z_idx) + assert len(zone_positions) + zones.append(Zone([state[c.FLOOR].by_pos(pos) for pos in zone_positions])) + state[z.ZONES].add_items(zones) + + n_agents = len(state[c.AGENT]) + assert len(state[z.ZONES]) >= n_agents + + z_idxs = choices(list(range(len(state[z.ZONES]))), k=n_agents) + for agent in state[c.AGENT]: + agent.move(state[z.ZONES][z_idxs.pop()].random_tile) + return [] + + def tick_step(self, state): + return [] diff --git a/marl_factory_grid/quickstart.py b/marl_factory_grid/quickstart.py index 3b96e2a..7e44610 100644 --- a/marl_factory_grid/quickstart.py +++ b/marl_factory_grid/quickstart.py @@ -10,10 +10,10 @@ def init(): ce = ConfigExplainer() cwd = Path(os.getcwd()) ce.save_all(cwd / 'full_config.yaml') - template_path = Path(__file__) / 'marl_factory_grid' / 'modules' / '_template' + template_path = Path(__file__).parent / 'modules' / '_template' print(f'Available config options saved to: {(cwd / "full_config.yaml").resolve()}') print('-----------------------------') print(f'Copying Templates....') shutil.copytree(template_path, cwd) - print(f'Templates copied to {template_path.resolve()}') + print(f'Templates copied to {cwd}"/"{template_path.name}') print(':wave:') diff --git a/marl_factory_grid/utils/config_parser.py b/marl_factory_grid/utils/config_parser.py index 2bf8639..9ac8234 100644 --- a/marl_factory_grid/utils/config_parser.py +++ b/marl_factory_grid/utils/config_parser.py @@ -18,11 +18,11 @@ class FactoryConfigParser(object): default_entites = [] default_rules = ['MaxStepsReached', 'Collision'] default_actions = [c.MOVE8, c.NOOP] - default_observations = [c.WALLS, c.AGENTS] + default_observations = [c.WALLS, c.AGENT] def __init__(self, config_path, custom_modules_path: Union[None, PathLike] = None): self.config_path = Path(config_path) - self.custom_modules_path = Path(config_path) if custom_modules_path is not None else custom_modules_path + self.custom_modules_path = Path(custom_modules_path) if custom_modules_path is not None else custom_modules_path self.config = yaml.safe_load(self.config_path.open()) self.do_record = False @@ -69,12 +69,20 @@ class FactoryConfigParser(object): for entity in entities: try: - folder_path = MODULE_PATH if entity not in self.default_entites else DEFAULT_PATH - folder_path = (Path(__file__) / '..' / '..' / '..' / folder_path) - entity_class = locate_and_import_class(entity, folder_path) - except AttributeError: - folder_path = self.custom_modules_path + folder_path = Path(__file__).parent.parent / DEFAULT_PATH entity_class = locate_and_import_class(entity, folder_path) + except AttributeError as e1: + try: + folder_path = Path(__file__).parent.parent / MODULE_PATH + entity_class = locate_and_import_class(entity, folder_path) + except AttributeError as e2: + try: + folder_path = self.custom_modules_path + entity_class = locate_and_import_class(entity, folder_path) + except AttributeError as e3: + ents = [y for x in [e1.argss[1], e2.argss[1], e3.argss[1]] for y in x] + raise AttributeError(e1.argss[0], e2.argss[0], e3.argss[0], 'Possible Entitys are>:', str(ents)) + entity_kwargs = self.entities.get(entity, {}) entity_symbol = entity_class.symbol if hasattr(entity_class, 'symbol') else None entity_classes.update({entity: {'class': entity_class, 'kwargs': entity_kwargs, 'symbol': entity_symbol}}) @@ -92,7 +100,7 @@ class FactoryConfigParser(object): parsed_actions = list() for action in actions: folder_path = MODULE_PATH if action not in base_env_actions else DEFAULT_PATH - folder_path = (Path(__file__) / '..' / '..' / '..' / folder_path) + folder_path = Path(__file__).parent.parent / folder_path try: class_or_classes = locate_and_import_class(action, folder_path) except AttributeError: @@ -124,12 +132,15 @@ class FactoryConfigParser(object): rules.extend(x for x in self.rules if x != c.DEFAULTS) for rule in rules: - folder_path = MODULE_PATH if rule not in self.default_rules else DEFAULT_PATH - folder_path = (Path(__file__) / '..' / '..' / '..' / folder_path) try: + folder_path = (Path(__file__).parent.parent / DEFAULT_PATH) rule_class = locate_and_import_class(rule, folder_path) except AttributeError: - rule_class = locate_and_import_class(rule, self.custom_modules_path) + try: + folder_path = (Path(__file__).parent.parent / MODULE_PATH) + rule_class = locate_and_import_class(rule, folder_path) + except AttributeError: + rule_class = locate_and_import_class(rule, self.custom_modules_path) rule_kwargs = self.rules.get(rule, {}) rules_classes.update({rule: {'class': rule_class, 'kwargs': rule_kwargs}}) return rules_classes diff --git a/marl_factory_grid/utils/helpers.py b/marl_factory_grid/utils/helpers.py index c1d469e..ca3a20c 100644 --- a/marl_factory_grid/utils/helpers.py +++ b/marl_factory_grid/utils/helpers.py @@ -176,7 +176,7 @@ def one_hot_level(level, symbol: str): grid = np.array(level) binary_grid = np.zeros(grid.shape, dtype=np.int8) - binary_grid[grid == symbol] = c.VALUE_OCCUPIED_CELL + binary_grid[grid == str(symbol)] = c.VALUE_OCCUPIED_CELL return binary_grid @@ -222,18 +222,15 @@ def locate_and_import_class(class_name, folder_path: Union[str, PurePath] = ''): for module_path in module_paths: module_parts = [x.replace('.py', '') for idx, x in enumerate(module_path.parts) if idx >= package_pos] mod = importlib.import_module('.'.join(module_parts)) - all_found_modules.extend([x for x in dir(mod) if not(x.startswith('__') or len(x) < 2 or x.isupper()) - and x not in ['Entity', 'NamedTuple', 'List', 'Rule', 'Union', 'random', 'Floor' - 'TickResult', 'ActionResult', 'Action', 'Agent', 'deque', - 'BoundEntityMixin', 'RenderEntity', 'TemplateRule', 'defaultdict', - 'is_move', 'Objects', 'PositionMixin', 'IsBoundMixin', 'EnvObject', - 'EnvObjects', 'Dict', 'locate_and_import_class', 'yaml', 'Any', - 'inspect']]) + all_found_modules.extend([x for x in dir(mod) if (not(x.startswith('__') or len(x) <= 2) and x.istitle()) + and x not in ['Entity', 'NamedTuple', 'List', 'Rule', 'Union', 'Floor' + 'TickResult', 'ActionResult', 'Action', 'Agent', 'BoundEntityMixin', + 'RenderEntity', 'TemplateRule', 'Objects', 'PositionMixin', + 'IsBoundMixin', 'EnvObject', 'EnvObjects', 'Dict', 'Any' + ]]) try: model_class = mod.__getattribute__(class_name) return model_class except AttributeError: continue - raise AttributeError(f'Class "{class_name}" was not found!!!"\n' - f'Check the {folder_path.name} name.\n' - f'Possible Options are:\n{set(all_found_modules)}') + raise AttributeError(f'Class "{class_name}" was not found in "{folder_path.name}"', list(set(all_found_modules))) diff --git a/marl_factory_grid/utils/level_parser.py b/marl_factory_grid/utils/level_parser.py index e603a63..69bcaeb 100644 --- a/marl_factory_grid/utils/level_parser.py +++ b/marl_factory_grid/utils/level_parser.py @@ -24,31 +24,40 @@ class LevelParser(object): self.level_shape = level_array.shape self.size = self.pomdp_r**2 if self.pomdp_r else np.prod(self.level_shape) + def get_coordinates_for_symbol(self, symbol, negate=False): + level_array = h.one_hot_level(self._parsed_level, symbol) + if negate: + return np.argwhere(level_array != c.VALUE_OCCUPIED_CELL) + else: + return np.argwhere(level_array == c.VALUE_OCCUPIED_CELL) + def do_init(self): entities = Entities() # Walls - level_array = h.one_hot_level(self._parsed_level, c.SYMBOL_WALL) - - walls = Walls.from_coordinates(np.argwhere(level_array == c.VALUE_OCCUPIED_CELL), self.size) + walls = Walls.from_coordinates(self.get_coordinates_for_symbol(c.SYMBOL_WALL), self.size) entities.add_items({c.WALL: walls}) # Floor - floor = Floors.from_coordinates(np.argwhere(level_array == c.VALUE_FREE_CELL), self.size) + floor = Floors.from_coordinates(self.get_coordinates_for_symbol(c.SYMBOL_WALL, negate=True), self.size) entities.add_items({c.FLOOR: floor}) # All other for es_name in self.e_p_dict: e_class, e_kwargs = self.e_p_dict[es_name]['class'], self.e_p_dict[es_name]['kwargs'] - if hasattr(e_class, 'symbol'): - level_array = h.one_hot_level(self._parsed_level, symbol=e_class.symbol) - if np.any(level_array): - e = e_class.from_coordinates(np.argwhere(level_array == c.VALUE_OCCUPIED_CELL).tolist(), - entities[c.FLOOR], self.size, entity_kwargs=e_kwargs - ) - else: - raise ValueError(f'No {e_class} (Symbol: {e_class.symbol}) could be found!\n' - f'Check your level file!') + if hasattr(e_class, 'symbol') and e_class.symbol is not None: + symbols = e_class.symbol + if isinstance(symbols, (str, int, float)): + symbols = [symbols] + for symbol in symbols: + level_array = h.one_hot_level(self._parsed_level, symbol=symbol) + if np.any(level_array): + e = e_class.from_coordinates(np.argwhere(level_array == c.VALUE_OCCUPIED_CELL).tolist(), + entities[c.FLOOR], self.size, entity_kwargs=e_kwargs + ) + else: + raise ValueError(f'No {e_class} (Symbol: {e_class.symbol}) could be found!\n' + f'Check your level file!') else: e = e_class(self.size, **e_kwargs) entities.add_items({e.name: e}) diff --git a/marl_factory_grid/utils/observation_builder.py b/marl_factory_grid/utils/observation_builder.py index 6f6717d..ed725cc 100644 --- a/marl_factory_grid/utils/observation_builder.py +++ b/marl_factory_grid/utils/observation_builder.py @@ -6,11 +6,10 @@ from typing import Dict, List import numpy as np from numba import njit +from marl_factory_grid.environment import constants as c from marl_factory_grid.environment.groups.utils import Combined from marl_factory_grid.utils.states import Gamestate -from marl_factory_grid.environment import constants as c - class OBSBuilder(object): @@ -111,10 +110,10 @@ class OBSBuilder(object): e = next(x for x in self.all_obs if l_name in x and agent.name in x) except StopIteration: raise KeyError( - f'Check typing!\n{l_name} could not be found in:\n{dict(self.all_obs).keys()}') + f'Check typing! {l_name} could not be found in: {list(dict(self.all_obs).keys())}') try: - positional = e.has_position + positional = e.var_has_position except AttributeError: positional = False if positional: @@ -172,7 +171,7 @@ class OBSBuilder(object): obs_layers.append(combined.name) elif obs_str == c.OTHERS: obs_layers.extend([x for x in self.all_obs if x != agent.name and x.startswith(f'{c.AGENT}[')]) - elif obs_str == c.AGENTS: + elif obs_str == c.AGENT: obs_layers.extend([x for x in self.all_obs if x.startswith(f'{c.AGENT}[')]) else: obs_layers.append(obs_str) @@ -222,7 +221,7 @@ class RayCaster: entities_hit = entities.pos_dict[(x, y)] hits = self.ray_block_cache(cache_blocking, (x, y), - lambda: any(e.is_blocking_light for e in entities_hit), + lambda: any(e.var_is_blocking_light for e in entities_hit), entities) try: @@ -237,8 +236,8 @@ class RayCaster: self.ray_block_cache( cache_blocking, key, - # lambda: all(False for e in entities.pos_dict[key] if not e.is_blocking_light), - lambda: any(e.is_blocking_light for e in entities.pos_dict[key]), + # lambda: all(False for e in entities.pos_dict[key] if not e.var_is_blocking_light), + lambda: any(e.var_is_blocking_light for e in entities.pos_dict[key]), entities) for key in ((x, y-cy), (x-cx, y)) ]) if (cx != 0 and cy != 0) else False diff --git a/marl_factory_grid/utils/renderer.py b/marl_factory_grid/utils/renderer.py index 84392d0..38a8e22 100644 --- a/marl_factory_grid/utils/renderer.py +++ b/marl_factory_grid/utils/renderer.py @@ -27,13 +27,13 @@ class Renderer: BG_COLOR = (178, 190, 195) # (99, 110, 114) WHITE = (223, 230, 233) # (200, 200, 200) AGENT_VIEW_COLOR = (9, 132, 227) - ASSETS = Path(__file__).parent.parent / 'assets' - MODULE_ASSETS = Path(__file__).parent.parent.parent / 'modules' + ASSETS = Path(__file__).parent.parent def __init__(self, lvl_shape: Tuple[int, int] = (16, 16), lvl_padded_shape: Union[Tuple[int, int], None] = None, cell_size: int = 40, fps: int = 7, grid_lines: bool = True, view_radius: int = 2): + # TODO: Customn_assets paths self.grid_h, self.grid_w = lvl_shape self.lvl_padded_shape = lvl_padded_shape if lvl_padded_shape is not None else lvl_shape self.cell_size = cell_size @@ -44,7 +44,7 @@ class Renderer: self.screen_size = (self.grid_w*cell_size, self.grid_h*cell_size) self.screen = pygame.display.set_mode(self.screen_size) self.clock = pygame.time.Clock() - assets = list(self.ASSETS.rglob('*.png')) + list(self.MODULE_ASSETS.rglob('*.png')) + assets = list(self.ASSETS.rglob('*.png')) self.assets = {path.stem: self.load_asset(str(path), 1) for path in assets} self.fill_bg() diff --git a/marl_factory_grid/utils/results.py b/marl_factory_grid/utils/results.py index 0b5a214..9f0fa38 100644 --- a/marl_factory_grid/utils/results.py +++ b/marl_factory_grid/utils/results.py @@ -1,8 +1,6 @@ from typing import Union from dataclasses import dataclass -from marl_factory_grid.environment.entity.entity import Entity - TYPE_VALUE = 'value' TYPE_REWARD = 'reward' types = [TYPE_VALUE, TYPE_REWARD] @@ -20,7 +18,7 @@ class Result: validity: bool reward: Union[float, None] = None value: Union[float, None] = None - entity: Union[Entity, None] = None + entity: None = None def get_infos(self): n = self.entity.name if self.entity is not None else "Global" diff --git a/marl_factory_grid/utils/states.py b/marl_factory_grid/utils/states.py index caee1a4..4eff5a7 100644 --- a/marl_factory_grid/utils/states.py +++ b/marl_factory_grid/utils/states.py @@ -2,10 +2,11 @@ from typing import List, Dict import numpy as np + +from marl_factory_grid.environment import constants as c from marl_factory_grid.environment.entity.wall_floor import Floor from marl_factory_grid.environment.rules import Rule from marl_factory_grid.utils.results import Result -from marl_factory_grid.environment import constants as c class StepRules: @@ -26,9 +27,9 @@ class StepRules: self.rules.append(item) return True - def do_all_init(self, state): + def do_all_init(self, state, lvl_map): for rule in self.rules: - if rule_init_printline := rule.on_init(state): + if rule_init_printline := rule.on_init(state, lvl_map): state.print(rule_init_printline) return c.VALID @@ -58,7 +59,7 @@ class Gamestate(object): @property def moving_entites(self): - return [y for x in self.entities for y in x if x.can_move] + return [y for x in self.entities for y in x if x.var_can_move] def __init__(self, entitites, rules: Dict[str, dict], env_seed=69, verbose=False): self.entities = entitites @@ -107,6 +108,6 @@ class Gamestate(object): def get_all_tiles_with_collisions(self) -> List[Floor]: tiles = [self[c.FLOOR].by_pos(pos) for pos, e in self.entities.pos_dict.items() - if sum([x.can_collide for x in e]) > 1] + if sum([x.var_can_collide for x in e]) > 1] # tiles = [x for x in self[c.FLOOR] if len(x.guests_that_can_collide) > 1] return tiles diff --git a/marl_factory_grid/utils/tools.py b/marl_factory_grid/utils/tools.py index eff3e01..7fc5aa5 100644 --- a/marl_factory_grid/utils/tools.py +++ b/marl_factory_grid/utils/tools.py @@ -15,7 +15,7 @@ ENTITIES = 'Objects' OBSERVATIONS = 'Observations' RULES = 'Rule' ASSETS = 'Assets' -EXCLUDED = ['identifier', 'args', 'kwargs', 'Move', +EXCLUDED = ['identifier', 'args', 'kwargs', 'Move', 'Floor', 'Agent', 'GlobalPositions', 'Walls', 'TemplateRule', 'Entities', 'EnvObjects', 'Zones', ] diff --git a/marl_factory_grid/utils/utility_classes.py b/marl_factory_grid/utils/utility_classes.py index 302535a..8fee782 100644 --- a/marl_factory_grid/utils/utility_classes.py +++ b/marl_factory_grid/utils/utility_classes.py @@ -1,21 +1,6 @@ import gymnasium as gym -class EnvCombiner(object): - - def __init__(self, *envs_cls): - self._env_dict = {env_cls.__name__: env_cls for env_cls in envs_cls} - - @staticmethod - def combine_cls(name, *envs_cls): - return type(name, envs_cls, {}) - - def build(self): - name = f'{"".join([x.lower().replace("factory").capitalize() for x in self._env_dict.keys()])}Factory' - - return self.combine_cls(name, tuple(self._env_dict.values())) - - class MarlFrameStack(gym.ObservationWrapper): """todo @romue404""" def __init__(self, env): diff --git a/reload_agent.py b/reload_agent.py index ddac858..99cddc8 100644 --- a/reload_agent.py +++ b/reload_agent.py @@ -3,7 +3,7 @@ from pathlib import Path import yaml -from marl_factory_grid.environment.factory import BaseFactory +from marl_factory_grid.environment.factory import Factory from marl_factory_grid.logging.envmonitor import EnvMonitor from marl_factory_grid.logging.recorder import EnvRecorder @@ -41,7 +41,7 @@ if __name__ == '__main__': pass # Init Env - with BaseFactory(**env_kwargs) as env: + with Factory(**env_kwargs) as env: env = EnvMonitor(env) env = EnvRecorder(env) if record else env obs_shape = env.observation_space.shape diff --git a/setup.py b/setup.py index 4749beb..2ec794c 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ long_description = (this_directory / "README.md").read_text() setup(name='Marl-Factory-Grid', - version='0.0.11', + version='0.0.12', description='A framework to research MARL agents in various setings.', author='Steffen Illium', author_email='steffen.illium@ifi.lmu.de',