SCHRITT 1 VON 6
SCHRITT 01 · MISSION BRIEFING

Die LunarLander API im Überblick

Du steuerst kein Programm mehr Zeile für Zeile — du nutzt eine fertige Spielengine. Die API übernimmt Physik, Zeichnen und Kollision. Du lieferst nur noch die Logik.

Was ist eine API?

API = Application Programming Interface. Du rufst Funktionen auf, ohne zu wissen, wie sie intern funktionieren — wie ein Fernseher mit Fernbedienung. Beispielsweise liefert getFuel() den aktuellen Treibstoffstand, und setGravity(0.05) macht die Schwerkraft sanfter — ohne dass du weißt, wie die Physik dahinter berechnet wird.

Ablauf in jedem Frame — klick auf eine Box für mehr Info

Das Spiel läuft in einer Schleife. Pro Frame passieren diese Dinge in dieser Reihenfolge. Klicke auf eine Box, um eine Erklärung mit Beispiel zu sehen.

init(keyHandler) WHILE IS_RUNNING updateFunction() falls gesetzt tick() Physik · Position · Grenzen render() Terrain · Lander · HUD · repaint checkLanding() gelandet / Absturz? gameEndFunction() falls gesetzt delay(delayMs) Spielende Update-Hook setUpdateFunction() Custom Drawer setCustomDrawer() GameEnd-Hook setGameEndFunction() Tastaturevent keyHandler(code) ESC = Abbruch
Blau = Deine Hooks

Diese Funktionen kannst du anmelden. Du schreibst sie, die Engine ruft sie auf — zur richtigen Zeit, automatisch.

Gestrichelt = intern

Physik und Rendering laufen automatisch. tick() und render() rufst du nie selbst auf.

SCHRITT 02 · ERSTER KONTAKT

init() und start()

Jedes LunarLander-Programm beginnt gleich: API importieren, Tastatur-Handler definieren, init() + start() aufrufen.

Die zwei Pflicht-Aufrufe
FunktionWas sie tut
init(keyHandler)Öffnet das Fenster, verbindet deine Tastatur-Funktion
start()Startet die Spielschleife — kehrt erst nach Spielende zurück
MINIMAL-BEISPIEL
from lunarlander import *

def handleKey(keyCode):
    if keyCode == 38:      # Pfeil hoch → Schub
        thrust()
    elif keyCode == 37:    # Pfeil links
        moveLeft()
    elif keyCode == 39:    # Pfeil rechts
        moveRight()

init(handleKey)            # Fenster öffnen, Tastatur verbinden
start()                    # Spielschleife starten

# Hier läuft Code erst NACH dem Spielende:
if hasLanded():
    print("Gelandet!")
elif hasCrashed():
    print("Absturz!")
⚠ DATEINAME BEACHTEN

Deine eigene Datei darf nicht lunarlander.py heißen — das ist der Name der API-Datei. Python würde sonst beim Import die falsche Datei laden und init() schlägt mit einem Fehler fehl. Wähle einen eigenen Namen, z.B. mein_lander.py.

Tastatur-Keycodes — Beispiele (nicht vollständig)

Jede Taste hat einen eindeutigen Code. Die Pfeiltasten sind nur ein Beispiel.

TasteKeycodeMögliche Aktion
Pfeil ←37moveLeft()
Pfeil ↑38thrust()
Pfeil →39moveRight()
Leertaste32thrust()
ESC27Spiel beenden (eingebaut)
W, A, S, D87, 65, 83, 68Typische WASD-Steuerung

TigerJython nutzt Javas getKeyCode() — das liefert immer den Code der physischen Taste, unabhängig von Shift. Buchstabentasten haben deshalb immer die Großbuchstaben-Codes (A–Z = 65–90), auch wenn man ohne Shift tippt.

  • Der Keycode für die A-Taste ist 65, für Z ist er 90. Erkennst du ein Muster?
  • TigerJython liefert immer den Code der physischen Taste — egal ob Shift gedrückt ist. Welche Codes braucht man also für eine WASD-Steuerung?
  • Wie, wenn die Leertaste Schub geben soll statt Pfeil hoch?

Starte das Minimal-Beispiel in TigerJython. Klicke ins Spielfenster für den Tastaturfokus.

  • Wie hoch ist deine V-Speed beim ersten Aufprall?
  • Was passiert, wenn der Treibstoff auf 0 sinkt?
  • Erweitere: Gib nach dem Spielende zusätzlich aus, wie viel Treibstoff verbraucht wurde (Startwert: 200).
