[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Sven Niederberger\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# ![GIF](https://thumbs.gfycat.com/CoarseEmbellishedIsopod-max-14mb.gif)\n[Click here for higher quality video](https://gfycat.com/CoarseEmbellishedIsopod)\n\nThe objective of this environment is to land a rocket on a ship. The environment is highly customizable and takes discrete or continuous inputs.\n\n### Installation\n```\ncd gym-rocketlander\npip install -e .\n```\n\n### Usage\nOnce the has been installed, it can be used like any other Gym environment:\n```py\nenv = gym.make(\"gym_rocketlander:rocketlander-v0\")\n```\n\n### STATE VARIABLES  \nThe state consists of the following variables:\n  * x position  \n  * y position  \n  * angle  \n  * first leg ground contact indicator  \n  * second leg ground contact indicator  \n  * throttle  \n  * engine gimbal  \n  \nIf VEL_STATE is set to true, the velocities are included:  \n  * x velocity  \n  * y velocity  \n  * angular velocity  \n  \n### CONTROL INPUTS  \nDiscrete control inputs are:  \n  * gimbal left  \n  * gimbal right  \n  * throttle up  \n  * throttle down  \n  * use first control thruster  \n  * use second control thruster  \n  * no action  \n    \nContinuous control inputs are:  \n  * gimbal (left/right)  \n  * throttle (up/down)  \n  * control thruster (left/right)  \n"
  },
  {
    "path": "gym_rocketlander/__init__.py",
    "content": "from gym.envs.registration import register\n\nregister(\n    id=\"rocketlander-v0\",\n    entry_point=\"gym_rocketlander.envs:RocketLander\",\n)"
  },
  {
    "path": "gym_rocketlander/envs/__init__.py",
    "content": "from gym_rocketlander.envs.rocket_lander_env import RocketLander\n"
  },
  {
    "path": "gym_rocketlander/envs/rocket_lander_env.py",
    "content": "import numpy as np\nimport Box2D\nfrom Box2D.b2 import (\n    fixtureDef,\n    polygonShape,\n    revoluteJointDef,\n    distanceJointDef,\n    contactListener,\n)\nimport gym\nfrom gym import spaces\nfrom gym.utils import seeding\nfrom gym.envs.classic_control import rendering\n\n\"\"\"\nThe objective of this environment is to land a rocket on a ship.\n\nSTATE VARIABLES\nThe state consists of the following variables:\n    - x position\n    - y position\n    - angle\n    - first leg ground contact indicator\n    - second leg ground contact indicator\n    - throttle\n    - engine gimbal\nIf VEL_STATE is set to true, the velocities are included:\n    - x velocity\n    - y velocity\n    - angular velocity\nall state variables are roughly in the range [-1, 1]\n\nCONTROL INPUTS\nDiscrete control inputs are:\n    - gimbal left\n    - gimbal right\n    - throttle up\n    - throttle down\n    - use first control thruster\n    - use second control thruster\n    - no action\n\nContinuous control inputs are:\n    - gimbal (left/right)\n    - throttle (up/down)\n    - control thruster (left/right)\n\"\"\"\n\nCONTINUOUS = True\nVEL_STATE = True  # Add velocity info to state\nFPS = 60\nSCALE_S = 0.35  # Temporal Scaling, lower is faster - adjust forces appropriately\nINITIAL_RANDOM = 0.4  # Random scaling of initial velocity, higher is more difficult\n\nSTART_HEIGHT = 800.0\nSTART_SPEED = 80.0\n\n# ROCKET\nMIN_THROTTLE = 0.4\nGIMBAL_THRESHOLD = 0.4\nMAIN_ENGINE_POWER = 1600 * SCALE_S\nSIDE_ENGINE_POWER = 100 / FPS * SCALE_S\n\nROCKET_WIDTH = 3.66 * SCALE_S\nROCKET_HEIGHT = ROCKET_WIDTH / 3.7 * 47.9\nENGINE_HEIGHT = ROCKET_WIDTH * 0.5\nENGINE_WIDTH = ENGINE_HEIGHT * 0.7\nTHRUSTER_HEIGHT = ROCKET_HEIGHT * 0.86\n\n# LEGS\nLEG_LENGTH = ROCKET_WIDTH * 2.2\nBASE_ANGLE = -0.27\nSPRING_ANGLE = 0.27\nLEG_AWAY = ROCKET_WIDTH / 2\n\n# SHIP\nSHIP_HEIGHT = ROCKET_WIDTH\nSHIP_WIDTH = SHIP_HEIGHT * 40\n\n# VIEWPORT\nVIEWPORT_H = 720\nVIEWPORT_W = 500\nH = 1.1 * START_HEIGHT * SCALE_S\nW = float(VIEWPORT_W) / VIEWPORT_H * H\n\n# SMOKE FOR VISUALS\nMAX_SMOKE_LIFETIME = 2 * FPS\n\n\nclass ContactDetector(contactListener):\n    def __init__(self, env):\n        contactListener.__init__(self)\n        self.env = env\n\n    def BeginContact(self, contact):\n        if (\n            self.env.water in [contact.fixtureA.body, contact.fixtureB.body]\n            or self.env.lander in [contact.fixtureA.body, contact.fixtureB.body]\n            or self.env.containers[0] in [contact.fixtureA.body, contact.fixtureB.body]\n            or self.env.containers[1] in [contact.fixtureA.body, contact.fixtureB.body]\n        ):\n            self.env.game_over = True\n        else:\n            for i in range(2):\n                if self.env.legs[i] in [contact.fixtureA.body, contact.fixtureB.body]:\n                    self.env.legs[i].ground_contact = True\n\n    def EndContact(self, contact):\n        for i in range(2):\n            if self.env.legs[i] in [contact.fixtureA.body, contact.fixtureB.body]:\n                self.env.legs[i].ground_contact = False\n\n\nclass RocketLander(gym.Env):\n    metadata = {\"render.modes\": [\"human\", \"rgb_array\"], \"video.frames_per_second\": FPS}\n\n    def __init__(self):\n        self._seed()\n        self.viewer = None\n        self.episode_number = 0\n\n        self.world = Box2D.b2World()\n        self.water = None\n        self.lander = None\n        self.engine = None\n        self.ship = None\n        self.legs = []\n\n        high = np.array([1, 1, 1, 1, 1, 1, 1, np.inf, np.inf, np.inf], dtype=np.float32)\n        low = -high\n        if not VEL_STATE:\n            high = high[0:7]\n            low = low[0:7]\n\n        self.observation_space = spaces.Box(low, high, dtype=np.float32)\n\n        if CONTINUOUS:\n            self.action_space = spaces.Box(-1.0, +1.0, (3,), dtype=np.float32)\n        else:\n            self.action_space = spaces.Discrete(7)\n\n        self.reset()\n\n    def _seed(self, seed=None):\n        self.np_random, seed = seeding.np_random(seed)\n        return [seed]\n\n    def _destroy(self):\n        if not self.water:\n            return\n        self.world.contactListener = None\n        self.world.DestroyBody(self.water)\n        self.water = None\n        self.world.DestroyBody(self.lander)\n        self.lander = None\n        self.world.DestroyBody(self.ship)\n        self.ship = None\n        self.world.DestroyBody(self.legs[0])\n        self.world.DestroyBody(self.legs[1])\n        self.legs = []\n        self.world.DestroyBody(self.containers[0])\n        self.world.DestroyBody(self.containers[1])\n        self.containers = []\n\n    def reset(self):\n        self._destroy()\n        self.world.contactListener_keepref = ContactDetector(self)\n        self.world.contactListener = self.world.contactListener_keepref\n        self.game_over = False\n        self.prev_shaping = None\n        self.throttle = 0\n        self.gimbal = 0.0\n        self.landed_ticks = 0\n        self.stepnumber = 0\n        self.smoke = []\n\n        # self.terrainheigth = self.np_random.uniform(H / 20, H / 10)\n        self.terrainheigth = H / 20\n        self.shipheight = self.terrainheigth + SHIP_HEIGHT\n        # ship_pos = self.np_random.uniform(0, SHIP_WIDTH / SCALE) + SHIP_WIDTH / SCALE\n        ship_pos = W / 2\n        self.helipad_x1 = ship_pos - SHIP_WIDTH / 2\n        self.helipad_x2 = self.helipad_x1 + SHIP_WIDTH\n        self.helipad_y = self.terrainheigth + SHIP_HEIGHT\n\n        self.water = self.world.CreateStaticBody(\n            fixtures=fixtureDef(\n                shape=polygonShape(\n                    vertices=(\n                        (0, 0),\n                        (W, 0),\n                        (W, self.terrainheigth),\n                        (0, self.terrainheigth),\n                    )\n                ),\n                friction=0.1,\n                restitution=0.0,\n            )\n        )\n        self.water.color1 = rgb(70, 96, 176)\n\n        self.ship = self.world.CreateStaticBody(\n            fixtures=fixtureDef(\n                shape=polygonShape(\n                    vertices=(\n                        (self.helipad_x1, self.terrainheigth),\n                        (self.helipad_x2, self.terrainheigth),\n                        (self.helipad_x2, self.terrainheigth + SHIP_HEIGHT),\n                        (self.helipad_x1, self.terrainheigth + SHIP_HEIGHT),\n                    )\n                ),\n                friction=0.5,\n                restitution=0.0,\n            )\n        )\n\n        self.containers = []\n        for side in [-1, 1]:\n            self.containers.append(\n                self.world.CreateStaticBody(\n                    fixtures=fixtureDef(\n                        shape=polygonShape(\n                            vertices=(\n                                (\n                                    ship_pos + side * 0.95 * SHIP_WIDTH / 2,\n                                    self.helipad_y,\n                                ),\n                                (\n                                    ship_pos + side * 0.95 * SHIP_WIDTH / 2,\n                                    self.helipad_y + SHIP_HEIGHT,\n                                ),\n                                (\n                                    ship_pos\n                                    + side * 0.95 * SHIP_WIDTH / 2\n                                    - side * SHIP_HEIGHT,\n                                    self.helipad_y + SHIP_HEIGHT,\n                                ),\n                                (\n                                    ship_pos\n                                    + side * 0.95 * SHIP_WIDTH / 2\n                                    - side * SHIP_HEIGHT,\n                                    self.helipad_y,\n                                ),\n                            )\n                        ),\n                        friction=0.2,\n                        restitution=0.0,\n                    )\n                )\n            )\n            self.containers[-1].color1 = rgb(206, 206, 2)\n\n        self.ship.color1 = (0.2, 0.2, 0.2)\n\n        initial_x = W / 2 + W * np.random.uniform(-0.3, 0.3)\n        initial_y = H * 0.95\n        self.lander = self.world.CreateDynamicBody(\n            position=(initial_x, initial_y),\n            angle=0.0,\n            fixtures=fixtureDef(\n                shape=polygonShape(\n                    vertices=(\n                        (-ROCKET_WIDTH / 2, 0),\n                        (+ROCKET_WIDTH / 2, 0),\n                        (ROCKET_WIDTH / 2, +ROCKET_HEIGHT),\n                        (-ROCKET_WIDTH / 2, +ROCKET_HEIGHT),\n                    )\n                ),\n                density=1.0,\n                friction=0.5,\n                categoryBits=0x0010,\n                maskBits=0x001,\n                restitution=0.0,\n            ),\n        )\n\n        self.lander.color1 = rgb(230, 230, 230)\n\n        for i in [-1, +1]:\n            leg = self.world.CreateDynamicBody(\n                position=(initial_x - i * LEG_AWAY, initial_y + ROCKET_WIDTH * 0.2),\n                angle=(i * BASE_ANGLE),\n                fixtures=fixtureDef(\n                    shape=polygonShape(\n                        vertices=(\n                            (0, 0),\n                            (0, LEG_LENGTH / 25),\n                            (i * LEG_LENGTH, 0),\n                            (i * LEG_LENGTH, -LEG_LENGTH / 20),\n                            (i * LEG_LENGTH / 3, -LEG_LENGTH / 7),\n                        )\n                    ),\n                    density=1,\n                    restitution=0.0,\n                    friction=0.2,\n                    categoryBits=0x0020,\n                    maskBits=0x001,\n                ),\n            )\n            leg.ground_contact = False\n            leg.color1 = (0.25, 0.25, 0.25)\n            rjd = revoluteJointDef(\n                bodyA=self.lander,\n                bodyB=leg,\n                localAnchorA=(i * LEG_AWAY, ROCKET_WIDTH * 0.2),\n                localAnchorB=(0, 0),\n                enableLimit=True,\n                maxMotorTorque=2500.0,\n                motorSpeed=-0.05 * i,\n                enableMotor=True,\n            )\n            djd = distanceJointDef(\n                bodyA=self.lander,\n                bodyB=leg,\n                anchorA=(i * LEG_AWAY, ROCKET_HEIGHT / 8),\n                anchorB=leg.fixtures[0].body.transform * (i * LEG_LENGTH, 0),\n                collideConnected=False,\n                frequencyHz=0.01,\n                dampingRatio=0.9,\n            )\n            if i == 1:\n                rjd.lowerAngle = -SPRING_ANGLE\n                rjd.upperAngle = 0\n            else:\n                rjd.lowerAngle = 0\n                rjd.upperAngle = +SPRING_ANGLE\n            leg.joint = self.world.CreateJoint(rjd)\n            leg.joint2 = self.world.CreateJoint(djd)\n\n            self.legs.append(leg)\n\n        self.lander.linearVelocity = (\n            -self.np_random.uniform(0, INITIAL_RANDOM)\n            * START_SPEED\n            * (initial_x - W / 2)\n            / W,\n            -START_SPEED,\n        )\n\n        self.lander.angularVelocity = (1 + INITIAL_RANDOM) * np.random.uniform(-1, 1)\n\n        self.drawlist = (\n            self.legs + [self.water] + [self.ship] + self.containers + [self.lander]\n        )\n\n        if CONTINUOUS:\n            return self.step([0, 0, 0])[0]\n        else:\n            return self.step(6)[0]\n\n    def step(self, action):\n\n        self.force_dir = 0\n\n        if CONTINUOUS:\n            np.clip(action, -1, 1)\n            self.gimbal += action[0] * 0.15 / FPS\n            self.throttle += action[1] * 0.5 / FPS\n            if action[2] > 0.5:\n                self.force_dir = 1\n            elif action[2] < -0.5:\n                self.force_dir = -1\n        else:\n            if action == 0:\n                self.gimbal += 0.01\n            elif action == 1:\n                self.gimbal -= 0.01\n            elif action == 2:\n                self.throttle += 0.01\n            elif action == 3:\n                self.throttle -= 0.01\n            elif action == 4:  # left\n                self.force_dir = -1\n            elif action == 5:  # right\n                self.force_dir = 1\n\n        self.gimbal = np.clip(self.gimbal, -GIMBAL_THRESHOLD, GIMBAL_THRESHOLD)\n        self.throttle = np.clip(self.throttle, 0.0, 1.0)\n        self.power = (\n            0\n            if self.throttle == 0.0\n            else MIN_THROTTLE + self.throttle * (1 - MIN_THROTTLE)\n        )\n\n        # main engine force\n        force_pos = (self.lander.position[0], self.lander.position[1])\n        force = (\n            -np.sin(self.lander.angle + self.gimbal) * MAIN_ENGINE_POWER * self.power,\n            np.cos(self.lander.angle + self.gimbal) * MAIN_ENGINE_POWER * self.power,\n        )\n        self.lander.ApplyForce(force=force, point=force_pos, wake=False)\n\n        # control thruster force\n        force_pos_c = self.lander.position + THRUSTER_HEIGHT * np.array(\n            (np.sin(self.lander.angle), np.cos(self.lander.angle))\n        )\n        force_c = (\n            -self.force_dir * np.cos(self.lander.angle) * SIDE_ENGINE_POWER,\n            self.force_dir * np.sin(self.lander.angle) * SIDE_ENGINE_POWER,\n        )\n        self.lander.ApplyLinearImpulse(impulse=force_c, point=force_pos_c, wake=False)\n\n        self.world.Step(1.0 / FPS, 60, 60)\n\n        pos = self.lander.position\n        vel_l = np.array(self.lander.linearVelocity) / START_SPEED\n        vel_a = self.lander.angularVelocity\n        x_distance = (pos.x - W / 2) / W\n        y_distance = (pos.y - self.shipheight) / (H - self.shipheight)\n\n        angle = (self.lander.angle / np.pi) % 2\n        if angle > 1:\n            angle -= 2\n\n        state = [\n            2 * x_distance,\n            2 * (y_distance - 0.5),\n            angle,\n            1.0 if self.legs[0].ground_contact else 0.0,\n            1.0 if self.legs[1].ground_contact else 0.0,\n            2 * (self.throttle - 0.5),\n            (self.gimbal / GIMBAL_THRESHOLD),\n        ]\n        if VEL_STATE:\n            state.extend([vel_l[0], vel_l[1], vel_a])\n\n        # REWARD -------------------------------------------------------------------------------------------------------\n\n        # state variables for reward\n        distance = np.linalg.norm(\n            (3 * x_distance, y_distance)\n        )  # weight x position more\n        speed = np.linalg.norm(vel_l)\n        groundcontact = self.legs[0].ground_contact or self.legs[1].ground_contact\n        brokenleg = (\n            self.legs[0].joint.angle < 0 or self.legs[1].joint.angle > -0\n        ) and groundcontact\n        outside = abs(pos.x - W / 2) > W / 2 or pos.y > H\n        fuelcost = 0.1 * (0.5 * self.power + abs(self.force_dir)) / FPS\n        landed = (\n            self.legs[0].ground_contact and self.legs[1].ground_contact and speed < 0.1\n        )\n        done = False\n\n        reward = -fuelcost\n\n        if outside or brokenleg:\n            self.game_over = True\n\n        if self.game_over:\n            done = True\n        else:\n            # reward shaping\n            shaping = -0.5 * (distance + speed + abs(angle) ** 2 + abs(vel_a) ** 2)\n            shaping += 0.1 * (self.legs[0].ground_contact + self.legs[1].ground_contact)\n            if self.prev_shaping is not None:\n                reward += shaping - self.prev_shaping\n            self.prev_shaping = shaping\n\n            if landed:\n                self.landed_ticks += 1\n            else:\n                self.landed_ticks = 0\n            if self.landed_ticks == FPS:\n                reward = 1.0\n                done = True\n\n        if done:\n            reward += max(-1, 0 - 2 * (speed + distance + abs(angle) + abs(vel_a)))\n\n        reward = np.clip(reward, -1, 1)\n\n        # REWARD -------------------------------------------------------------------------------------------------------\n\n        self.stepnumber += 1\n\n        return np.array(state), reward, done, {}\n\n    def render(self, mode=\"human\", close=False):\n        if close:\n            if self.viewer is not None:\n                self.viewer.close()\n                self.viewer = None\n            return\n\n        if self.viewer is None:\n\n            self.viewer = rendering.Viewer(VIEWPORT_W, VIEWPORT_H)\n            self.viewer.set_bounds(0, W, 0, H)\n\n            sky = rendering.FilledPolygon(((0, 0), (0, H), (W, H), (W, 0)))\n            self.sky_color = rgb(126, 150, 233)\n            sky.set_color(*self.sky_color)\n            self.sky_color_half_transparent = (\n                np.array((np.array(self.sky_color) + rgb(255, 255, 255))) / 2\n            )\n            self.viewer.add_geom(sky)\n\n            self.rockettrans = rendering.Transform()\n\n            engine = rendering.FilledPolygon(\n                (\n                    (0, 0),\n                    (ENGINE_WIDTH / 2, -ENGINE_HEIGHT),\n                    (-ENGINE_WIDTH / 2, -ENGINE_HEIGHT),\n                )\n            )\n            self.enginetrans = rendering.Transform()\n            engine.add_attr(self.enginetrans)\n            engine.add_attr(self.rockettrans)\n            engine.set_color(0.4, 0.4, 0.4)\n            self.viewer.add_geom(engine)\n\n            self.fire = rendering.FilledPolygon(\n                (\n                    (ENGINE_WIDTH * 0.4, 0),\n                    (-ENGINE_WIDTH * 0.4, 0),\n                    (-ENGINE_WIDTH * 1.2, -ENGINE_HEIGHT * 5),\n                    (0, -ENGINE_HEIGHT * 8),\n                    (ENGINE_WIDTH * 1.2, -ENGINE_HEIGHT * 5),\n                )\n            )\n            self.fire.set_color(*rgb(255, 230, 107))\n            self.firescale = rendering.Transform(scale=(1, 1))\n            self.firetrans = rendering.Transform(translation=(0, -ENGINE_HEIGHT))\n            self.fire.add_attr(self.firescale)\n            self.fire.add_attr(self.firetrans)\n            self.fire.add_attr(self.enginetrans)\n            self.fire.add_attr(self.rockettrans)\n\n            smoke = rendering.FilledPolygon(\n                (\n                    (ROCKET_WIDTH / 2, THRUSTER_HEIGHT * 1),\n                    (ROCKET_WIDTH * 3, THRUSTER_HEIGHT * 1.03),\n                    (ROCKET_WIDTH * 4, THRUSTER_HEIGHT * 1),\n                    (ROCKET_WIDTH * 3, THRUSTER_HEIGHT * 0.97),\n                )\n            )\n            smoke.set_color(*self.sky_color_half_transparent)\n            self.smokescale = rendering.Transform(scale=(1, 1))\n            smoke.add_attr(self.smokescale)\n            smoke.add_attr(self.rockettrans)\n            self.viewer.add_geom(smoke)\n\n            self.gridfins = []\n            for i in (-1, 1):\n                finpoly = (\n                    (i * ROCKET_WIDTH * 1.1, THRUSTER_HEIGHT * 1.01),\n                    (i * ROCKET_WIDTH * 0.4, THRUSTER_HEIGHT * 1.01),\n                    (i * ROCKET_WIDTH * 0.4, THRUSTER_HEIGHT * 0.99),\n                    (i * ROCKET_WIDTH * 1.1, THRUSTER_HEIGHT * 0.99),\n                )\n                gridfin = rendering.FilledPolygon(finpoly)\n                gridfin.add_attr(self.rockettrans)\n                gridfin.set_color(0.25, 0.25, 0.25)\n                self.gridfins.append(gridfin)\n\n        if self.stepnumber % round(FPS / 10) == 0 and self.power > 0:\n            s = [\n                MAX_SMOKE_LIFETIME * self.power,  # total lifetime\n                0,  # current lifetime\n                self.power * (1 + 0.2 * np.random.random()),  # size\n                np.array(self.lander.position)\n                + self.power\n                * ROCKET_WIDTH\n                * 10\n                * np.array(\n                    (\n                        np.sin(self.lander.angle + self.gimbal),\n                        -np.cos(self.lander.angle + self.gimbal),\n                    )\n                )\n                + self.power * 5 * (np.random.random(2) - 0.5),\n            ]  # position\n            self.smoke.append(s)\n\n        for s in self.smoke:\n            s[1] += 1\n            if s[1] > s[0]:\n                self.smoke.remove(s)\n                continue\n            t = rendering.Transform(translation=(s[3][0], s[3][1] + H * s[1] / 2000))\n            self.viewer.draw_circle(\n                radius=0.05 * s[1] + s[2],\n                color=self.sky_color\n                + (1 - (2 * s[1] / s[0] - 1) ** 2)\n                / 3\n                * (self.sky_color_half_transparent - self.sky_color),\n            ).add_attr(t)\n\n        self.viewer.add_onetime(self.fire)\n        for g in self.gridfins:\n            self.viewer.add_onetime(g)\n\n        for obj in self.drawlist:\n            for f in obj.fixtures:\n                trans = f.body.transform\n                path = [trans * v for v in f.shape.vertices]\n                self.viewer.draw_polygon(path, color=obj.color1)\n\n        for l in zip(self.legs, [-1, 1]):\n            path = [\n                self.lander.fixtures[0].body.transform\n                * (l[1] * ROCKET_WIDTH / 2, ROCKET_HEIGHT / 8),\n                l[0].fixtures[0].body.transform * (l[1] * LEG_LENGTH * 0.8, 0),\n            ]\n            self.viewer.draw_polyline(\n                path, color=self.ship.color1, linewidth=1 if START_HEIGHT > 500 else 2\n            )\n\n        self.viewer.draw_polyline(\n            (\n                (self.helipad_x2, self.terrainheigth + SHIP_HEIGHT),\n                (self.helipad_x1, self.terrainheigth + SHIP_HEIGHT),\n            ),\n            color=rgb(206, 206, 2),\n            linewidth=1,\n        )\n\n        self.rockettrans.set_translation(*self.lander.position)\n        self.rockettrans.set_rotation(self.lander.angle)\n        self.enginetrans.set_rotation(self.gimbal)\n        self.firescale.set_scale(newx=1, newy=self.power * np.random.uniform(1, 1.3))\n        self.smokescale.set_scale(newx=self.force_dir, newy=1)\n\n        return self.viewer.render(return_rgb_array=mode == \"rgb_array\")\n\n\ndef rgb(r, g, b):\n    return float(r) / 255, float(g) / 255, float(b) / 255\n"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup\n\nsetup(\n    name=\"gym_rocketlander\",\n    version=\"0.0.1\",\n    install_requires=[\"gym\", \"Box2D\"],\n)"
  }
]