From 5afa8b22b2c516972cd97190699f9e3c4f870ee2 Mon Sep 17 00:00:00 2001 From: Dominik Roth Date: Fri, 9 Dec 2022 11:20:15 +0100 Subject: [PATCH] Better path visualizations; more agent & path configs --- columbus/entities.py | 11 +++++++- columbus/env.py | 61 ++++++++++++++++------------------------- columbus/observables.py | 4 +++ columbus/utils.py | 42 ++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 38 deletions(-) create mode 100644 columbus/utils.py diff --git a/columbus/entities.py b/columbus/entities.py index 1866971..fa9088c 100644 --- a/columbus/entities.py +++ b/columbus/entities.py @@ -28,6 +28,9 @@ class Entity(object): self.draw_path = False self.draw_path_col = [int(c/5) for c in self.col] self.draw_path_width = 2 + self.draw_path_harm = False + self.draw_path_harm_col = [c for c in self.draw_path_col] + self.draw_path_harm_col[0] += int(255/3) def __post_init__(self): pass @@ -63,8 +66,14 @@ class Entity(object): def _draw_path(self): if self.draw_path and self.last_pos: - pygame.draw.line(self.env.path_overlay, self.draw_path_col, + col = self.draw_path_col + if self.draw_path_harm: + if self.env.gotHarm: + col = self.draw_path_harm_col + pygame.draw.line(self.env.path_overlay, col, (self.last_pos[0]*self.env.width, self.last_pos[1]*self.env.height), (self.pos[0]*self.env.width, self.pos[1]*self.env.height), self.draw_path_width) + pygame.draw.circle(self.env.path_overlay, col, + (self.pos[0]*self.env.width, self.pos[1]*self.env.height), max(0, self.draw_path_width/2-1)) self.last_pos = self.pos[0], self.pos[1] def on_collision(self, other, depth): diff --git a/columbus/env.py b/columbus/env.py index 282fc99..1c16434 100644 --- a/columbus/env.py +++ b/columbus/env.py @@ -6,47 +6,16 @@ import pygame import random as random_dont_use from os import urandom import math -from columbus import entities, observables import torch as th - -def parseObs(obsConf): - # Parsing Observable Definitions - if type(obsConf) == list: - obs = [] - for i, c in enumerate(obsConf): - obs.append(parseObs(c)) - if len(obs) == 1: - return obs[0] - else: - return observables.CompositionalObservable(obs) - - if obsConf['type'] == 'State': - conf = {k: v for k, v in obsConf.items() if k not in ['type']} - return observables.StateObservable(**conf) - elif obsConf['type'] == 'Compass': - conf = {k: v for k, v in obsConf.items() if k not in ['type']} - return observables.CompassObservable(**conf) - elif obsConf['type'] == 'RayCast': - chans = [] - for chan in obsConf.get('chans', []): - chans.append(getattr(entities, chan)) - conf = {k: v for k, v in obsConf.items() if k not in ['type', 'chans']} - return observables.RayObservable(chans=chans, **conf) - elif obsConf['type'] == 'CNN': - conf = {k: v for k, v in obsConf.items() if k not in ['type']} - return observables.CnnObservable(**conf) - elif obsConf['type'] == 'Dummy': - conf = {k: v for k, v in obsConf.items() if k not in ['type']} - return observables.Observable(**conf) - else: - raise Exception('Unknown Observable selected') +from columbus import entities, observables +from columbus.utils import soft_int, parseObs class ColumbusEnv(gym.Env): metadata = {'render.modes': ['human']} - def __init__(self, observable=observables.Observable(), fps=60, env_seed=3.1, master_seed=None, start_pos=(0.5, 0.5), start_score=0, speed_fac=0.01, acc_fac=0.04, die_on_zero=False, return_on_score=-1, reward_mult=1, agent_drag=0, controll_type='SPEED', aux_reward_max=1, aux_penalty_max=0, aux_reward_discretize=0, void_is_type_barrier=True, void_damage=1, torus_topology=False, default_collision_elasticity=1, terminate_on_reward=False, agent_draw_path=False, clear_path_on_reset=True, max_steps=-1, value_color_mapper='tanh', width=720, height=720): + def __init__(self, observable=observables.Observable(), fps=60, env_seed=3.1, master_seed=None, start_pos=(0.5, 0.5), start_score=0, speed_fac=0.01, acc_fac=0.04, die_on_zero=False, return_on_score=-1, reward_mult=1, agent_drag=0, controll_type='SPEED', aux_reward_max=1, aux_penalty_max=0, aux_reward_discretize=0, void_is_type_barrier=True, void_damage=1, torus_topology=False, default_collision_elasticity=1, terminate_on_reward=False, agent_draw_path=False, clear_path_on_reset=True, max_steps=-1, value_color_mapper='tanh', width=720, height=720, agent_attrs={}): super(ColumbusEnv, self).__init__() self.action_space = spaces.Box( low=-1, high=1, shape=(2,), dtype=np.float32) @@ -92,6 +61,9 @@ class ColumbusEnv(gym.Env): self.terminate_on_reward = terminate_on_reward self.agent_draw_path = agent_draw_path self.clear_path_on_reset = clear_path_on_reset + self.path_decay = 0.1 + + self.agent_attrs = agent_attrs if value_color_mapper == 'atan': def value_color_mapper(x): return th.atan(x*2)/0.786/2 @@ -241,6 +213,7 @@ class ColumbusEnv(gym.Env): self._step_entities() observation = self.observable.get_observation() gotRew = self.new_reward > 0 or self.new_abs_reward > 0 + self.gotHarm = self.new_reward < 0 or self.new_abs_reward < 0 reward, self.new_reward, self.new_abs_reward = self.new_reward / \ self.fps + self.new_abs_reward, 0, 0 if not self.torus_topology: @@ -296,6 +269,12 @@ class ColumbusEnv(gym.Env): self.agent.pos = self.start_pos # Expand this function + def _spawnAgent(self): + self.agent = entities.Agent(self) + self.agent.draw_path = self.agent_draw_path + for k, v in self.agent_attrs.items(): + setattr(self.agent, k, v) + def reset(self, force_reset_path=False): pygame.init() self._init = True @@ -308,11 +287,11 @@ class ColumbusEnv(gym.Env): # will get rescaled acording to fps (=reward per second) self.new_reward = 0 self.new_abs_reward = 0 # will not get rescaled. should be used for one-time rewards + self.gotHarm = False self.score = self.start_score self.entities = [] self.timers = [] - self.agent = entities.Agent(self) - self.agent.draw_path = self.agent_draw_path + self._spawnAgent() self.setup() self.entities.append(self.agent) # add it last, will be drawn on top self.observable.reset() @@ -428,6 +407,14 @@ class ColumbusEnv(gym.Env): self.screen.blit(rotated_surf, rotated_surf.get_rect( center=rect.center)) + def _draw_paths(self): + if self.path_decay != 0.0: + s = pygame.Surface((self.width, self.height)) + s.set_alpha(soft_int(255*self.path_decay/self.fps)) + s.fill((0, 0, 0)) + self.path_overlay.blit(s, (0, 0)) + self.surface.blit(self.path_overlay, (0, 0)) + def _handle_user_input(self): for event in pygame.event.get(): pass @@ -472,7 +459,7 @@ class ColumbusEnv(gym.Env): if value_func != None: self._draw_values(value_func, values_static, color_mapper=self.value_color_mapper) - self.surface.blit(self.path_overlay, (0, 0)) + self._draw_paths() if self.draw_entities: self._draw_entities() else: diff --git a/columbus/observables.py b/columbus/observables.py index 6dd0689..d24074c 100644 --- a/columbus/observables.py +++ b/columbus/observables.py @@ -132,6 +132,8 @@ class RayObservable(Observable): 'Can only raycast circular and rectangular entities!') return False + # Filter out entities, that we sure are out of range + # (so we have to do less work for the ray collisions) def _get_possible_entities(self): entities_l = [] if entities.Void in self.chans or self.env.void_barrier: @@ -153,6 +155,8 @@ class RayObservable(Observable): entities_l.append(entity) # cannot use yield here! return entities_l + # Ugly, inefficient ray casting + # Oh well, it works... def get_observation(self): entities = self._get_possible_entities() self.rays = np.zeros((self.num_rays+self.include_rand, self.num_chans)) diff --git a/columbus/utils.py b/columbus/utils.py new file mode 100644 index 0000000..06423a8 --- /dev/null +++ b/columbus/utils.py @@ -0,0 +1,42 @@ +from columbus import entities, observables + +import random as random_dont_use + + +def parseObs(obsConf): + # Parsing Observable Definitions + if type(obsConf) == list: + obs = [] + for i, c in enumerate(obsConf): + obs.append(parseObs(c)) + if len(obs) == 1: + return obs[0] + else: + return observables.CompositionalObservable(obs) + + if obsConf['type'] == 'State': + conf = {k: v for k, v in obsConf.items() if k not in ['type']} + return observables.StateObservable(**conf) + elif obsConf['type'] == 'Compass': + conf = {k: v for k, v in obsConf.items() if k not in ['type']} + return observables.CompassObservable(**conf) + elif obsConf['type'] == 'RayCast': + chans = [] + for chan in obsConf.get('chans', []): + chans.append(getattr(entities, chan)) + conf = {k: v for k, v in obsConf.items() if k not in ['type', 'chans']} + return observables.RayObservable(chans=chans, **conf) + elif obsConf['type'] == 'CNN': + conf = {k: v for k, v in obsConf.items() if k not in ['type']} + return observables.CnnObservable(**conf) + elif obsConf['type'] == 'Dummy': + conf = {k: v for k, v in obsConf.items() if k not in ['type']} + return observables.Observable(**conf) + else: + raise Exception('Unknown Observable selected') + + +def soft_int(num): + i = int(num) + r = num - i + return i + int(random_dont_use.random() < r)