Kapitel 8 · Evolution im Computer

Mondlandung: ein Lunar-Lander-Spiel in Python

Eine Mondfähre fällt auf die Mondoberfläche, und du musst sie mit Bremsrakete und Steuerdüsen sanft aufsetzen. Das Spiel aus dem Evolutionskapitel des Buchs zeigt, wie wenig Physik für ein spannendes Spiel reicht: Schwerkraft, Schub und zwei Rechtecke.

Aus dem Buch · Kapitel 8 Fortgeschritten SpielePhysik Python 3 · läuft im Browser
Bereit · Python startet beim ersten Klick
# Mondlandung – Kapitel 8 »Evolution im Computer«
# aus »Coding for Fun mit Python«, portiert auf Python 3 und c4f
from random import randint
from c4f import Screen, sleep, KEYDOWN, KEYUP
from c4f import K_LEFT, K_RIGHT, K_UP, K_DOWN, K_ESCAPE

fieldWidth  = 800
fieldHeight = 600
fps         = 80

maxVelocity     = 12.0   # schneller darf die Fähre nicht aufsetzen
maxSideVelocity = 2.0    # und seitlich nicht schneller driften

command      = (0, 0)
velocity     = 0
sideVelocity = randint(5, 20)
height       = 400
xPos         = 200


def toScreen(x, y):
    # OpenGL (glOrtho) zählte y von unten nach oben, die Leinwand zählt
    # von oben nach unten – diese Funktion rechnet die Punkte um
    return x + 10, fieldHeight - y


def drawRect(dimension, color=(230, 40, 40)):
    x, y, width, height = dimension
    left, top = toScreen(x, y + height)
    screen.rect(left, top, width, height, color)


def calculateDelta(acceleration, velocity, position):
    t = 1.0 / fps
    h = 0.5 * acceleration * t**2 + velocity * t + position
    v = acceleration + velocity

    return v, h


def collisionDetection(rocket, ground):
    rx1, ry1, width, height = rocket
    rx2, ry2                = rx1 + width, ry1 + height
    gx1, gy1, width, height = ground
    gx2, gy2                = gx1 + width, gy1 + height

    if (rx1 >= gx1 and rx1 <= gx2) or (rx2 >= gx1 and rx2 <= gx2):
        # horizontal betrachtet befinden wir uns in Bodennähe
        if (ry1 >= gy1 and ry1 <= gy2) or (ry2 >= gy1 and ry2 <= gy2):
            # die Körper überdecken sich auch vertikal
            return True
    return False


def checkRules(velocity, sideVelocity):
    velocity = abs(velocity)
    sideVelocity = abs(sideVelocity)
    print(f"Aufsetzen: {velocity:.1f} nach unten, {sideVelocity:.1f} zur Seite")
    if velocity < maxVelocity and sideVelocity <= maxSideVelocity:
        return True
    else:
        return False


def drawElement(element, color):
    for part in element:
        x, y = part
        x = x * 10.0
        y = y * 10.0
        drawRect((x, y, 9.0, 9.0), color)


async def showASCIIArt(fileName, delay, color):
    datei = open(fileName, "r")
    asciiArt = []
    y = 30
    for zeile in datei:
        x = -1
        y -= 1
        for zeichen in zeile:
            if zeichen != "\n":
                x += 1
                if zeichen == "X":
                    asciiArt.append((x, y))
    datei.close()

    # die Schrift mittig setzen (im Buch stand sie unten links)
    xs = [x for x, y in asciiArt]
    ys = [y for x, y in asciiArt]
    dx = (fieldWidth / 10 - max(xs) - min(xs) - 1) / 2
    dy = (fieldHeight / 10 - max(ys) - min(ys) - 1) / 2
    asciiArt = [(x + dx, y + dy) for x, y in asciiArt]

    screen.clear()
    drawElement(asciiArt, color)
    await sleep(delay / 1000)


async def showGameOver():
    print("Bruchlandung!")
    await showASCIIArt("GameOver.txt", 3000, (230, 40, 40))
    restartGame()


