Items and combination of item and dirt
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import abc
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List, Union, Iterable
|
||||
@@ -10,6 +11,7 @@ import yaml
|
||||
from gym.wrappers import FrameStack
|
||||
|
||||
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
|
||||
@@ -28,20 +30,7 @@ class BaseFactory(gym.Env):
|
||||
|
||||
@property
|
||||
def observation_space(self):
|
||||
if self.combin_agent_slices_in_obs and self.omit_agent_slice_in_obs:
|
||||
if self.n_agents > 1:
|
||||
slices = self._slices.n - (self._agents.n - 1)
|
||||
else:
|
||||
slices = self._slices.n - 1
|
||||
elif self.combin_agent_slices_in_obs and not self.omit_agent_slice_in_obs:
|
||||
slices = self._slices.n - (self._agents.n - 1)
|
||||
elif not self.combin_agent_slices_in_obs and self.omit_agent_slice_in_obs:
|
||||
slices = self._slices.n - self._agents.n
|
||||
elif not self.combin_agent_slices_in_obs and not self.omit_agent_slice_in_obs:
|
||||
slices = self._slices.n
|
||||
else:
|
||||
raise RuntimeError('This should not happen!')
|
||||
|
||||
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)
|
||||
return space
|
||||
@@ -54,36 +43,6 @@ class BaseFactory(gym.Env):
|
||||
def movement_actions(self):
|
||||
return self._actions.movement_actions
|
||||
|
||||
@property
|
||||
def additional_actions(self) -> Union[str, List[str]]:
|
||||
"""
|
||||
When heriting from this Base Class, you musst implement this methode!!!
|
||||
|
||||
:return: A list of Actions-object holding all additional actions.
|
||||
:rtype: List[Action]
|
||||
"""
|
||||
raise NotImplementedError('Please register additional actions ')
|
||||
|
||||
@property
|
||||
def additional_entities(self) -> Union[Entities, List[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]]
|
||||
"""
|
||||
raise NotImplementedError('Please register additional entities.')
|
||||
|
||||
@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]
|
||||
"""
|
||||
raise NotImplementedError('Please register additional slices.')
|
||||
|
||||
def __enter__(self):
|
||||
return self if self.frames_to_stack == 0 else FrameStack(self, self.frames_to_stack)
|
||||
|
||||
@@ -94,17 +53,20 @@ class BaseFactory(gym.Env):
|
||||
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,
|
||||
verbose=False, doors_have_area=True, **kwargs):
|
||||
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."
|
||||
|
||||
# Attribute Assignment
|
||||
self._base_rng = np.random.default_rng(kwargs.get('seed', default=time.time_ns()))
|
||||
self.env_seed = env_seed
|
||||
self._base_rng = np.random.default_rng(self.env_seed)
|
||||
self.movement_properties = movement_properties
|
||||
self.level_name = level_name
|
||||
self._level_shape = None
|
||||
self.verbose = verbose
|
||||
self._renderer = None # expensive - don't use it when not required !
|
||||
|
||||
self.n_agents = n_agents
|
||||
|
||||
self.max_steps = max_steps
|
||||
self.pomdp_r = pomdp_r
|
||||
self.combin_agent_slices_in_obs = combin_agent_slices_in_obs
|
||||
@@ -132,25 +94,37 @@ class BaseFactory(gym.Env):
|
||||
# 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.name, h.one_hot_level(parsed_level), is_blocking_light=True)]
|
||||
level = [Slice(c.LEVEL, h.one_hot_level(parsed_level), is_blocking_light=True)]
|
||||
self._level_shape = level[0].shape
|
||||
|
||||
# Doors
|
||||
parsed_doors = h.one_hot_level(parsed_level, c.DOOR)
|
||||
if parsed_doors.any():
|
||||
doors = [Slice(c.DOORS.name, parsed_doors, is_blocking_light=True)]
|
||||
doors = [Slice(c.DOORS, parsed_doors, is_blocking_light=True)]
|
||||
else:
|
||||
doors = []
|
||||
|
||||
# Agents
|
||||
agents = []
|
||||
for i in range(self.n_agents):
|
||||
agents.append(Slice(f'{c.AGENT.name}#{i}', np.zeros_like(level[0].slice, dtype=np.float32)))
|
||||
state_slices.register_additional_items(level+doors+agents)
|
||||
agent_names = [f'{c.AGENT.value}#{i}' for i in range(self.n_agents)]
|
||||
|
||||
# Additional Slices from SubDomains
|
||||
if additional_slices := self.additional_slices:
|
||||
state_slices.register_additional_items(additional_slices)
|
||||
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:
|
||||
@@ -198,18 +172,6 @@ class BaseFactory(gym.Env):
|
||||
obs = self._get_observations()
|
||||
return obs
|
||||
|
||||
def pre_step(self) -> None:
|
||||
pass
|
||||
|
||||
def do_additional_reset(self) -> None:
|
||||
pass
|
||||
|
||||
def do_additional_step(self) -> dict:
|
||||
return {}
|
||||
|
||||
def post_step(self) -> dict:
|
||||
return {}
|
||||
|
||||
def step(self, actions):
|
||||
actions = [actions] if isinstance(actions, int) or np.isscalar(actions) else actions
|
||||
assert isinstance(actions, Iterable), f'"actions" has to be in [{int, list}]'
|
||||
@@ -217,31 +179,22 @@ class BaseFactory(gym.Env):
|
||||
done = False
|
||||
|
||||
# Pre step Hook for later use
|
||||
self.pre_step()
|
||||
self.hook_pre_step()
|
||||
|
||||
# Move this in a seperate function?
|
||||
for action, agent in zip(actions, self._agents):
|
||||
agent.clear_temp_sate()
|
||||
action_name = self._actions[action]
|
||||
if self._actions.is_moving_action(action):
|
||||
valid = self._move_or_colide(agent, action_name)
|
||||
elif self._actions.is_no_op(action):
|
||||
action_obj = self._actions[action]
|
||||
if self._actions.is_moving_action(action_obj):
|
||||
valid = self._move_or_colide(agent, action_obj)
|
||||
elif self._actions.is_no_op(action_obj):
|
||||
valid = c.VALID.value
|
||||
elif self._actions.is_door_usage(action):
|
||||
# Check if agent really is standing on a door:
|
||||
if self.doors_have_area:
|
||||
door = self._doors.get_near_position(agent.pos)
|
||||
else:
|
||||
door = self._doors.by_pos(agent.pos)
|
||||
if door is not None:
|
||||
door.use()
|
||||
valid = c.VALID.value
|
||||
# When he doesn't...
|
||||
else:
|
||||
valid = c.NOT_VALID.value
|
||||
elif self._actions.is_door_usage(action_obj):
|
||||
valid = self._handle_door_interaction(agent)
|
||||
else:
|
||||
valid = self.do_additional_actions(agent, action)
|
||||
agent.temp_action = action
|
||||
valid = self.do_additional_actions(agent, action_obj)
|
||||
assert valid is not None, 'This should not happen, every Action musst be detected correctly!'
|
||||
agent.temp_action = action_obj
|
||||
agent.temp_valid = valid
|
||||
|
||||
# In-between step Hook for later use
|
||||
@@ -275,12 +228,25 @@ class BaseFactory(gym.Env):
|
||||
info.update(self._summarize_state())
|
||||
|
||||
# Post step Hook for later use
|
||||
info.update(self.post_step())
|
||||
info.update(self.hook_post_step())
|
||||
|
||||
obs = self._get_observations()
|
||||
|
||||
return obs, reward, done, info
|
||||
|
||||
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)
|
||||
else:
|
||||
door = self._doors.by_pos(agent.pos)
|
||||
if door is not None:
|
||||
door.use()
|
||||
return c.VALID.value
|
||||
# When he doesn't...
|
||||
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:
|
||||
@@ -291,7 +257,7 @@ class BaseFactory(gym.Env):
|
||||
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 != h.NO_POS:
|
||||
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:
|
||||
@@ -318,8 +284,8 @@ class BaseFactory(gym.Env):
|
||||
obs = self._obs_cube
|
||||
|
||||
if self.cast_shadows:
|
||||
obs_block_light = [obs[idx] != c.OCCUPIED_CELL.value for idx, slice
|
||||
in enumerate(self._slices) if slice.is_blocking_light]
|
||||
obs_block_light = [obs[idx] != c.OCCUPIED_CELL.value for idx, obs_slice
|
||||
in enumerate(self._slices) if obs_slice.is_blocking_light]
|
||||
door_shadowing = False
|
||||
if door := self._doors.by_pos(agent.pos):
|
||||
if door.is_closed:
|
||||
@@ -332,6 +298,7 @@ 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
|
||||
|
||||
light_block_map = Map((np.prod(obs_block_light, axis=0) != True).astype(int))
|
||||
@@ -340,9 +307,14 @@ class BaseFactory(gym.Env):
|
||||
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
|
||||
obs = (obs * light_block_map) - ((1 - light_block_map) * obs[self._slices.get_idx(c.LEVEL)])
|
||||
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)]
|
||||
)
|
||||
|
||||
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
|
||||
@@ -357,9 +329,6 @@ class BaseFactory(gym.Env):
|
||||
else:
|
||||
return obs
|
||||
|
||||
def do_additional_actions(self, agent: Agent, action: int) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_all_tiles_with_collisions(self) -> List[Tile]:
|
||||
tiles_with_collisions = list()
|
||||
for tile in self._tiles:
|
||||
@@ -392,7 +361,7 @@ class BaseFactory(gym.Env):
|
||||
valid = c.VALID
|
||||
return tile, valid
|
||||
|
||||
if self.parse_doors and agent.last_pos != h.NO_POS:
|
||||
if self.parse_doors and agent.last_pos != c.NO_POS:
|
||||
if door := self._doors.by_pos(new_tile.pos):
|
||||
if door.can_collide:
|
||||
return agent.tile, c.NOT_VALID
|
||||
@@ -416,10 +385,63 @@ class BaseFactory(gym.Env):
|
||||
|
||||
def calculate_reward(self) -> (int, dict):
|
||||
# Returns: Reward, Info
|
||||
raise NotImplementedError
|
||||
info_dict = dict()
|
||||
reward = 0
|
||||
|
||||
for agent in self._agents:
|
||||
if self._actions.is_moving_action(agent.temp_action):
|
||||
if agent.temp_valid:
|
||||
# info_dict.update(movement=1)
|
||||
reward -= 0.00
|
||||
else:
|
||||
# self.print('collision')
|
||||
reward -= 0.01
|
||||
self.print(f'{agent.name} just hit the wall at {agent.pos}.')
|
||||
info_dict.update({f'{agent.name}_vs_LEVEL': 1})
|
||||
|
||||
elif self._actions.is_door_usage(agent.temp_action):
|
||||
if agent.temp_valid:
|
||||
self.print(f'{agent.name} did just use the door at {agent.pos}.')
|
||||
info_dict.update(door_used=1)
|
||||
else:
|
||||
reward -= 0.01
|
||||
self.print(f'{agent.name} just tried to use a door at {agent.pos}, but failed.')
|
||||
info_dict.update({f'{agent.name}_failed_action': 1})
|
||||
info_dict.update({f'{agent.name}_failed_door_open': 1})
|
||||
elif self._actions.is_no_op(agent.temp_action):
|
||||
info_dict.update(no_op=1)
|
||||
reward -= 0.00
|
||||
|
||||
additional_reward, additional_info_dict = self.calculate_additional_reward(agent)
|
||||
reward += additional_reward
|
||||
info_dict.update(additional_info_dict)
|
||||
|
||||
for other_agent in agent.temp_collisions:
|
||||
info_dict.update({f'{agent.name}_vs_{other_agent.name}': 1})
|
||||
|
||||
self.print(f"reward is {reward}")
|
||||
return reward, info_dict
|
||||
|
||||
def render(self, mode='human'):
|
||||
raise NotImplementedError
|
||||
if not self._renderer: # lazy init
|
||||
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)]
|
||||
|
||||
agents = []
|
||||
for i, agent in enumerate(self._agents):
|
||||
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):
|
||||
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()
|
||||
|
||||
self._renderer.render(walls + doors + additional_assets + agents)
|
||||
|
||||
def save_params(self, filepath: Path):
|
||||
# noinspection PyProtectedMember
|
||||
@@ -440,3 +462,66 @@ class BaseFactory(gym.Env):
|
||||
def print(self, string):
|
||||
if self.verbose:
|
||||
print(string)
|
||||
|
||||
# Properties which are called by the base class to extend beyond attributes of the base class
|
||||
@property
|
||||
def additional_actions(self) -> Union[Action, List[Action]]:
|
||||
"""
|
||||
When heriting from this Base Class, you musst implement this methode!!!
|
||||
|
||||
:return: A list of Actions-object holding all additional actions.
|
||||
:rtype: List[Action]
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def additional_entities(self) -> Union[Entities, List[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 []
|
||||
|
||||
# Functions which provide additions to functions of the base class
|
||||
# Always call super!!!!!!
|
||||
@abc.abstractmethod
|
||||
def do_additional_reset(self) -> None:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def do_additional_step(self) -> dict:
|
||||
return {}
|
||||
|
||||
@abc.abstractmethod
|
||||
def do_additional_actions(self, agent: Agent, action: int) -> Union[None, bool]:
|
||||
return None
|
||||
|
||||
@abc.abstractmethod
|
||||
def calculate_additional_reward(self, agent: Agent) -> (int, dict):
|
||||
return 0, {}
|
||||
|
||||
@abc.abstractmethod
|
||||
def render_additional_assets(self):
|
||||
return []
|
||||
|
||||
# Hooks for in between operations.
|
||||
# Always call super!!!!!!
|
||||
@abc.abstractmethod
|
||||
def hook_pre_step(self) -> None:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def hook_post_step(self) -> dict:
|
||||
return {}
|
||||
|
||||
Reference in New Issue
Block a user