Examples/Lander

Lander

Created from a prompt

Re-run on 2026-10-16 with OpenCode (DeepSeek V4 Pro via OpenRouter) against the updated starter (scoped picture bundle, answer scrub, moveButton, key_input clocks, check_env.py). No hardcoded demo code. Cursor did not edit workspace/.

2026-10-16 · task CRztSzH8CvBF · opencode · openrouter/deepseek/deepseek-v4-pro-0813

The prompt

Build a lunar-lander style game that runs smoothly in the page. A ship falls under gravity toward a green pad on the coast. I do not get a thruster for every direction. I tilt the nose left or right, and I fire one main engine that pushes the way the nose is pointing. Holding a tilt key and the engine together is how I steer. I need to touch down on the pad, slowly, and nearly upright. Landing anywhere else, landing hard, or landing tipped over is a crash and ends the round. A real landing also ends the round, as a win. Running out of time ends the round too. Burning uses fuel.

Show the coast, the ocean with waves, the pad, the ship with its tilt, the flame when the engine is on, and the score. Two tilt keys and one engine key. Different starts can begin higher, faster, or off to the side. The same start should fall the same way.

Live environment

Running the prompt-created workspace on this machine — not a mock. Save an episode, then open Replay below to scrub the timeline.

opening · 0 ms

Generated workspace

Exact files the agent wrote — not rewritten by hand.

from __future__ import annotations

import math
from typing import Any, Dict, List, Tuple

from helpers import base_observation, clamp01, key_input, pick_task, rng_for, seed_from_task, task_ids

GAME_CLASS = "LunarLander"

W = 100.0
H = 100.0
COAST_X = 42.0
WATER_Y = 18.0
GROUND_Y = 24.0
PAD_LEFT = 52.0
PAD_RIGHT = 68.0

DT = 0.05
GRAVITY = 1.6
THRUST = 3.0
TILT_RATE = 1.5
MAX_TILT = 1.5
MAX_SPEED = 14.0
FUEL0 = 120.0
BURN_RATE = 9.0
TIME_LIMIT = 35.0
SAFE_SPEED = 1.4
SAFE_TILT = 0.35

STARTS: List[Tuple[str, float, float, float, float, float]] = [
    ("Gentle", 60.0, 82.0, 0.0, 0.0, 0.0),
    ("High", 60.0, 94.0, 0.0, -0.4, 0.0),
    ("Fast", 57.0, 88.0, 0.0, -3.0, 0.0),
    ("Far side", 28.0, 82.0, 0.0, 0.0, 0.0),
    ("Drifting", 24.0, 88.0, 1.5, -0.8, 0.10),
    ("Tilted", 72.0, 78.0, -0.6, -0.3, 0.40),
    ("High drift", 38.0, 95.0, 1.2, -2.2, -0.08),
    ("Deep water", 13.0, 90.0, 1.8, -0.9, -0.15),
]

FLIGHT_MOVES = ["up", "left", "right", "up left", "up right", "stay"]


