Was du hier lernst
- genetische Algorithmen
- Fitnessfunktion
- Roulette-Wheel-Selection
- Kreuzung und Mutation
- Elitismus
- Klassen und Properties
- Sortieren mit key
- tiefe Kopien mit deepcopy
So lernt der Computer das Landen
In der Mondlandung steuerst du selbst. Hier bekommt der Computer dieselbe Aufgabe, aber keine Strategie. Er soll sie wie die Natur finden: durch Evolution. Eine Population von Flugplänen wird getestet, die besseren dürfen sich fortpflanzen, ihre Nachkommen werden leicht verändert – und nach einigen Generationen setzt die Fähre sanft auf der roten Fläche auf.
1. Ein Flug ohne Bildschirm
Evolution braucht viele Versuche, deshalb muss ein Flug schnell zu simulieren sein. Die Klasse lander kennt Position und Geschwindigkeit der Fähre und rechnet sie Schritt für Schritt weiter:
def calculateDelta(self, fps, xAcceleration, yAcceleration):
dt = 1.0 / fps
self.xVelocity += xAcceleration * dt
self.xPos += self.xVelocity * dt
self.yVelocity += yAcceleration * dt
self.yPos += self.yVelocity * dt
Anders als im Spiel wird hier korrekt mit Beschleunigung mal Zeit gerechnet, deshalb lauten auch die Zahlen anders: −16,35 für die Schwerkraft, +20 für die Bremsrakete. landerSimulation.start spielt mit einer Liste von Befehlen einen ganzen Flug durch – rund tausend Schritte, ganz ohne Grafik – und liefert am Ende eine Bewertung.
2. Ein Flugplan als Erbgut
Wie beschreibt man eine Landung so, dass Evolution damit arbeiten kann? Das Buch zerlegt sie in Gene. Ein Gen ist eine Aktion und ihre Dauer:
leftRocket = (-1, 0)
rightRocket = (+1, 0)
centralRocket = ( 0, +1)
doNothing = ( 0, -1)
Ein Gen (centralRocket, 25) heißt: 25 Schritte lang die Bremsrakete zünden. Dreißig Gene hintereinander bilden ein Genom, und getCommandList macht daraus die Befehlsliste für die Simulation. Am Anfang sind alle Gene zufällig – die ersten Flüge sehen entsprechend wild aus.
3. Die Fitness: Wie gut war der Flug?
Die Evolution muss gute von schlechten Flügen unterscheiden. Dafür bekommt jeder Flug eine Fitness:
self.fitness = 100000
self.fitness -= dist * 10
self.fitness -= abs(self.lunarLander.xVelocity) * 4
self.fitness -= abs(self.lunarLander.yVelocity) * 8
Abzüge gibt es für den Abstand zur Mitte der Landefläche und für die Geschwindigkeit beim Aufsetzen. Wer auf der Fläche landet und die Regeln einhält, bekommt 100.000 Bonuspunkte. Die abgestufte Bewertung ist wichtig: Auch ein knapper Absturz ist besser als einer weit daneben, und genau diese kleinen Unterschiede zeigen der Evolution die Richtung. Der Startwert von 100.000 hält alle Werte positiv – das braucht die Auswahl im nächsten Schritt.
4. Auslese, Kreuzung, Mutation
evolve wiederholt immer denselben Ablauf. Zuerst wird die Population nach Fitness sortiert:
population.sort(key=lambda x: x.fitness, reverse=True)
Die vier Besten kommen unverändert in die nächste Generation. Die übrigen Plätze füllen Nachkommen: rouletteWheelSelection wählt Eltern aus, und je höher die Fitness, desto größer das Stück vom Glücksrad. multiCrossOver mischt in 70 Prozent der Fälle die Gene von Vater und Mutter, und mutate verändert jedes Gen mit 20 Prozent Wahrscheinlichkeit in seiner Aktion und mit 10 Prozent in seiner Dauer. Sobald ein Flug die Bonuspunkte erreicht, ist eine Lösung gefunden.
Wozu Mutation?
Nur Kreuzen würde vorhandene Gene immer neu mischen, aber nie etwas Neues erfinden. Die Population könnte an einer mittelmäßigen Lösung hängen bleiben, einem lokalen Optimum. Mutation bringt frische Varianten ins Spiel. Zu viel Mutation zerstört dagegen gute Lösungen schneller, als die Auslese sie findet.
5. Die Evolution schummelt
Im Buch prüfte die Simulation erst nach dem letzten Befehl, ob die Fähre den Boden erreicht hat. Die Evolution hat das gnadenlos ausgenutzt: Viele »erfolgreiche« Flugpläne tauchten mitten im Flug durch den Mond und kamen am Ende langsam genug wieder heraus. In Tests passierte das in über der Hälfte der Läufe. Die Wiedergabe prüft den Boden dagegen in jedem Schritt – dort endete der Flug schon beim ersten Eintauchen, und in etwa jedem vierten Lauf war das eine Bruchlandung. Deshalb prüft die Simulation hier ebenfalls in jedem Schritt:
if commandAvailable == False or yPos < ground[1] + ground[3]:
Das ist eine echte Lektion über Optimierung: Ein genetischer Algorithmus optimiert genau das, was du misst – nicht das, was du meinst. Unter »Probier mal« kannst du die alte Regel zurückholen.
6. Zusehen, Generation für Generation
Damit du der Evolution zuschauen kannst, merkt sich jede Simulation ihre Flugbahn. Nach jeder Generation zeichnet showGeneration alle 50 Bahnen: weiß die Fehlversuche, grün die gelungenen Landungen, gelb die beste. await screen.frame(4) hält jedes Bild mindestens eine Viertelsekunde. Ist eine Lösung gefunden, fliegt die beste Fähre ihre Landung vor, danach beginnt eine ganz neue Evolution – mit neuen Zufallsgenen und meist einem anderen Weg zum Ziel.
Genau hingeschaut
In multiCrossOver liegt swapRate zwischen 0 und 30, verglichen wird aber mit random.random(), das immer kleiner als 1 ist. Fast immer erbt das erste Baby deshalb alle Gene vom Vater, es wird kaum gekreuzt. Außerdem gibt die Funktion ohne Kreuzung die Eltern selbst zurück, und mutate verändert dann die Eltern. Die Evolution funktioniert trotzdem, weil Auslese und Mutation die Hauptarbeit leisten. Unter »Probier mal« findest du die korrigierte Kreuzung.
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.
-
01Echte Kreuzung
In
multiCrossOversteckt ein kleiner Fehler:swapRateliegt zwischen 0 und 30,random.random()aber immer unter 1. Deshalb bekommt das erste Baby fast immer alle Gene vom Vater – gekreuzt wird kaum, die Evolution lebt fast nur von Mutation und Auslese. Mit einer echten Wahrscheinlichkeit zwischen 0 und 1 mischen sich die Gene wirklich.- swapRate = random.random() * Evolution.numberOfGenes + swapRate = random.random() -
02Strengere Landung
Die Fähre muss jetzt deutlich langsamer aufsetzen. Die Evolution braucht dafür viel mehr Generationen – im Schnitt etwa dreimal so viele –, deshalb darf sie hier bis zu 100 Generationen lang suchen. Jetzt siehst du richtig, wie sich die Bahnen Schritt für Schritt der Landefläche nähern.
- if yVelocity < 40 and xVelocity < 30: + if yVelocity < 30 and xVelocity < 20: - maxGenerations = 50 + maxGenerations = 100 -
03Doppelt so viele Individuen
Eine größere Population probiert in jeder Generation mehr Flugpläne aus. Meist findet sie in weniger Generationen eine Landung – dafür dauert jede Generation doppelt so lange. Mehr Vielfalt gegen mehr Rechenzeit: eine typische Abwägung bei genetischen Algorithmen.
- populationSize = 50 + populationSize = 100 -
04Die Lücke im Simulator
So stand es im Buch: Die Simulation prüft erst nach dem letzten Befehl, ob die Fähre am Boden ist. Die Evolution nutzt das schamlos aus – Flugpläne dürfen mitten im Flug durch den Mond tauchen und trotzdem als Landung zählen. Die Wiedergabe prüft dagegen in jedem Schritt – und zeigt in etwa jedem vierten Lauf eine Bruchlandung, obwohl die Evolution eine Landung gemeldet hat. Evolution optimiert immer genau das, was du misst.
- if commandAvailable == False or yPos < ground[1] + ground[3]: + if commandAvailable == False: -
05Wiedergabe in Echtzeit
Die beste Landung läuft sonst doppelt so schnell ab wie simuliert. Mit
replaySpeed = 1siehst du sie im Originaltempo des Buchs: 80 Simulationsschritte pro Sekunde.- replaySpeed = 2 + replaySpeed = 1
Vom Buch in den Browser
Im Buch verteilt sich das Programm auf vier Dateien: lander.py (die Fähre), landerSimulation.py (ein Flug ohne Grafik), Evolution.py (Gene, Genome und die Evolution) und AutomatedLunarLander_v1.py (Anzeige mit OpenGL). Im Browser gibt es nur eine Datei im Editor, deshalb stehen alle vier hier untereinander. Die Klassen, Methoden und Namen sind die aus dem Buch – auch die klein geschriebene Klasse lander.
Von Python 2 nach Python 3:
| Im Buch (Python 2) | Hier (Python 3) |
|---|---|
population.sort(lambda x, y: cmp(x.fitness, y.fitness), reverse=True) |
population.sort(key=lambda x: x.fitness, reverse=True) |
for i in xrange(n): |
for i in range(n): |
print generationCounter, " ", |
print(f"Generation {generationCounter:2d}: …") |
import psyco und psyco.full() |
entfällt |
Sortieren mit key. Python 2 sortierte mit einer Vergleichsfunktion, die für zwei Elemente −1, 0 oder 1 liefert. Python 3 kennt nur noch key: eine Funktion, die für jedes Element einmal einen Wert berechnet, nach dem sortiert wird. Das ist kürzer und schneller, weil die Funktion nur 50-mal statt bei jedem Vergleich aufgerufen wird.
psyco war ein Beschleuniger für Python 2, der Programme zur Laufzeit in Maschinencode übersetzte. Für Python 3 gab es ihn nie – und er wird nicht gebraucht: Eine Generation mit 50 Flügen rechnet auch im Browser nur Sekundenbruchteile.
Zuschauen ermöglichen. evolve ist jetzt eine async-Methode und bekommt eine Funktion show, die nach jeder Generation die Bahnen zeichnet. Dafür speichert die Simulation jede vierte Position in self.path. Die Wiedergabe der besten Landung ist aus main() von AutomatedLunarLander_v1.py übernommen, mit einer Anpassung: Sie zeigt nur dann »YOU WON«, wenn die Fähre auch wirklich auf der Landefläche steht. Im Buch genügte die Geschwindigkeit.
Eine Korrektur der Simulation. Die Simulation prüft den Bodenkontakt in jedem Schritt, nicht erst nach dem letzten Befehl – warum, steht oben unter »Die Evolution schummelt«.
Für die Grafik gilt dasselbe wie bei der Mondlandung: OpenGL zählte y von unten nach oben, die Leinwand von oben nach unten. toScreen rechnet um, screen.rect ersetzt die GL_QUADS, und await screen.frame(fps) ersetzt pygame.display.flip() und pygame.time.delay(). Die Ausgabe in der Konsole ist lesbarer formatiert, und die ASCII-Art erscheint mittig.
Original aus dem Buch ansehen Evolution.py · Python 2
#coding: latin1
'''
Created on 03.01.2010
@author: sourcer
'''
import psyco
psyco.full()
import random
from landerSimulation import landerSimulation
import copy
class Gen(object):
__maxActionDuration = 80
__maxMutationDuration = 20
leftRocket = (-1, 0)
rightRocket = (+1, 0)
centralRocket = ( 0, +1)
doNothing = ( 0, -1)
actions = (leftRocket, rightRocket, centralRocket, doNothing)
def __init__(self):
self.action = random.choice(Gen.actions)
self.duration = random.randint(1, Gen.__maxActionDuration)
def mutateAction(self):
self.action = random.choice(Gen.actions)
def mutateDuration(self):
delta = Gen.__maxMutationDuration - random.randint(1, 2*Gen.__maxMutationDuration)
self.duration += delta
if self.duration > self.__maxActionDuration:
self.duration = self.__maxActionDuration
def __eq__(self, other):
return (self.action == other.action) and (self.duration == other.duration)
class Genome(object):
def __init__(self, genes=None):
if genes:
# es ist wichtig hier eine Kopie zu erzeugen!
# ansonsten werden die gleichen Gene in verschiedenen
# Individuen referenziert und manipuliert
self.__genes = copy.deepcopy(genes)
else:
self.__genes = []
for i in xrange(Evolution.numberOfGenes):
self.__genes.append(Gen())
def getGenes(self):
return self.__genes
def setGenes(self, genes):
self.__genes = genes
genes = property(getGenes, setGenes)
def getCommandList(self):
commandList = []
for gen in self.__genes:
for i in xrange(gen.duration):
commandList.append(gen.action)
return commandList
def updateFitness(self):
simulation = landerSimulation()
commands = self.getCommandList()
self.fitness = simulation.start(commands)
return self.fitness
def mutate(self):
for gen in self.genes:
if random.random() < Evolution.mutationRate:
gen.mutateAction()
if random.random() < Evolution.mutationRate/2.0:
gen.mutateDuration()
class Evolution(object):
mutationRate = 0.2
crossOverRate = 0.7
numberOfGenes = 30
populationSize = 50
saveBestCount = 4 # has to be even! (dad + mum)
maxGenerations = 50
def createNewGeneration(self, populationSize):
population = []
for i in xrange(populationSize):
population.append(Genome())
return population
def multiCrossOver(self, dad, mum):
if (random.random() > Evolution.crossOverRate):
# keine Kreuzung
return dad, mum
swapRate = random.random() * Evolution.numberOfGenes
baby1, baby2 = [], []
dadGenes = dad.genes
mumGenes = mum.genes
for x in range(Evolution.numberOfGenes):
if random.random() < swapRate:
baby1.append(dadGenes[x])
baby2.append(mumGenes[x])
else:
baby1.append(mumGenes[x])
baby2.append(dadGenes[x])
return Genome(baby1), Genome(baby2)
def rouletteWheelSelection(self, population):
totalFitness = 0
for x in population:
totalFitness += x.fitness
randomSlice = random.random() * totalFitness;
fitnessSum = 0
for x in population:
fitnessSum += x.fitness
if fitnessSum >= randomSlice:
return x
return population[0]
def sortByFitness(self, population):
for individuum in population:
individuum.updateFitness()
population.sort(lambda x, y: cmp(x.fitness, y.fitness), reverse=True)
currentBest = Genome(population[0].genes)
currentBest.fitness = population[0].fitness
return currentBest
def evolve(self):
population = self.createNewGeneration(Evolution.populationSize)
nextGeneration = []
generationCounter = 0
bestIndividuum = None
while (generationCounter < Evolution.maxGenerations):
print generationCounter, " ",
if generationCounter % 20 == 0:
print
currentBest = self.sortByFitness(population)
if (bestIndividuum == None) or (currentBest.fitness > bestIndividuum.fitness):
bestIndividuum = currentBest
print bestIndividuum.fitness
if bestIndividuum.fitness > 100000:
# wir haben eine Lösung
return bestIndividuum
# erhalte die besten Individuen für die nächste Generation
# dabei benötigen wir eine gerade Anzahl von Individuen um immer
# jeweils einen Vater und eine Mutter für die Kreuzung zur Verfügung
# zu haben, da sonst die Roulette-Wheel-Selection nicht funktioniert
nextGeneration = []
nextGeneration += population[0:Evolution.saveBestCount]
babyCount = 0
while babyCount < (Evolution.populationSize):
dad = self.rouletteWheelSelection(population)
mum = self.rouletteWheelSelection(population)
baby1, baby2 = self.multiCrossOver(dad, mum)
baby1.mutate()
baby2.mutate()
nextGeneration += baby1, baby2
babyCount += 2
population = nextGeneration
generationCounter += 1
return bestIndividuum
if __name__ == '__main__':
evo = Evolution()
bestIndividual = evo.evolve()
Original aus dem Buch ansehen landerSimulation.py · Python 2
'''
Created on 03.01.2010
@author: sourcer
'''
from lander import lander
import math
class landerSimulation(object):
def __init__(self):
self.lunarLander = lander(200, 400, 0, 0)
def loadNextCommand(self):
if self.commandList:
self.command = self.commandList.pop(0)
return True
else:
self.command = (0, 0)
return False
def collisionDetection(self, 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 calculateDistance(self, rocket, ground):
rx1, ry1, rx2, ry2 = rocket
gx1, gy1, gx2, gy2 = ground
groundCenterUpside = (gx1 + gx2/2.0, gy1 + gy2)
rocketCenterDownside = (rx1 + rx2/2.0, ry1)
return math.sqrt((rocketCenterDownside[0] - groundCenterUpside[0])**2 +
(rocketCenterDownside[1] - groundCenterUpside[1])**2)
def start(self, commandList):
# commandList wird kopiert um
# die Ausgangsliste zu erhalten
self.commandList = commandList[:]
while True:
self.yAcceleration = -16.35
self.xAcceleration = +0.0
commandAvailable = self.loadNextCommand()
if commandAvailable:
if self.command == ( 0, +1):
self.yAcceleration = 20.0
if self.command == ( 1, 0):
self.xAcceleration = -10
if self.command == (-1, 0):
self.xAcceleration = +10
xPos, yPos = self.lunarLander.calculateDelta(80, self.xAcceleration, self.yAcceleration)
rocket = xPos, yPos, 20, 30
ground = 350, 50, 100, 30
self.fitness = 100000 # to keep fitness scores positive
if commandAvailable == False:
# auf Boden warten
while yPos >= ground[1]+ground[3]:
xPos, yPos = self.lunarLander.calculateDelta(80, self.xAcceleration, self.yAcceleration)
rocket = xPos, yPos, 20, 30
dist = self.calculateDistance(rocket, ground)
self.fitness -= dist * 10
self.fitness -= abs(self.lunarLander.xVelocity) * 4
self.fitness -= abs(self.lunarLander.yVelocity) * 8
if self.collisionDetection(rocket, ground):
if self.lunarLander.checkRules():
self.fitness += 100000
break
if (self.fitness < 0):
raise Exception("negative fitness scores are evil")
return self.fitness
Original aus dem Buch ansehen lander.py · Python 2
'''
Created on 03.01.2010
@author: sourcer
'''
import random
class lander(object):
def __init__(self, xPos, yPos, xVelocity, yVelocity):
self.xVelocity = xVelocity
self.yVelocity = yVelocity
self.xPos = xPos
self.yPos = yPos
def calculateDelta(self, fps, xAcceleration, yAcceleration):
dt = 1.0/fps
self.xVelocity += xAcceleration * dt
self.xPos += self.xVelocity * dt
self.yVelocity += yAcceleration * dt
self.yPos += self.yVelocity * dt
return self.xPos, self.yPos
def checkRules(self):
yVelocity = abs(self.yVelocity)
xVelocity = abs(self.xVelocity)
if yVelocity < 40 and xVelocity < 30:
return True
else:
return False
Original aus dem Buch ansehen AutomatedLunarLander_v1.py · Python 2
'''
Created on 25.12.2009
@author: Lars Heppert
'''
from Evolution import Evolution
import psyco
psyco.full()
from OpenGL.GL import *
from OpenGL.GLU import *
from random import random, randint
import pygame, math, datetime
from pygame.locals import *
from lander import lander
from landerSimulation import landerSimulation
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 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 calculateDistance(rocket, ground):
rx1, ry1, rx2, ry2 = rocket
gx1, gy1, gx2, gy2 = ground
groundCenterUpside = (gx1 + gx2/2.0, gy1 + gy2)
rocketCenterDownside = (rx1 + rx2/2.0, ry1)
return math.sqrt((rocketCenterDownside[0] - groundCenterUpside[0])**2 +
(rocketCenterDownside[1] - groundCenterUpside[1])**2)
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 loadNextCommand():
global command
if commandList:
command = commandList.pop(0)
return True
else:
command = (0, 0)
return False
def findBestCommands():
evo = Evolution()
bestIndividual = evo.evolve()
cmdList = bestIndividual.getCommandList()
# test sim
sim = landerSimulation()
fitness = sim.start(cmdList)
print "Fitness, Sim1: %f" % bestIndividual.fitness
print "Fitness, Sim2: %f" % fitness
return cmdList
def restartGame():
# reinitialize the game (restart)
global command, commandList, lunarLander
command = (0, 0)
lunarLander = lander(200, 400, 0, 0)
commandList = findBestCommands()
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
fieldWidth = 800
fieldHeight = 600
fps = 80
command = [0, 0]
commandList = findBestCommands()
lunarLander = lander(200, 400, 0, 0)
def main():
pygame.init()
video_flags = OPENGL | HWSURFACE | DOUBLEBUF
screenSize = (fieldWidth, fieldHeight)
pygame.display.set_mode(screenSize, video_flags)
resize(screenSize)
init()
ground = 350, 50, 100, 30
while True:
yAcceleration = -16.35
xAcceleration = +0.0
if not handleEvent(pygame.event.poll()):
break
commandAvailable = loadNextCommand()
if commandAvailable:
if command == ( 0, +1):
yAcceleration = 20.0
if command == ( 1, 0):
xAcceleration = -10.0
if command == (-1, 0):
xAcceleration = +10.0
xPos, yPos = lunarLander.calculateDelta(fps, xAcceleration, yAcceleration)
clearScreen()
rocket = xPos, yPos, 20, 30
drawRect(rocket)
drawRect(ground)
#if collisionDetection(rocket, ground):
if yPos < ground[1]+ground[3]:
print " Distance:", calculateDistance(rocket, ground)
print "xVelocity:", lunarLander.xVelocity
print "yVelocity:", lunarLander.yVelocity
if lunarLander.checkRules():
showYouWon()
else:
showGameOver()
pygame.display.flip()
pygame.time.delay(int(1000.0/fps))
if __name__ == '__main__':
main()