Everything is an object now

This commit is contained in:
Steffen Illium
2021-08-26 17:47:15 +02:00
parent bd0a8090ab
commit 0fc4db193f
7 changed files with 613 additions and 447 deletions

View File

@ -1,7 +1,8 @@
import abc
import time
from enum import Enum
from pathlib import Path
from typing import List, Union, Iterable
from typing import List, Union, Iterable, Dict
import gym
import numpy as np
@ -14,8 +15,8 @@ from environments.factory.base.shadow_casting import Map
from environments.factory.renderer import Renderer, RenderEntity
from environments.helpers import Constants as c, Constants
from environments import helpers as h
from environments.factory.base.objects import Slice, Agent, Tile, Action
from environments.factory.base.registers import StateSlices, Actions, Entities, Agents, Doors, FloorTiles
from environments.factory.base.objects import Agent, Tile, Action
from environments.factory.base.registers import Actions, Entities, Agents, Doors, FloorTiles, WallTiles
from environments.utility_classes import MovementProperties
REC_TAC = 'rec'
@ -30,9 +31,13 @@ class BaseFactory(gym.Env):
@property
def observation_space(self):
slices = self._slices.n_observable_slices
level_shape = (self.pomdp_r * 2 + 1, self.pomdp_r * 2 + 1) if self.pomdp_r else self._level_shape
space = spaces.Box(low=0, high=1, shape=(slices, *level_shape), dtype=np.float32)
if r := self.pomdp_r:
z = self._obs_cube.shape[0]
xy = r*2 + 1
level_shape = (z, xy, xy)
else:
level_shape = self._obs_cube.shape
space = spaces.Box(low=0, high=1, shape=level_shape, dtype=np.float32)
return space
@property
@ -51,8 +56,8 @@ class BaseFactory(gym.Env):
def __init__(self, level_name='simple', n_agents=1, max_steps=int(5e2), pomdp_r: Union[None, int] = 0,
movement_properties: MovementProperties = MovementProperties(), parse_doors=False,
combin_agent_slices_in_obs: bool = False, frames_to_stack=0, record_episodes=False,
omit_agent_slice_in_obs=False, done_at_collision=False, cast_shadows=True,
combin_agent_obs: bool = False, frames_to_stack=0, record_episodes=False,
omit_agent_in_obs=False, done_at_collision=False, cast_shadows=True,
verbose=False, doors_have_area=True, env_seed=time.time_ns(), **kwargs):
assert frames_to_stack != 1 and frames_to_stack >= 0, "'frames_to_stack' cannot be negative or 1."
@ -69,8 +74,8 @@ class BaseFactory(gym.Env):
self.max_steps = max_steps
self.pomdp_r = pomdp_r
self.combin_agent_slices_in_obs = combin_agent_slices_in_obs
self.omit_agent_slice_in_obs = omit_agent_slice_in_obs
self.combin_agent_obs = combin_agent_obs
self.omit_agent_in_obs = omit_agent_in_obs
self.cast_shadows = cast_shadows
self.frames_to_stack = frames_to_stack
@ -87,86 +92,74 @@ class BaseFactory(gym.Env):
# Reset
self.reset()
def _init_state_slices(self) -> StateSlices:
state_slices = StateSlices()
def _base_init_env(self):
# Objects
entities = {}
# Level
level_filepath = Path(__file__).parent.parent / h.LEVELS_DIR / f'{self.level_name}.txt'
parsed_level = h.parse_level(level_filepath)
level = [Slice(c.LEVEL, h.one_hot_level(parsed_level), is_blocking_light=True)]
self._level_shape = level[0].shape
level_array = h.one_hot_level(parsed_level)
self._level_shape = level_array.shape
# Walls
walls = WallTiles.from_argwhere_coordinates(
np.argwhere(level_array == c.OCCUPIED_CELL.value),
self._level_shape
)
entities.update({c.WALLS: walls})
# Floor
floor = FloorTiles.from_argwhere_coordinates(
np.argwhere(level_array == c.FREE_CELL.value),
self._level_shape
)
entities.update({c.FLOOR: floor})
# NOPOS
self.NO_POS_TILE = Tile(c.NO_POS, c.NO_POS.value)
# Doors
parsed_doors = h.one_hot_level(parsed_level, c.DOOR)
if parsed_doors.any():
doors = [Slice(c.DOORS, parsed_doors, is_blocking_light=True)]
else:
doors = []
if np.any(parsed_doors):
door_tiles = [floor.by_pos(pos) for pos in np.argwhere(parsed_doors == c.OCCUPIED_CELL.value)]
doors = Doors.from_tiles(door_tiles, self._level_shape, context=floor, is_blocking_light=True)
entities.update({c.DOORS: doors})
# Agents
agents = []
agent_names = [f'{c.AGENT.value}#{i}' for i in range(self.n_agents)]
agents = Agents.from_tiles(floor.empty_tiles[:self.n_agents], self._level_shape)
entities.update({c.AGENT: agents})
if self.combin_agent_slices_in_obs and self.omit_agent_slice_in_obs:
if self.n_agents == 1:
observables = [False]
else:
observables = [True] + ([False] * (self.n_agents - 1))
elif self.combin_agent_slices_in_obs and not self.omit_agent_slice_in_obs:
observables = [True] + ([False] * (self.n_agents - 1))
elif not self.combin_agent_slices_in_obs and self.omit_agent_slice_in_obs:
observables = [False] + ([True] * (self.n_agents - 1))
elif not self.combin_agent_slices_in_obs and not self.omit_agent_slice_in_obs:
observables = [True] * self.n_agents
else:
raise RuntimeError('This should not happen!')
for observable, agent_name in zip(observables, agent_names):
agents.append(Slice(agent_name, np.zeros_like(level[0].slice, dtype=np.float32), is_observable=observable))
state_slices.register_additional_items(level+doors+agents+self.additional_slices)
return state_slices
def _init_obs_cube(self) -> np.ndarray:
x, y = self._slices.by_enum(c.LEVEL).shape
state = np.zeros((len(self._slices), x, y), dtype=np.float32)
state[0] = self._slices.by_enum(c.LEVEL).slice
if r := self.pomdp_r:
self._padded_obs_cube = np.full((len(self._slices), x + r*2, y + r*2), c.FREE_CELL.value, dtype=np.float32)
self._padded_obs_cube[0] = c.OCCUPIED_CELL.value
self._padded_obs_cube[:, r:r+x, r:r+y] = state
if self.combin_agent_slices_in_obs and self.n_agents > 1:
self._combined_obs_cube = np.zeros(self.observation_space.shape, dtype=np.float32)
return state
def _init_entities(self):
# Tile Init
self._tiles = FloorTiles.from_argwhere_coordinates(self._slices.by_enum(c.LEVEL).free_tiles)
# Door Init
if self.parse_doors:
tiles = [self._tiles.by_pos(x) for x in self._slices.by_enum(c.DOORS).occupied_tiles]
self._doors = Doors.from_tiles(tiles, context=self._tiles, has_area=self.doors_have_area)
# Agent Init on random positions
self._agents = Agents.from_tiles(self._base_rng.choice(self._tiles, self.n_agents))
entities = Entities()
entities.register_additional_items([self._agents])
if self.parse_doors:
entities.register_additional_items([self._doors])
# All entities
self._entities = Entities()
self._entities.register_additional_items(entities)
# Additional Entitites from SubEnvs
if additional_entities := self.additional_entities:
entities.register_additional_items(additional_entities)
self._entities.register_additional_items(additional_entities)
return entities
# Return
return self._entities
def _init_obs_cube(self):
arrays = self._entities.arrays
if self.omit_agent_in_obs and self.n_agents == 1:
del arrays[c.AGENT]
obs_cube_z = sum([a.shape[0] if not self._entities[key].is_per_agent else 1 for key, a in arrays.items()])
self._obs_cube = np.zeros((obs_cube_z, *self._level_shape), dtype=np.float32)
# Optionally Pad this obs cube for pomdp cases
if r := self.pomdp_r:
x, y = self._level_shape
self._padded_obs_cube = np.full((obs_cube_z, x + r*2, y + r*2), c.SHADOWED_CELL.value, dtype=np.float32)
# self._padded_obs_cube[0] = c.OCCUPIED_CELL.value
self._padded_obs_cube[:, r:r+x, r:r+y] = self._obs_cube
def reset(self) -> (np.ndarray, int, bool, dict):
self._slices = self._init_state_slices()
self._obs_cube = self._init_obs_cube()
self._entitites = self._init_entities()
_ = self._base_init_env()
self._init_obs_cube()
self.do_additional_reset()
self._flush_state()
self._steps = 0
obs = self._get_observations()
@ -182,7 +175,7 @@ class BaseFactory(gym.Env):
self.hook_pre_step()
# Move this in a seperate function?
for action, agent in zip(actions, self._agents):
for action, agent in zip(actions, self._entities[c.AGENT]):
agent.clear_temp_sate()
action_obj = self._actions[action]
if self._actions.is_moving_action(action_obj):
@ -200,9 +193,6 @@ class BaseFactory(gym.Env):
# In-between step Hook for later use
info = self.do_additional_step()
# Write to observation cube
self._flush_state()
tiles_with_collisions = self.get_all_tiles_with_collisions()
for tile in tiles_with_collisions:
guests = tile.guests_that_can_collide
@ -216,7 +206,7 @@ class BaseFactory(gym.Env):
# Step the door close intervall
if self.parse_doors:
self._doors.tick_doors()
self._entities[c.DOORS].tick_doors()
# Finalize
reward, reward_info = self.calculate_reward()
@ -237,9 +227,9 @@ class BaseFactory(gym.Env):
def _handle_door_interaction(self, agent):
# Check if agent really is standing on a door:
if self.doors_have_area:
door = self._doors.get_near_position(agent.pos)
door = self._entities[c.DOORS].get_near_position(agent.pos)
else:
door = self._doors.by_pos(agent.pos)
door = self._entities[c.DOORS].by_pos(agent.pos)
if door is not None:
door.use()
return c.VALID.value
@ -247,36 +237,44 @@ class BaseFactory(gym.Env):
else:
return c.NOT_VALID.value
def _flush_state(self):
self._obs_cube[np.arange(len(self._slices)) != self._slices.get_idx(c.LEVEL)] = c.FREE_CELL.value
if self.parse_doors:
for door in self._doors:
if door.is_open and self._obs_cube[self._slices.get_idx(c.DOORS)][door.pos] != c.OPEN_DOOR.value:
self._obs_cube[self._slices.get_idx(c.DOORS)][door.pos] = c.OPEN_DOOR.value
elif door.is_closed and self._obs_cube[self._slices.get_idx(c.DOORS)][door.pos] != c.CLOSED_DOOR.value:
self._obs_cube[self._slices.get_idx(c.DOORS)][door.pos] = c.CLOSED_DOOR.value
for agent in self._agents:
self._obs_cube[self._slices.get_idx_by_name(agent.name)][agent.pos] = c.OCCUPIED_CELL.value
if agent.last_pos != c.NO_POS:
self._obs_cube[self._slices.get_idx_by_name(agent.name)][agent.last_pos] = c.FREE_CELL.value
def _get_observations(self) -> np.ndarray:
if self.n_agents == 1:
obs = self._build_per_agent_obs(self._agents[0])
obs = self._build_per_agent_obs(self._entities[c.AGENT][0])
elif self.n_agents >= 2:
obs = np.stack([self._build_per_agent_obs(agent) for agent in self._agents])
obs = np.stack([self._build_per_agent_obs(agent) for agent in self._entities[c.AGENT]])
else:
raise ValueError('n_agents cannot be smaller than 1!!')
return obs
def _build_per_agent_obs(self, agent: Agent) -> np.ndarray:
first_agent_slice = self._slices.AGENTSTARTIDX
plain_arrays = self._entities.arrays
if self.omit_agent_in_obs and self.n_agents == 1:
del plain_arrays[c.AGENT]
running_idx, shadowing_idxs, can_be_shadowed_idxs = 0, [], []
for key, array in plain_arrays.items():
if self._entities[key].is_per_agent:
per_agent_idx = self._entities[key].get_idx_by_name(agent.name)
z = 1
self._obs_cube[running_idx: z] = array[per_agent_idx]
else:
z = array.shape[0]
self._obs_cube[running_idx: z] = array
# Define which OBS SLices cast a Shadow
if self._entities[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._entities[key].can_be_shadowed:
for i in range(z):
can_be_shadowed_idxs.append(running_idx + i)
running_idx += z
if r := self.pomdp_r:
x, y = self._level_shape
self._padded_obs_cube[:, r:r + x, r:r + y] = self._obs_cube
global_x, global_y = agent.pos
global_x += r
global_y += r
global_x, global_y = map(sum, zip(agent.pos, (r, r)))
x0, x1 = max(0, global_x - self.pomdp_r), global_x + self.pomdp_r + 1
y0, y1 = max(0, global_y - self.pomdp_r), global_y + self.pomdp_r + 1
obs = self._padded_obs_cube[:, x0:x1, y0:y1]
@ -284,10 +282,9 @@ class BaseFactory(gym.Env):
obs = self._obs_cube
if self.cast_shadows:
obs_block_light = [obs[idx] != c.OCCUPIED_CELL.value for idx, obs_slice
in enumerate(self._slices) if obs_slice.is_blocking_light]
obs_block_light = [obs[idx] != c.OCCUPIED_CELL.value for idx in shadowing_idxs]
door_shadowing = False
if door := self._doors.by_pos(agent.pos):
if door := self._entities[c.DOORS].by_pos(agent.pos):
if door.is_closed:
for group in door.connectivity_subgroups:
if agent.last_pos not in group:
@ -298,8 +295,9 @@ class BaseFactory(gym.Env):
xs, ys = zip(*blocking)
else:
xs, ys = zip(*group)
# noinspection PyTypeChecker
obs_block_light[self._slices.get_idx(c.LEVEL)][xs, ys] = False
# 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:
@ -310,28 +308,18 @@ class BaseFactory(gym.Env):
# noinspection PyUnboundLocalVariable
light_block_map[xs, ys] = 0
agent.temp_light_map = light_block_map
for obs_idx in range(obs.shape[0]):
if self._slices[obs_idx].can_be_shadowed:
obs[obs_idx] = (obs[obs_idx] * light_block_map) - (
(1 - light_block_map) * obs[self._slices.get_idx(c.LEVEL)]
)
for obs_idx in can_be_shadowed_idxs:
obs[obs_idx] = (obs[obs_idx] * light_block_map) - (
(1 - light_block_map) * obs[0]
)
if self.combin_agent_slices_in_obs and self.n_agents > 1:
agent_obs = np.sum(obs[[key for key, l_slice in self._slices.items() if c.AGENT.name in l_slice.name and
(not self.omit_agent_slice_in_obs and l_slice.name != agent.name)]],
axis=0, keepdims=True)
obs = np.concatenate((obs[:first_agent_slice], agent_obs, obs[first_agent_slice+self.n_agents:]))
return obs
else:
if self.omit_agent_slice_in_obs:
obs_new = obs[[key for key, val in self._slices.items() if val.name != agent.name]]
return obs_new
else:
return obs
return obs
def get_all_tiles_with_collisions(self) -> List[Tile]:
tiles_with_collisions = list()
for tile in self._tiles:
for tile in self._entities[c.FLOOR]:
if tile.is_occupied():
guests = [guest for guest in tile.guests if guest.can_collide]
if len(guests) >= 2:
@ -353,7 +341,7 @@ class BaseFactory(gym.Env):
x_new = agent.x + x_diff
y_new = agent.y + y_diff
new_tile = self._tiles.by_pos((x_new, y_new))
new_tile = self._entities[c.FLOOR].by_pos((x_new, y_new))
if new_tile:
valid = c.VALID
else:
@ -362,13 +350,13 @@ class BaseFactory(gym.Env):
return tile, valid
if self.parse_doors and agent.last_pos != c.NO_POS:
if door := self._doors.by_pos(new_tile.pos):
if door := self._entities[c.DOORS].by_pos(new_tile.pos):
if door.can_collide:
return agent.tile, c.NOT_VALID
else: # door.is_closed:
pass
if door := self._doors.by_pos(agent.pos):
if door := self._entities[c.DOORS].by_pos(agent.pos):
if door.is_open:
pass
else: # door.is_closed:
@ -388,7 +376,7 @@ class BaseFactory(gym.Env):
info_dict = dict()
reward = 0
for agent in self._agents:
for agent in self._entities[c.AGENT]:
if self._actions.is_moving_action(agent.temp_action):
if agent.temp_valid:
# info_dict.update(movement=1)
@ -427,16 +415,15 @@ class BaseFactory(gym.Env):
height, width = self._obs_cube.shape[1:]
self._renderer = Renderer(width, height, view_radius=self.pomdp_r, fps=5)
walls = [RenderEntity('wall', pos)
for pos in np.argwhere(self._slices.by_enum(c.LEVEL).slice == c.OCCUPIED_CELL.value)]
walls = [RenderEntity('wall', wall.pos) for wall in self._entities[c.WALLS]]
agents = []
for i, agent in enumerate(self._agents):
for i, agent in enumerate(self._entities[c.AGENT]):
name, state = h.asset_str(agent)
agents.append(RenderEntity(name, agent.pos, 1, 'none', state, i + 1, agent.temp_light_map))
doors = []
if self.parse_doors:
for i, door in enumerate(self._doors):
for i, door in enumerate(self._entities[c.DOORS]):
name, state = 'door_open' if door.is_open else 'door_closed', 'blank'
doors.append(RenderEntity(name, door.pos, 1, 'none', state, i + 1))
additional_assets = self.render_additional_assets()
@ -454,7 +441,9 @@ class BaseFactory(gym.Env):
def _summarize_state(self):
summary = {f'{REC_TAC}_step': self._steps}
for entity in self._entitites:
self._entities[c.WALLS].summarize_state()
for entity in self._entities:
if hasattr(entity, 'summarize_state'):
summary.update({f'{REC_TAC}_{entity.name}': entity.summarize_state()})
return summary
@ -475,24 +464,14 @@ class BaseFactory(gym.Env):
return []
@property
def additional_entities(self) -> Union[Entities, List[Entities]]:
def additional_entities(self) -> Dict[(Enum, Entities)]:
"""
When heriting from this Base Class, you musst implement this methode!!!
:return: A single Entites collection or a list of such.
:rtype: Union[Entities, List[Entities]]
"""
return []
@property
def additional_slices(self) -> Union[Slice, List[Slice]]:
"""
When heriting from this Base Class, you musst implement this methode!!!
:return: A list of Slice-objects.
:rtype: List[Slice]
"""
return []
return {}
# Functions which provide additions to functions of the base class
# Always call super!!!!!!

View File

@ -4,22 +4,23 @@ from environments.helpers import Constants as c
import itertools
def sub(p, q):
return p - q
class Object:
def __bool__(self):
return True
@property
def is_blocking_light(self):
return self._is_blocking_light
@property
def name(self):
return self._name
def __init__(self, name, name_is_identifier=False, **kwargs):
def __init__(self, name, name_is_identifier=False, is_blocking_light=False, **kwargs):
name = name.name if hasattr(name, 'name') else name
self._name = f'{self.__class__.__name__}#{name}' if name_is_identifier else name
self._is_blocking_light = is_blocking_light
if kwargs:
print(f'Following kwargs were passed, but ignored: {kwargs}')
@ -33,40 +34,6 @@ class Action(Object):
super(Action, self).__init__(*args)
class Slice(Object):
@property
def is_observable(self):
return self._is_observable
@property
def shape(self):
return self.slice.shape
@property
def occupied_tiles(self):
return np.argwhere(self.slice == c.OCCUPIED_CELL.value)
@property
def free_tiles(self):
return np.argwhere(self.slice == c.FREE_CELL.value)
def __init__(self, identifier, arrayslice, is_blocking_light=False, can_be_shadowed=True, is_observable=True):
super(Slice, self).__init__(identifier)
self.slice = arrayslice
self.is_blocking_light = is_blocking_light
self.can_be_shadowed = can_be_shadowed
self._is_observable = is_observable
def set_slice(self, new_slice: np.ndarray):
assert self.slice.shape == new_slice.shape
self.slice = new_slice
class Wall(Object):
pass
class Tile(Object):
@property
@ -118,6 +85,10 @@ class Tile(Object):
return True
class Wall(Tile):
pass
class Entity(Object):
@property
@ -153,41 +124,6 @@ class Entity(Object):
return self.__dict__.copy()
class MoveableEntity(Entity):
@property
def last_tile(self):
return self._last_tile
@property
def last_pos(self):
if self._last_tile:
return self._last_tile.pos
else:
return c.NO_POS
@property
def direction_of_view(self):
last_x, last_y = self.last_pos
curr_x, curr_y = self.pos
return last_x-curr_x, last_y-curr_y
def __init__(self, *args, **kwargs):
super(MoveableEntity, self).__init__(*args, **kwargs)
self._last_tile = None
def move(self, next_tile):
curr_tile = self.tile
if curr_tile != next_tile:
next_tile.enter(self)
curr_tile.leave(self)
self._tile = next_tile
self._last_tile = curr_tile
return True
else:
return False
class Door(Entity):
@property
@ -268,6 +204,41 @@ class Door(Entity):
return False
class MoveableEntity(Entity):
@property
def last_tile(self):
return self._last_tile
@property
def last_pos(self):
if self._last_tile:
return self._last_tile.pos
else:
return c.NO_POS
@property
def direction_of_view(self):
last_x, last_y = self.last_pos
curr_x, curr_y = self.pos
return last_x-curr_x, last_y-curr_y
def __init__(self, *args, **kwargs):
super(MoveableEntity, self).__init__(*args, **kwargs)
self._last_tile = None
def move(self, next_tile):
curr_tile = self.tile
if curr_tile != next_tile:
next_tile.enter(self)
curr_tile.leave(self)
self._tile = next_tile
self._last_tile = curr_tile
return True
else:
return False
class Agent(MoveableEntity):
def __init__(self, *args, **kwargs):

View File

@ -1,10 +1,11 @@
import random
from abc import ABC
from enum import Enum
from typing import List, Union
from typing import List, Union, Dict
import numpy as np
from environments.factory.base.objects import Entity, Tile, Agent, Door, Slice, Action
from environments.factory.base.objects import Entity, Tile, Agent, Door, Action, Wall
from environments.utility_classes import MovementProperties
from environments import helpers as h
from environments.helpers import Constants as c
@ -13,10 +14,6 @@ from environments.helpers import Constants as c
class Register:
_accepted_objects = Entity
@classmethod
def from_argwhere_coordinates(cls, positions: [(int, int)], tiles):
return cls.from_tiles([tiles.by_pos(position) for position in positions])
@property
def name(self):
return self.__class__.__name__
@ -25,7 +22,7 @@ class Register:
def n(self):
return len(self)
def __init__(self):
def __init__(self, *args, **kwargs):
self._register = dict()
self._names = dict()
@ -35,17 +32,18 @@ class Register:
def __iter__(self):
return iter(self.values())
def __add__(self, other: _accepted_objects):
def register_item(self, other: _accepted_objects):
assert isinstance(other, self._accepted_objects), f'All item names have to be of type ' \
f'{self._accepted_objects}, ' \
f'but were {other.__class__}.,'
self._names.update({other.name: len(self._register)})
self._register.update({len(self._register): other})
new_idx = len(self._register)
self._names.update({other.name: new_idx})
self._register.update({new_idx: other})
return self
def register_additional_items(self, others: List[_accepted_objects]):
for other in others:
self + other
self.register_item(other)
return self
def keys(self):
@ -60,8 +58,9 @@ class Register:
def __getitem__(self, item):
try:
return self._register[item]
except KeyError:
except KeyError as e:
print('NO')
print(e)
raise
def by_name(self, item):
@ -82,29 +81,66 @@ class Register:
def get_idx(self, enum_obj: Enum):
return self._names[enum_obj.name]
class ObjectRegister(Register):
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
self._array = None
def register_item(self, other):
super(ObjectRegister, 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._level_shape)))
class EntityObjectRegister(ObjectRegister, ABC):
def as_array(self):
raise NotImplementedError
@classmethod
def from_tiles(cls, tiles, **kwargs):
def from_tiles(cls, tiles, *args, **kwargs):
# objects_name = cls._accepted_objects.__name__
entities = [cls._accepted_objects(i, tile, name_is_identifier=True, **kwargs) for i, tile in enumerate(tiles)]
registered_obj = cls()
registered_obj.register_additional_items(entities)
return registered_obj
entities = [cls._accepted_objects(i, tile, name_is_identifier=True, **kwargs)
for i, tile in enumerate(tiles)]
register_obj = cls(*args)
register_obj.register_additional_items(entities)
return register_obj
class EntityRegister(Register):
@classmethod
def from_argwhere_coordinates(cls, positions: [(int, int)], tiles, *args, **kwargs):
return cls.from_tiles([tiles.by_pos(position) for position in positions], *args, **kwargs)
@property
def positions(self):
return [agent.pos for agent in self]
return list(self._tiles.keys())
def __init__(self):
super(EntityRegister, self).__init__()
@property
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._tiles = dict()
self.is_blocking_light = is_blocking_light
self.is_observable = is_observable
def __add__(self, other):
super(EntityRegister, self).__add__(other)
def register_item(self, other):
super(EntityObjectRegister, self).register_item(other)
self._tiles[other.pos] = other
def register_additional_items(self, others):
for other in others:
self.register_item(other)
return self
def by_pos(self, pos):
if isinstance(pos, np.ndarray):
pos = tuple(pos)
@ -114,9 +150,34 @@ class EntityRegister(Register):
return None
class MovingEntityObjectRegister(EntityObjectRegister, 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)
try:
return [x for x in self if x == pos][0]
except IndexError:
return None
def delete_item(self, item):
self
class Entities(Register):
_accepted_objects = Register
_accepted_objects = EntityObjectRegister
@property
def arrays(self):
return {key: val.as_array() for key, val in self.items() if val.is_observable}
@property
def names(self):
return list(self._register.keys())
def __init__(self):
super(Entities, self).__init__()
@ -124,23 +185,64 @@ class Entities(Register):
def __iter__(self):
return iter([x for sublist in self.values() for x in sublist])
@classmethod
def from_argwhere_coordinates(cls, positions):
raise AttributeError()
def register_item(self, other: dict):
assert not any([key for key in other.keys() if key in self._names]), \
"This group of entities has already been registered!"
self._register.update(other)
return self
def register_additional_items(self, others: Dict):
return self.register_item(others)
class FloorTiles(EntityRegister):
_accepted_objects = Tile
class WallTiles(EntityObjectRegister):
_accepted_objects = Wall
_light_blocking = True
def as_array(self):
if not np.any(self._array):
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)
@property
def encoding(self):
return c.OCCUPIED_CELL.value
@property
def array(self):
return self._array
@classmethod
def from_argwhere_coordinates(cls, argwhere_coordinates):
tiles = cls()
def from_argwhere_coordinates(cls, argwhere_coordinates, *args, **kwargs):
tiles = cls(*args, **kwargs)
# noinspection PyTypeChecker
tiles.register_additional_items(
[cls._accepted_objects(i, pos, name_is_identifier=True) for i, pos in enumerate(argwhere_coordinates)]
[cls._accepted_objects(i, pos, name_is_identifier=True, is_blocking_light=cls._light_blocking)
for i, pos in enumerate(argwhere_coordinates)]
)
return tiles
@classmethod
def from_tiles(cls, tiles, *args, **kwargs):
raise RuntimeError()
class FloorTiles(WallTiles):
_accepted_objects = Tile
_light_blocking = False
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, is_observable=False, **kwargs)
@property
def encoding(self):
return c.FREE_CELL.value
@property
def occupied_tiles(self):
tiles = [tile for tile in self if tile.is_occupied()]
@ -153,8 +255,22 @@ class FloorTiles(EntityRegister):
random.shuffle(tiles)
return tiles
@classmethod
def from_tiles(cls, tiles, *args, **kwargs):
raise RuntimeError()
class Agents(EntityRegister):
class Agents(MovingEntityObjectRegister):
def as_array(self):
self._array[:] = c.FREE_CELL.value
# noinspection PyTupleAssignmentBalance
z, x, y = range(len(self)), *zip(*[x.pos for x in self])
self._array[z, x, y] = c.OCCUPIED_CELL.value
if self.individual_slices:
return self._array
else:
return self._array.sum(axis=0, keepdims=True)
_accepted_objects = Agent
@ -163,7 +279,17 @@ class Agents(EntityRegister):
return [agent.pos for agent in self]
class Doors(EntityRegister):
class Doors(EntityObjectRegister):
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]:
@ -221,47 +347,6 @@ class Actions(Register):
return action == h.EnvActions.USE_DOOR.name
class StateSlices(Register):
_accepted_objects = Slice
@property
def n_observable_slices(self):
return len([x for x in self if x.is_observable])
@property
def AGENTSTARTIDX(self):
if self._agent_start_idx:
return self._agent_start_idx
else:
self._agent_start_idx = min([idx for idx, x in self.items() if c.AGENT.value in x.name])
return self._agent_start_idx
def __init__(self):
super(StateSlices, self).__init__()
self._agent_start_idx = None
def _gather_occupation(self, excluded_slices):
exclusion = excluded_slices or []
assert isinstance(exclusion, (int, list))
exclusion = exclusion if isinstance(exclusion, list) else [exclusion]
result = np.sum([x for i, x in self.items() if i not in exclusion], axis=0)
return result
def free_cells(self, excluded_slices: Union[None, List[int], int] = None) -> np.array:
occupation = self._gather_occupation(excluded_slices)
free_cells = np.argwhere(occupation == c.IS_FREE_CELL)
np.random.shuffle(free_cells)
return free_cells
def occupied_cells(self, excluded_slices: Union[None, List[int], int] = None) -> np.array:
occupation = self._gather_occupation(excluded_slices)
occupied_cells = np.argwhere(occupation == c.IS_OCCUPIED_CELL.value)
np.random.shuffle(occupied_cells)
return occupied_cells
class Zones(Register):
@property
@ -279,9 +364,9 @@ class Zones(Register):
self._accounting_zones = list()
self._danger_zones = list()
for symbol in np.unique(parsed_level):
if symbol == h.WALL:
if symbol == c.WALL.value:
continue
elif symbol == h.DANGER_ZONE:
elif symbol == c.DANGER_ZONE.value:
self + symbol
slices.append(h.one_hot_level(parsed_level, symbol))
self._danger_zones.append(symbol)