async def showYouWon():
    print("Sanft gelandet – gut gemacht!")
    await showASCIIArt("YouWon.txt", 5000, (90, 220, 120))
    restartGame()


def restartGame():
    # das Spiel neu initialisieren (Neustart)
    global command, velocity, sideVelocity, height, xPos
    command      = (0, 0)
    velocity     = 0
    sideVelocity = randint(5, 20)
    height       = 400
    xPos         = 200
    screen.events()  # liegengebliebene Tastendrücke verwerfen


def handleEvent(event):
    if event.type == KEYDOWN and event.key == K_ESCAPE:
        return False

    global command
    if event.type == KEYDOWN:
        if event.key == K_RIGHT:
            command = (+1, 0)
        if event.key == K_LEFT:
            command = (-1, 0)
        if event.key == K_UP:
            command = (0, +1)  # im Buch ohne Funktion, hier auch Bremsrakete
        if event.key == K_DOWN:
            command = (0, +1)

    if event.type == KEYUP:
        command = (0, 0)

    return True


def drawFlames(rocket, gravity, sideAcceleration):
    # kleine Flammen zeigen, welche Düse gerade feuert
    x, y, width, height = rocket
    flame = (255, 170, 40)
    if gravity > 0:
        tip = toScreen(x + width / 2, y - randint(12, 24))
        screen.polygon([toScreen(x + 4, y), toScreen(x + width - 4, y), tip], flame)
    if sideAcceleration > 0:
        tip = toScreen(x - randint(8, 14), y + 15)
        screen.polygon([toScreen(x, y + 21), toScreen(x, y + 9), tip], flame)
    if sideAcceleration < 0:
        tip = toScreen(x + width + randint(8, 14), y + 15)
        screen.polygon([toScreen(x + width, y + 21),
                        toScreen(x + width, y + 9), tip], flame)


def drawHud():
    # Anzeige: grün heißt »langsam genug zum Landen«
    ok, bad = (120, 220, 140), (255, 110, 90)
    fall = abs(velocity)
    side = abs(sideVelocity)
    screen.text(f"Fallen {fall:6.1f}  (unter {maxVelocity:.0f})", (20, 18),
                ok if fall < maxVelocity else bad, size=17, font="mono")
    screen.text(f"Seite  {side:6.1f}  (bis {maxSideVelocity:.0f})", (20, 42),
                ok if side <= maxSideVelocity else bad, size=17, font="mono")
    screen.text(f"Höhe   {height - 80:6.0f}", (20, 66), (190, 195, 210),
                size=17, font="mono")


def drawStars(stars):
    for x, y in stars:
        screen.rect(x, y, 2, 2, (80, 85, 105))


async def showIntro(stars, ground):
    screen.clear()
    drawStars(stars)
    drawRect(ground)
    drawRect((xPos, height, 20, 30))
    screen.text("Lande sanft auf der roten Fläche!", (fieldWidth / 2 + 5, 250),
                "white", size=26, align="center", bold=True)
    screen.text("▼ Bremsrakete   ◀ ▶ Steuerdüsen", (fieldWidth / 2 + 5, 290),
                (190, 195, 210), size=18, align="center")
    await sleep(2.5)
    screen.events()


async def main():
    global screen, velocity, sideVelocity, height, xPos
    screen = Screen(fieldWidth + 10, fieldHeight + 10, background=(0, 0, 0),
                    title="Lunar Lander")
    screen.controls(["LEFT", ("DOWN", "▼ Bremsrakete"), "RIGHT"])
    stars = [(randint(0, fieldWidth), randint(0, fieldHeight - 120))
             for i in range(70)]
    ground = 100, 50, 600, 30

    await showIntro(stars, ground)

    while True:
        gravity          = -1.635
        sideAcceleration = +0.0
        for event in screen.events():
            if not handleEvent(event):
                return

        if command != (0, 0):
            if command == (0, +1):
                gravity = 2.0
            if command == (1, 0):
                sideAcceleration = +0.5
            if command == (-1, 0):
                sideAcceleration = -0.5

        velocity, height   = calculateDelta(gravity, velocity, height)
        sideVelocity, xPos = calculateDelta(sideAcceleration, sideVelocity, xPos)

        screen.clear()
        drawStars(stars)
        rocket = xPos, height, 20, 30
        drawFlames(rocket, gravity, sideAcceleration)
        drawRect(rocket)
        drawRect(ground)
        drawHud()

        if collisionDetection(rocket, ground):
            if checkRules(velocity, sideVelocity):
                await showYouWon()
            else:
                await showGameOver()
        elif height < 0:
            print("Die Fähre hat die Landefläche verfehlt.")
            await showGameOver()

        await screen.frame(fps)