SCHRITT 03 · ZUSTANDSABFRAGE & HUD

Getter-Funktionen und HUD

Die API liefert jederzeit den aktuellen Zustand des Landers. Diese Werte kannst du nach dem Spielende auswerten — oder im HUD anzeigen lassen.

Steuerung
FunktionWirkung
thrust()Schub nach oben
moveLeft()Schub nach links
moveRight()Schub nach rechts
Zustandsabfrage
FunktionRückgabe
getHeight()Höhe über Terrain
getVSpeed()Vertikalgeschw.
getHSpeed()Horizontalgeschw.
getFuel()Treibstoff (0–200)
getX()X-Position
isOverLandingZone()True / False
hasLanded()True nach Landung
hasCrashed()True nach Absturz
HUD konfigurieren
FunktionParameter / Beispiel
setHudItems(itemListe)itemListe = ["fuel", "vSpeed", "height"]
setHudFontSize(groesse)setHudFontSize(18)
setHudPosition(abstandLinks, abstandOben)setHudPosition(10, 30)

Erlaubte Werte für itemListe:

fuel vSpeed hSpeed height gravity x collisionX collisionY frameTime rotation scale paused
HUD + AUSWERTUNG NACH SPIELENDE
from lunarlander import *

itemListe = ["fuel", "vSpeed", "height"]  # was im HUD erscheint
setHudItems(itemListe)
setHudFontSize(16)                         # Schriftgröße in Pixeln
setHudPosition(10, 30)                     # Abstand vom linken / oberen Rand

def handleKey(keyCode):
    if keyCode == 37:
        moveLeft()
    elif keyCode == 38:
        thrust()
    elif keyCode == 39:
        moveRight()

init(handleKey)
start()

# Ab hier: Spiel ist beendet
if hasLanded():
    print("Gelandet! Treibstoff übrig:", getFuel())
elif hasCrashed():
    print("Absturz! V-Speed:", round(getVSpeed(), 2))

Experimentiere mit dem Programm:

  • Ergänze itemListe um "hSpeed" und "x".
  • Füge vor init() ein: setGravity(0.05) — was ändert sich?
  • Berechne und gib nach dem Spiel aus: Verbrauch = 200 − getFuel().
SCHRITT 04 · EIGENER LANDER

setCustomDrawer() — Hook 1

Dein erster Hook: statt des gelben Standard-Quadrats zeichnest du deinen Lander selbst. Die Engine ruft deine Funktion automatisch jeden Frame auf. Alle Koordinaten sind relativ zur Lander-Mitte (0, 0).

So funktioniert der Hook

Du schreibst eine Funktion, die den Lander zeichnet. Du meldest sie einmalig vor init() an. Die Engine kümmert sich darum, dass sie jeden Frame zur richtigen Zeit aufgerufen wird.

Verfügbare Zeichenbefehle:

forward(n) right(w) / left(w) setPos(x, y) moveTo(x, y) penUp() / penDown() setPenColor(f) setPenWidth(b) setHeading(w) dot(g)

Wichtig: setPos() teleportiert immer ohne Linie — auch bei penDown(). Für Linien: penDown() + moveTo().

Absolut vs. relativ: Es gibt zwei Wege zu zeichnen. Empfohlen: absolut — du gibst Koordinaten direkt an (setPos(x,y) + moveTo(x,y)). Du siehst sofort welcher Punkt wo landet. Alternativ: relativ mit forward(), left(), right() — funktioniert, aber Achtung: die Winkelkonvention ist 0° = oben, 90° = rechts (Uhrzeigersinn) — nicht wie im Matheunterricht. Das führt leicht zu gespiegelten Formen.

EIGENER LANDER MIT RUMPF UND BEINEN
from lunarlander import *

def meinLander():
    # Rumpf: Rechteck — absolut mit moveTo(), (0,0) = Lander-Mitte
    # +Y = oben, –Y = unten, +X = rechts, –X = links
    setPenColor("cyan")
    setPenWidth(2)
    penUp()
    setPos(-12, 14)        # oben-links
    penDown()
    moveTo(12, 14)         # → oben-rechts
    moveTo(12, -14)        # → unten-rechts
    moveTo(-12, -14)       # → unten-links
    moveTo(-12, 14)        # → schließt Rechteck
    penUp()
    # Linkes Landegestell: schräg nach links-unten
    setPenColor("gray")
    setPos(-12, -14)
    penDown()
    moveTo(-22, -26)
    penUp()
    # Rechtes Landegestell: schräg nach rechts-unten
    setPos(12, -14)
    penDown()
    moveTo(22, -26)
    penUp()
    # Cockpit-Fenster
    setPenColor("white")
    setPos(0, 2)
    dot(8)

