major redesign ob observations and entittes
0
modules/__init__.py
Normal file
0
modules/_template/__init__.py
Normal file
11
modules/_template/constants.py
Normal file
@ -0,0 +1,11 @@
|
||||
TEMPLATE = '#' # TEMPLATE _identifier. Define your own!
|
||||
|
||||
# Movements
|
||||
NORTH = 'north'
|
||||
EAST = 'east'
|
||||
SOUTH = 'south'
|
||||
WEST = 'west'
|
||||
NORTHEAST = 'north_east'
|
||||
SOUTHEAST = 'south_east'
|
||||
SOUTHWEST = 'south_west'
|
||||
NORTHWEST = 'north_west'
|
24
modules/_template/rules.py
Normal file
@ -0,0 +1,24 @@
|
||||
from typing import List
|
||||
from environment.rules import Rule
|
||||
from environment.utils.results import TickResult, DoneResult
|
||||
|
||||
|
||||
class TemplateRule(Rule):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TemplateRule, self).__init__(*args, **kwargs)
|
||||
|
||||
def on_init(self, state):
|
||||
pass
|
||||
|
||||
def tick_pre_step(self, state) -> List[TickResult]:
|
||||
pass
|
||||
|
||||
def tick_step(self, state) -> List[TickResult]:
|
||||
pass
|
||||
|
||||
def tick_post_step(self, state) -> List[TickResult]:
|
||||
pass
|
||||
|
||||
def on_check_done(self, state) -> List[DoneResult]:
|
||||
pass
|
0
modules/batteries/__init__.py
Normal file
26
modules/batteries/actions.py
Normal file
@ -0,0 +1,26 @@
|
||||
from typing import Union
|
||||
|
||||
from environment.actions import Action
|
||||
from environment.utils.results import ActionResult
|
||||
|
||||
from modules.batteries import constants as b, rewards as r
|
||||
from environment import constants as c
|
||||
|
||||
|
||||
class BtryCharge(Action):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(b.CHARGE)
|
||||
|
||||
def do(self, entity, state) -> Union[None, ActionResult]:
|
||||
if charge_pod := state[b.CHARGE_PODS].by_pos(entity.pos):
|
||||
valid = charge_pod.charge_battery(state[b.BATTERIES].by_entity(entity))
|
||||
if valid:
|
||||
state.print(f'{entity.name} just charged batteries at {charge_pod.name}.')
|
||||
else:
|
||||
state.print(f'{entity.name} failed to charged batteries at {charge_pod.name}.')
|
||||
else:
|
||||
valid = c.NOT_VALID
|
||||
state.print(f'{entity.name} failed to charged batteries at {entity.pos}.')
|
||||
return ActionResult(entity=entity, identifier=self._identifier, validity=valid,
|
||||
reward=r.CHARGE_VALID if valid else r.CHARGE_FAIL)
|
19
modules/batteries/constants.py
Normal file
@ -0,0 +1,19 @@
|
||||
from typing import NamedTuple, Union
|
||||
|
||||
# Battery Env
|
||||
CHARGE_PODS = 'ChargePods'
|
||||
BATTERIES = 'Batteries'
|
||||
BATTERY_DISCHARGED = 'DISCHARGED'
|
||||
CHARGE_POD_SYMBOL = 1
|
||||
|
||||
|
||||
CHARGE = 'do_charge_action'
|
||||
|
||||
|
||||
class BatteryProperties(NamedTuple):
|
||||
initial_charge: float = 0.8 #
|
||||
charge_rate: float = 0.4 #
|
||||
charge_locations: int = 20 #
|
||||
per_action_costs: Union[dict, float] = 0.02
|
||||
done_when_discharged: bool = False
|
||||
multi_charge: bool = False
|
75
modules/batteries/entitites.py
Normal file
@ -0,0 +1,75 @@
|
||||
from environment.entity.mixin import BoundEntityMixin
|
||||
from environment.entity.object import EnvObject
|
||||
from environment.entity.entity import Entity
|
||||
from environment import constants as c
|
||||
from environment.utils.render import RenderEntity
|
||||
|
||||
from modules.batteries import constants as b
|
||||
|
||||
|
||||
class Battery(BoundEntityMixin, EnvObject):
|
||||
|
||||
@property
|
||||
def is_discharged(self):
|
||||
return self.charge_level == 0
|
||||
|
||||
@property
|
||||
def obs_tag(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return self.charge_level
|
||||
|
||||
def __init__(self, initial_charge_level: float, owner: Entity, *args, **kwargs):
|
||||
super(Battery, self).__init__(*args, **kwargs)
|
||||
self.charge_level = initial_charge_level
|
||||
self.bind_to(owner)
|
||||
|
||||
def do_charge_action(self, amount):
|
||||
if self.charge_level < 1:
|
||||
# noinspection PyTypeChecker
|
||||
self.charge_level = min(1, amount + self.charge_level)
|
||||
return c.VALID
|
||||
else:
|
||||
return c.NOT_VALID
|
||||
|
||||
def decharge(self, amount) -> float:
|
||||
if self.charge_level != 0:
|
||||
# noinspection PyTypeChecker
|
||||
self.charge_level = max(0, amount + self.charge_level)
|
||||
return c.VALID
|
||||
else:
|
||||
return c.NOT_VALID
|
||||
|
||||
def summarize_state(self, **_):
|
||||
attr_dict = {key: str(val) for key, val in self.__dict__.items() if not key.startswith('_') and key != 'data'}
|
||||
attr_dict.update(dict(name=self.name, belongs_to=self._bound_entity.name))
|
||||
return attr_dict
|
||||
|
||||
def render(self):
|
||||
return None
|
||||
|
||||
|
||||
class ChargePod(Entity):
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return b.CHARGE_POD_SYMBOL
|
||||
|
||||
def __init__(self, *args, charge_rate: float = 0.4,
|
||||
multi_charge: bool = False, **kwargs):
|
||||
super(ChargePod, self).__init__(*args, **kwargs)
|
||||
self.charge_rate = charge_rate
|
||||
self.multi_charge = multi_charge
|
||||
|
||||
def charge_battery(self, battery: Battery):
|
||||
if battery.charge_level == 1.0:
|
||||
return c.NOT_VALID
|
||||
if sum(guest for guest in self.tile.guests if 'agent' in guest.name.lower()) > 1:
|
||||
return c.NOT_VALID
|
||||
valid = battery.do_charge_action(self.charge_rate)
|
||||
return valid
|
||||
|
||||
def render(self):
|
||||
return RenderEntity(b.CHARGE_PODS, self.pos)
|
36
modules/batteries/groups.py
Normal file
@ -0,0 +1,36 @@
|
||||
from environment.groups.env_objects import EnvObjects
|
||||
from environment.groups.mixins import PositionMixin, HasBoundedMixin
|
||||
from modules.batteries.entitites import ChargePod, Battery
|
||||
|
||||
|
||||
class Batteries(HasBoundedMixin, EnvObjects):
|
||||
|
||||
_entity = Battery
|
||||
is_blocking_light: bool = False
|
||||
can_collide: bool = False
|
||||
|
||||
@property
|
||||
def obs_tag(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
@property
|
||||
def obs_pairs(self):
|
||||
return [(x.name, x) for x in self]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Batteries, self).__init__(*args, **kwargs)
|
||||
|
||||
def spawn_batteries(self, agents, initial_charge_level):
|
||||
batteries = [self._entity(initial_charge_level, agent) for _, agent in enumerate(agents)]
|
||||
self.add_items(batteries)
|
||||
|
||||
|
||||
class ChargePods(PositionMixin, EnvObjects):
|
||||
|
||||
_entity = ChargePod
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ChargePods, self).__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return super(ChargePods, self).__repr__()
|
3
modules/batteries/rewards.py
Normal file
@ -0,0 +1,3 @@
|
||||
CHARGE_VALID: float = 0.1
|
||||
CHARGE_FAIL: float = -0.1
|
||||
BATTERY_DISCHARGED: float = -1.0
|
61
modules/batteries/rules.py
Normal file
@ -0,0 +1,61 @@
|
||||
from typing import List, Union
|
||||
from environment.rules import Rule
|
||||
from environment.utils.results import TickResult, DoneResult
|
||||
|
||||
from environment import constants as c
|
||||
from modules.batteries import constants as b, rewards as r
|
||||
|
||||
|
||||
class Btry(Rule):
|
||||
|
||||
def __init__(self, initial_charge: float = 0.8, per_action_costs: Union[dict, float] = 0.02):
|
||||
super().__init__()
|
||||
self.per_action_costs = per_action_costs
|
||||
self.initial_charge = initial_charge
|
||||
|
||||
def on_init(self, state):
|
||||
state[b.BATTERIES].spawn_batteries(state[c.AGENT], self.initial_charge)
|
||||
|
||||
def tick_pre_step(self, state) -> List[TickResult]:
|
||||
pass
|
||||
|
||||
def tick_step(self, state) -> List[TickResult]:
|
||||
# Decharge
|
||||
batteries = state[b.BATTERIES]
|
||||
results = []
|
||||
|
||||
for agent in state[c.AGENT]:
|
||||
if isinstance(self.per_action_costs, dict):
|
||||
energy_consumption = self.per_action_costs[agent.step_result()['action']]
|
||||
else:
|
||||
energy_consumption = self.per_action_costs
|
||||
|
||||
batteries.by_entity(agent).decharge(energy_consumption)
|
||||
|
||||
results.append(TickResult(self.name, reward=0, entity=agent, validity=c.VALID))
|
||||
|
||||
return results
|
||||
|
||||
def tick_post_step(self, state) -> List[TickResult]:
|
||||
results = []
|
||||
for btry in state[b.BATTERIES]:
|
||||
if btry.is_discharged:
|
||||
state.print(f'Battery of {btry.bound_entity.name} is discharged!')
|
||||
results.append(
|
||||
TickResult(self.name, entity=btry.bound_entity, reward=r.BATTERY_DISCHARGED, validity=c.VALID))
|
||||
else:
|
||||
pass
|
||||
return results
|
||||
|
||||
|
||||
class BtryDoneAtDischarge(Rule):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def on_check_done(self, state) -> List[DoneResult]:
|
||||
if btry_done := any(battery.is_discharged for battery in state[b.BATTERIES]):
|
||||
return [DoneResult(self.name, validity=c.VALID, reward=r.BATTERY_DISCHARGED)]
|
||||
else:
|
||||
return [DoneResult(self.name, validity=c.NOT_VALID, reward=0)]
|
||||
|
0
modules/clean_up/__init__.py
Normal file
36
modules/clean_up/actions.py
Normal file
@ -0,0 +1,36 @@
|
||||
from typing import Union
|
||||
|
||||
from environment.actions import Action
|
||||
from environment.utils.results import ActionResult
|
||||
|
||||
from modules.clean_up import constants as d, rewards as r
|
||||
|
||||
from environment import constants as c
|
||||
|
||||
|
||||
class CleanUp(Action):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(d.CLEAN_UP)
|
||||
|
||||
def do(self, entity, state) -> Union[None, ActionResult]:
|
||||
if dirt := state[d.DIRT].by_pos(entity.pos):
|
||||
new_dirt_amount = dirt.amount - state[d.DIRT].clean_amount
|
||||
|
||||
if new_dirt_amount <= 0:
|
||||
state[d.DIRT].delete_env_object(dirt)
|
||||
else:
|
||||
dirt.set_new_amount(max(new_dirt_amount, c.VALUE_FREE_CELL))
|
||||
valid = c.VALID
|
||||
print_str = f'{entity.name} did just clean up some dirt at {entity.pos}.'
|
||||
state.print(print_str)
|
||||
reward = r.CLEAN_UP_VALID
|
||||
identifier = d.CLEAN_UP
|
||||
else:
|
||||
valid = c.NOT_VALID
|
||||
print_str = f'{entity.name} just tried to clean up some dirt at {entity.pos}, but failed.'
|
||||
state.print(print_str)
|
||||
reward = r.CLEAN_UP_FAIL
|
||||
identifier = d.CLEAN_UP_FAIL
|
||||
|
||||
return ActionResult(identifier=identifier, validity=valid, reward=reward, entity=entity)
|
7
modules/clean_up/constants.py
Normal file
@ -0,0 +1,7 @@
|
||||
DIRT = 'DirtPiles'
|
||||
|
||||
CLEAN_UP = 'do_cleanup_action'
|
||||
|
||||
CLEAN_UP_VALID = 'clean_up_valid'
|
||||
CLEAN_UP_FAIL = 'clean_up_fail'
|
||||
CLEAN_UP_ALL = 'all_cleaned_up'
|
BIN
modules/clean_up/dirtpiles.png
Normal file
After Width: | Height: | Size: 38 KiB |
35
modules/clean_up/entitites.py
Normal file
@ -0,0 +1,35 @@
|
||||
from numpy import random
|
||||
|
||||
from environment.entity.entity import Entity
|
||||
from environment.utils.render import RenderEntity
|
||||
from modules.clean_up import constants as d
|
||||
|
||||
|
||||
class DirtPile(Entity):
|
||||
|
||||
@property
|
||||
def amount(self):
|
||||
return self._amount
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
# Edit this if you want items to be drawn in the ops differntly
|
||||
return self._amount
|
||||
|
||||
def __init__(self, *args, max_local_amount=5, initial_amount=2, spawn_variation=0.05, **kwargs):
|
||||
super(DirtPile, self).__init__(*args, **kwargs)
|
||||
self._amount = abs(initial_amount + (
|
||||
random.normal(loc=0, scale=spawn_variation, size=1).item() * initial_amount)
|
||||
)
|
||||
self.max_local_amount = max_local_amount
|
||||
|
||||
def set_new_amount(self, amount):
|
||||
self._amount = min(amount, self.max_local_amount)
|
||||
|
||||
def summarize_state(self):
|
||||
state_dict = super().summarize_state()
|
||||
state_dict.update(amount=float(self.amount))
|
||||
return state_dict
|
||||
|
||||
def render(self):
|
||||
return RenderEntity(d.DIRT, self.tile.pos, min(0.15 + self.amount, 1.5), 'scale')
|
64
modules/clean_up/groups.py
Normal file
@ -0,0 +1,64 @@
|
||||
from environment.groups.env_objects import EnvObjects
|
||||
from environment.groups.mixins import PositionMixin
|
||||
from environment.entity.wall_floor import Floor
|
||||
from modules.clean_up.entitites import DirtPile
|
||||
|
||||
from environment import constants as c
|
||||
|
||||
|
||||
class DirtPiles(PositionMixin, EnvObjects):
|
||||
|
||||
_entity = DirtPile
|
||||
is_blocking_light: bool = False
|
||||
can_collide: bool = False
|
||||
|
||||
@property
|
||||
def amount(self):
|
||||
return sum([dirt.amount for dirt in self])
|
||||
|
||||
def __init__(self, *args,
|
||||
initial_amount=2,
|
||||
initial_dirt_ratio=0.05,
|
||||
dirt_spawn_r_var=0.1,
|
||||
max_local_amount=5,
|
||||
clean_amount=1,
|
||||
max_global_amount: int = 20, **kwargs):
|
||||
super(DirtPiles, self).__init__(*args, **kwargs)
|
||||
self.clean_amount = clean_amount
|
||||
self.initial_amount = initial_amount
|
||||
self.initial_dirt_ratio = initial_dirt_ratio
|
||||
self.dirt_spawn_r_var = dirt_spawn_r_var
|
||||
self.max_global_amount = max_global_amount
|
||||
self.max_local_amount = max_local_amount
|
||||
|
||||
def spawn_dirt(self, then_dirty_tiles, amount) -> bool:
|
||||
if isinstance(then_dirty_tiles, Floor):
|
||||
then_dirty_tiles = [then_dirty_tiles]
|
||||
for tile in then_dirty_tiles:
|
||||
if not self.amount > self.max_global_amount:
|
||||
if dirt := self.by_pos(tile.pos):
|
||||
new_value = dirt.amount + amount
|
||||
dirt.set_new_amount(new_value)
|
||||
else:
|
||||
dirt = DirtPile(tile, initial_amount=amount, spawn_variation=self.dirt_spawn_r_var)
|
||||
self.add_item(dirt)
|
||||
else:
|
||||
return c.NOT_VALID
|
||||
return c.VALID
|
||||
|
||||
def trigger_dirt_spawn(self, state, initial_spawn=False) -> bool:
|
||||
free_for_dirt = [x for x in state[c.FLOOR]
|
||||
if len(x.guests) == 0 or (
|
||||
len(x.guests) == 1 and
|
||||
isinstance(next(y for y in x.guests), DirtPile))
|
||||
]
|
||||
state.rng.shuffle(free_for_dirt)
|
||||
|
||||
var = self.dirt_spawn_r_var
|
||||
new_spawn = abs(self.initial_dirt_ratio + (state.rng.uniform(-var, var) if initial_spawn else 0))
|
||||
n_dirt_tiles = max(0, int(new_spawn * len(free_for_dirt)))
|
||||
return self.spawn_dirt(free_for_dirt[:n_dirt_tiles], self.initial_amount)
|
||||
|
||||
def __repr__(self):
|
||||
s = super(DirtPiles, self).__repr__()
|
||||
return f'{s[:-1]}, {self.amount})'
|
3
modules/clean_up/rewards.py
Normal file
@ -0,0 +1,3 @@
|
||||
CLEAN_UP_VALID: float = 0.5
|
||||
CLEAN_UP_FAIL: float = -0.1
|
||||
CLEAN_UP_ALL: float = 4.5
|
15
modules/clean_up/rule_done_on_all_clean.py
Normal file
@ -0,0 +1,15 @@
|
||||
from environment import constants as c
|
||||
from environment.rules import Rule
|
||||
from environment.utils.results import DoneResult
|
||||
from modules.clean_up import constants as d, rewards as r
|
||||
|
||||
|
||||
class DirtAllCleanDone(Rule):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def on_check_done(self, state) -> [DoneResult]:
|
||||
if len(state[d.DIRT]) == 0 and state.curr_step:
|
||||
return [DoneResult(validity=c.VALID, identifier=self.name, reward=r.CLEAN_UP_ALL)]
|
||||
return [DoneResult(validity=c.NOT_VALID, identifier=self.name, reward=0)]
|
28
modules/clean_up/rule_respawn.py
Normal file
@ -0,0 +1,28 @@
|
||||
from environment.rules import Rule
|
||||
from environment.utils.results import TickResult
|
||||
|
||||
from modules.clean_up import constants as d
|
||||
|
||||
|
||||
class DirtRespawnRule(Rule):
|
||||
|
||||
def __init__(self, spawn_freq=15):
|
||||
super().__init__()
|
||||
self.spawn_freq = spawn_freq
|
||||
self._next_dirt_spawn = spawn_freq
|
||||
|
||||
def on_init(self, state) -> str:
|
||||
state[d.DIRT].trigger_dirt_spawn(state, initial_spawn=True)
|
||||
return f'Initial Dirt was spawned on: {[x.pos for x in state[d.DIRT]]}'
|
||||
|
||||
def tick_step(self, state):
|
||||
if self._next_dirt_spawn < 0:
|
||||
pass # No DirtPile Spawn
|
||||
elif not self._next_dirt_spawn:
|
||||
validity = state[d.DIRT].trigger_dirt_spawn(state)
|
||||
|
||||
return [TickResult(entity=None, validity=validity, identifier=self.name, reward=0)]
|
||||
self._next_dirt_spawn = self.spawn_freq
|
||||
else:
|
||||
self._next_dirt_spawn -= 1
|
||||
return []
|
24
modules/clean_up/rule_smear_on_move.py
Normal file
@ -0,0 +1,24 @@
|
||||
from environment.rules import Rule
|
||||
from environment.utils.helpers import is_move
|
||||
from environment.utils.results import TickResult
|
||||
|
||||
from environment import constants as c
|
||||
from modules.clean_up import constants as d
|
||||
|
||||
|
||||
class DirtSmearOnMove(Rule):
|
||||
|
||||
def __init__(self, smear_amount: float = 0.2):
|
||||
super().__init__()
|
||||
self.smear_amount = smear_amount
|
||||
|
||||
def tick_post_step(self, state):
|
||||
results = list()
|
||||
for entity in state.moving_entites:
|
||||
if is_move(entity.state.identifier) and entity.state.validity == c.VALID:
|
||||
if old_pos_dirt := state[d.DIRT].by_pos(entity.last_pos):
|
||||
if smeared_dirt := round(old_pos_dirt.amount * self.smear_amount, 2):
|
||||
if state[d.DIRT].spawn_dirt(entity.tile, amount=smeared_dirt):
|
||||
results.append(TickResult(identifier=self.name, entity=entity,
|
||||
reward=0, validity=c.VALID))
|
||||
return results
|
0
modules/destinations/__init__.py
Normal file
23
modules/destinations/actions.py
Normal file
@ -0,0 +1,23 @@
|
||||
from typing import Union
|
||||
|
||||
from environment.actions import Action
|
||||
from environment.utils.results import ActionResult
|
||||
|
||||
from modules.destinations import constants as d, rewards as r
|
||||
from environment import constants as c
|
||||
|
||||
|
||||
class DestAction(Action):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(d.DESTINATION)
|
||||
|
||||
def do(self, entity, state) -> Union[None, ActionResult]:
|
||||
if destination := state[d.DESTINATION].by_pos(entity.pos):
|
||||
valid = destination.do_wait_action(entity)
|
||||
state.print(f'{entity.name} just waited at {entity.pos}')
|
||||
else:
|
||||
valid = c.NOT_VALID
|
||||
state.print(f'{entity.name} just tried to do_wait_action do_wait_action at {entity.pos} but failed')
|
||||
return ActionResult(entity=entity, identifier=self._identifier, validity=valid,
|
||||
reward=r.WAIT_VALID if valid else r.WAIT_FAIL)
|
14
modules/destinations/constants.py
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
# Destination Env
|
||||
DESTINATION = 'Destinations'
|
||||
DEST_SYMBOL = 1
|
||||
DEST_REACHED_REWARD = 0.5
|
||||
DEST_REACHED = 'ReachedDestinations'
|
||||
|
||||
WAIT_ON_DEST = 'WAIT'
|
||||
|
||||
MODE_SINGLE = 'SINGLE'
|
||||
MODE_GROUPED = 'GROUPED'
|
||||
|
||||
DONE_ALL = 'DONE_ALL'
|
||||
DONE_SINGLE = 'DONE_SINGLE'
|
BIN
modules/destinations/destinations.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
51
modules/destinations/entitites.py
Normal file
@ -0,0 +1,51 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from environment.entity.agent import Agent
|
||||
from environment.entity.entity import Entity
|
||||
from environment import constants as c
|
||||
from environment.utils.render import RenderEntity
|
||||
from modules.destinations import constants as d
|
||||
|
||||
|
||||
class Destination(Entity):
|
||||
|
||||
@property
|
||||
def any_agent_has_dwelled(self):
|
||||
return bool(len(self._per_agent_times))
|
||||
|
||||
@property
|
||||
def currently_dwelling_names(self):
|
||||
return list(self._per_agent_times.keys())
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return d.DEST_SYMBOL
|
||||
|
||||
def __init__(self, *args, dwell_time: int = 0, **kwargs):
|
||||
super(Destination, self).__init__(*args, **kwargs)
|
||||
self.dwell_time = dwell_time
|
||||
self._per_agent_times = defaultdict(lambda: dwell_time)
|
||||
|
||||
def do_wait_action(self, agent: Agent):
|
||||
self._per_agent_times[agent.name] -= 1
|
||||
return c.VALID
|
||||
|
||||
def leave(self, agent: Agent):
|
||||
del self._per_agent_times[agent.name]
|
||||
|
||||
@property
|
||||
def is_considered_reached(self):
|
||||
agent_at_position = any(c.AGENT.lower() in x.name.lower() for x in self.tile.guests_that_can_collide)
|
||||
return (agent_at_position and not self.dwell_time) or any(x == 0 for x in self._per_agent_times.values())
|
||||
|
||||
def agent_is_dwelling(self, agent: Agent):
|
||||
return self._per_agent_times[agent.name] < self.dwell_time
|
||||
|
||||
def summarize_state(self) -> dict:
|
||||
state_summary = super().summarize_state()
|
||||
state_summary.update(per_agent_times=[
|
||||
dict(belongs_to=key, time=val) for key, val in self._per_agent_times.keys()], dwell_time=self.dwell_time)
|
||||
return state_summary
|
||||
|
||||
def render(self):
|
||||
return RenderEntity(d.DESTINATION, self.pos)
|
28
modules/destinations/groups.py
Normal file
@ -0,0 +1,28 @@
|
||||
from environment.groups.env_objects import EnvObjects
|
||||
from environment.groups.mixins import PositionMixin
|
||||
from modules.destinations.entitites import Destination
|
||||
|
||||
|
||||
class Destinations(PositionMixin, EnvObjects):
|
||||
|
||||
_entity = Destination
|
||||
is_blocking_light: bool = False
|
||||
can_collide: bool = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return super(Destinations, self).__repr__()
|
||||
|
||||
|
||||
class ReachedDestinations(Destinations):
|
||||
_entity = Destination
|
||||
is_blocking_light = False
|
||||
can_collide = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ReachedDestinations, self).__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return super(ReachedDestinations, self).__repr__()
|
3
modules/destinations/rewards.py
Normal file
@ -0,0 +1,3 @@
|
||||
WAIT_VALID: float = 0.1
|
||||
WAIT_FAIL: float = -0.1
|
||||
DEST_REACHED: float = 5.0
|
89
modules/destinations/rules.py
Normal file
@ -0,0 +1,89 @@
|
||||
from typing import List, Union
|
||||
from environment.rules import Rule
|
||||
from environment.utils.results import TickResult, DoneResult
|
||||
from environment import constants as c
|
||||
|
||||
from modules.destinations import constants as d, rewards as r
|
||||
from modules.destinations.entitites import Destination
|
||||
|
||||
|
||||
class DestinationReach(Rule):
|
||||
|
||||
def __init__(self, n_dests: int = 1, tiles: Union[List, None] = None):
|
||||
super(DestinationReach, self).__init__()
|
||||
self.n_dests = n_dests or len(tiles)
|
||||
self._tiles = tiles
|
||||
|
||||
def tick_step(self, state) -> List[TickResult]:
|
||||
|
||||
for dest in list(state[d.DESTINATION].values()):
|
||||
if dest.is_considered_reached:
|
||||
dest.change_parent_collection(state[d.DEST_REACHED])
|
||||
state.print(f'{dest.name} is reached now, removing...')
|
||||
else:
|
||||
for agent_name in dest.currently_dwelling_names:
|
||||
agent = state[c.AGENT][agent_name]
|
||||
if agent.pos == dest.pos:
|
||||
state.print(f'{agent.name} is still waiting.')
|
||||
pass
|
||||
else:
|
||||
dest.leave(agent)
|
||||
state.print(f'{agent.name} left the destination early.')
|
||||
return [TickResult(self.name, validity=c.VALID, reward=0, entity=None)]
|
||||
|
||||
def tick_post_step(self, state) -> List[TickResult]:
|
||||
results = list()
|
||||
for reached_dest in state[d.DEST_REACHED]:
|
||||
for guest in reached_dest.tile.guests:
|
||||
if guest in state[c.AGENT]:
|
||||
state.print(f'{guest.name} just reached destination at {guest.pos}')
|
||||
state[d.DEST_REACHED].delete_env_object(reached_dest)
|
||||
results.append(TickResult(self.name, validity=c.VALID, reward=r.DEST_REACHED, entity=guest))
|
||||
return results
|
||||
|
||||
|
||||
class DestinationDone(Rule):
|
||||
|
||||
def __init__(self):
|
||||
super(DestinationDone, self).__init__()
|
||||
|
||||
def on_check_done(self, state) -> List[DoneResult]:
|
||||
if not len(state[d.DESTINATION]):
|
||||
return [DoneResult(self.name, validity=c.VALID, reward=r.DEST_REACHED)]
|
||||
return []
|
||||
|
||||
|
||||
class DestinationSpawn(Rule):
|
||||
|
||||
def __init__(self, spawn_frequency: int = 5, n_dests: int = 1,
|
||||
spawn_mode: str = d.MODE_GROUPED):
|
||||
super(DestinationSpawn, self).__init__()
|
||||
self.spawn_frequency = spawn_frequency
|
||||
self.n_dests = n_dests
|
||||
self.spawn_mode = spawn_mode
|
||||
|
||||
def on_init(self, state):
|
||||
# noinspection PyAttributeOutsideInit
|
||||
self._dest_spawn_timer = self.spawn_frequency
|
||||
self.trigger_destination_spawn(self.n_dests, state)
|
||||
pass
|
||||
|
||||
def tick_pre_step(self, state) -> List[TickResult]:
|
||||
pass
|
||||
|
||||
def tick_step(self, state) -> List[TickResult]:
|
||||
if n_dest_spawn := max(0, self.n_dests - len(state[d.DESTINATION])):
|
||||
if self.spawn_mode == d.MODE_GROUPED and n_dest_spawn == self.n_dests:
|
||||
validity = state.rules['DestinationReach'].trigger_destination_spawn(n_dest_spawn, state)
|
||||
return [TickResult(self.name, validity=validity, entity=None, value=n_dest_spawn)]
|
||||
|
||||
@staticmethod
|
||||
def trigger_destination_spawn(n_dests, state, tiles=None):
|
||||
tiles = tiles or state[c.FLOOR].empty_tiles[:n_dests]
|
||||
if destinations := [Destination(tile) for tile in tiles]:
|
||||
state[d.DESTINATION].add_items(destinations)
|
||||
state.print(f'{n_dests} new destinations have been spawned')
|
||||
return c.VALID
|
||||
else:
|
||||
state.print('No Destiantions are spawning, limit is reached.')
|
||||
return c.NOT_VALID
|
0
modules/doors/__init__.py
Normal file
28
modules/doors/actions.py
Normal file
@ -0,0 +1,28 @@
|
||||
from typing import Union
|
||||
|
||||
from environment.actions import Action
|
||||
from environment.utils.results import ActionResult
|
||||
|
||||
from modules.doors import constants as d, rewards as r
|
||||
from environment import constants as c
|
||||
|
||||
|
||||
class DoorUse(Action):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(d.ACTION_DOOR_USE)
|
||||
|
||||
def do(self, entity, state) -> Union[None, ActionResult]:
|
||||
# Check if agent really is standing on a door:
|
||||
e = state.entities.get_near_pos(entity.pos)
|
||||
try:
|
||||
door = next(x for x in e if x.name.startswith(d.DOOR))
|
||||
valid = door.use()
|
||||
state.print(f'{entity.name} just used a {door.name} at {door.pos}')
|
||||
return ActionResult(entity=entity, identifier=self._identifier, validity=valid, reward=r.USE_DOOR_VALID)
|
||||
|
||||
except StopIteration:
|
||||
# When he doesn't...
|
||||
state.print(f'{entity.name} just tried to use a door at {entity.pos}, but there is none.')
|
||||
return ActionResult(entity=entity, identifier=self._identifier,
|
||||
validity=c.NOT_VALID, reward=r.USE_DOOR_FAIL)
|
18
modules/doors/constants.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Names / Identifiers
|
||||
DOOR = 'Door' # Identifier of Single-Door Entities.
|
||||
DOORS = 'Doors' # Identifier of Door-objects and groups (groups).
|
||||
|
||||
# Symbols (in map)
|
||||
SYMBOL_DOOR = 'D' # Door _identifier for resolving the string based map files.
|
||||
|
||||
# Values
|
||||
VALUE_ACCESS_INDICATOR = 1 / 3 # Access-door-Cell value used in observation
|
||||
VALUE_OPEN_DOOR = 2 / 3 # Open-door-Cell value used in observation
|
||||
VALUE_CLOSED_DOOR = 3 / 3 # Closed-door-Cell value used in observation
|
||||
|
||||
# States
|
||||
STATE_CLOSED = 'closed' # Identifier to compare door-is-closed state
|
||||
STATE_OPEN = 'open' # Identifier to compare door-is-open state
|
||||
|
||||
# Actions
|
||||
ACTION_DOOR_USE = 'use_door'
|
BIN
modules/doors/door_closed.png
Normal file
After Width: | Height: | Size: 699 B |
BIN
modules/doors/door_open.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
97
modules/doors/entitites.py
Normal file
@ -0,0 +1,97 @@
|
||||
from environment.entity.entity import Entity
|
||||
from environment.utils.render import RenderEntity
|
||||
from environment import constants as c
|
||||
|
||||
from modules.doors import constants as d
|
||||
|
||||
|
||||
class DoorIndicator(Entity):
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return d.VALUE_ACCESS_INDICATOR
|
||||
|
||||
def render(self):
|
||||
return None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.__delattr__('move')
|
||||
|
||||
|
||||
class Door(Entity):
|
||||
|
||||
@property
|
||||
def is_blocking_pos(self):
|
||||
return False if self.is_open else True
|
||||
|
||||
@property
|
||||
def is_blocking_light(self):
|
||||
return False if self.is_open else True
|
||||
|
||||
@property
|
||||
def can_collide(self):
|
||||
return False if self.is_open else True
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return d.VALUE_CLOSED_DOOR if self.is_closed else d.VALUE_OPEN_DOOR
|
||||
|
||||
@property
|
||||
def str_state(self):
|
||||
return 'open' if self.is_open else 'closed'
|
||||
|
||||
def __init__(self, *args, closed_on_init=True, auto_close_interval=10, indicate_area=False, **kwargs):
|
||||
self._state = d.STATE_CLOSED
|
||||
super(Door, self).__init__(*args, **kwargs)
|
||||
self.auto_close_interval = auto_close_interval
|
||||
self.time_to_close = 0
|
||||
if not closed_on_init:
|
||||
self._open()
|
||||
if indicate_area:
|
||||
self._collection.add_items([DoorIndicator(x) for x in self.tile.neighboring_floor])
|
||||
|
||||
def summarize_state(self):
|
||||
state_dict = super().summarize_state()
|
||||
state_dict.update(state=str(self.str_state), time_to_close=int(self.time_to_close))
|
||||
return state_dict
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
return self._state == d.STATE_CLOSED
|
||||
|
||||
@property
|
||||
def is_open(self):
|
||||
return self._state == d.STATE_OPEN
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._state
|
||||
|
||||
def render(self):
|
||||
name, state = 'door_open' if self.is_open else 'door_closed', 'blank'
|
||||
return RenderEntity(name, self.pos, 1, 'none', state, self.identifier_int + 1)
|
||||
|
||||
def use(self):
|
||||
if self._state == d.STATE_OPEN:
|
||||
self._close()
|
||||
else:
|
||||
self._open()
|
||||
return c.VALID
|
||||
|
||||
def tick(self):
|
||||
if self.is_open and len(self.tile) == 1 and self.time_to_close:
|
||||
self.time_to_close -= 1
|
||||
return c.NOT_VALID
|
||||
elif self.is_open and not self.time_to_close and len(self.tile) == 1:
|
||||
self.use()
|
||||
return c.VALID
|
||||
else:
|
||||
return c.NOT_VALID
|
||||
|
||||
def _open(self):
|
||||
self._state = d.STATE_OPEN
|
||||
self.time_to_close = self.auto_close_interval
|
||||
|
||||
def _close(self):
|
||||
self._state = d.STATE_CLOSED
|
28
modules/doors/groups.py
Normal file
@ -0,0 +1,28 @@
|
||||
from typing import Union
|
||||
|
||||
from environment.groups.env_objects import EnvObjects
|
||||
from environment.groups.mixins import PositionMixin
|
||||
from modules.doors import constants as d
|
||||
from modules.doors.entitites import Door
|
||||
|
||||
|
||||
class Doors(PositionMixin, EnvObjects):
|
||||
|
||||
symbol = d.SYMBOL_DOOR
|
||||
_entity = Door
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Doors, self).__init__(*args, can_collide=True, **kwargs)
|
||||
|
||||
def get_near_position(self, position: (int, int)) -> Union[None, Door]:
|
||||
try:
|
||||
return next(door for door in self if position in door.tile.neighboring_floor_pos)
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
def tick_doors(self):
|
||||
result_dict = dict()
|
||||
for door in self:
|
||||
did_tick = door.tick()
|
||||
result_dict.update({door.name: did_tick})
|
||||
return result_dict
|
2
modules/doors/rewards.py
Normal file
@ -0,0 +1,2 @@
|
||||
USE_DOOR_VALID: float = -0.00
|
||||
USE_DOOR_FAIL: float = -0.01
|
21
modules/doors/rule_door_auto_close.py
Normal file
@ -0,0 +1,21 @@
|
||||
from environment.rules import Rule
|
||||
from environment import constants as c
|
||||
from environment.utils.results import TickResult
|
||||
from modules.doors import constants as d
|
||||
|
||||
|
||||
class DoorAutoClose(Rule):
|
||||
|
||||
def __init__(self, close_frequency: int = 10):
|
||||
super().__init__()
|
||||
self.close_frequency = close_frequency
|
||||
|
||||
def tick_step(self, state):
|
||||
if doors := state[d.DOORS]:
|
||||
doors_tick_result = doors.tick_doors()
|
||||
doors_that_ticked = [key for key, val in doors_tick_result.items() if val]
|
||||
state.print(f'{doors_that_ticked} were auto-closed'
|
||||
if doors_that_ticked else 'No Doors were auto-closed')
|
||||
return [TickResult(self.name, validity=c.VALID, value=0)]
|
||||
state.print('There are no doors, but you loaded the corresponding Module')
|
||||
return []
|
0
modules/items/__init__.py
Normal file
37
modules/items/actions.py
Normal file
@ -0,0 +1,37 @@
|
||||
from typing import Union
|
||||
|
||||
from environment.actions import Action
|
||||
from environment.utils.results import ActionResult
|
||||
|
||||
from modules.items import constants as i, rewards as r
|
||||
from environment import constants as c
|
||||
|
||||
|
||||
class ItemAction(Action):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(i.ITEM_ACTION)
|
||||
|
||||
def do(self, entity, state) -> Union[None, ActionResult]:
|
||||
inventory = state[i.INVENTORY].by_entity(entity)
|
||||
if drop_off := state[i.DROP_OFF].by_pos(entity.pos):
|
||||
if inventory:
|
||||
valid = drop_off.place_item(inventory.pop())
|
||||
else:
|
||||
valid = c.NOT_VALID
|
||||
if valid:
|
||||
state.print(f'{entity.name} just dropped of an item at {drop_off.pos}.')
|
||||
else:
|
||||
state.print(f'{entity.name} just tried to drop off at {entity.pos}, but failed.')
|
||||
reward = r.DROP_OFF_VALID if valid else r.DROP_OFF_FAIL
|
||||
return ActionResult(entity=entity, identifier=self._identifier, validity=valid, reward=reward)
|
||||
|
||||
elif item := state[i.ITEM].by_pos(entity.pos):
|
||||
item.change_parent_collection(inventory)
|
||||
item.set_tile_to(state.NO_POS_TILE)
|
||||
state.print(f'{entity.name} just picked up an item at {entity.pos}')
|
||||
return ActionResult(entity=entity, identifier=self._identifier, validity=c.VALID, reward=r.PICK_UP_VALID)
|
||||
|
||||
else:
|
||||
state.print(f'{entity.name} just tried to pick up an item at {entity.pos}, but failed.')
|
||||
return ActionResult(entity=entity, identifier=self._identifier, validity=c.NOT_VALID, reward=r.PICK_UP_FAIL)
|
BIN
modules/items/assets/charge_pod.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
modules/items/assets/dropofflocations.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
modules/items/assets/items.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
11
modules/items/constants.py
Normal file
@ -0,0 +1,11 @@
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
SYMBOL_NO_ITEM = 0
|
||||
SYMBOL_DROP_OFF = 1
|
||||
# Item Env
|
||||
ITEM = 'Items'
|
||||
INVENTORY = 'Inventories'
|
||||
DROP_OFF = 'DropOffLocations'
|
||||
|
||||
ITEM_ACTION = 'ITEMACTION'
|
64
modules/items/entitites.py
Normal file
@ -0,0 +1,64 @@
|
||||
from collections import deque
|
||||
|
||||
from environment.entity.entity import Entity
|
||||
from environment import constants as c
|
||||
from environment.utils.render import RenderEntity
|
||||
from modules.items import constants as i
|
||||
|
||||
|
||||
class Item(Entity):
|
||||
|
||||
def render(self):
|
||||
return RenderEntity(i.ITEM, self.tile.pos) if self.pos != c.VALUE_NO_POS else None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._auto_despawn = -1
|
||||
|
||||
@property
|
||||
def auto_despawn(self):
|
||||
return self._auto_despawn
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
# Edit this if you want items to be drawn in the ops differently
|
||||
return 1
|
||||
|
||||
def set_auto_despawn(self, auto_despawn):
|
||||
self._auto_despawn = auto_despawn
|
||||
|
||||
def set_tile_to(self, no_pos_tile):
|
||||
self._tile = no_pos_tile
|
||||
|
||||
def summarize_state(self) -> dict:
|
||||
super_summarization = super(Item, self).summarize_state()
|
||||
super_summarization.update(dict(auto_despawn=self.auto_despawn))
|
||||
return super_summarization
|
||||
|
||||
|
||||
class DropOffLocation(Entity):
|
||||
|
||||
def render(self):
|
||||
return RenderEntity(i.DROP_OFF, self.tile.pos)
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return i.SYMBOL_DROP_OFF
|
||||
|
||||
def __init__(self, *args, storage_size_until_full: int = 5, auto_item_despawn_interval: int = 5, **kwargs):
|
||||
super(DropOffLocation, self).__init__(*args, **kwargs)
|
||||
self.auto_item_despawn_interval = auto_item_despawn_interval
|
||||
self.storage = deque(maxlen=storage_size_until_full or None)
|
||||
|
||||
def place_item(self, item: Item):
|
||||
if self.is_full:
|
||||
raise RuntimeWarning("There is currently no way to clear the storage or make it unfull.")
|
||||
return bc.NOT_VALID
|
||||
else:
|
||||
self.storage.append(item)
|
||||
item.set_auto_despawn(self.auto_item_despawn_interval)
|
||||
return c.VALID
|
||||
|
||||
@property
|
||||
def is_full(self):
|
||||
return False if not self.storage.maxlen else self.storage.maxlen == len(self.storage)
|
101
modules/items/groups.py
Normal file
@ -0,0 +1,101 @@
|
||||
from typing import List
|
||||
|
||||
from environment.groups.env_objects import EnvObjects
|
||||
from environment.groups.objects import Objects
|
||||
from environment.groups.mixins import PositionMixin, IsBoundMixin, HasBoundedMixin
|
||||
from environment.entity.wall_floor import Floor
|
||||
from environment.entity.agent import Agent
|
||||
from modules.items.entitites import Item, DropOffLocation
|
||||
|
||||
|
||||
class Items(PositionMixin, EnvObjects):
|
||||
|
||||
_entity = Item
|
||||
is_blocking_light: bool = False
|
||||
can_collide: bool = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def spawn_items(self, tiles: List[Floor]):
|
||||
items = [self._entity(tile) for tile in tiles]
|
||||
self.add_items(items)
|
||||
|
||||
def despawn_items(self, items: List[Item]):
|
||||
items = [items] if isinstance(items, Item) else items
|
||||
for item in items:
|
||||
del self[item]
|
||||
|
||||
|
||||
class Inventory(IsBoundMixin, EnvObjects):
|
||||
|
||||
_accepted_objects = Item
|
||||
|
||||
@property
|
||||
def obs_tag(self):
|
||||
return self.name
|
||||
|
||||
def __init__(self, agent: Agent, *args, **kwargs):
|
||||
super(Inventory, self).__init__(*args, **kwargs)
|
||||
self._collection = None
|
||||
self.bind(agent)
|
||||
|
||||
def summarize_states(self, **kwargs):
|
||||
attr_dict = {key: val for key, val in self.__dict__.items() if not key.startswith('_') and key != 'data'}
|
||||
attr_dict.update(dict(items=[val.summarize_state(**kwargs) for key, val in self.items()]))
|
||||
attr_dict.update(dict(name=self.name, belongs_to=self._bound_entity.name))
|
||||
return attr_dict
|
||||
|
||||
def pop(self):
|
||||
item_to_pop = self[0]
|
||||
self.delete_env_object(item_to_pop)
|
||||
return item_to_pop
|
||||
|
||||
def set_collection(self, collection):
|
||||
self._collection = collection
|
||||
|
||||
|
||||
class Inventories(HasBoundedMixin, Objects):
|
||||
|
||||
_entity = Inventory
|
||||
can_move = False
|
||||
|
||||
@property
|
||||
def obs_pairs(self):
|
||||
return [(x.name, x) for x in self]
|
||||
|
||||
def __init__(self, size, *args, **kwargs):
|
||||
super(Inventories, self).__init__(*args, **kwargs)
|
||||
self.size = size
|
||||
self._obs = None
|
||||
self._lazy_eval_transforms = []
|
||||
|
||||
def spawn_inventories(self, agents):
|
||||
inventories = [self._entity(agent, self.size,)
|
||||
for _, agent in enumerate(agents)]
|
||||
self.add_items(inventories)
|
||||
|
||||
def idx_by_entity(self, entity):
|
||||
try:
|
||||
return next((idx for idx, inv in enumerate(self) if inv.belongs_to_entity(entity)))
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
def by_entity(self, entity):
|
||||
try:
|
||||
return next((inv for inv in self if inv.belongs_to_entity(entity)))
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
def summarize_states(self, **kwargs):
|
||||
return [val.summarize_states(**kwargs) for key, val in self.items()]
|
||||
|
||||
|
||||
class DropOffLocations(PositionMixin, EnvObjects):
|
||||
|
||||
_entity = DropOffLocation
|
||||
is_blocking_light: bool = False
|
||||
can_collide: bool = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DropOffLocations, self).__init__(*args, **kwargs)
|
4
modules/items/rewards.py
Normal file
@ -0,0 +1,4 @@
|
||||
DROP_OFF_VALID: float = 0.1
|
||||
DROP_OFF_FAIL: float = -0.1
|
||||
PICK_UP_FAIL: float = -0.1
|
||||
PICK_UP_VALID: float = 0.1
|
79
modules/items/rules.py
Normal file
@ -0,0 +1,79 @@
|
||||
from typing import List
|
||||
|
||||
from environment.rules import Rule
|
||||
from environment import constants as c
|
||||
from environment.utils.results import TickResult
|
||||
from modules.items import constants as i
|
||||
from modules.items.entitites import DropOffLocation
|
||||
|
||||
|
||||
class ItemRules(Rule):
|
||||
|
||||
def __init__(self, n_items: int = 5, spawn_frequency: int = 15,
|
||||
n_locations: int = 5, max_dropoff_storage_size: int = 0):
|
||||
super().__init__()
|
||||
self.spawn_frequency = spawn_frequency
|
||||
self._next_item_spawn = spawn_frequency
|
||||
self.n_items = n_items
|
||||
self.max_dropoff_storage_size = max_dropoff_storage_size
|
||||
self.n_locations = n_locations
|
||||
|
||||
def on_init(self, state):
|
||||
self.trigger_drop_off_location_spawn(state)
|
||||
self._next_item_spawn = self.spawn_frequency
|
||||
self.trigger_inventory_spawn(state)
|
||||
self.trigger_item_spawn(state)
|
||||
|
||||
def tick_step(self, state):
|
||||
for item in list(state[i.ITEM].values()):
|
||||
if item.auto_despawn >= 1:
|
||||
item.set_auto_despawn(item.auto_despawn - 1)
|
||||
elif not item.auto_despawn:
|
||||
state[i.ITEM].delete_env_object(item)
|
||||
else:
|
||||
pass
|
||||
|
||||
if not self._next_item_spawn:
|
||||
self.trigger_item_spawn(state)
|
||||
else:
|
||||
self._next_item_spawn = max(0, self._next_item_spawn - 1)
|
||||
return []
|
||||
|
||||
def trigger_item_spawn(self, state):
|
||||
if item_to_spawns := max(0, (self.n_items - len(state[i.ITEM]))):
|
||||
empty_tiles = state[c.FLOOR].empty_tiles[:item_to_spawns]
|
||||
state[i.ITEM].spawn_items(empty_tiles)
|
||||
self._next_item_spawn = self.spawn_frequency
|
||||
state.print(f'{item_to_spawns} new items have been spawned; next spawn in {self._next_item_spawn}')
|
||||
return len(empty_tiles)
|
||||
else:
|
||||
state.print('No Items are spawning, limit is reached.')
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def trigger_inventory_spawn(state):
|
||||
state[i.INVENTORY].spawn_inventories(state[c.AGENT])
|
||||
|
||||
def tick_post_step(self, state) -> List[TickResult]:
|
||||
for item in list(state[i.ITEM].values()):
|
||||
if item.auto_despawn >= 1:
|
||||
item.set_auto_despawn(item.auto_despawn-1)
|
||||
elif not item.auto_despawn:
|
||||
state[i.ITEM].delete_env_object(item)
|
||||
else:
|
||||
pass
|
||||
|
||||
if not self._next_item_spawn:
|
||||
if spawned_items := self.trigger_item_spawn(state):
|
||||
return [TickResult(self.name, validity=c.VALID, value=spawned_items, entity=None)]
|
||||
else:
|
||||
return [TickResult(self.name, validity=c.NOT_VALID, value=0, entity=None)]
|
||||
else:
|
||||
self._next_item_spawn = max(0, self._next_item_spawn-1)
|
||||
return []
|
||||
|
||||
def trigger_drop_off_location_spawn(self, state):
|
||||
empty_tiles = state[c.FLOOR].empty_tiles[:self.n_locations]
|
||||
do_entites = state[i.DROP_OFF]
|
||||
drop_offs = [DropOffLocation(tile) for tile in empty_tiles]
|
||||
do_entites.add_items(drop_offs)
|
0
modules/levels/__init__.py
Normal file
24
modules/levels/large.txt
Normal file
@ -0,0 +1,24 @@
|
||||
##############################################################
|
||||
#-----------#---#--------------------------------------------#
|
||||
#-----------#---#--------------------------------------------#
|
||||
#-----------#---#------##------##------##------##------##----#
|
||||
#-----------#---D------##------##------##------##------##----#
|
||||
#-----------D---#--------------------------------------------#
|
||||
#-----------#---#--------------------------------------------#
|
||||
#############---####################D####################D####
|
||||
#------------------------------------------------------------#
|
||||
#------------------------------------------------------------#
|
||||
#------------------------------------------------------------#
|
||||
####################-####################################D####
|
||||
#-----------------#---#------------------------------#-------#
|
||||
#-----------------#---D------------------------------#-------#
|
||||
#-----------------D---#------------------------------#-------#
|
||||
#-----------------#---#######D#############D##########-------#
|
||||
#-----------------#---D------------------------------D-------#
|
||||
###################---#------------------------------#-------#
|
||||
#-----------------#---#######D#############D##########-------#
|
||||
#-----------------D---#------------------------------#-------#
|
||||
#-----------------#---#------------------------------#-------#
|
||||
#-----------------#---#------------------------------D-------#
|
||||
#-----------------#---#------------------------------#-------#
|
||||
##############################################################
|
47
modules/levels/large_qquad.txt
Normal file
@ -0,0 +1,47 @@
|
||||
###########################################################################################################################
|
||||
#-----------#---#--------------------------------------------#-----------#---#--------------------------------------------#
|
||||
#-----------#---#--------------------------------------------#-----------#---#--------------------------------------------#
|
||||
#-----------#---#------##------##------##------##------##----#-----------#---#------##------##------##------##------##----#
|
||||
#-----------#---D------##------##------##------##------##----#-----------#---D------##------##------##------##------##----#
|
||||
#-----------D---#--------------------------------------------#-----------D---#--------------------------------------------#
|
||||
#-----------#---#--------------------------------------------#-----------#---#--------------------------------------------#
|
||||
#############---####################D####################D################---####################D####################D####
|
||||
#------------------------------------------------------------#------------------------------------------------------------#
|
||||
#------------------------------------------------------------D------------------------------------------------------------#
|
||||
#------------------------------------------------------------#------------------------------------------------------------#
|
||||
####################-####################################D#######################-####################################D####
|
||||
#-----------------#---#------------------------------#-------#-----------------#---#------------------------------#-------#
|
||||
#-----------------#---D------------------------------#-------#-----------------#---D------------------------------#-------#
|
||||
#-----------------D---#------------------------------#-------#-----------------D---#------------------------------#-------#
|
||||
#-----------------#---#######D#############D##########-------#-----------------#---#######D#############D##########-------#
|
||||
#-----------------#---D------------------------------D-------#-----------------#---D------------------------------D-------#
|
||||
###################---#------------------------------#-------###################---#------------------------------#-------#
|
||||
#-----------------#---#######D#############D##########-------#-----------------#---#######D#############D##########-------#
|
||||
#-----------------D---#------------------------------#-------D-----------------D---#------------------------------#-------#
|
||||
#-----------------#---#------------------------------#-------#-----------------#---#------------------------------#-------#
|
||||
#-----------------#---#------------------------------D-------#-----------------#---#------------------------------D-------#
|
||||
#-----------------#---#------------------------------#-------#-----------------#---#------------------------------#-------#
|
||||
##############D############################################################D###############################################
|
||||
#-----------#---#--------------------------------------------#-----------#---#--------------------------------------------#
|
||||
#-----------#---#--------------------------------------------#-----------#---#--------------------------------------------#
|
||||
#-----------#---#------##------##------##------##------##----#-----------#---#------##------##------##------##------##----#
|
||||
#-----------#---D------##------##------##------##------##----#-----------#---D------##------##------##------##------##----#
|
||||
#-----------D---#--------------------------------------------#-----------D---#--------------------------------------------#
|
||||
#-----------#---#--------------------------------------------#-----------#---#--------------------------------------------#
|
||||
#############---####################D####################D################---####################D####################D####
|
||||
#------------------------------------------------------------#------------------------------------------------------------#
|
||||
#------------------------------------------------------------D------------------------------------------------------------#
|
||||
#------------------------------------------------------------#------------------------------------------------------------#
|
||||
###################---###################################D######################---###################################D####
|
||||
#-----------------#---#------------------------------#-------#-----------------#---#------------------------------#-------#
|
||||
#-----------------#---D------------------------------#-------#-----------------#---D------------------------------#-------#
|
||||
#-----------------D---#------------------------------#-------#-----------------D---#------------------------------#-------#
|
||||
#-----------------#---#######D#############D##########-------#-----------------#---#######D#############D##########-------#
|
||||
#-----------------#---D------------------------------D-------#-----------------#---D------------------------------D-------#
|
||||
###################---#------------------------------#-------###################---#------------------------------#-------#
|
||||
#-----------------#---#######D#############D##########-------#-----------------#---#######D#############D##########-------#
|
||||
#-----------------D---#------------------------------#-------#-----------------D---#------------------------------#-------#
|
||||
#-----------------#---#------------------------------#-------#-----------------#---#------------------------------#-------#
|
||||
#-----------------#---#------------------------------D-------#-----------------#---#------------------------------D-------#
|
||||
#-----------------#---#------------------------------#-------#-----------------#---#------------------------------#-------#
|
||||
###########################################################################################################################
|
13
modules/levels/rooms.txt
Normal file
@ -0,0 +1,13 @@
|
||||
###############
|
||||
#333x33#444444#
|
||||
#333#33#444444#
|
||||
#333333xx#4444#
|
||||
#333333#444444#
|
||||
#333333#444444#
|
||||
###x#######D###
|
||||
#1111##2222222#
|
||||
#11111#2222#22#
|
||||
#11111D2222222#
|
||||
#11111#2222222#
|
||||
#11111#2222222#
|
||||
###############
|
13
modules/levels/shelves.txt
Normal file
@ -0,0 +1,13 @@
|
||||
############
|
||||
#----------#
|
||||
#--######--#
|
||||
#----------#
|
||||
#--######--#
|
||||
#----------#
|
||||
#--######--#
|
||||
#----------#
|
||||
#--######--#
|
||||
#----------#
|
||||
#--######--#
|
||||
#----------#
|
||||
############
|
12
modules/levels/simple.txt
Normal file
@ -0,0 +1,12 @@
|
||||
############
|
||||
#----------#
|
||||
#---#------#
|
||||
#--------#-#
|
||||
#----------#
|
||||
#--#-------#
|
||||
#----------#
|
||||
#----#-----#
|
||||
#----------#
|
||||
#-------#--#
|
||||
#----------#
|
||||
############
|
0
modules/machines/__init__.py
Normal file
11
modules/machines/constants.py
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
MACHINES = 'Machines'
|
||||
MACHINE = 'Machine'
|
||||
|
||||
STATE_WORK = 'working'
|
||||
STATE_IDLE = 'idling'
|
||||
STATE_MAINTAIN = 'maintenance'
|
||||
|
||||
SYMBOL_WORK = 1
|
||||
SYMBOL_IDLE = 0.6
|
||||
SYMBOL_MAINTAIN = 0.3
|
53
modules/machines/entitites.py
Normal file
@ -0,0 +1,53 @@
|
||||
from environment.entity.entity import Entity
|
||||
from environment.utils.render import RenderEntity
|
||||
from environment import constants as c
|
||||
from environment.utils.results import TickResult
|
||||
from modules.machines import constants as m, rewards as r
|
||||
|
||||
|
||||
class Machine(Entity):
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return self._encodings[self.state]
|
||||
|
||||
def __init__(self, *args, work_interval: int = 10, pause_interval: int = 15, **kwargs):
|
||||
super(Machine, self).__init__(*args, **kwargs)
|
||||
self._intervals = dict({m.STATE_IDLE: pause_interval, m.STATE_WORK: work_interval})
|
||||
self._encodings = dict({m.STATE_IDLE: pause_interval, m.STATE_WORK: work_interval})
|
||||
|
||||
self.state = m.STATE_IDLE
|
||||
self.health = 100
|
||||
self._counter = 0
|
||||
self.__delattr__('move')
|
||||
|
||||
def maintain(self):
|
||||
if self.state == m.STATE_WORK:
|
||||
return c.NOT_VALID
|
||||
if self.health <= 98:
|
||||
self.health = 100
|
||||
return c.VALID
|
||||
else:
|
||||
return c.NOT_VALID
|
||||
|
||||
def tick(self):
|
||||
if self.state == m.STATE_MAINTAIN and any([c.AGENT in x.name for x in self.tile.guests]):
|
||||
return TickResult(self.name, c.VALID, r.NONE, self)
|
||||
elif self.state == m.STATE_MAINTAIN and not any([c.AGENT in x.name for x in self.tile.guests]):
|
||||
self.state = m.STATE_WORK
|
||||
self.reset_counter()
|
||||
return None
|
||||
elif self._counter:
|
||||
self._counter -= 1
|
||||
self.health -= 1
|
||||
return None
|
||||
else:
|
||||
self.state = m.STATE_WORK if self.state == m.STATE_IDLE else m.STATE_IDLE
|
||||
self.reset_counter()
|
||||
return None
|
||||
|
||||
def reset_counter(self):
|
||||
self._counter = self._intervals[self.state]
|
||||
|
||||
def render(self):
|
||||
return RenderEntity(m.MACHINE, self.pos)
|
13
modules/machines/groups.py
Normal file
@ -0,0 +1,13 @@
|
||||
from environment.groups.env_objects import EnvObjects
|
||||
from environment.groups.mixins import PositionMixin
|
||||
from modules.machines.entitites import Machine
|
||||
|
||||
|
||||
class Machines(PositionMixin, EnvObjects):
|
||||
|
||||
_entity = Machine
|
||||
is_blocking_light: bool = False
|
||||
can_collide: bool = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Machines, self).__init__(*args, **kwargs)
|
5
modules/machines/rewards.py
Normal file
@ -0,0 +1,5 @@
|
||||
MAINTAIN_VALID: float = 0.5
|
||||
MAINTAIN_FAIL: float = -0.1
|
||||
FAIL_MISSING_MAINTENANCE: float = -0.5
|
||||
|
||||
NONE: float = 0
|
29
modules/machines/rules.py
Normal file
@ -0,0 +1,29 @@
|
||||
from typing import List
|
||||
from environment.rules import Rule
|
||||
from environment.utils.results import TickResult, DoneResult
|
||||
from environment import constants as c
|
||||
from modules.machines import constants as m
|
||||
from modules.machines.entitites import Machine
|
||||
|
||||
|
||||
class MachineRule(Rule):
|
||||
|
||||
def __init__(self, n_machines: int = 2):
|
||||
super(MachineRule, self).__init__()
|
||||
self.n_machines = n_machines
|
||||
|
||||
def on_init(self, state):
|
||||
empty_tiles = state[c.FLOOR].empty_tiles[:self.n_machines]
|
||||
state[m.MACHINES].add_items(Machine(tile) for tile in empty_tiles)
|
||||
|
||||
def tick_pre_step(self, state) -> List[TickResult]:
|
||||
pass
|
||||
|
||||
def tick_step(self, state) -> List[TickResult]:
|
||||
pass
|
||||
|
||||
def tick_post_step(self, state) -> List[TickResult]:
|
||||
pass
|
||||
|
||||
def on_check_done(self, state) -> List[DoneResult]:
|
||||
pass
|