await main()
Mondfähre als rotes Rechteck über einer roten Landefläche im Sternenhimmel, oben links Anzeigen für Geschwindigkeit und Höhe

Erst ins Bild klicken, dann steuern die Pfeiltasten: ▼ (oder ▲) zündet die Bremsrakete, ◀ ▶ die Steuerdüsen. Auf dem Handy nimmst du die Tasten unter dem Bild.

Konsole

    

Was du hier lernst

  • Physik-Simulation
  • Geschwindigkeit und Beschleunigung
  • Kollisionserkennung mit Rechtecken
  • Tastatur-Ereignisse
  • Koordinatensysteme umrechnen
  • Dateien lesen

So funktioniert die Mondlandung

Im Buch ist dieses Spiel die erste Hälfte des Kapitels »Evolution im Computer«: Erst landest du selbst, später übernimmt ein genetischer Algorithmus das Steuer. Die Spielwelt ist bewusst schlicht – eine Fähre von 20 × 30 Pixeln und eine Landefläche von 600 × 30 Pixeln. Das Buch nennt das augenzwinkernd Suprematismus: »Zwei rote Rechtecke auf schwarzem Grund«. Spannend wird es durch die Physik.

1. Ein bisschen Physik

Der Mond hat keine Luft, also gibt es keine Reibung – nur die Schwerkraft zieht. Die Fähre merkt sich ihre Höhe und ihre Geschwindigkeit, und calculateDelta rechnet beides für den nächsten Moment aus:

def calculateDelta(acceleration, velocity, position):
    t = 1.0 / fps
    h = 0.5 * acceleration * t**2 + velocity * t + position
    v = acceleration + velocity
    return v, h

t ist die Zeit zwischen zwei Bildern, bei 80 Bildern pro Sekunde also 1/80 Sekunde. Die Beschleunigung verändert die Geschwindigkeit, die Geschwindigkeit verändert die Höhe. Der Wert −1,635 ist die Fallbeschleunigung auf dem Mond, etwa ein Sechstel der Erdanziehung.

Schub als umgedrehte Schwerkraft

Das Buch vereinfacht die Bremsrakete geschickt: Statt Kräfte zu addieren, wird die Schwerkraft einfach ersetzt. Solange die Rakete brennt, gilt gravity = 2.0 statt −1,635 – die Fähre wird nach oben beschleunigt. Außerdem wächst die Geschwindigkeit pro Bild um die volle Beschleunigung, nicht um Beschleunigung mal Zeit. Die Zahlen sind deshalb Spiel-Einheiten, keine Meter pro Sekunde. Für ein Spiel zählt nur, dass es sich richtig anfühlt.

2. Die Steuerung: ein Befehl zur Zeit

handleEvent übersetzt Tastendrücke in einen Befehl – ein Tupel, das eine Richtung beschreibt. Lässt du eine Taste los, gilt wieder (0, 0):

if event.type == KEYDOWN:
    if event.key == K_RIGHT:
        command = (+1, 0)
    if event.key == K_DOWN:
        command = (0, +1)
if event.type == KEYUP:
    command = (0, 0)

In der Hauptschleife wird aus dem Befehl eine Beschleunigung: (0, +1) zündet die Bremsrakete, (+1, 0) und (-1, 0) die Steuerdüsen. Für die Seite benutzt das Programm dieselbe Funktion calculateDelta noch einmal, nur mit eigenen Variablen. Weil die Fähre mit einer zufälligen Seitwärtsdrift startet (randint(5, 20)), musst du jedes Mal etwas anders gegensteuern.