setCustomDrawer(meinLander)   # Hook anmelden — vor init()

def handleKey(keyCode):
    if keyCode == 37: moveLeft()
    elif keyCode == 38: thrust()
    elif keyCode == 39: moveRight()

setHudItems(["fuel", "vSpeed", "height"])
init(handleKey)
start()

Entwirf deinen eigenen Lander. Mindestanforderungen:

  • Mindestens zwei Farben
  • Eine erkennbare Landebasis (Beine, Stützen oder Kufen)
  • Ein sichtbares Triebwerk oder eine Düse

Tipp: Teste deinen Lander ohne den Rest des Programms — zeichne ihn zuerst einfach auf Papier mit dem Koordinatensystem (0,0 = Mitte).

SCHRITT 05 · UPDATE-FUNKTION

setUpdateFunction() — Hook 2

Dein zweiter Hook: eine Funktion, die die Engine jeden Frame aufruft — bevor die Physik berechnet wird. Hier baust du Spiellogik, Warnungen und Zähler ein.

Hook 1 vs. Hook 2 — wann was?
keyHandler (Hook 1)updateFunction (Hook 2)
WannBei TastendruckJeden Frame (~70 ms)
WofürSteuerung des LandersLogik, Warnungen, Zähler
Getter nutzbarJaJa
TREIBSTOFF-WARNUNG + FRAME-ZÄHLER
from lunarlander import *

frameZaehler = 0           # zählt die Frames seit Spielstart
warnungAusgegeben = False  # Merker: Warnung nur einmal ausgeben

def update():
    global frameZaehler, warnungAusgegeben   # Variablen von außen verändern

    frameZaehler = frameZaehler + 1

    # alle 30 Frames einen Status ausgeben
    if frameZaehler % 30 == 0:
        print("Höhe:", round(getHeight(), 1), "| Treibstoff:", getFuel())

    # einmalige Warnung bei niedrigem Treibstoff
    if getFuel() < 40 and not warnungAusgegeben:
        print("*** TREIBSTOFF KRITISCH ***")
        warnungAusgegeben = True

setUpdateFunction(update)   # Hook anmelden — vor init()

def handleKey(keyCode):
    if keyCode == 37: moveLeft()
    elif keyCode == 38: thrust()
    elif keyCode == 39: moveRight()

setHudItems(["fuel", "vSpeed", "hSpeed", "height"])
init(handleKey)
start()

Regel: Variablen, die du in einer Funktion nur liest, brauchen kein global. Variablen, die du veränderst, müssen mit global deklariert werden — sonst erstellt Python stillschweigend eine lokale Kopie und der ursprüngliche Wert bleibt unverändert.

Eigene Hilfsfunktionen und global

Du kannst eigene Funktionen schreiben und diese aus Hooks heraus aufrufen. Auch dort gilt die global-Regel:

MUSTER: HILFSFUNKTION + HOOK
punkte = 0
warnungGezeigt = False

def zeigeWarnung():             # eigene Hilfsfunktion
    global warnungGezeigt       # wird verändert → global nötig
    if not warnungGezeigt:
        print("Treibstoff niedrig!")
        warnungGezeigt = True

def update():
    global punkte               # wird verändert → global nötig
    punkte = punkte + 1
    if getFuel() < 30:
        zeigeWarnung()          # Hilfsfunktion aus dem Hook aufrufen

setUpdateFunction(update)

Lesen: kein global nötig  ·  Verändern: global erforderlich  ·  Hilfsfunktion aufrufen: einfach den Namen schreiben

Neu in v0.9.9 — Pause

Mit togglePause() kannst du das Spiel in der Update-Funktion oder im keyHandler pausieren:

FunktionWirkung
pauseGame()Spielschleife anhalten
resumeGame()Fortsetzen
togglePause()Pause ein/aus — ideal für eine Taste
isPaused()True wenn pausiert

Ergänze die Update-Funktion:

  • Gib eine Meldung aus, wenn der Lander über der Landezone ist (isOverLandingZone()) — aber nur alle 20 Frames, nicht jeden Frame.
  • Warne, wenn Höhe unter 60 und Vertikalgeschwindigkeit über 4.0 liegt.
  • Bonus: Baue eine Pause-Taste (z.B. P = Keycode 80) in deinen handleKey ein.
