Rework of Observations and Entity Differentiation, lazy obs build by notification
This commit is contained in:
@ -1,7 +1,9 @@
|
||||
import abc
|
||||
import enum
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from enum import Enum
|
||||
from itertools import chain
|
||||
from pathlib import Path
|
||||
from typing import List, Union, Iterable, Dict
|
||||
import numpy as np
|
||||
@ -14,7 +16,8 @@ from environments.factory.base.shadow_casting import Map
|
||||
from environments.helpers import Constants as c, Constants
|
||||
from environments import helpers as h
|
||||
from environments.factory.base.objects import Agent, Tile, Action
|
||||
from environments.factory.base.registers import Actions, Entities, Agents, Doors, FloorTiles, WallTiles, PlaceHolders
|
||||
from environments.factory.base.registers import Actions, Entities, Agents, Doors, FloorTiles, WallTiles, PlaceHolders, \
|
||||
GlobalPositions
|
||||
from environments.utility_classes import MovementProperties, ObservationProperties, MarlFrameStack
|
||||
from environments.utility_classes import AgentRenderOptions as a_obs
|
||||
|
||||
@ -30,17 +33,31 @@ class BaseFactory(gym.Env):
|
||||
def action_space(self):
|
||||
return spaces.Discrete(len(self._actions))
|
||||
|
||||
@property
|
||||
def named_action_space(self):
|
||||
return {x.identifier.value: idx for idx, x in enumerate(self._actions.values())}
|
||||
|
||||
@property
|
||||
def observation_space(self):
|
||||
if r := self._pomdp_r:
|
||||
z = self._obs_cube.shape[0]
|
||||
xy = r*2 + 1
|
||||
level_shape = (z, xy, xy)
|
||||
obs, _ = self._build_observations()
|
||||
if self.n_agents > 1:
|
||||
shape = obs[0].shape
|
||||
else:
|
||||
level_shape = self._obs_cube.shape
|
||||
space = spaces.Box(low=0, high=1, shape=level_shape, dtype=np.float32)
|
||||
shape = obs.shape
|
||||
space = spaces.Box(low=0, high=1, shape=shape, dtype=np.float32)
|
||||
return space
|
||||
|
||||
@property
|
||||
def named_observation_space(self):
|
||||
# Build it
|
||||
_, named_obs = self._build_observations()
|
||||
if self.n_agents > 1:
|
||||
# Only return the first named obs space, as their structure at the moment is same.
|
||||
return [{key.name: val for key, val in named_ob.items()} for named_ob in named_obs.values()][0]
|
||||
else:
|
||||
return {key.name: val for key, val in named_obs.items()}
|
||||
|
||||
|
||||
@property
|
||||
def pomdp_diameter(self):
|
||||
return self._pomdp_r * 2 + 1
|
||||
@ -86,11 +103,14 @@ class BaseFactory(gym.Env):
|
||||
self.obs_prop = obs_prop
|
||||
self.level_name = level_name
|
||||
self._level_shape = None
|
||||
self._obs_shape = None
|
||||
self.verbose = verbose
|
||||
self._renderer = None # expensive - don't use it when not required !
|
||||
self._entities = Entities()
|
||||
|
||||
self.n_agents = n_agents
|
||||
level_filepath = Path(__file__).parent.parent / h.LEVELS_DIR / f'{self.level_name}.txt'
|
||||
self._parsed_level = h.parse_level(level_filepath)
|
||||
|
||||
self.max_steps = max_steps
|
||||
self._pomdp_r = self.obs_prop.pomdp_r
|
||||
@ -114,10 +134,12 @@ class BaseFactory(gym.Env):
|
||||
# Objects
|
||||
self._entities = Entities()
|
||||
# Level
|
||||
level_filepath = Path(__file__).parent.parent / h.LEVELS_DIR / f'{self.level_name}.txt'
|
||||
parsed_level = h.parse_level(level_filepath)
|
||||
level_array = h.one_hot_level(parsed_level)
|
||||
|
||||
level_array = h.one_hot_level(self._parsed_level)
|
||||
level_array = np.pad(level_array, self.obs_prop.pomdp_r, 'constant', constant_values=1)
|
||||
|
||||
self._level_shape = level_array.shape
|
||||
self._obs_shape = self._level_shape if not self.obs_prop.pomdp_r else (self.pomdp_diameter, ) * 2
|
||||
|
||||
# Walls
|
||||
walls = WallTiles.from_argwhere_coordinates(
|
||||
@ -134,13 +156,14 @@ class BaseFactory(gym.Env):
|
||||
self._entities.register_additional_items({c.FLOOR: floor})
|
||||
|
||||
# NOPOS
|
||||
self._NO_POS_TILE = Tile(c.NO_POS.value)
|
||||
self._NO_POS_TILE = Tile(c.NO_POS.value, None)
|
||||
|
||||
# Doors
|
||||
if self.parse_doors:
|
||||
parsed_doors = h.one_hot_level(parsed_level, c.DOOR)
|
||||
parsed_doors = h.one_hot_level(self._parsed_level, c.DOOR)
|
||||
parsed_doors = np.pad(parsed_doors, self.obs_prop.pomdp_r, 'constant', constant_values=0)
|
||||
if np.any(parsed_doors):
|
||||
door_tiles = [floor.by_pos(pos) for pos in np.argwhere(parsed_doors == c.OCCUPIED_CELL.value)]
|
||||
door_tiles = [floor.by_pos(tuple(pos)) for pos in np.argwhere(parsed_doors == c.OCCUPIED_CELL.value)]
|
||||
doors = Doors.from_tiles(door_tiles, self._level_shape,
|
||||
entity_kwargs=dict(context=floor)
|
||||
)
|
||||
@ -153,12 +176,11 @@ class BaseFactory(gym.Env):
|
||||
|
||||
# Agents
|
||||
agents_to_spawn = self.n_agents-len(self._injected_agents)
|
||||
agents_kwargs = dict(level_shape=self._level_shape,
|
||||
individual_slices=self.obs_prop.render_agents == a_obs.SEPERATE,
|
||||
hide_from_obs_builder=self.obs_prop.render_agents == a_obs.LEVEL,
|
||||
is_observable=self.obs_prop.render_agents != a_obs.NOT)
|
||||
agents_kwargs = dict(individual_slices=self.obs_prop.render_agents == a_obs.SEPERATE,
|
||||
hide_from_obs_builder=self.obs_prop.render_agents in [a_obs.NOT, a_obs.LEVEL],
|
||||
)
|
||||
if agents_to_spawn:
|
||||
agents = Agents.from_tiles(floor.empty_tiles[:agents_to_spawn], **agents_kwargs)
|
||||
agents = Agents.from_tiles(floor.empty_tiles[:agents_to_spawn], self._level_shape, **agents_kwargs)
|
||||
else:
|
||||
agents = Agents(**agents_kwargs)
|
||||
if self._injected_agents:
|
||||
@ -173,10 +195,10 @@ class BaseFactory(gym.Env):
|
||||
# TODO: Make this accept Lists for multiple placeholders
|
||||
|
||||
# Empty Observations with either [0, 1, N(0, 1)]
|
||||
placeholder = PlaceHolders.from_tiles([self._NO_POS_TILE], self._level_shape,
|
||||
entity_kwargs=dict(
|
||||
fill_value=self.obs_prop.additional_agent_placeholder)
|
||||
)
|
||||
placeholder = PlaceHolders.from_values(self.obs_prop.additional_agent_placeholder, self._level_shape,
|
||||
entity_kwargs=dict(
|
||||
fill_value=self.obs_prop.additional_agent_placeholder)
|
||||
)
|
||||
|
||||
self._entities.register_additional_items({c.AGENT_PLACEHOLDER: placeholder})
|
||||
|
||||
@ -184,24 +206,22 @@ class BaseFactory(gym.Env):
|
||||
if additional_entities := self.additional_entities:
|
||||
self._entities.register_additional_items(additional_entities)
|
||||
|
||||
if self.obs_prop.show_global_position_info:
|
||||
global_positions = GlobalPositions(self._level_shape)
|
||||
obs_shape_2d = self._level_shape if not self._pomdp_r else ((self.pomdp_diameter,) * 2)
|
||||
global_positions.spawn_GlobalPositionObjects(obs_shape_2d, self[c.AGENT])
|
||||
self._entities.register_additional_items({c.GLOBAL_POSITION: global_positions})
|
||||
|
||||
# Return
|
||||
return self._entities
|
||||
|
||||
def _init_obs_cube(self):
|
||||
arrays = self._entities.obs_arrays
|
||||
|
||||
obs_cube_z = sum([a.shape[0] if not self[key].is_per_agent else 1 for key, a in arrays.items()])
|
||||
obs_cube_z += 1 if self.obs_prop.show_global_position_info else 0
|
||||
self._obs_cube = np.zeros((obs_cube_z, *self._level_shape), dtype=np.float32)
|
||||
|
||||
def reset(self) -> (np.ndarray, int, bool, dict):
|
||||
def reset(self) -> (np.typing.ArrayLike, int, bool, dict):
|
||||
_ = self._base_init_env()
|
||||
self._init_obs_cube()
|
||||
self.do_additional_reset()
|
||||
|
||||
self._steps = 0
|
||||
|
||||
obs = self._get_observations()
|
||||
obs, _ = self._build_observations()
|
||||
return obs
|
||||
|
||||
def step(self, actions):
|
||||
@ -264,7 +284,7 @@ class BaseFactory(gym.Env):
|
||||
# Post step Hook for later use
|
||||
info.update(self.hook_post_step())
|
||||
|
||||
obs = self._get_observations()
|
||||
obs, _ = self._build_observations()
|
||||
|
||||
return obs, reward, done, info
|
||||
|
||||
@ -284,141 +304,120 @@ class BaseFactory(gym.Env):
|
||||
else:
|
||||
return c.NOT_VALID
|
||||
|
||||
def _get_observations(self) -> np.ndarray:
|
||||
state_array_dict = self._entities.obs_arrays
|
||||
if self.n_agents == 1:
|
||||
obs = self._build_per_agent_obs(self[c.AGENT][0], state_array_dict)
|
||||
elif self.n_agents >= 2:
|
||||
obs = np.stack([self._build_per_agent_obs(agent, state_array_dict) for agent in self[c.AGENT]])
|
||||
else:
|
||||
raise ValueError('n_agents cannot be smaller than 1!!')
|
||||
return obs
|
||||
def _build_observations(self) -> np.typing.ArrayLike:
|
||||
# Observation dict:
|
||||
per_agent_expl_idx = dict()
|
||||
per_agent_obsn = dict()
|
||||
# Generel Observations
|
||||
lvl_obs = self[c.WALLS].as_array()
|
||||
door_obs = self[c.DOORS].as_array()
|
||||
agent_obs = self[c.AGENT].as_array() if self.obs_prop.render_agents != a_obs.NOT else None
|
||||
placeholder_obs = self[c.AGENT_PLACEHOLDER].as_array() if self[c.AGENT_PLACEHOLDER] else None
|
||||
add_obs_dict = self._additional_observations()
|
||||
|
||||
def _build_per_agent_obs(self, agent: Agent, state_array_dict) -> np.ndarray:
|
||||
agent_pos_is_omitted = False
|
||||
agent_omit_idx = None
|
||||
|
||||
if self.obs_prop.omit_agent_self and self.n_agents == 1:
|
||||
pass
|
||||
elif self.obs_prop.omit_agent_self and self.obs_prop.render_agents in [a_obs.COMBINED, ] and self.n_agents > 1:
|
||||
state_array_dict[c.AGENT][0, agent.x, agent.y] -= agent.encoding
|
||||
agent_pos_is_omitted = True
|
||||
elif self.obs_prop.omit_agent_self and self.obs_prop.render_agents == a_obs.SEPERATE and self.n_agents > 1:
|
||||
agent_omit_idx = next((i for i, a in enumerate(self[c.AGENT]) if a == agent))
|
||||
|
||||
running_idx, shadowing_idxs, can_be_shadowed_idxs = 0, [], []
|
||||
self._obs_cube[:] = 0
|
||||
|
||||
# FIXME: Refactor this! Make a globally build observation, then add individual per-agent-obs
|
||||
for key, array in state_array_dict.items():
|
||||
# Flush state array object representation to obs cube
|
||||
if not self[key].hide_from_obs_builder:
|
||||
if self[key].is_per_agent:
|
||||
per_agent_idx = self[key].idx_by_entity(agent)
|
||||
z = 1
|
||||
self._obs_cube[running_idx: running_idx+z] = array[per_agent_idx]
|
||||
else:
|
||||
if key == c.AGENT and agent_omit_idx is not None:
|
||||
z = array.shape[0] - 1
|
||||
for array_idx in range(array.shape[0]):
|
||||
self._obs_cube[running_idx: running_idx+z] = array[[x for x in range(array.shape[0])
|
||||
if x != agent_omit_idx]]
|
||||
# Agent OBS are combined
|
||||
elif key == c.AGENT and self.obs_prop.omit_agent_self \
|
||||
and self.obs_prop.render_agents == a_obs.COMBINED:
|
||||
z = 1
|
||||
self._obs_cube[running_idx: running_idx + z] = array
|
||||
# Each Agent is rendered on a seperate array slice
|
||||
for agent_idx, agent in enumerate(self[c.AGENT]):
|
||||
obs_dict = dict()
|
||||
# Build Agent Observations
|
||||
if self.obs_prop.render_agents != a_obs.NOT:
|
||||
if self.obs_prop.omit_agent_self:
|
||||
if self.obs_prop.render_agents == a_obs.SEPERATE:
|
||||
agent_obs = np.take(agent_obs, [x for x in range(self.n_agents) if x != agent_idx], axis=0)
|
||||
else:
|
||||
z = array.shape[0]
|
||||
self._obs_cube[running_idx: running_idx + z] = array
|
||||
# Define which OBS SLices cast a Shadow
|
||||
if self[key].is_blocking_light:
|
||||
for i in range(z):
|
||||
shadowing_idxs.append(running_idx + i)
|
||||
# Define which OBS SLices are effected by shadows
|
||||
if self[key].can_be_shadowed:
|
||||
for i in range(z):
|
||||
can_be_shadowed_idxs.append(running_idx + i)
|
||||
running_idx += z
|
||||
agent_obs = agent_obs.copy()
|
||||
agent_obs[(0, *agent.pos)] -= agent.encoding
|
||||
|
||||
if agent_pos_is_omitted:
|
||||
state_array_dict[c.AGENT][0, agent.x, agent.y] += agent.encoding
|
||||
|
||||
if self._pomdp_r:
|
||||
obs = self._do_pomdp_obs_cutout(agent, self._obs_cube)
|
||||
else:
|
||||
obs = self._obs_cube
|
||||
|
||||
obs = obs.copy()
|
||||
|
||||
if self.obs_prop.cast_shadows:
|
||||
obs_block_light = [obs[idx] != c.OCCUPIED_CELL.value for idx in shadowing_idxs]
|
||||
door_shadowing = False
|
||||
if self.parse_doors:
|
||||
if doors := self[c.DOORS]:
|
||||
if door := doors.by_pos(agent.pos):
|
||||
if door.is_closed:
|
||||
for group in door.connectivity_subgroups:
|
||||
if agent.last_pos not in group:
|
||||
door_shadowing = True
|
||||
if self._pomdp_r:
|
||||
blocking = [tuple(np.subtract(x, agent.pos) + (self._pomdp_r, self._pomdp_r))
|
||||
for x in group]
|
||||
xs, ys = zip(*blocking)
|
||||
else:
|
||||
xs, ys = zip(*group)
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
obs_block_light[0][xs, ys] = False
|
||||
|
||||
light_block_map = Map((np.prod(obs_block_light, axis=0) != True).astype(int))
|
||||
if self._pomdp_r:
|
||||
light_block_map = light_block_map.do_fov(self._pomdp_r, self._pomdp_r, max(self._level_shape))
|
||||
else:
|
||||
light_block_map = light_block_map.do_fov(*agent.pos, max(self._level_shape))
|
||||
if door_shadowing:
|
||||
# noinspection PyUnboundLocalVariable
|
||||
light_block_map[xs, ys] = 0
|
||||
agent.temp_light_map = light_block_map
|
||||
for obs_idx in can_be_shadowed_idxs:
|
||||
obs[obs_idx] = ((obs[obs_idx] * light_block_map) + 0.) - (1 - light_block_map) # * obs[0])
|
||||
else:
|
||||
pass
|
||||
|
||||
# Agents observe other agents as wall
|
||||
if self.obs_prop.render_agents == a_obs.LEVEL and self.n_agents > 1:
|
||||
other_agent_obs = self[c.AGENT].as_array()
|
||||
if self.obs_prop.omit_agent_self:
|
||||
other_agent_obs[:, agent.x, agent.y] -= agent.encoding
|
||||
# Build Level Observations
|
||||
if self.obs_prop.render_agents == a_obs.LEVEL:
|
||||
lvl_obs = lvl_obs.copy()
|
||||
lvl_obs += agent_obs
|
||||
|
||||
obs_dict[c.WALLS] = lvl_obs
|
||||
if self.obs_prop.render_agents in [a_obs.SEPERATE, a_obs.COMBINED]:
|
||||
obs_dict[c.AGENT] = agent_obs
|
||||
if self[c.AGENT_PLACEHOLDER]:
|
||||
obs_dict[c.AGENT_PLACEHOLDER] = placeholder_obs
|
||||
obs_dict[c.DOORS] = door_obs
|
||||
obs_dict.update(add_obs_dict)
|
||||
observations = np.vstack(list(obs_dict.values()))
|
||||
if self.obs_prop.pomdp_r:
|
||||
oobs = self._do_pomdp_obs_cutout(agent, other_agent_obs)[0]
|
||||
# noinspection PyUnresolvedReferences
|
||||
mask = (oobs != c.SHADOWED_CELL.value).astype(int)
|
||||
obs[0] += oobs * mask
|
||||
observations = self._do_pomdp_cutout(agent, observations)
|
||||
|
||||
raw_obs = self._additional_raw_observations(agent)
|
||||
observations = np.vstack((observations, *list(raw_obs.values())))
|
||||
|
||||
keys = list(chain(obs_dict.keys(), raw_obs.keys()))
|
||||
idxs = np.cumsum([x.shape[0] for x in chain(obs_dict.values(), raw_obs.values())]) - 1
|
||||
per_agent_expl_idx[agent.name] = {key: list(range(a, b)) for key, a, b in
|
||||
zip(keys, idxs, list(idxs[1:]) + [idxs[-1]+1, ])}
|
||||
|
||||
# Shadow Casting
|
||||
try:
|
||||
light_block_obs = [obs_idx for key, obs_idx in per_agent_expl_idx[agent.name].items()
|
||||
if self[key].is_blocking_light]
|
||||
# Flatten
|
||||
light_block_obs = [x for y in light_block_obs for x in y]
|
||||
shadowed_obs = [obs_idx for key, obs_idx in per_agent_expl_idx[agent.name].items()
|
||||
if self[key].can_be_shadowed]
|
||||
# Flatten
|
||||
shadowed_obs = [x for y in shadowed_obs for x in y]
|
||||
except AttributeError as e:
|
||||
print('Check your Keys! Only use Constants as Keys!')
|
||||
print(e)
|
||||
raise e
|
||||
if self.obs_prop.cast_shadows:
|
||||
obs_block_light = observations[light_block_obs] != c.OCCUPIED_CELL.value
|
||||
door_shadowing = False
|
||||
if self.parse_doors:
|
||||
if doors := self[c.DOORS]:
|
||||
if door := doors.by_pos(agent.pos):
|
||||
if door.is_closed:
|
||||
for group in door.connectivity_subgroups:
|
||||
if agent.last_pos not in group:
|
||||
door_shadowing = True
|
||||
if self._pomdp_r:
|
||||
blocking = [
|
||||
tuple(np.subtract(x, agent.pos) + (self._pomdp_r, self._pomdp_r))
|
||||
for x in group]
|
||||
xs, ys = zip(*blocking)
|
||||
else:
|
||||
xs, ys = zip(*group)
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
obs_block_light[:, xs, ys] = False
|
||||
|
||||
light_block_map = Map((np.prod(obs_block_light, axis=0) != True).astype(int).squeeze())
|
||||
if self._pomdp_r:
|
||||
light_block_map = light_block_map.do_fov(self._pomdp_r, self._pomdp_r, max(self._level_shape))
|
||||
else:
|
||||
light_block_map = light_block_map.do_fov(*agent.pos, max(self._level_shape))
|
||||
if door_shadowing:
|
||||
# noinspection PyUnboundLocalVariable
|
||||
light_block_map[xs, ys] = 0
|
||||
agent.temp_light_map = light_block_map.copy()
|
||||
|
||||
observations[shadowed_obs] = ((observations[shadowed_obs] * light_block_map) + 0.) - (1 - light_block_map)
|
||||
else:
|
||||
obs[0] += other_agent_obs
|
||||
pass
|
||||
|
||||
# Additional Observation:
|
||||
for additional_obs in self.additional_obs_build():
|
||||
obs[running_idx:running_idx+additional_obs.shape[0]] = additional_obs
|
||||
running_idx += additional_obs.shape[0]
|
||||
for additional_per_agent_obs in self.additional_per_agent_obs_build(agent):
|
||||
obs[running_idx:running_idx + additional_per_agent_obs.shape[0]] = additional_per_agent_obs
|
||||
running_idx += additional_per_agent_obs.shape[0]
|
||||
per_agent_obsn[agent.name] = observations
|
||||
|
||||
return obs
|
||||
if self.n_agents == 1:
|
||||
agent_name = self[c.AGENT][0].name
|
||||
obs, explained_idx = per_agent_obsn[agent_name], per_agent_expl_idx[agent_name]
|
||||
elif self.n_agents >= 2:
|
||||
obs, explained_idx = np.stack(list(per_agent_obsn.values())), per_agent_expl_idx
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
def _do_pomdp_obs_cutout(self, agent, obs_to_be_padded):
|
||||
return obs, explained_idx
|
||||
|
||||
def _do_pomdp_cutout(self, agent, obs_to_be_padded):
|
||||
assert obs_to_be_padded.ndim == 3
|
||||
r, d = self._pomdp_r, self.pomdp_diameter
|
||||
x0, x1 = max(0, agent.x - r), min(agent.x + r + 1, self._level_shape[0])
|
||||
y0, y1 = max(0, agent.y - r), min(agent.y + r + 1, self._level_shape[1])
|
||||
# Other Agent Obs = oobs
|
||||
oobs = obs_to_be_padded[:, x0:x1, y0:y1]
|
||||
if oobs.shape[0:] != (d, d):
|
||||
if oobs.shape[1:] != (d, d):
|
||||
if xd := oobs.shape[1] % d:
|
||||
if agent.x > r:
|
||||
x0_pad = 0
|
||||
@ -478,7 +477,7 @@ class BaseFactory(gym.Env):
|
||||
if doors := self[c.DOORS]:
|
||||
if self.doors_have_area:
|
||||
if door := doors.by_pos(new_tile.pos):
|
||||
if door.can_collide:
|
||||
if door.is_open:
|
||||
return agent.tile, c.NOT_VALID
|
||||
else: # door.is_closed:
|
||||
pass
|
||||
@ -569,7 +568,7 @@ class BaseFactory(gym.Env):
|
||||
if not self._renderer: # lazy init
|
||||
from environments.factory.base.renderer import Renderer, RenderEntity
|
||||
global Renderer, RenderEntity
|
||||
height, width = self._obs_cube.shape[1:]
|
||||
height, width = self._level_shape
|
||||
self._renderer = Renderer(width, height, view_radius=self._pomdp_r, fps=5)
|
||||
|
||||
# noinspection PyUnboundLocalVariable
|
||||
@ -636,20 +635,6 @@ class BaseFactory(gym.Env):
|
||||
|
||||
# Functions which provide additions to functions of the base class
|
||||
# Always call super!!!!!!
|
||||
@abc.abstractmethod
|
||||
def additional_obs_build(self) -> List[np.ndarray]:
|
||||
return []
|
||||
|
||||
def additional_per_agent_obs_build(self, agent) -> List[np.ndarray]:
|
||||
additional_per_agent_obs = []
|
||||
if self.obs_prop.show_global_position_info:
|
||||
pos_array = np.zeros(self.observation_space.shape[1:])
|
||||
for xy in range(1):
|
||||
pos_array[0, xy] = agent.pos[xy] / self._level_shape[xy]
|
||||
additional_per_agent_obs.append(pos_array)
|
||||
|
||||
return additional_per_agent_obs
|
||||
|
||||
@abc.abstractmethod
|
||||
def do_additional_reset(self) -> None:
|
||||
pass
|
||||
@ -666,6 +651,17 @@ class BaseFactory(gym.Env):
|
||||
def check_additional_done(self) -> bool:
|
||||
return False
|
||||
|
||||
@abc.abstractmethod
|
||||
def _additional_observations(self) -> Dict[Constants, np.typing.ArrayLike]:
|
||||
return {}
|
||||
|
||||
@abc.abstractmethod
|
||||
def _additional_raw_observations(self, agent) -> Dict[Constants, np.typing.ArrayLike]:
|
||||
additional_raw_observations = {}
|
||||
if self.obs_prop.show_global_position_info:
|
||||
additional_raw_observations.update({c.GLOBAL_POSITION: self[c.GLOBAL_POSITION].by_entity(agent).as_array()})
|
||||
return additional_raw_observations
|
||||
|
||||
@abc.abstractmethod
|
||||
def calculate_additional_reward(self, agent: Agent) -> (int, dict):
|
||||
return 0, {}
|
||||
|
@ -3,22 +3,26 @@ from enum import Enum
|
||||
from typing import Union
|
||||
|
||||
import networkx as nx
|
||||
import numpy as np
|
||||
|
||||
from environments import helpers as h
|
||||
from environments.helpers import Constants as c
|
||||
import itertools
|
||||
|
||||
##########################################################################
|
||||
# ##################### Base Object Definition ######################### #
|
||||
##########################################################################
|
||||
|
||||
|
||||
class Object:
|
||||
|
||||
"""Generell Objects for Organisation and Maintanance such as Actions etc..."""
|
||||
|
||||
_u_idx = defaultdict(lambda: 0)
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def is_blocking_light(self):
|
||||
return self._is_blocking_light
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
@ -43,7 +47,7 @@ class Object:
|
||||
elif self._str_ident is not None and self._enum_ident is None:
|
||||
self._name = f'{self.__class__.__name__}[{self._str_ident}]'
|
||||
elif self._str_ident is None and self._enum_ident is None:
|
||||
self._name = f'{self.__class__.__name__}#{self._u_idx[self.__class__.__name__]}'
|
||||
self._name = f'{self.__class__.__name__}#{Object._u_idx[self.__class__.__name__]}'
|
||||
Object._u_idx[self.__class__.__name__] += 1
|
||||
else:
|
||||
raise ValueError('Please use either of the idents.')
|
||||
@ -68,16 +72,56 @@ class Object:
|
||||
return other.name == self.name
|
||||
|
||||
|
||||
class Entity(Object):
|
||||
class EnvObject(Object):
|
||||
|
||||
@property
|
||||
def can_collide(self):
|
||||
return True
|
||||
"""Objects that hold Information that are observable, but have no position on the env grid. Inventories etc..."""
|
||||
|
||||
_u_idx = defaultdict(lambda: 0)
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return c.OCCUPIED_CELL.value
|
||||
|
||||
def __init__(self, register, **kwargs):
|
||||
super(EnvObject, self).__init__(**kwargs)
|
||||
self._register = register
|
||||
|
||||
|
||||
class BoundingMixin:
|
||||
|
||||
@property
|
||||
def bound_entity(self):
|
||||
return self._bound_entity
|
||||
|
||||
def __init__(self, entity_to_be_bound, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
assert entity_to_be_bound is not None
|
||||
self._bound_entity = entity_to_be_bound
|
||||
|
||||
def __repr__(self):
|
||||
s = super(BoundingMixin, self).__repr__()
|
||||
i = s[:s.find('(')]
|
||||
return f'{s[:i]}[{self.bound_entity.name}]{s[i:]}'
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return f'{super(BoundingMixin, self).name}({self._bound_entity.name})'
|
||||
|
||||
def belongs_to_entity(self, entity):
|
||||
return entity == self.bound_entity
|
||||
|
||||
|
||||
class Entity(EnvObject):
|
||||
"""Full Env Entity that lives on the env Grid. Doors, Items, Dirt etc..."""
|
||||
|
||||
@property
|
||||
def is_blocking_light(self):
|
||||
return self._is_blocking_light
|
||||
|
||||
@property
|
||||
def can_collide(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
return self.pos[0]
|
||||
@ -94,9 +138,10 @@ class Entity(Object):
|
||||
def tile(self):
|
||||
return self._tile
|
||||
|
||||
def __init__(self, tile, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
def __init__(self, tile, *args, is_blocking_light=True, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._tile = tile
|
||||
self._is_blocking_light = is_blocking_light
|
||||
tile.enter(self)
|
||||
|
||||
def summarize_state(self, **_) -> dict:
|
||||
@ -104,7 +149,7 @@ class Entity(Object):
|
||||
tile=str(self.tile.name), can_collide=bool(self.can_collide))
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.name}(@{self.pos})'
|
||||
return super(Entity, self).__repr__() + f'(@{self.pos})'
|
||||
|
||||
|
||||
class MoveableEntity(Entity):
|
||||
@ -118,7 +163,7 @@ class MoveableEntity(Entity):
|
||||
if self._last_tile:
|
||||
return self._last_tile.pos
|
||||
else:
|
||||
return c.NO_POS
|
||||
return c.NO_POS.value
|
||||
|
||||
@property
|
||||
def direction_of_view(self):
|
||||
@ -137,45 +182,66 @@ class MoveableEntity(Entity):
|
||||
curr_tile.leave(self)
|
||||
self._tile = next_tile
|
||||
self._last_tile = curr_tile
|
||||
self._register.notify_change_to_value(self)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
##########################################################################
|
||||
# ####################### Objects and Entitys ########################## #
|
||||
##########################################################################
|
||||
|
||||
|
||||
class Action(Object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class PlaceHolder(MoveableEntity):
|
||||
class PlaceHolder(Object):
|
||||
|
||||
def __init__(self, *args, fill_value=0, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._fill_value = fill_value
|
||||
|
||||
@property
|
||||
def last_tile(self):
|
||||
return self.tile
|
||||
|
||||
@property
|
||||
def direction_of_view(self):
|
||||
return self.pos
|
||||
|
||||
@property
|
||||
def can_collide(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return c.NO_POS.value[0]
|
||||
return self._fill_value
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return "PlaceHolder"
|
||||
|
||||
|
||||
class Tile(Object):
|
||||
class GlobalPosition(EnvObject):
|
||||
|
||||
def belongs_to_entity(self, entity):
|
||||
return self._agent == entity
|
||||
|
||||
def __init__(self, level_shape, obs_shape, agent, normalized: bool = True):
|
||||
super(GlobalPosition, self).__init__(self)
|
||||
self._obs_shape = (1, *obs_shape) if len(obs_shape) == 2 else obs_shape
|
||||
self._agent = agent
|
||||
self._level_shape = level_shape
|
||||
self._normalized = normalized
|
||||
|
||||
def as_array(self):
|
||||
pos_array = np.zeros(self._obs_shape)
|
||||
for xy in range(1):
|
||||
pos_array[0, 0, xy] = self._agent.pos[xy] / self._level_shape[xy]
|
||||
return pos_array
|
||||
|
||||
|
||||
class Tile(EnvObject):
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return c.FREE_CELL.value
|
||||
|
||||
@property
|
||||
def guests_that_can_collide(self):
|
||||
@ -197,8 +263,8 @@ class Tile(Object):
|
||||
def pos(self):
|
||||
return self._pos
|
||||
|
||||
def __init__(self, pos, **kwargs):
|
||||
super(Tile, self).__init__(**kwargs)
|
||||
def __init__(self, pos, *args, **kwargs):
|
||||
super(Tile, self).__init__(*args, **kwargs)
|
||||
self._guests = dict()
|
||||
self._pos = tuple(pos)
|
||||
|
||||
@ -233,6 +299,11 @@ class Tile(Object):
|
||||
|
||||
|
||||
class Wall(Tile):
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return c.OCCUPIED_CELL.value
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -247,7 +318,8 @@ class Door(Entity):
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return 1 if self.is_closed else 2
|
||||
# This is important as it shadow is checked by occupation value
|
||||
return c.OCCUPIED_CELL.value if self.is_closed else 2
|
||||
|
||||
@property
|
||||
def str_state(self):
|
||||
@ -307,11 +379,13 @@ class Door(Entity):
|
||||
def _open(self):
|
||||
self.connectivity.add_edges_from([(self.pos, x) for x in range(len(self.connectivity_subgroups))])
|
||||
self._state = c.OPEN_DOOR
|
||||
self._register.notify_change_to_value(self)
|
||||
self.time_to_close = self.auto_close_interval
|
||||
|
||||
def _close(self):
|
||||
self.connectivity.remove_node(self.pos)
|
||||
self._state = c.CLOSED_DOOR
|
||||
self._register.notify_change_to_value(self)
|
||||
|
||||
def is_linked(self, old_pos, new_pos):
|
||||
try:
|
||||
|
@ -1,18 +1,23 @@
|
||||
import numbers
|
||||
import random
|
||||
from abc import ABC
|
||||
from typing import List, Union, Dict
|
||||
from typing import List, Union, Dict, Tuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
from environments.factory.base.objects import Entity, Tile, Agent, Door, Action, Wall, Object, PlaceHolder
|
||||
from environments.factory.base.objects import Entity, Tile, Agent, Door, Action, Wall, PlaceHolder, GlobalPosition, \
|
||||
Object, EnvObject
|
||||
from environments.utility_classes import MovementProperties
|
||||
from environments import helpers as h
|
||||
from environments.helpers import Constants as c
|
||||
|
||||
##########################################################################
|
||||
# ##################### Base Register Definition ####################### #
|
||||
##########################################################################
|
||||
|
||||
class Register:
|
||||
_accepted_objects = Entity
|
||||
|
||||
class ObjectRegister:
|
||||
_accepted_objects = Object
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -48,6 +53,12 @@ class Register:
|
||||
def items(self):
|
||||
return self._register.items()
|
||||
|
||||
def _get_index(self, item):
|
||||
try:
|
||||
return next(i for i, v in enumerate(self._register.values()) if v == item)
|
||||
except (StopIteration, AssertionError):
|
||||
return None
|
||||
|
||||
def __getitem__(self, item):
|
||||
if isinstance(item, (int, np.int64, np.int32)):
|
||||
if item < 0:
|
||||
@ -65,39 +76,66 @@ class Register:
|
||||
return f'{self.__class__.__name__}({self._register})'
|
||||
|
||||
|
||||
class ObjectRegister(Register):
|
||||
class EnvObjectRegister(ObjectRegister):
|
||||
|
||||
hide_from_obs_builder = False
|
||||
_accepted_objects = EnvObject
|
||||
|
||||
def __init__(self, level_shape: (int, int), *args, individual_slices=False, is_per_agent=False, **kwargs):
|
||||
super(ObjectRegister, self).__init__(*args, **kwargs)
|
||||
self.is_per_agent = is_per_agent
|
||||
self.individual_slices = individual_slices
|
||||
self._level_shape = level_shape
|
||||
def __init__(self, obs_shape: (int, int), *args, **kwargs):
|
||||
super(EnvObjectRegister, self).__init__(*args, **kwargs)
|
||||
self._shape = obs_shape
|
||||
self._array = None
|
||||
self.hide_from_obs_builder = False
|
||||
self._lazy_eval_transforms = []
|
||||
|
||||
def register_item(self, other):
|
||||
super(ObjectRegister, self).register_item(other)
|
||||
def register_item(self, other: EnvObject):
|
||||
super(EnvObjectRegister, self).register_item(other)
|
||||
if self._array is None:
|
||||
self._array = np.zeros((1, *self._level_shape))
|
||||
else:
|
||||
if self.individual_slices:
|
||||
self._array = np.concatenate((self._array, np.zeros((1, *self._array.shape[1:]))))
|
||||
self._array = np.zeros((1, *self._shape))
|
||||
self.notify_change_to_value(other)
|
||||
|
||||
def as_array(self):
|
||||
if self._lazy_eval_transforms:
|
||||
idxs, values = zip(*self._lazy_eval_transforms)
|
||||
# nuumpy put repects the ordering so that
|
||||
np.put(self._array, idxs, values)
|
||||
self._lazy_eval_transforms = []
|
||||
return self._array
|
||||
|
||||
def summarize_states(self, n_steps=None):
|
||||
return [val.summarize_state(n_steps=n_steps) for val in self.values()]
|
||||
|
||||
def notify_change_to_free(self, env_object: EnvObject):
|
||||
self._array_change_notifyer(env_object, value=c.FREE_CELL.value)
|
||||
|
||||
class EntityObjectRegister(ObjectRegister, ABC):
|
||||
def notify_change_to_value(self, env_object: EnvObject):
|
||||
self._array_change_notifyer(env_object)
|
||||
|
||||
def as_array(self):
|
||||
raise NotImplementedError
|
||||
def _array_change_notifyer(self, env_object: EnvObject, value=None):
|
||||
pos = self._get_index(env_object)
|
||||
value = value if value is not None else env_object.encoding
|
||||
self._lazy_eval_transforms.append((pos, value))
|
||||
|
||||
def __delitem__(self, name):
|
||||
self.notify_change_to_free(self._register[name])
|
||||
del self._register[name]
|
||||
|
||||
def delete_env_object(self, env_object: EnvObject):
|
||||
del self[env_object.name]
|
||||
|
||||
def delete_env_object_by_name(self, name):
|
||||
del self[name]
|
||||
|
||||
|
||||
class EntityRegister(EnvObjectRegister, ABC):
|
||||
|
||||
_accepted_objects = Entity
|
||||
|
||||
@classmethod
|
||||
def from_tiles(cls, tiles, *args, entity_kwargs=None, **kwargs):
|
||||
# objects_name = cls._accepted_objects.__name__
|
||||
register_obj = cls(*args, **kwargs)
|
||||
entities = [cls._accepted_objects(tile, str_ident=i, **entity_kwargs if entity_kwargs is not None else {})
|
||||
entities = [cls._accepted_objects(tile, register_obj, str_ident=i,
|
||||
**entity_kwargs if entity_kwargs is not None else {})
|
||||
for i, tile in enumerate(tiles)]
|
||||
register_obj.register_additional_items(entities)
|
||||
return register_obj
|
||||
@ -115,86 +153,172 @@ class EntityObjectRegister(ObjectRegister, ABC):
|
||||
def tiles(self):
|
||||
return [entity.tile for entity in self]
|
||||
|
||||
def __init__(self, *args, is_blocking_light=False, is_observable=True, can_be_shadowed=True, **kwargs):
|
||||
super(EntityObjectRegister, self).__init__(*args, **kwargs)
|
||||
self.can_be_shadowed = can_be_shadowed
|
||||
self.is_blocking_light = is_blocking_light
|
||||
self.is_observable = is_observable
|
||||
@property
|
||||
def encodings(self):
|
||||
return [x.encoding for x in self]
|
||||
|
||||
def by_pos(self, pos):
|
||||
if isinstance(pos, np.ndarray):
|
||||
pos = tuple(pos)
|
||||
def __init__(self, level_shape, *args,
|
||||
is_blocking_light: bool = False,
|
||||
can_be_shadowed: bool = True,
|
||||
individual_slices: bool = False, **kwargs):
|
||||
super(EntityRegister, self).__init__(level_shape, *args, **kwargs)
|
||||
self._lazy_eval_transforms = []
|
||||
self.can_be_shadowed = can_be_shadowed
|
||||
self.individual_slices = individual_slices
|
||||
self.is_blocking_light = is_blocking_light
|
||||
|
||||
def __delitem__(self, name):
|
||||
idx, obj = next((i, obj) for i, obj in enumerate(self) if obj.name == name)
|
||||
obj.tile.leave(obj)
|
||||
super(EntityRegister, self).__delitem__(name)
|
||||
if self.individual_slices:
|
||||
self._array = np.delete(self._array, idx, axis=0)
|
||||
|
||||
def as_array(self):
|
||||
if self._lazy_eval_transforms:
|
||||
idxs, values = zip(*self._lazy_eval_transforms)
|
||||
# numpy put repects the ordering so that
|
||||
# Todo: Export the index building in a seperate function
|
||||
np.put(self._array, [np.ravel_multi_index(idx, self._array.shape) for idx in idxs], values)
|
||||
self._lazy_eval_transforms = []
|
||||
return self._array
|
||||
|
||||
def _array_change_notifyer(self, entity, pos=None, value=None):
|
||||
# Todo: Export the contruction in a seperate function
|
||||
pos = pos if pos is not None else entity.pos
|
||||
value = value if value is not None else entity.encoding
|
||||
x, y = pos
|
||||
if self.individual_slices:
|
||||
idx = (self._get_index(entity), x, y)
|
||||
else:
|
||||
idx = (0, x, y)
|
||||
self._lazy_eval_transforms.append((idx, value))
|
||||
|
||||
def by_pos(self, pos: Tuple[int, int]):
|
||||
try:
|
||||
return next(item for item in self.values() if item.pos == pos)
|
||||
return next(item for item in self if item.pos == tuple(pos))
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
|
||||
class MovingEntityObjectRegister(EntityObjectRegister, ABC):
|
||||
class BoundRegisterMixin(EnvObjectRegister, ABC):
|
||||
|
||||
@classmethod
|
||||
def from_entities_to_bind(self, entitites):
|
||||
def from_values(cls, values: Union[str, numbers.Number, List[Union[str, numbers.Number]]],
|
||||
*args, object_kwargs=None, **kwargs):
|
||||
# objects_name = cls._accepted_objects.__name__
|
||||
if isinstance(values, (str, numbers.Number)):
|
||||
values = [values]
|
||||
register_obj = cls(*args, **kwargs)
|
||||
objects = [cls._accepted_objects(register_obj, str_ident=i, fill_value=value,
|
||||
**object_kwargs if object_kwargs is not None else {})
|
||||
for i, value in enumerate(values)]
|
||||
register_obj.register_additional_items(objects)
|
||||
return register_obj
|
||||
|
||||
|
||||
class MovingEntityObjectRegister(EntityRegister, ABC):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MovingEntityObjectRegister, self).__init__(*args, **kwargs)
|
||||
|
||||
def by_pos(self, pos):
|
||||
if isinstance(pos, np.ndarray):
|
||||
pos = tuple(pos)
|
||||
def notify_change_to_value(self, entity):
|
||||
super(MovingEntityObjectRegister, self).notify_change_to_value(entity)
|
||||
if entity.last_pos != c.NO_POS.value:
|
||||
try:
|
||||
self._array_change_notifyer(entity, entity.last_pos, value=c.FREE_CELL.value)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
##########################################################################
|
||||
# ################# Objects and Entity Registers ####################### #
|
||||
##########################################################################
|
||||
|
||||
|
||||
class GlobalPositions(EnvObjectRegister):
|
||||
_accepted_objects = GlobalPosition
|
||||
is_blocking_light = False
|
||||
can_be_shadowed = False
|
||||
hide_from_obs_builder = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(GlobalPositions, self).__init__(*args, is_per_agent=True, individual_slices=True, **kwargs)
|
||||
|
||||
def as_array(self):
|
||||
# Todo make this lazy?
|
||||
return np.stack([gp.as_array() for inv_idx, gp in enumerate(self)])
|
||||
|
||||
def spawn_GlobalPositionObjects(self, obs_shape, agents):
|
||||
global_positions = [self._accepted_objects(self._shape, obs_shape, agent)
|
||||
for _, agent in enumerate(agents)]
|
||||
# noinspection PyTypeChecker
|
||||
self.register_additional_items(global_positions)
|
||||
|
||||
def summarize_states(self, n_steps=None):
|
||||
return {}
|
||||
|
||||
def idx_by_entity(self, entity):
|
||||
try:
|
||||
return next(x for x in self if x.pos == pos)
|
||||
return next((idx for idx, inv in enumerate(self) if inv.belongs_to_entity(entity)))
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
def __delitem__(self, name):
|
||||
idx = next(i for i, entity in enumerate(self) if entity.name == name)
|
||||
del self._register[name]
|
||||
if self.individual_slices:
|
||||
self._array = np.delete(self._array, idx, axis=0)
|
||||
|
||||
def delete_entity(self, item):
|
||||
self.delete_entity_by_name(item.name)
|
||||
|
||||
def delete_entity_by_name(self, name):
|
||||
del self[name]
|
||||
def by_entity(self, entity):
|
||||
try:
|
||||
return next((inv for inv in self if inv.belongs_to_entity(entity)))
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
|
||||
class PlaceHolders(MovingEntityObjectRegister):
|
||||
|
||||
class PlaceHolders(EnvObjectRegister):
|
||||
_accepted_objects = PlaceHolder
|
||||
|
||||
def __init__(self, *args, fill_value: Union[str, int] = 0, **kwargs):
|
||||
def __init__(self, *args, **kwargs):
|
||||
assert not 'individual_slices' in kwargs, 'Keyword - "individual_slices": "True" and must not be altered'
|
||||
kwargs.update(individual_slices=False)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fill_value = fill_value
|
||||
|
||||
@classmethod
|
||||
def from_values(cls, values: Union[str, numbers.Number, List[Union[str, numbers.Number]]],
|
||||
*args, object_kwargs=None, **kwargs):
|
||||
# objects_name = cls._accepted_objects.__name__
|
||||
if isinstance(values, (str, numbers.Number)):
|
||||
values = [values]
|
||||
register_obj = cls(*args, **kwargs)
|
||||
objects = [cls._accepted_objects(register_obj, str_ident=i, fill_value=value,
|
||||
**object_kwargs if object_kwargs is not None else {})
|
||||
for i, value in enumerate(values)]
|
||||
register_obj.register_additional_items(objects)
|
||||
return register_obj
|
||||
|
||||
# noinspection DuplicatedCode
|
||||
def as_array(self):
|
||||
if isinstance(self.fill_value, numbers.Number):
|
||||
self._array[:] = self.fill_value
|
||||
elif isinstance(self.fill_value, str):
|
||||
if self.fill_value.lower() in ['normal', 'n']:
|
||||
self._array = np.random.normal(size=self._array.shape)
|
||||
for idx, placeholder in enumerate(self):
|
||||
if isinstance(placeholder.encoding, numbers.Number):
|
||||
self._array[idx][:] = placeholder.fill_value
|
||||
elif isinstance(placeholder.fill_value, str):
|
||||
if placeholder.fill_value.lower() in ['normal', 'n']:
|
||||
self._array[:] = np.random.normal(size=self._array.shape)
|
||||
else:
|
||||
raise ValueError('Choose one of: ["normal", "N"]')
|
||||
else:
|
||||
raise ValueError('Choose one of: ["normal", "N"]')
|
||||
else:
|
||||
raise TypeError('Objects of type "str" or "number" is required here.')
|
||||
raise TypeError('Objects of type "str" or "number" is required here.')
|
||||
|
||||
if self.individual_slices:
|
||||
return self._array
|
||||
else:
|
||||
return self._array[None, 0]
|
||||
return self._array
|
||||
|
||||
|
||||
class Entities(Register):
|
||||
|
||||
_accepted_objects = EntityObjectRegister
|
||||
class Entities(ObjectRegister):
|
||||
_accepted_objects = EntityRegister
|
||||
|
||||
@property
|
||||
def observable_arrays(self):
|
||||
# FIXME: Find a better name
|
||||
return {key: val.as_array() for key, val in self.items() if val.is_observable}
|
||||
def arrays(self):
|
||||
return {key: val.as_array() for key, val in self.items()}
|
||||
|
||||
@property
|
||||
def obs_arrays(self):
|
||||
# FIXME: Find a better name
|
||||
return {key: val.as_array() for key, val in self.items() if val.is_observable and not val.hide_from_obs_builder}
|
||||
return {key: val.as_array() for key, val in self.items() if not val.hide_from_obs_builder}
|
||||
|
||||
@property
|
||||
def names(self):
|
||||
@ -220,34 +344,34 @@ class Entities(Register):
|
||||
return found_entities
|
||||
|
||||
|
||||
class WallTiles(EntityObjectRegister):
|
||||
class WallTiles(EntityRegister):
|
||||
_accepted_objects = Wall
|
||||
_light_blocking = True
|
||||
hide_from_obs_builder = True
|
||||
|
||||
def as_array(self):
|
||||
if not np.any(self._array):
|
||||
# Which is Faster?
|
||||
# indices = [x.pos for x in self]
|
||||
# np.put(self._array, [np.ravel_multi_index((0, *x), self._array.shape) for x in indices], self.encodings)
|
||||
x, y = zip(*[x.pos for x in self])
|
||||
self._array[0, x, y] = self.encoding
|
||||
return self._array
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WallTiles, self).__init__(*args, individual_slices=False,
|
||||
is_blocking_light=self._light_blocking, **kwargs)
|
||||
super(WallTiles, self).__init__(*args, is_blocking_light=self._light_blocking, individual_slices=False,
|
||||
**kwargs)
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return c.OCCUPIED_CELL.value
|
||||
|
||||
@property
|
||||
def array(self):
|
||||
return self._array
|
||||
|
||||
@classmethod
|
||||
def from_argwhere_coordinates(cls, argwhere_coordinates, *args, **kwargs):
|
||||
tiles = cls(*args, **kwargs)
|
||||
# noinspection PyTypeChecker
|
||||
tiles.register_additional_items(
|
||||
[cls._accepted_objects(pos, is_blocking_light=cls._light_blocking)
|
||||
[cls._accepted_objects(pos, tiles, is_blocking_light=cls._light_blocking)
|
||||
for pos in argwhere_coordinates]
|
||||
)
|
||||
return tiles
|
||||
@ -264,12 +388,11 @@ class WallTiles(EntityObjectRegister):
|
||||
|
||||
|
||||
class FloorTiles(WallTiles):
|
||||
|
||||
_accepted_objects = Tile
|
||||
_light_blocking = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FloorTiles, self).__init__(*args, is_observable=False, **kwargs)
|
||||
super(FloorTiles, self).__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
@ -297,22 +420,21 @@ class FloorTiles(WallTiles):
|
||||
|
||||
|
||||
class Agents(MovingEntityObjectRegister):
|
||||
|
||||
_accepted_objects = Agent
|
||||
|
||||
def __init__(self, *args, hide_from_obs_builder=False, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.hide_from_obs_builder = hide_from_obs_builder
|
||||
|
||||
# noinspection DuplicatedCode
|
||||
def as_array(self):
|
||||
self._array[:] = c.FREE_CELL.value
|
||||
# noinspection PyTupleAssignmentBalance
|
||||
for z, x, y, v in zip(range(len(self)), *zip(*[x.pos for x in self]), [x.encoding for x in self]):
|
||||
if self.individual_slices:
|
||||
self._array[z, x, y] += v
|
||||
else:
|
||||
self._array[0, x, y] += v
|
||||
@DeprecationWarning
|
||||
def Xas_array(self):
|
||||
# Super Safe Version
|
||||
# self._array[:] = c.FREE_CELL.value
|
||||
indices = list(zip(range(len(self)), *zip(*[x.last_pos for x in self])))
|
||||
np.put(self._array, [np.ravel_multi_index(x, self._array.shape) for x in indices], c.FREE_CELL.value)
|
||||
indices = list(zip(range(len(self)), *zip(*[x.pos for x in self])))
|
||||
np.put(self._array, [np.ravel_multi_index(x, self._array.shape) for x in indices], self.encodings)
|
||||
|
||||
if self.individual_slices:
|
||||
return self._array
|
||||
else:
|
||||
@ -329,17 +451,11 @@ class Agents(MovingEntityObjectRegister):
|
||||
self._register[agent.name] = agent
|
||||
|
||||
|
||||
class Doors(EntityObjectRegister):
|
||||
class Doors(EntityRegister):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Doors, self).__init__(*args, is_blocking_light=True, **kwargs)
|
||||
|
||||
def as_array(self):
|
||||
self._array[:] = 0
|
||||
for door in self:
|
||||
self._array[0, door.x, door.y] = door.encoding
|
||||
return self._array
|
||||
|
||||
_accepted_objects = Door
|
||||
|
||||
def get_near_position(self, position: (int, int)) -> Union[None, Door]:
|
||||
@ -353,8 +469,7 @@ class Doors(EntityObjectRegister):
|
||||
door.tick()
|
||||
|
||||
|
||||
class Actions(Register):
|
||||
|
||||
class Actions(ObjectRegister):
|
||||
_accepted_objects = Action
|
||||
|
||||
@property
|
||||
@ -385,7 +500,7 @@ class Actions(Register):
|
||||
return action in self.movement_actions.values()
|
||||
|
||||
|
||||
class Zones(Register):
|
||||
class Zones(ObjectRegister):
|
||||
|
||||
@property
|
||||
def accounting_zones(self):
|
||||
|
@ -13,7 +13,7 @@ mult_array = np.asarray([
|
||||
class Map(object):
|
||||
# Multipliers for transforming coordinates to other octants:
|
||||
|
||||
def __init__(self, map_array: np.ndarray, diamond_slope: float = 0.9):
|
||||
def __init__(self, map_array: np.typing.ArrayLike, diamond_slope: float = 0.9):
|
||||
self.data = map_array
|
||||
self.width, self.height = map_array.shape
|
||||
self.light = np.full_like(self.data, c.FREE_CELL.value)
|
||||
|
Reference in New Issue
Block a user