class LunarLander:
    def __init__(self) -> None:
        self._catalog = task_ids(8, "s")

    def reset(self, task_id: str = "") -> Dict[str, Any]:
        picked = pick_task(task_id, self._catalog, default="s1")
        if picked in self._catalog:
            idx = self._catalog.index(picked)
        else:
            idx = seed_from_task(task_id) % len(STARTS)
        name, x, y, vx, vy, theta = STARTS[idx]
        self.task_id = picked
        self.start = name
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.theta = theta
        self.fuel = FUEL0
        self.t = 0.0
        self.engine_on = False
        self.done = False
        self.result = ""
        self.score = 0.0
        self.reward = 0.0
        self.message = "Land slowly and upright on the green pad."
        self.last_move = None
        self.wave_seed = rng_for(picked).uniform(0.0, 6.283)
        return self._obs()

    def step(self, action: Any) -> Tuple[Dict[str, Any], bool, str]:
        move = getattr(action, "move", "") or ""
        self.last_move = move
        if self.done:
            return self._obs(), True, ""
        engine, tilt = self._parse(move)

        self.theta += tilt * TILT_RATE * DT
        self.theta = max(-MAX_TILT, min(MAX_TILT, self.theta))

        ax = 0.0
        ay = 0.0
        if engine and self.fuel > 0.0:
            ax = THRUST * math.sin(self.theta)
            ay = THRUST * math.cos(self.theta)
            self.fuel -= BURN_RATE * DT
            if self.fuel < 0.0:
                self.fuel = 0.0
        self.engine_on = engine and self.fuel > 0.0

        ay -= GRAVITY
        self.vx += ax * DT
        self.vy += ay * DT
        self.vx = max(-MAX_SPEED, min(MAX_SPEED, self.vx))
        self.vy = max(-MAX_SPEED, min(MAX_SPEED, self.vy))
        self.x += self.vx * DT
        self.y += self.vy * DT

        self.x = max(0.5, min(W - 0.5, self.x))
        self.y = min(H - 0.5, self.y)
        self.t += DT

        surface = WATER_Y if self.x < COAST_X else GROUND_Y
        if self.y <= surface:
            self._land(surface)
        elif self.t >= TIME_LIMIT:
            self.done = True
            self.result = "timeout"
            self.reward = -1.0
            self.score = 0.0
            self.message = "Out of time — the round ended."
        return self._obs(), True, ""

    def _parse(self, move: str) -> Tuple[bool, int]:
        parts = (move or "").strip().split()
        engine = "up" in parts
        tilt = 0
        if "left" in parts:
            tilt -= 1
        if "right" in parts:
            tilt += 1
        return engine, tilt

    def _land(self, surface: float) -> None:
        self.y = surface
        self.done = True
        on_pad = PAD_LEFT <= self.x <= PAD_RIGHT and self.x >= COAST_X
        downward = -self.vy
        upright = abs(self.theta)
        if on_pad and downward <= SAFE_SPEED and upright <= SAFE_TILT:
            self.result = "win"
            speed_q = clamp01(1.0 - max(0.0, downward) / SAFE_SPEED)
            tilt_q = clamp01(1.0 - upright / SAFE_TILT)
            fuel_q = self.fuel / FUEL0
            quality = 0.45 * speed_q + 0.35 * tilt_q + 0.20 * fuel_q
            quality = max(0.05, min(1.0, quality))
            self.score = round(quality * 100.0)
            self.reward = quality
            self.message = "Safe landing! Score %d." % self.score
        else:
            self.result = "crash"
            self.reward = -1.0
            self.score = 0.0
            if not on_pad:
                if self.x < COAST_X:
                    self.message = "Splashed into the ocean — crashed."
                else:
                    self.message = "Missed the pad — crashed."
            elif downward > SAFE_SPEED:
                self.message = "Too fast — the ship was destroyed."
            else:
                self.message = "Tipped over on landing — crashed."

    def _obs(self) -> Dict[str, Any]:
        if self.done:
            moves: List[str] = []
            inputs: List[Dict[str, Any]] = []
        else:
            moves = list(FLIGHT_MOVES)
            inputs = [
                key_input(
                    "Tilt",
                    "tip the nose left or right",
                    {
                        "a": "left",
                        "ArrowLeft": "left",
                        "d": "right",
                        "ArrowRight": "right",
                    },
                    hold=True,
                    every_ms=50,
                    idle="stay",
                ),
                key_input(
                    "Engine",
                    "fire the main engine",
                    {" ": "up", "w": "up", "ArrowUp": "up"},
                    hold=True,
                ),
            ]
        return base_observation(
            reward=self.reward,
            done=self.done,
            legal_moves=moves,
            last_move=self.last_move,
            task_id=self.task_id,
            start=self.start,
            ship_x=round(self.x, 3),
            ship_y=round(self.y, 3),
            ship_vx=round(self.vx, 3),
            ship_vy=round(self.vy, 3),
            ship_theta=round(self.theta, 4),
            engine=self.engine_on,
            fuel=round(self.fuel, 1),
            time_left=round(max(0.0, TIME_LIMIT - self.t), 2),
            score=self.score,
            message=self.message,
            result=self.result,
            time=round(self.t, 3),
            wave_seed=round(self.wave_seed, 3),
            pad={"left": PAD_LEFT, "right": PAD_RIGHT, "y": GROUND_Y},
            coast={"x": COAST_X, "water_y": WATER_Y, "ground_y": GROUND_Y},
            world={"w": W, "h": H},
            inputs=inputs,
        )