Turtle-Graphik und Lindenmayersysteme

Werbung
Diskrete Modellierung
Wintersemester 2016/17
Martin Mundhenk
Uni Jena, Institut für Informatik
6. November 2016
3.x Exkurs: Lindenmayer-Systeme
The Algorithmic Beauty of Plants
Mit Turtle-Graphik lassen sich mit einfachen Prinzipien komplizierte“ Bilder
”
programmieren.
Beispiel 1: Die Koch-Snowflake.
Startregel:
Male ein Dreieck aus drei geraden Strichen.
Ersetzungsregel:
Ersetze jeden Strich durch 4 Striche wie folgt:
flocke.py malt die Koch-Snowflake mit
Rekursionstiefe n
import sys, stddraw
from turtle import Turtle
from color import Color
def
# t
#
# n
flocke(t,n,strichlaenge):
ist die Turtle, sie malt einen Strich
der Länge strichlaenge
ist die Anzahl der Ersetzungen
if n==0:
t.goForward(strichlaenge)
return
flocke(t,n-1,strichlaenge/3)
t.turnLeft(60)
flocke(t,n-1,strichlaenge/3)
t.turnLeft(-120)
flocke(t,n-1,strichlaenge/3)
t.turnLeft(60)
flocke(t,n-1,strichlaenge/3)
def male():
n = int(sys.argv[1])
stddraw.setPenRadius(0.001)
stddraw.setXscale(-0.3,1.3)
stddraw.setYscale(-1,0.6)
t = Turtle(0.0,0.0,0.0,Color(240,100,80),1,1)
# Beginne mit dem Dreieck aus 3 Strichen.
# n gibt an, wie oft der Strich mit der
#
Ersetzungsregel ersetzt werden soll.
flocke(t,n,1.0)
t.turnLeft(-120)
flocke(t,n,1.0)
t.turnLeft(-120)
flocke(t,n,1.0)
t.turnLeft(-120)
stddraw.show()
if __name__ == '__main__': male()
3.2.2
Die Spur der Funktionsaufrufe von flocke
flocke(t,0,1)
| t.goForward(1)
|_return
flocke(t,1,1)
| flocke(t,0,1/3)
| | t.goForward(1/3)
| |_return
| t.turnLeft(60)
| flocke(t,0,1/3)
| | t.goForward(1/3)
| |_return
| t.turnLeft(-120)
| flocke(t,0,1/3)
| | t.goForward(1/3)
| |_return
| t.turnLeft(-120)
| flocke(t,0,1/3)
| | t.goForward(1/3)
| |_return
|_return
flocke(t,2,1)
| flocke(t,1,1/3)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| | t.turnLeft(60)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| | t.turnLeft(-120)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| | t.turnLeft(-120)
| |_return
| t.turnLeft(60)
| flocke(t,1,1/3)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| | t.turnLeft(60)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| | t.turnLeft(-120)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| | t.turnLeft(-120)
| |_return
| t.turnLeft(-120)
| flocke(t,1,1/3)
| | ...
| |_return
| t.turnLeft(60)
|_return
Die Spur . . . mit den Aktionen der Turtle
flocke(t,0,1)
| t.goForward(1)
|_return
flocke(t,1,1)
| flocke(t,0,1/3)
| | t.goForward(1/3)
| |_return
| t.turnLeft(60)
| flocke(t,0,1/3)
| | t.goForward(1/3)
| |_return
| t.turnLeft(-120)
| flocke(t,0,1/3)
| | t.goForward(1/3)
| |_return
| t.turnLeft(60)
| flocke(t,0,1/3)
| | t.goForward(1/3)
| |_return
|_return
F
F
F
++
F
F
flocke(t,2,1)
| flocke(t,1,1/3)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| | t.turnLeft(60)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| | t.turnLeft(-120)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| | t.turnLeft(60)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| |_return
| t.turnLeft(60)
| flocke(t,1,1/3)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| | t.turnLeft(60)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| | t.turnLeft(-120)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| | t.turnLeft(60)
| | flocke(t,0,1/9)
| | | t.goForward(1/9)
| | |_return
| |_return
| t.turnLeft(-120)
| flocke(t,1,1/3)
| | ...
F
F
++
F
F
F
F
++
F
F
++
...
Eine Programmiersprache für die Turtle
Beim Malen der Schneeflocke führt die Turtle nur 3 verschiedene Aktionen“ aus:
”
Aktion
t.goForward(strichlaenge)
t.turnLeft(-60)
t.turnLeft(60)
Beschreibung der Aktion
F
+
-
Mit dieser Programmiersprache beschreiben wir das Malen der n-Schneeflocken:
n “ 0: F++F++F
n “ 1: F-F++F-F ++ F-F++F-F ++ F-F++F-F
n “ 2: F-F++F-F - F-F++F-F ++ F-F++F-F - F-F++F-F ++ ...++ ...
Diese Programme bauen sich nach einfachen Regeln auf:
§ Startregel: starte mit F++F++F
§ Ersetzungsregel: ersetze jedes F durch F-F++F-F
Das Programm zu Malen der n-Schneeflocke entsteht durch n-maliges Anwenden der
Ersetzungsregel auf die Startregel.
3.2.5
Etwas abstrakter: Lindenmayer-Systeme
Lindenmayer-Systeme (kurz: L-Systeme) sind Ersetzungssysteme für Strings.
Beispiel:
Startwort:
Ersetzungsregel 1:
Ersetzungsregel 2:
a
a Ñ ab
bÑa
Die Ersetzung beginnt mit einem Startstring.
Ersetzungsschritt: ersetze (gleichzeitig) jede Zeichenfolge im String, die auf
der linken Seite einer Regel vorkommt, durch die rechte Seite der Regel.
Startwort:
1.Ersetzungsschritt
2.Ersetzungsschritt
3.Ersetzungsschritt
4.Ersetzungsschritt
a
ab
aba
abaab
abaababa
Programmierung von Turtles mit
Lindenmayer-Systemen
Wir wollen ein Programm schreiben,
das ein Lindenmayer-System einliest
und die Turtle-Programme, die daraus erzeugt werden, ausführt.
Die Turtle-Programme bestehen aus folgenden Befehlen:
F
f
+
-
gehe eine Schrittlänge vorwärts und male einen Strich dabei
gehe eine Schrittlänge vorwärts und male keinen Strich dabei
drehe vorgegebene Gradzahl nach rechts
drehe vorgegebene Gradzahl nach links
3.2.7
Ein Lindenmayer-System geben wir als Datei an.
In der ersten Zeile steht die Gradzahl für die Drehungen + und -.
Dahinter stehen zeilenweise die Regeln:
zuerst das Zeichen auf der linken Seite der Regel,
dann ein Leerzeichen,
und dann (ohne Leerzeichen) die Zeichen der rechten Seite der Regel.
Bei der Startregel steht auf der linken Seite start.
Beispiel:
90
start F+F+F+F
F F-F+F+FF-F-F+F
3.2.8
LSystem.py
Die Klasse LSystem dient zum Einlesen eines Lindenmayer-Systems und
zur Berechnung der Strings, die es mit beliebig vielen Ersetzungsschritten
erzeugt.
Sie hat folgende API:
Operation
Beschreibung
LSystem(eingabeStream) ein neues Lindenmayer-System wird erzeugt;
die Regeln werden zeilenweise aus eingabeStream
gelesen
ls.erzeugeString(i)
der String des Lindenmayer-Systems mit i Iterationen wird als Ergebnis zurückgegeben
3.2.9
Bemerkungen zu den Funktionen von LSystem.py
§
init : die Regeln des Lindenmayer-Systems werden im Dictionary regeln
gespeichert. Eine Regel F Ñ F ` F ´ F , die in der Datei für das
Lindenmayer-System als Zeile
F F+F-F
steht, kommt dann als Eintrag {’F’:’F+F-F’} in das Dictionary.
Die rechte Seite dieser Regel erhält man dann mit regeln[’F’].
§
ersetzungsSchritt: erzeugt aus einem String altS den String neuS, indem
jedes Zeichen zeichen von altS durch regeln[zeichen] – also durch die
rechte Seite der Regel mit zeichen auf der linken Seite – ersetzt wird (falls es
eine passende Regel gibt). Das entspricht einem Ersetzungsschritt.
§
erzeugeString(i): beginnt mit dem Startstring und wendet darauf i
Ersetzungschritte an.
3.2.10
# LSystem.py
import sys
from instream import InStream
class LSystem:
def __init__(self, eingabe):
def _ersetzungsSchritt(self,altS):
# Die Regeln werden zeilenweise
# Aus altS entsteht neuS durch Anwendung einer
# aus dem Eingabestream eingabe gelesen
# Regel des L-Systems auf jedes Zeichen.
# und in ein Dictionary eingetragen.
neuS = ""
self._regeln = dict()
for zeichen in altS:
while not eingabe.isEmpty():
if zeichen in self._regeln:
name = eingabe.readString()
neuS = neuS + self._regeln[zeichen]
wert = eingabe.readString()
else: neuS = neuS + zeichen
self._regeln[name] = wert
return neuerString
def erzeugeString(self,iterationen):
# Der Startstring wird genommen
# und iterationen-mal wird auf jedes seiner Zeichen
# die passende Regel angewendet.
# Dadurch entsteht der ergebnisString.
startString = self._regeln['start']
ergebnisString = ""
if iterationen==0: return startString
for i in range(iterationen):
ergebnisString = self._ersetzungsSchritt(startString)
startString = ergebnisString
return ergebnisString
runTurtle.py
Das Modul runTurtle stellt die Funktion runTurtle zur Verfügung, mit
der ein Turtle-Programm ausgeführt werden kann. D.h., das durch das
Turtle-Programm und den Winkel für die Drehungen bestimmte Bild wird
gemalt.
runTurtle.py hat folgende API:
Operation
runTurtle(programm,winkel)
Beschreibung
gibt auf stddraw das vom Programm und dem
Winkel beschrieben Bild aus
3.2.12
Bemerkungen zu runTurtle.py
runTurtle(programm,winkel) benötigt die minimalen und maximalen x- und
y -Koordinaten (extreme Koordinaten), die im Bild vorkommen.
Dafür wurde die API der Klasse Turtle erweitert:
Operation
t.maxx()
Beschreibung
gibt die maximale x-Koordinate, die bisher bemalt wurde, zurück
t.maxy()
gibt die maximale y -Koordinate, die bisher bemalt wurde, zurück
t.minx()
gibt die minimale x-Koordinate, die bisher bemalt wurde, zurück
t.miny()
gibt die minimale y -Koordinate, die bisher bemalt wurde, zurück
t.goForward(s,zeigen=True) Turtle t geht s Schritte vorwärts;
falls zeigen==True, malt sie dabei einen Strich;
falls zeigen==False, wird kein Strich gemalt
runTurtle(programm,winkel) berechnet zuerst die extremen Koordinaten, indem
das Bild gemalt, aber nicht auf die Leinwand gebracht wird.
Dazu dient die Funktion durchlauf. Sie setzt die Befehle des Turtle-Programms in
Operationen der Turtle um. Nach einem Durchlauf ohne Zeichnen liefert die Turtle die
extremen Koordinaten.
Aus ihnen wird dann die X - und Y -Skalierung der Ausgabegraphik bestimmt. Damit
das Bild nicht verzerrt wird, muss der Bereich für beide Skalen gleichgroß sein. Sind
z.B. die x-Werte im Bereich ´3 . . . 5 und die y -Werte im Bereich 1 . . . 4, so hat die
quadratische Ausgabegraphik die Seitenlänge 8 und die x-Koordinaten ´3 . . . 5 und
die y -Koordinaten ´1.5 . . . 6.5.
Mit diesen Werten wird die X - und Y ´Skalierung der Ausgabegraphik eingestellt.
Beim folgenden Aufruf von durchlauf wird das Bild gemalt.
import stddraw
from turtle import Turtle
from color import Color
def durchlauf(t,programm,winkel,zeichnen=True):
for befehl in programm:
if
befehl=='f':
t.goForward(1,zeichnen)
elif befehl=='F':
t.goForward(1,zeichnen)
elif befehl=='+':
t.turnLeft(winkel)
elif befehl=='-':
t.turnLeft(-winkel)
def runTurtle(programm, winkel):
# starte zuerst einen Trockendurchlauf ohne zu zeichnen, um die Größe des Bildes festzustellen
t = Turtle(0.0,0.0,0.0)
durchlauf(t,programm,winkel,False)
# Nun kennt t die extremen Koordinaten im Bild. Wir nutzen sie, um eine
# quadratische Leinwand zu konstruieren, auf die das ganze Bild passt.
breite = t.maxx() - t.minx()
xmitte = t.maxx() - breite/2.0
hoehe = t.maxy() - t.miny()
ymitte = t.maxy() - hoehe/2.0
halbebildseite = max(breite,hoehe) / 2.0
stddraw.setXscale(xmitte-halbebildseite,xmitte+halbebildseite)
stddraw.setYscale(ymitte-halbebildseite,ymitte+halbebildseite)
# Jetzt kann das Zeichnen beginnen.
t = Turtle(0.0,0.0,0.0,Color(240,100,80),1,1)
durchlauf(t,programm,winkel,True)
3.2.15
Der Klient, der alles zusammenbringt
Der Klient maleLSBild.py
§
liest aus einer Datei den Winkel für die Drehungen der Turtle und das
Lindenmayer-System ein,
und liest von der Konsole die Anzahl der Iterationen, für die das
Turtle-Programm erzeugt werden soll,
§
erzeugt das Turtle-Programm mittels LSystem und
§
malt das Bild mittels runTurtle.
import sys, stddraw
from instream import InStream
from LSystem import LSystem
from runTurtle import runTurtle
def main():
# Die Datei mit der Bildbeschreibung hat in der ersten Zeile
# den Winkel (float) für die + und - Operationen,
# und in den darauffolgenden Zeilen die Regeln (ohne Pfeile, also z.B. F F+F+F+F).
dateiname = sys.argv[1]
eingabe = InStream(dateiname)
# Zuerst wird der Winkel eingelesen.
winkel = eingabe.readFloat()
# Aus dem Rest der Eingabedatei wird das Lindenmayer-System erzeugt.
LS = LSystem(eingabe)
# Es wird eingelesen, durch wieviele Iterationen des L-Systems
# das Turtle-Programm erzeugt werden soll.
iterationen = int(sys.argv[2])
# Das Turtle-Programm wird erzeugt.
programm = LS.erzeugeString(iterationen)
# Das Bild wird gemalt.
stddraw.setPenRadius(0.001)
runTurtle(programm,winkel)
stddraw.show()
if __name__ == '__main__': main()
3.2.17
Beispiele
Datei sierpinski.ls:
60
start F
F +G-F-G+
G -F+G+F(Bei F und G wird gemalt.)
python maleLSBild.py sierpinski.ls 7
Datei dragon.ls:
90
start FX
X X+YF+
Y -FX-Y
(Bei F wird gemalt, bei X und Y nicht.)
python maleLSBild.py dragon.ls 12
Es geht noch schöner . . .
Man kann die Turtle-Programm um einen Verzweigungsoperator erweitern:
’[’ bedeutet: starte eine neue Turtle am aktuellen Punkt und mache
mit der alten Turtle weiter, wenn das korrespondierende ’]’ erreicht wird
(Dazu kann man die Datenstruktur Stack benutzen.)
Datei fractal-plant.ls:
25
start +++X
X F-[[X]+X]+F[+FX]-X
F FF
(Bei F wird gemalt, bei X nicht.)
python maleLSBild.py fractal-plant.ls 9
Zusammenfassung
Es gibt viele weitere Varianten von Lindenmayer-Systemen.
Wir haben gesehen, wie man
§
mit Programmen für Bilder das Malen vereinfachen kann
§
Programme einer Programmiersprache“ zum Bildermalen laufen
”
lässt . . .
3.2.20
Herunterladen