SCHRITT 06 · ALLES ZUSAMMEN

Drei Hooks — ein Programm

Du kennst jetzt alle Bausteine. Statt einem fertigen Programm zum Kopieren bekommst du drei kleine Startpunkte — Stubs mit Lücken, die du selbst vervollständigst.

STUB A · NUR HOOK 1: keyHandler Steuerung + Auswertung nach Spielende
from lunarlander import *

# ── Dein keyHandler ────────────────────────────────────────
def handleKey(keyCode):
    # TODO: Pfeil hoch → thrust(), links → moveLeft(), rechts → moveRight()
    pass

# ── Einstellungen ──────────────────────────────────────────
setHudItems(["fuel", "vSpeed", "height"])
# TODO: Probiere setGravity(...) und setFuel(...)

init(handleKey)
start()

# ── Auswertung ─────────────────────────────────────────────
# TODO: hasLanded() / hasCrashed() abfragen und Ergebnis ausgeben
STUB B · HOOK 1 + HOOK 2: eigener Lander Custom Drawer hinzufügen
from lunarlander import *

# ── Hook 2: Dein Lander ────────────────────────────────────
def meinLander():
    # TODO: mindestens Rumpf + 1 Farbe + 1 weiteres Element
    pass

setCustomDrawer(meinLander)   # Hook anmelden — vor init()

# ── Hook 1: Steuerung ──────────────────────────────────────
def handleKey(keyCode):
    if keyCode == 37: moveLeft()
    elif keyCode == 38: thrust()
    elif keyCode == 39: moveRight()

setHudItems(["fuel", "vSpeed", "height"])
init(handleKey)
start()

if hasLanded():
    print("Gelandet! Treibstoff:", getFuel())
elif hasCrashed():
    print("Absturz! V-Speed:", round(getVSpeed(), 2))
STUB C · ALLE DREI HOOKS + Update-Funktion mit eigener Logik
from lunarlander import *

# ── Hook 2: Lander ────────────────────────────────────────
def meinLander():
    # TODO: dein Design von Stub B
    pass

setCustomDrawer(meinLander)

# ── Hook 3: Update-Logik ──────────────────────────────────
zaehler = 0

def update():
    global zaehler
    zaehler = zaehler + 1
    # TODO: Warnmeldung bei niedrigem Treibstoff (einmalig)
    # TODO: Landehinweis wenn über Landezone und Höhe < 80

setUpdateFunction(update)     # Hook anmelden — vor init()

# ── Hook 1: Steuerung ─────────────────────────────────────
def handleKey(keyCode):
    if keyCode == 37: moveLeft()
    elif keyCode == 38: thrust()
    elif keyCode == 39: moveRight()
    # TODO (Bonus): P-Taste = togglePause()

setHudItems(["fuel", "vSpeed", "height"])
init(handleKey)
start()

# ── Ergebnis ──────────────────────────────────────────────
# TODO: Auswertung ausgeben

AUSBLICK — DAS NÄCHSTE PROJEKT

✦ PFLICHT
Mein LunarLander
  • Eigenes Lander-Design (alle 3 Elemente)
  • Eigenes Terrain + Landezone
  • Mindestens 2 Warnmeldungen im Update
  • Auswertung nach Spielende
★ KÜR — wähle selbst
Eigene Idee
  • Punktesystem mit Treibstoffbonus
  • Mehrere Landeplätze setLandingZones()
  • Zeitlimit via setGameEndFunction()
  • Animation: setRotation() im Update
  • Schub-Flamme im Custom Drawer
  • Eigene Idee — sprich mit mir
Weitere API-Funktionen für das Projekt
FunktionWirkung
setTerrain([...])Eigenes Geländeprofil (mind. 8 Werte)
setGravity(wert)Schwerkraft ändern (Standard: 0.15)
setFuel(menge)Startmenge Treibstoff setzen
setLandingZone(start, ende)Landeplatz-Indizes setzen
setLandingZones([[s,e],...])Mehrere Landeplätze setzen
setScale(faktor)Lander-Größe skalieren (Standard: 1.0)
setRotation(winkel)Lander rotieren in Grad (Standard: 0)
setGameEndFunction(fn)Eigene Endbedingung (True = Spiel endet)
getFrameTime()Dauer des letzten Frames in ms
restart()Spiel neu starten
setWindowSize(b, h)Fenstergröße ändern — Standard: 600 × 400

Alle Funktionen und ihre genaue Beschreibung findest du in der API-Dokumentation.