# Path: magical/phys_vars.py
# class PhysicsVariablesBase(metaclass=_PhysicsVariablesMeta, _pbm_skip=True):
#     """Base class for physics variables with convenience methods for sampling
#     random variables, instantiating from defaults, etc."""
#     variables: collections.OrderedDict
# 
#     def __init__(self, *, _var_values):
#         """Constructor for classes holding physics variable values. This should
#         NOT be called directly. Instead, call the sample() or defaults()
#         methods, as appropriate."""
#         if _var_values.keys() != self.variables.keys():
#             raise ValueError("must supply all & only given variable names")
#         for k, v in _var_values.items():
#             assert not isinstance(v, PhysVar), \
#                 f"are you sure '{k}' is meant to be a PhysVar?"
#             setattr(self, k, v)
# 
#     @classmethod
#     def defaults(cls):
#         """Return a new instance of the class with default variable values."""
#         return cls(
#             _var_values={k: v.default
#                          for k, v in cls.variables.items()})
# 
#     @classmethod
#     def sample(cls, rng):
#         """Return a new instance of the class with randomly sampled variable
#         values."""
#         return cls(
#             _var_values={k: v.sample(rng)
#                          for k, v in cls.variables.items()})
# 
#     def __repr__(self):
#         pairs = ', '.join(f'{k}={getattr(self, k)}' for k in self.variables)
#         return f'{self.__class__.__name__}({pairs})'
# 
# class PhysVar:
#     """Container for the default value and randomisation bounds of a physical
#     variable. Randomisation is always uniform."""
#     def __init__(self, default, bounds):
#         assert len(bounds) == 2, bounds
#         lower, upper = bounds
#         assert lower <= default <= upper, (lower, default, upper)
#         self.default = default
#         self.lower = lower
#         self.upper = upper
# 
#     def sample(self, rng):
#         result = rng.uniform(self.lower, self.upper)
#         return result
# 
# Path: magical/style.py
# ARENA_ZOOM_OUT = 1.02
# 
# COLOURS_RGB = {
#     # I'm using Berkeley-branded versions of RGBY from
#     # https://brand.berkeley.edu/colors/ (lightened).
#     'blue': lighten_rgb(rgb(0x3B, 0x7E, 0xA1), 1.7),  # founder's rock
#     'yellow': lighten_rgb(rgb(0xFD, 0xB5, 0x15), 1.7),  # california gold
#     'red': lighten_rgb(rgb(0xEE, 0x1F, 0x60), 1.7),  # rose garden
#     'green': lighten_rgb(rgb(0x85, 0x94, 0x38), 1.7),  # soybean
#     'grey': rgb(162, 163, 175),  # cool grey (not sure which one)
#     'brown': rgb(224, 171, 118),  # buff
# }
# 
# def lighten_rgb(rgb, times=1):
#     """Produce a lighter version of a given base colour."""
#     h, l, s = colorsys.rgb_to_hls(*rgb)
#     mult = 1.4**times
#     hls_new = (h, 1 - (1 - l) / mult, s)
#     return colorsys.hls_to_rgb(*hls_new)


import abc
import collections
import functools
import inspect
import gym
import numpy as np
import pymunk as pm
import magical.entities as en
import magical.gym_render as r
from gym import spaces
from gym.utils import EzPickle
from magical.phys_vars import PhysicsVariablesBase, PhysVar
from magical.style import ARENA_ZOOM_OUT, COLOURS_RGB, lighten_rgb
            done = done or self._episode_steps >= self.max_episode_steps
        if done:
            eval_score = self.score_on_end_of_traj()
            assert 0 <= eval_score <= 1, \
                f'eval score {eval_score} out of range for env {self}'
            # These were my attempts at sneaking episode termination info
            # through the Monitor and SubprocVecEnv wrappers ('monitor_info'
            # was a keyword I gave to info_keywords in the Monitor
            # constructor). I don't know why, but neither approach worked.
            # info['monitor_info'] = end_ep_dict
            # info['episode'] = end_ep_dict
        # we *always* include a score, even if it's zero, because at least one
        # RL framework (rlpyt) refuses to recognise keys that aren't present at
        # the first time step.
        info.update(eval_score=eval_score)

        obs_dict_u8 = self.render(mode='rgb_array')

        return obs_dict_u8, reward, done, info

    def _use_ego_cam(self):
        self.viewer.set_cam_follow(
            source_xy_world=(self._robot.robot_body.position.x,
                             self._robot.robot_body.position.y),
            target_xy_01=(0.5, 0.15),
            viewport_hw_world=(self._arena_h * ARENA_ZOOM_OUT,
                               self._arena_w * ARENA_ZOOM_OUT),
            rotation=self._robot.robot_body.angle)

    def _use_allo_cam(self):