3. Kollision zweier Rechtecke

Wann berührt die Fähre den Boden? collisionDetection vergleicht die Kanten der beiden Rechtecke – erst waagerecht, dann senkrecht:

if (rx1 >= gx1 and rx1 <= gx2) or (rx2 >= gx1 and rx2 <= gx2):
    if (ry1 >= gy1 and ry1 <= gy2) or (ry2 >= gy1 and ry2 <= gy2):
        return True

Liegt eine der beiden senkrechten Kanten der Fähre über der Landefläche und überlappen sich auch die Höhen, haben sich die Körper berührt. Für zwei Rechtecke genügt das. Sehr schnelle Objekte könnten zwischen zwei Bildern durch ein dünnes Hindernis hindurchspringen, doch dafür ist die Fähre zu langsam.

4. Die Landeregeln

Ob die Berührung eine Landung oder ein Absturz ist, entscheidet checkRules. Die Grenzwerte stehen als benannte Konstanten ganz oben im Programm – das empfiehlt das Buch selbst, denn eine nackte 12 im Code verrät nicht, was sie bedeutet:

if velocity < maxVelocity and sideVelocity <= maxSideVelocity:
    return True

Die Anzeige oben links benutzt dieselben Konstanten und färbt die Werte grün, sobald sie zum Landen passen.

5. Oben ist, wo y groß ist

Im Buch zeichnet OpenGL, und dort zeigt die y-Achse nach oben – wie in der Schule. Die Leinwand im Browser zählt dagegen von oben nach unten. toScreen rechnet um, und drawRect nimmt als obere Ecke eines Rechtecks deshalb y + height:

def toScreen(x, y):
    return x + 10, fieldHeight - y

So kann die gesamte Physik unverändert bleiben: Eine größere Höhe bedeutet weiter oben, genau wie im Original.

6. Gewonnen oder verloren

Nach der Landung zeigt showASCIIArt den Text aus »YouWon.txt« oder »GameOver.txt«. Jedes »X« in der Textdatei wird zu einem Klötzchen von 10 Pixeln. Weil y nach oben zählt, beginnt die Zeilennummer bei 30 und wird pro Zeile kleiner. Danach startet restartGame eine neue Runde mit neuer Seitwärtsdrift.

So gelingt die Landung

Lass die Fähre zuerst fallen und korrigiere nur die Seitwärtsdrift, bis »Seite« grün ist. Kurz über dem Boden bremst du dann kräftig. Wer zu früh bremst, schwebt ewig – bei den erlaubten Geschwindigkeiten ist der letzte Meter sehr langsam.

Probier mal

Jede Variante ändert den Code oben. Ein Klick auf »Ausprobieren« übernimmt die Änderung in den Editor und startet das Programm. »Zurücksetzen« holt das Original zurück.

  1. 01Anfängermodus

    Die Grenzwerte für eine gelungene Landung stehen als Konstanten ganz oben. Mit großzügigeren Werten wird aus der Zitterpartie eine entspannte Landung. Die Anzeige passt sich automatisch an, weil sie dieselben Konstanten benutzt.

    - maxVelocity     = 12.0
    + maxVelocity     = 25.0
    - maxSideVelocity = 2.0
    + maxSideVelocity = 5.0
  2. 02Stärkere Bremsrakete

    Im Buch ersetzt der Schub einfach die Schwerkraft: Statt −1,635 wirken beim Bremsen +2,0. Verdoppelst du den Wert, bremst die Fähre schneller – aber jede Korrektur fällt auch heftiger aus. Zu stark ist genauso schwer zu steuern wie zu schwach.

    -                 gravity = 2.0
    +                 gravity = 4.0
  3. 03Landung auf der Erde

    Auf dem Mond ist die Schwerkraft etwa sechsmal schwächer als auf der Erde – daher der Wert 1,635. Mit 9,81 fällt die Fähre wie ein Stein, und die Bremsrakete muss entsprechend stärker sein. Viel Glück!

    -         gravity          = -1.635
    +         gravity          = -9.81
    -                 gravity = 2.0
    +                 gravity = 12.0
  4. 04Mehrere Düsen gleichzeitig

    Im Buch merkt sich handleEvent nur den letzten Befehl: Wer bremst, kann nicht gleichzeitig zur Seite steuern. Fragst du stattdessen mit screen.pressed() ab, welche Tasten gerade gedrückt sind, arbeiten alle Düsen zusammen – so steuert es sich viel natürlicher.

    -         if command != (0, 0):
    -             if command == (0, +1):
    -                 gravity = 2.0
    -             if command == (1, 0):
    -                 sideAcceleration = +0.5
    -             if command == (-1, 0):
    -                 sideAcceleration = -0.5
    +         # Tastenzustand statt »letzter Befehl«: alle Düsen gleichzeitig
    +         if screen.pressed(K_DOWN) or screen.pressed(K_UP):
    +             gravity = 2.0
    +         if screen.pressed(K_RIGHT):
    +             sideAcceleration = +0.5
    +         if screen.pressed(K_LEFT):
    +             sideAcceleration = -0.5

