Lunar Lander Game Asks You To Write A Simple Autopilot

Everyone likes a good lunar landing simulator, and [Dominic Doty] wrote a fun take on the idea: your goal is to write an autopilot controller to manage the landing. Try it out!

Virtual landers are far cheaper than real ones, thank goodness.

[Dominic] was inspired in part by this simple rocket landing game which is very much an exercise in reflex and intuition, not to mention being much faster-paced than the classic 1979 video game (which you can also play in your browser here.)

[Dominic]’s version has a similar classic look to the original, but embraces a more thoughtful approach. In it, one uses plain JavaScript to try to minimize the lander’s angle, velocity, and angular velocity in order to land safely on the generated terrain.

Want to see if you have the right stuff? Here’s a direct link to Lunar Pilot. Don’t get discouraged if you don’t succeed right away, though. Moon landings have had plenty of failures, and are actually very hard.

28 thoughts on “Lunar Lander Game Asks You To Write A Simple Autopilot

    1. Haha… that’s funny. I too searched for a way to log variables. console.log works but is not super comfortable…

      Should have a variable log window, also single step possibilities…

        1. Minimizing angular velocity is important. If you come down softly with low vertical and lateral velocity and perfectly upright but have significant angular velocity (rate of spin), the angular momentum (moment of inertia x angular velocity) will keep the ship spinning after the touchdown and it will crash.

          1. That’s what I was firetruckin’ saying! Don’t let the spaceship spin because it’ll make landing superdifficult. So no need to control it. The ship also isn’t going up so no need to control the upward velocity.

            If it’s a fancy way off saying “don’t let it wiggle” fine, but please no tarting it up in tech language. Parentheses (()) are your friend. Yes, recursive parentheses!

        2. The only way to set the thrust vector is to rotate the craft which requires good angular velocity control. That means you must be able to control the angular pose by increasing the angular velocity towards the desired angular pose and then decreasing the angular velocity at the correct rate to stop at the desired angular pose. Since you can only command angular acceleration (thrust) and angular velocity doesn’t automagically zero or decrease with no angular acceleration, your control system does require some thought.

  1. This would be much easier to play with on the phone if there was a text area to send debug to… I guess I’ll have to try again later on the laptop where there’s a console

    1. Here’s my take, with a little PID like control loop stuff mixed in, works with ballistic and tumbling most of the time (quotes are replaced with non compatible 6 and 9 quotes so change them to normal double quotes):

      // Arguments:
      let {x_position, altitude, angle, userStore} = arguments[0];

      // Example of how to initialize user storage
      if (!(“store” in userStore)) {
      userStore.store = 0;
      userStore.initialAltitude = altitude;
      userStore.prevAltitude = 0;
      userStore.prevSpeed = 0;
      userStore.prevAngle = 0;
      userStore.prevX = 0;
      }

      speed = 0;
      xspeed = userStore.prevX-x_position;
      AngleSpeed = userStore.prevAngle – angle;

      rotSpeed = 0;
      if( angle > .1 || angle < .1 ) rotSpeed = ((AngleSpeed/6)-(((angle^2)/360)))*5 + (xspeed/6);

      rotThrust = rotSpeed;

      if( altitude < 260 ) speed = (userStore.prevAltitude – altitude)/1.5;

      aftThrust = speed;

      if( speed < 0 ) aftThrust = 0;

      //rotThrust = 0;
      userStore.prevAltitude = altitude;
      userStore.prevAngle = angle;
      userStore.prevX = x_position;
      // Return:
      return { rotThrust, aftThrust, userStore:userStore };

  2. // Arguments:
    let {x_position, altitude, angle, userStore} = arguments[0];

    // Example of how to initialize user storage
    if (!(“store” in userStore)) {
    userStore.store = {
    lastAltitude: altitude,
    };
    }

    // Calculate descent speed and decide on thrust
    let descentSpeed = userStore.store.lastAltitude - altitude;
    let requiredThrust = 0;

    if(altitude<250 && descentSpeed>.5){
    requiredThrust = 0.75;
    }else if(altitude<50 && descentSpeed>0.25){
    requiredThrust = .50;
    }else if(altitude<10 && descentSpeed>0.05){
    requiredThrust = .25;
    }

    // Update user store with the current state
    userStore.store.lastAltitude = altitude;

    // Return thrust commands
    return {
    rotThrust: 0, // Assume no rotational control needed for simplicity
    aftThrust: requiredThrust, // Apply aft thrust to slow descent
    userStore: userStore // Preserve user storage state
    };

    1. //Lovingly coded by Pauline Pounds
      //Please enjoy my lazy implementation using PD control. As-is, works in most situations; not so great on tumbling ballistic. If I had the time I’d do a better (minimum fuel) implementation of velocity compensation prior to switching to PD. I figure as a professor in mechatronics specialising in control of UAVs I should be ok at this…

      // Arguments:
      let {x_position, altitude, angle, userStore} = arguments[0];

      //User storage
      if (!(“rot” in userStore)) {userStore.rot = 0;}
      if (!(“thr” in userStore)) {userStore.thr = 0;}
      if (!(“angle0” in userStore)) {userStore.angle0 = 0;}
      if (!(“x0” in userStore)) {userStore.x0 = 0;}
      if (!(“alt0” in userStore)) {userStore.alt0 = 0;}

      //PD Control laws
      userStore.rot = -1angle – 20(angle-userStore.angle0) + 20(userStore.x0 – x_position);
      userStore.thr = 0.01
      (10-altitude) -1(altitude-userStore.alt0) – 5(userStore.x0 – x_position);

      //Bounds checking
      if(userStore.rot > 1){userStore.rot = 1;}
      if(userStore.rot < -1){userStore.rot = -1;}
      if(userStore.thr > 1){userStore.thr = 1;}
      if(userStore.thr < 0){userStore.thr = 0;}

      //Update history
      userStore.x0 = x_position;
      userStore.angle0 = angle;
      userStore.alt0 = altitude;

      // Return:
      return { rotThrust:userStore.rot, aftThrust:userStore.thr, userStore:userStore };

      1. Thanks for the concise code! If we disable the main thruster when upside down it sticks most ballistic landings.

        userStore.thr = 0.01(10-altitude) -1(altitude-userStore.alt0) – 1*(userStore.x0 – x_position); // save some fuel here

        // do not use main thruster when upside down
        if (angle < -90) {userStore.thr = 0;}
        if (angle > 90) {userStore.thr = 0;}

  3. I played with this for a while, if anyone wants a nice console.table print-out for live debugging, please enjoy.

    let { x_position, altitude, angle, userStore } = arguments[0];
    
    
    if (!("store" in userStore)) {
      userStore.store = {
        frame: 0,
        initFuel: 200,
        estimatedFuel: 200,
        velocity: { x: 0, y: 0, a: 0},
        lastAltitude: altitude,
        lastX: x_position,
        lastAngle: angle,
      };
    }
    userStore.store.frame += 1;
    
    let aftInput = 0;
    let rotInput = 0;
    
    // calculate diff since last frame
    userStore.store.velocity.x = userStore.store.lastX - x_position;
    userStore.store.velocity.y = userStore.store.lastAltitude - altitude;
    userStore.store.velocity.a = userStore.store.lastAngle - angle;
    
    let curVelocity = Math.sqrt(
      Math.pow(userStore.store.velocity.x, 2) +
        Math.pow(userStore.store.velocity.y, 2)
    );
    
    // Update the "last" values
    userStore.store.lastX = x_position;
    userStore.store.lastAltitude = altitude;
    userStore.store.lastAngle = angle;
    
    userStore.store.estimatedFuel -= rotInput;
    userStore.store.estimatedFuel -= aftInput;
    
    // Telemetry
    console.table({
      Frame: userStore.store.frame,
      Altitude: altitude,
      Angle: angle,
      Velocity: curVelocity,
      Fuel: userStore.store.estimatedFuel,
      X_position: x_position,
      Thrust_Input: aftInput,
      Rotation_Input: rotInput,
    });
    
    return {
      rotThrust: rotInput, // Assume no rotational control needed for simplicity
      aftThrust: aftInput, // Apply aft thrust to slow descent
      userStore: userStore, // Preserve user storage state
    };
    
  4. gravity_accel = 1/60
    mass_ship = 10
    fuelLevel_x+1 = fuelLevel_x – (rotThrust + aftThrust) * 0.5
    mass_fuel = fuelLevel / 10
    So necessary thrust for hovering is gravity_accel * (mass_ship + mass_fuel)

    1/3 for full fuel, 10/60 for empty fuel

    Who will do the same work for the moment of inertia?

    Attention: Altitude is absolute, not height above ground.

    Overall very nice for e.g. a control engineering class, but too much bookkeeping up front…

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.