# Path: magical/phys_vars.py
# class PhysicsVariablesBase(metaclass=_PhysicsVariablesMeta, _pbm_skip=True):
#     """Base class for physics variables with convenience methods for sampling
#     random variables, instantiating from defaults, etc."""
#     variables: collections.OrderedDict
# 
#     def __init__(self, *, _var_values):
#         """Constructor for classes holding physics variable values. This should
#         NOT be called directly. Instead, call the sample() or defaults()
#         methods, as appropriate."""
#         if _var_values.keys() != self.variables.keys():
#             raise ValueError("must supply all & only given variable names")
#         for k, v in _var_values.items():
#             assert not isinstance(v, PhysVar), \
#                 f"are you sure '{k}' is meant to be a PhysVar?"
#             setattr(self, k, v)
# 
#     @classmethod
#     def defaults(cls):
#         """Return a new instance of the class with default variable values."""
#         return cls(
#             _var_values={k: v.default
#                          for k, v in cls.variables.items()})
# 
#     @classmethod
#     def sample(cls, rng):
#         """Return a new instance of the class with randomly sampled variable
#         values."""
#         return cls(
#             _var_values={k: v.sample(rng)
#                          for k, v in cls.variables.items()})
# 
#     def __repr__(self):
#         pairs = ', '.join(f'{k}={getattr(self, k)}' for k in self.variables)
#         return f'{self.__class__.__name__}({pairs})'
# 
# class PhysVar:
#     """Container for the default value and randomisation bounds of a physical
#     variable. Randomisation is always uniform."""
#     def __init__(self, default, bounds):
#         assert len(bounds) == 2, bounds
#         lower, upper = bounds
#         assert lower <= default <= upper, (lower, default, upper)
#         self.default = default
#         self.lower = lower
#         self.upper = upper
# 
#     def sample(self, rng):
#         result = rng.uniform(self.lower, self.upper)
#         return result
# 
# Path: magical/style.py
# ARENA_ZOOM_OUT = 1.02
# 
# COLOURS_RGB = {
#     # I'm using Berkeley-branded versions of RGBY from
#     # https://brand.berkeley.edu/colors/ (lightened).
#     'blue': lighten_rgb(rgb(0x3B, 0x7E, 0xA1), 1.7),  # founder's rock
#     'yellow': lighten_rgb(rgb(0xFD, 0xB5, 0x15), 1.7),  # california gold
#     'red': lighten_rgb(rgb(0xEE, 0x1F, 0x60), 1.7),  # rose garden
#     'green': lighten_rgb(rgb(0x85, 0x94, 0x38), 1.7),  # soybean
#     'grey': rgb(162, 163, 175),  # cool grey (not sure which one)
#     'brown': rgb(224, 171, 118),  # buff
# }
# 
# def lighten_rgb(rgb, times=1):
#     """Produce a lighter version of a given base colour."""
#     h, l, s = colorsys.rgb_to_hls(*rgb)
#     mult = 1.4**times
#     hls_new = (h, 1 - (1 - l) / mult, s)
#     return colorsys.hls_to_rgb(*hls_new)


# Path: magical/base_env.py
import abc
import collections
import functools
import inspect
import gym
import numpy as np
import pymunk as pm
import magical.entities as en
import magical.gym_render as r
from gym import spaces
from gym.utils import EzPickle
from magical.phys_vars import PhysicsVariablesBase, PhysVar
from magical.style import ARENA_ZOOM_OUT, COLOURS_RGB, lighten_rgb

            done = done or self._episode_steps >= self.max_episode_steps
        if done:
            eval_score = self.score_on_end_of_traj()
            assert 0 <= eval_score <= 1, \
                f'eval score {eval_score} out of range for env {self}'
            # These were my attempts at sneaking episode termination info
            # through the Monitor and SubprocVecEnv wrappers ('monitor_info'
            # was a keyword I gave to info_keywords in the Monitor
            # constructor). I don't know why, but neither approach worked.
            # info['monitor_info'] = end_ep_dict
            # info['episode'] = end_ep_dict
        # we *always* include a score, even if it's zero, because at least one
        # RL framework (rlpyt) refuses to recognise keys that aren't present at
        # the first time step.
        info.update(eval_score=eval_score)

        obs_dict_u8 = self.render(mode='rgb_array')

        return obs_dict_u8, reward, done, info

    def _use_ego_cam(self):
        self.viewer.set_cam_follow(
            source_xy_world=(self._robot.robot_body.position.x,
                             self._robot.robot_body.position.y),
            target_xy_01=(0.5, 0.15),
            viewport_hw_world=(self._arena_h * ARENA_ZOOM_OUT,
                               self._arena_w * ARENA_ZOOM_OUT),
            rotation=self._robot.robot_body.angle)

    def _use_allo_cam(self):

        self.viewer.set_bounds(left=self._arena.left * ARENA_ZOOM_OUT,