Vom Buch in den Browser

Im Buch zeichnet das Spiel mit OpenGL in ein pygame-Fenster, geschrieben für Python 2.6. Im Browser zeichnet es mit c4f, der kleinen Zeichen-API dieser Website. Physik, Kollision, Regeln und Ablauf sind die aus dem Buch.

Im Buch (pygame + OpenGL) Hier (c4f)
glOrtho(-10, 800, -10, 600, …), y zeigt nach oben Screen(810, 610) und toScreen(x, y)
glBegin(GL_QUADS) mit vier glVertex3f screen.rect(x, y, breite, höhe, farbe)
pygame.event.poll() (ein Ereignis pro Bild) screen.events() (alle Ereignisse seit dem letzten Bild)
pygame.display.flip() + pygame.time.delay(1000 / fps) await screen.frame(fps)
pygame.time.delay(3000) await sleep(3)
pygame.event.clear() screen.events() – die Ereignisse werden abgeholt und verworfen

Weil der Browser zwischen zwei Bildern kurz die Kontrolle zurückbekommen muss, sind main() und die Funktionen mit Wartezeiten jetzt async, und await main() startet das Spiel.

Die gespiegelte y-Achse. glOrtho legte den Nullpunkt nach unten links, die Leinwand hat ihn oben links. Statt die ganze Physik umzuschreiben, rechnet nur das Zeichnen um: toScreen spiegelt die Höhe, und ein Rechteck beginnt oben bei y + height.

Steuerung. Hier beschleunigt ▶ die Fähre nach rechts. In der Originaldatei zum Buch waren die Vorzeichen andersherum: Dort feuerte ▶ die rechte Düse, und die schob die Fähre nach links – physikalisch korrekt, aber beim Spielen verwirrend. Die Taste ▲ war im Buch ohne Funktion und zündet jetzt ebenfalls die Bremsrakete, weil viele Spieler sie zuerst probieren. Für Handys gibt es Touch-Tasten unter dem Bild.

Neu hinzugekommen sind die Anzeige für Geschwindigkeit und Höhe, kleine Flammen an den aktiven Düsen, ein Sternenhimmel, ein kurzer Starthinweis und ein Ende der Runde, wenn die Fähre an der Landefläche vorbeifällt – im Buch fiel sie dann endlos weiter. Die ASCII-Art erscheint mittig statt unten links, und die Landeregeln benutzen benannte Konstanten.

Aufgeräumt: def resize((width, height)) – das Auspacken von Tupeln in der Parameterliste – gibt es in Python 3 nicht mehr; die Funktion entfällt ohnehin. Die ungenutzten Variablen gameCycle und direction stammen noch aus dem Snake-Kapitel und sind entfernt. print ist eine Funktion.

Zum Vergleich kannst du unten auch die erste Fassung aufklappen: LunarLander_v1.py kennt nur den freien Fall und die Bremsrakete.

Original aus dem Buch ansehen LunarLander_v2.py · Python 2
'''
Created on 25.12.2009

@author: Lars Heppert
'''


