Interactive Module

🚀 Lunar Lander

Learn Python by landing on the Moon. Every concept is introduced because the game needs it — not because it’s next in a textbook.

8 steps ~90 minutes Python 3 Google Colab
Open in Colab →
Step 1 of 80%
🎯 Your outcome

By the end of this module, you will have built a working Lunar Lander simulator that you can play, test automatically, and extend with an autopilot. Every Python concept is introduced the moment the game needs it.

📦Variables & constants
📚Dictionaries
⚙️Functions
🔁Loops
🔀Conditionals
🧪Automated tests
📊Matplotlib
robots;Autopilot challenge
1969
The original Lunar Lander was written by David Ahl on a DEC PDP-8 in FOCAL — a computer with 4KB of RAM. The units were Imperial: feet, feet per second, pounds of fuel. We’re keeping the Imperial units. It feels right.

The Apollo Guidance Computer that actually landed humans on the Moon ran at 2 MHz with 4KB of RAM. It solved real-time trajectory control. You’ll be doing the same — just with slightly more memory.
01
Constants — The Rules of the Universe
Variables · Naming conventions
Why do we need this? The computer has no memory of its own. You have to tell it everything — and give each thing a name. Constants are values that never change: gravity doesn’t change, the safe landing speed doesn’t change.

We write constants in UPPER_CASE by convention so we remember they’re fixed.

Try it: Change START_ALT to 500 and re-run. What changes? Change it back when you’re done.
💡 Variables are named containers for values. Constants are variables we promise never to change.
02
State — Tracking the Lander
Dictionaries · Functions · Docstrings
Why do we need this? The lander has a state — a snapshot of where it is and what it’s doing. A dictionary keeps everything together as one thing.

Our lander state has four values: altitude, velocity, fuel, and thrust_pct.

Try it: Access a single value with print(lander['altitude']). Change a value: lander['thrust_pct'] = 50. Print the whole dict again — did it change?
💡 Dictionaries store named values. Functions (def) let you reuse code.
03
Physics — Gravity and Thrust
Arithmetic · Functions · Return values
Why do we need this? Every 0.5 seconds, we calculate what happens to the lander based on physics. Thrust pushes up. Gravity pulls down.

acceleration = (thrust% ÷ 100) × MAX_ACCEL − G

velocity = velocity + acceleration × DT

altitude = altitude + velocity × DT

fuel = fuel − (thrust% ÷ 100) × FUEL_RATE × DT

Try it: Set s['thrust_pct'] = 100 before the loop and re-run. What happens to velocity?
💡 Functions return new data. We don’t modify state in place — we return a new copy.
04
Display — The HUD
F-strings · String formatting
Why do we need this? Numbers are useless if you can’t read them. A Heads-Up Display shows the player what’s happening each tick.
t= 12.5s | alt= 1843.2 ft | vel= -387.1 ft/s | fuel= 548 lb | thrust= 75%
Try it: The format spec :7.1f means 7 characters wide, 1 decimal place. Change it to :10.2f. What changes?
💡 F-strings embed expressions directly in text. Format specs control width and precision.
05
The Game Loop — Making it Playable
While loops · Input · Conditionals · Break
Why do we need this? A game loop runs continuously: show state → get input → update → check if over → repeat.
Play it. Try 0% all the way down. Try 100%. Hint: burn hard early to slow the initial 450 ft/s fall, ease off, then burn again for final approach.
💡 while True loops forever until break. input() pauses and waits for the user.
06
Replay & Analysis — Visualising Runs
Lists · Matplotlib · Functions as arguments
Why do we need this? If we record every state, we can plot the full trajectory and compare strategies.
Try it: Change the thrust schedule values. What does 90% at t=0 do to the altitude curve?
💡 Lists of tuples. List comprehensions. Matplotlib subplots.
07
Automated Tests — Verifying Your Physics
Assert statements · Test functions · TDD thinking
Why do we need this? Automated tests run in milliseconds and never lie. Write code that checks your code.
Break a test on purpose: Change G = 5.0 to G = -5.0 and re-run. Which tests fail? Why? Change it back.
💡 assert condition, "message" raises an error if the condition is false.
08
Autopilot Challenge 🏆
Functions as arguments · Control logic · Optimisation
This step has no ceiling. Start simple — go as far as you want.
The challenge: Write a function that lands the lander automatically, using as little fuel as possible.
At any altitude, there’s a speed that would bring you to a safe stop at the surface. If you’re falling faster than that, burn harder. If you’re already slow enough, ease off.
The simplest effective controller: error = target_velocity - actual_velocity. Try: thrust = 50 + k * error for some constant k. Clamp the result to 0–100.
Try: target_vel = -alt / 20. At 2500ft that’s −125 ft/s. At 100ft it’s −5 ft/s. It naturally gets gentler as you approach the surface.
Your autopilot must pass these three tests:
🌕

Module complete.

You didn’t learn Python by memorising syntax. You learned it by needing it. That’s how it works.

Constants (G, MAX_ACCEL)Variables, namingConfig files, environment vars
Lander stateDictionaryDatabase record / JSON
Physics tickFunctionData processing step
Game loopwhile loopWeb server request loop
Player inputinput()API request / form submit
HUD displayf-stringsReport generation
Smoke testsassert, test functionsUnit tests in production
AutopilotFunction as argumentCallbacks, strategy pattern
Open the full notebook in Colab →