from OpenGL.GL import *
from OpenGL.GLU import *
from random import random, randint
import pygame, math, datetime
from pygame.locals import *

fieldWidth  = 800
fieldHeight = 600
fps         = 80

gameCycle           = 0
direction           = (0, 0)
command             = (0, 0)
velocity            = 0
sideVelocity        = randint(5, 20)
height              = 400
xPos                = 200

def resize((width, height)):
    if height == 0:
        height = 1
    glViewport(0, 0, width, height)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    glOrtho(-10.0, fieldWidth, -10.0, fieldHeight, -6.0, 0.0)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

def init():
    glClearColor(0.0, 0.0, 0.0, 0.0)
                      
def clearScreen():
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glTranslatef(0.0, 0.0, 3.0)
                
def drawRect(dimension):
    x, y, width, height = dimension
    glColor3f(1.0, 0.0, 0.0)
    glBegin(GL_QUADS)
    glVertex3f(x, y, 0.0)
    glVertex3f(x + width, y, 0.0)
    glVertex3f(x + width, y + height, 0.0)
    glVertex3f(x, y + height, 0.0)
    glEnd()
                        
def calculateDelta(acceleration, velocity, position):
    t = 1.0/fps
    h = 0.5*acceleration*t**2 + velocity*t + position
    v = acceleration + velocity
    
    return v, h

def collisionDetection(rocket, ground):
    rx1, ry1, width, height = rocket
    rx2, ry2                = rx1 + width, ry1 + height
    gx1, gy1, width, height = ground
    gx2, gy2                = gx1 + width, gy1 + height
    
    if (rx1 >= gx1 and rx1 <= gx2) or (rx2 >= gx1 and rx2 <= gx2):
        # horizontal betrachtet befinden wir uns in Bodennaehe
        if (ry1 >= gy1 and ry1 <= gy2) or (ry2 >= gy1 and ry2 <= gy2):
            # die Koerper ueberdecken sich auch vertikal
            return True
    return False

def checkRules(velocity, sideVelocity):
    velocity = abs(velocity)
    sideVelocity = abs(sideVelocity)
    print velocity, sideVelocity
    if velocity < 12.0 and sideVelocity <= 2.0:
        return True
    else:
        return False
    
def drawElement(element, color):
    r, g, b = color
    glColor3f(r, g, b)
    glBegin(GL_QUADS)
    for part in element:
        x, y    = part
        x       = x * 10.0
        y       = y * 10.0
        glVertex3f(x, y, 0.0)
        glVertex3f(9.0 + x, y, 0.0)
        glVertex3f(9.0 + x, 9.0 + y, 0.0)
        glVertex3f(x, 9.0 + y, 0.0)
    glEnd()

def showASCIIArt(fileName, delay):
    datei = open(fileName, "r")
    asciiArt = []
    y = 30
    for zeile in datei:
        x = -1
        y -= 1
        for zeichen in zeile:
            if zeichen != '\n':
                x += 1
                if zeichen == 'X':
                    asciiArt.append((x, y))

    clearScreen()
    drawElement(asciiArt, (1.0, 0.0, 0.0))
    pygame.display.flip()
    pygame.time.delay(delay)
        
def showGameOver():
    showASCIIArt("GameOver.txt", 3000)
    restartGame()
        
def showYouWon():
    showASCIIArt("YouWon.txt", 5000)        
    restartGame()

def restartGame():
    # reinitialize the game (restart)
    global direction, command, gameCycle, velocity, sideVelocity, height, xPos
    gameCycle           = 0
    direction           = (0, 0)
    command             = (0, 0)
    velocity            = 0
    sideVelocity        = randint(5, 20)
    height              = 400
    xPos                = 200
    pygame.event.clear()

def handleEvent(event):            
    if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
        return False
    
    global command
    if event.type == KEYDOWN:
        if event.key == K_RIGHT:
            command = ( +1,  0)
        if event.key == K_LEFT:
            command = ( -1,  0)
        if event.key == K_UP:
            command = (  0, -1)
        if event.key == K_DOWN:
            command = (  0, +1)

    if event.type == KEYUP:
        command = (0, 0)
    
    return True
        
def main():
    pygame.init()
    video_flags = OPENGL | HWSURFACE | DOUBLEBUF
    
    screenSize = (fieldWidth, fieldHeight)    
    pygame.display.set_mode(screenSize, video_flags)
    resize(screenSize)
    
    init()
    ground = 100, 50, 600, 30
    global velocity, sideVelocity, height, xPos
    while True:
        gravity             = -1.635
        sideAcceleration    = +0.0
        if not handleEvent(pygame.event.poll()):
            break
        
        global command
        if command != (0, 0):
            if command == ( 0, +1):
                gravity = 2.0
            if command == ( 1,  0):
                sideAcceleration = -0.5
            if command == (-1,  0):
                sideAcceleration = +0.5

        velocity, height    = calculateDelta(gravity, velocity, height)
        sideVelocity, xPos  = calculateDelta(sideAcceleration, sideVelocity, xPos)

        clearScreen()
        rocket = xPos, height, 20, 30
        drawRect(rocket)
        drawRect(ground)
        if collisionDetection(rocket, ground):
            if checkRules(velocity, sideVelocity):
                showYouWon()
            else:
                showGameOver()
            
        pygame.display.flip()
        
        pygame.time.delay(int(1000.0/fps))

if __name__ == '__main__':
    main()
Original aus dem Buch ansehen LunarLander_v1.py · Python 2
'''
Created on 25.12.2009

@author: Lars Heppert
'''


from OpenGL.GL import *
from OpenGL.GLU import *
import pygame, math, datetime
from pygame.locals import *

fieldWidth  = 800
fieldHeight = 600
fps         = 80

gameCycle   = 0
direction   = (0, 0)
command     = (0, 0)

def resize((width, height)):
    if height == 0:
        height = 1
    glViewport(0, 0, width, height)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    glOrtho(-10.0, fieldWidth, -10.0, fieldHeight, -6.0, 0.0)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

def init():
    glClearColor(0.0, 0.0, 0.0, 0.0)
                      
def clearScreen():
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glTranslatef(0.0, 0.0, 3.0)
                
def drawRect(x, y, width, height):
    glColor3f(1.0, 0.0, 0.0)
    glBegin(GL_QUADS)
    glVertex3f(x, y, 0.0)
    glVertex3f(x + width, y, 0.0)
    glVertex3f(x + width, y + height, 0.0)
    glVertex3f(x, y + height, 0.0)
    glEnd()
                        
def drawRocket(x, y):
    width   = 20.0
    height  = 30.0
    drawRect(x, y, width, height)
    
def drawGround(x, y):
    width   = 600.0
    height  = 30.0
    drawRect(x, y, width, height)

def calculateDelta(gravity, velocity, height):
    print gravity
    t = 1.0/fps
    h = -0.5*gravity*t**2 + velocity*t + height
    v = -gravity + velocity
    
    return v, h

def handleEvent(event):            
    if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
        return False
    
    global command
    if event.type == KEYDOWN:
        if event.key == K_RIGHT:
            command = ( +1,  0)
        if event.key == K_LEFT:
            command = ( -1,  0)
        if event.key == K_UP:
            command = (  0, -1)
        if event.key == K_DOWN:
            command = (  0, +1)

    if event.type == KEYUP:
        command = (0, 0)
    
    return True
        
def main():
    pygame.init()
    video_flags = OPENGL | HWSURFACE | DOUBLEBUF
    
    screenSize = (fieldWidth, fieldHeight)    
    pygame.display.set_mode(screenSize, video_flags)
    resize(screenSize)
    
    init()
    velocity = 0
    height = 400
    while True:
        gravity = 1.635
        if not handleEvent(pygame.event.poll()):
            break
        
        global command
        if command != (0, 0):
            if command == (0, +1):
                gravity = -2.0

        velocity, height = calculateDelta(gravity, velocity, height)

        clearScreen()
        drawRocket(200, height)
        drawGround(100, 50)
            
        pygame.display.flip()
        
        pygame.time.delay(int(1000.0/fps))

if __name__ == '__main__':
    main()