Einführung in RubyKara

Transcription

Einführung in RubyKara
Einführung in R UBY K ARA
Gerhard Bitsch∗
Kepler-Gymnasium Tübingen
24. August 2008
1
Die R UBY K ARA -Umgebung
Die
Programmierumgebung
R UBY K ARA erlaubt es, Kara mit
der Programmiersprache R UBY
zu steuern.
Dazu stellt R UBY K ARA einen
kleinen Programmeditor zur
Verfügung, der beim Aufruf einige Programmierhinweise und
ein einfaches Programm enthält.
Befehle für K ARA müssen mit
einem vorgestellten kara. geschrieben werden, also etwa
kara.move, um K ARA ein Feld
nach vorn zu bewegen.
Dieses Programm ist unmittelbar ausführbar. Man muss lediglich den entsprechenden Knopf
in der K ARA -Welt betätigen.
Der grün gefärbte untere Teil des Programmfensters dient zur Ausgabe von Fehlermeldungen.
Das im Bild dargestellte Programm zeigt schon einige Besonderheiten der Programmierung
mit R UBY im Gegensatz zum Automatenmodell.
Der grau dargestellte obere Fensterteil enthält Kommentare, die einige der zur Verfügung stehenden Befehle darstellen. Ein Kommentar beginnt mit dem Zeichen #. Kommentare werden
vom Programm während des Ablaufs ignoriert. Sie sollen lediglich die Verständlichkeit von
Code verbessern.
Das im Beispiel angezeigte Programm lasst K ARA bis zum nächsten Baum laufen und dort
stehen bleiben.
∗
mailto:[email protected]
1
Das Bild zeigt ein Automatenprogramm mit
der gleichen Wirkung. Die Anweisung, dass
K ARA , wenn er vor keinem Baum steht, einen
Schritt machen soll und dann im Zustand
FindeBaum bleiben soll, wird mit der whileAnweisung übertragen (while ' solange). Der
Schrittbefehl ist kara.move.
Mit kara.treeFront wird der Sensor abgefragt. Diese Abfrage liefert true, falls K ARA vor
einem Baum steht und false andernfalls. Der Operator not steht für die Negation, d.h. er
vertauscht die Werte true und false.
2
Einfache Anweisungen in R UBY
In diesem Abschnitt werden einfache R UBY -Anweisungen zur Strukturierung von Programmen vorgestellt. R UBY ist eine objektorientierte Sprache, die nicht kompiliert, sondern interpretiert wird. Das bedeutet, dass jeder Ausdruck in einem Programm unmittelbar vor der
Ausführung in Anweisungen für den Rechner übersetzt wird. Das hat den Vorteil, dass man
auch einzelne Anweisungen direkt austesten kann. Der Nachteil solcher Programme besteht in
ihrem gegenüber kompilierten Programmen deutlich langsameren Ablauf.
Die Objektorientierung hat in R UBY zur Folge, dass selbst Zahlen als Objekte definiert sind, die
gewisse Methoden zur Verfügung stellen.
Bedingungen: Beim Automatenmodell wird ein unterschiedliches Verhalten von K ARA durch die Zustände der Sensoren gesteuert. In R UBY kann man diese Sensoren abfragen. Sie liefern
dabei einen der Werte true oder false . Ausdrücke mit der Eigenschaft, einen dieser Werte
zu liefern, nennt man Bedingungen.
Bedingungen können mit aussagenlogischen Operatoren verknüpft werden. Die folgende Tabelle gibt das Ergebnis einer solchen Verknüpfung für zwei Bedingungen a und b
wieder:
a
b
not a a and b a or b
nicht
und
oder
true true false
true
true
true false false
false
true
false true true
false
true
false false true
false
false
An Stelle von not kann man auch !, für and auch && und für or auch || verwenden.
Beispiele:
not kara.onLeaf
hat den Wert true , falls K ARA auf einem leeren Feld steht.
kara.treeLeft and kara.treeRight
K ARA ein Baum steht.
kara.treeFront or kara.onLeaf
Bedingungen zutrifft.
hat den Wert true , falls links und rechts von
hat den Wert true , falls mindestens eine der beiden
while: Die while-Schleife, die schon im vorigen Abschnitt angesprochen wurde, hat folgende
Gestalt:
2
while Bedingung
Anweisung1
..
.
Diese Anweisungen bilden einen Block
Anweisungn
end
Bei der Ausführung einer while-Anweisung wird zunächst die Bedingung überprüft.
Liefert sie den Wert true , so werden die Anweisungen (jeweils durch einen Zeilenwechsel
abgeschlossen) nacheinander ausgeführt. Anschließend beginnt der Vorgang mit der Überprüfung der Bedingung neu. Dies wird so lange wiederholt, bis die Bedingung den Wert
false liefert.
Verzweigung: Verzweigungen entstehen, wenn je nach dem Wert einer Bedingung unterschiedliche Anweisungen ausgeführt werden. Die allgemeine Form ist:
if Bedingung
Anweisungen falls true
else
Anweisungen falls false
end
Beispiel:
1
2
3
4
5
i f kara . onLeaf
kara . removeLeaf
else
kara . putLeaf
end
//
//
//
//
f a l l s Kara auf einem B l a t t s t e h t
B l a t t entfernen
andernfalls
B l a t t ablegen
Soll keine Anweisung ausgeführt werden, wenn die Bedingung nicht erfüllt ist, so kann
man den else-Teil der Anweisung weglassen.
Beispiel:
1
2
3
i f kara . t r e e F r o n t
kara . t u r n L e f t
end
4
//
//
//
//
f a l l s Kara vor einem Baum s t e h t
nach l i n k s drehen
ansonsten n i c h t s tun ,
Programm f o r t s e t z e n
Methoden: Man kann auch eigenen Methoden definieren, die dann wie die in der Umgebung vorhandenen Methoden verwendet werden können. Es gibt dafür mehrere Möglichkeiten,
die einfachste führen wir jetzt ein.
def Methodenname(Parameter)
Anweisungen
end
Der zwischen def und end eingeschlossene Programmtext wird bei der Ausführung des
Programms zunächst nicht durchgeführt, sondern nur als Methode registriert“, die im
”
weiteren Programm verwendet werden kann. Beim Aufruf der Methode muss für ihre Parameter jeweils ein Objekt übergeben werden, das im Text der Methode für den entsprechenden Parameter eingesetzt wird. Danach erst wird die Methode durchgeführt. Dies ist
3
vergleichbar zur Definition einer Funktion in der Mathematik in der Form f (x) = ...x...,
bei der zur Berechnung bestimmter Funktionswerte für x ein Wert eingesetzt wird, mit
dem dann die rechte Seite der Definition ausgewertet wird.
Beispiel: K ARA soll zum nächsten Baum gehen, sich dort umdrehen und weiter laufen
und dies endlos wiederholen.
1
2
3
4
@kara=kara
def drehen
2 . t im es { @kara . t u r n L e f t }
end
5
6
7
8
9
while t r u e
drehen i f kara . t r e e F r o n t
kara . move
end
Das Programm muss gespeichert werden, ehe man es starten kann.
K ARA ist (momentan) für Methoden nicht verfügbar. Mit der ersten Zeile @kara=kara
wird K ARA an die globale Variable @kara gebunden und kann auch von Methoden verwendet werden. Man sollte diese Zuweisung immer an den Anfang einer neuen Programmdatei stellen.
Die Methode times wiederholt den durch geschweifte Klammern abgetrennten Block so
oft, wie die ganze Zahl, für die times aufgerufen wurde angibt.
Eine weitere Besonderheit ist die Verwendung von if in der while-Schleife als Modifikator. Der vor if stehende Ausdruck wird nur ausgewertet, wenn die hinter if stehende
Bedingung erfüllt ist.
Die Schleife läuft endlos, weil die hinter while stehende Schleifenbedingung nie falsch
werden kann.
3
Struktogramme
Struktogramme ermöglichen es, den Ablauf eines Programms graphisch zu entwerfen. Das ganze Programm wird in einem Rechteck eingetragen. Schleifen, Fallunterscheidungen und Methodenaufrufe (für selbstgeschriebene Methoden) werden wie folgt dargestellt:
• while-Schleifen
Schleif enbedingung
Ausdruck1
···
Ausdruckn
4
• Fallunterscheidungen
PP
PP
Bedingung
PP
P
T PPPP F
True-Teil
False-Teil
• Methodenaufrufe
Methode
Beispiel:
Hier ist ein einfaches Beispiel für ein Struktogramm zu einem R UBY K ARA -Programm. K ARA
soll zum nächsten Baum laufen und unterwegs alle Felder invertieren, d.h. auf Feldern mit Blatt
das Blatt wegnehmen und auf Feldern ohne Blatt ein Blatt ablegen.
ZumBaum
invertiereFeld
not treeFront
move
invertiereFeld
In diesem Struktogramm wird eine Anweisung verwendet, die K ARA nicht kennt: invertiereFeld.
Diese Anweisung wird als Methode implementiert. Das zugehörige Struktogramm ist:
invertiereFeld()
PP
PP
P
onLeaf
PP
P
PP
T
F
P
removeLeaf
putLeaf
Diese Struktogramme übersetzen sich in folgendes R UBY K ARA -Programm:
1
2
3
4
5
6
7
8
@kara=kara
def i n v e r t i e r e F e l d
i f @kara . onLeaf
@kara . removeLeaf
else
@kara . putLeaf
end
end
9
10
11
invertiereFeld
while ! kara . t r e e F r o n t
5
12
13
14
kara . move
invertiereFeld
end
4
Variablen
Aufgabe: K ARA soll zu einem Baum laufen, sich umdrehen und dann weiter laufen. Das soll
er fünf mal wiederholen.
Im Automatenmodell wurden für diese Aufgabe fünf Zustände benötigt. Ändert man die Anzahl der Wiederholungen, so ändert sich die Anzahl der benötigten Zustände entsprechend. Es
gibt im Automatenmodell keine einfache Möglichkeit, beliebig weit zu zählen.
In R UBY K ARA kann man zählen, indem man Variablen verwendet. Variablen bieten eine Möglichkeit, Daten im Speicher des Rechners abzulegen und zu verändern. Dazu muss man sich
den Speicherort merken, um auf die gespeicherten Daten zugreifen zu können. Zu diesem
Zweck werden Variablen verwendet. In R UBY erfolgt das durch die Angabe eines Namens und
eines Anfangswertes:
zähler = 0
Diese Anweisung erzeugt eine Variable zähler und speichert 0 als Wert dieser Variablen.
Wenn man sich ein Gedankenmodell für Variable machen will, kann man sich folgendes vorstellen: mit einer Deklaration einer Variablen wird eine Schachtel passender Größe angefertigt und
mit einem Namensschild versehen. Ist ein Anfangswert angegeben, so wird dieser auf einen
Zettel geschrieben und der Zettel in die Schachtel gelegt. Die Schachtel kommt in ein Lager,
wo sie über den aufgeklebten Namen wieder gefunden werden kann.
Nun soll mit Hilfe einer Variablen das anfangs gestellte Problem gelöst werden. Zunächst ein
Struktogramm:
FünfRunden
int runden ← 0
runden < 5
h
hhhh
hhhh
hhtreeFront
hhh
W
h
h F
drehen
move
runden ← runden + 1
Die Zuweisung eines Wertes zu einer Variablen symbolisiert man im Struktogramm durch
einen linksgerichteten Pfeil ←. Besonders interessant ist dabei die Zuweisung in der Fallunterscheidung. Sie zeigt an, dass der Variable runden ihr um 1 erhöhter alter Wert zugewiesen
werden soll. Da dies jedesmal geschieht, wenn K ARA vor einem Baum steht, zählt diese Variable die von K ARA zurückgelegten Runden. Nach 5 Runden hat die Variable den Wert 5, die
Schleifenbedingung ist also nicht mehr erfüllt und K ARA bleibt stehen.
Im zugehörige R UBY K ARA -Programm wird die Zuweisung von Werten an die Variable runden
mit dem Gleichheitszeichen = geschrieben. Diese Verwendung entspricht natürlich nicht dem
Gebrauch von = in der Mathematik, ist aber in vielen Programmiersprachen üblich. Für Vergleiche von Zahlen kann man mit <, >, == Bedingungen formulieren (das doppelte Gleichheitszeichen überprüft die Gleichheit zweier Zahlen).
6
1
z ä h l e r = 0
2
3
4
5
6
7
8
9
10
while z ä h l e r < 5
i f kara . t r e e F r o n t
2 . ti me s { kara . t u r n L e f t }
z ä h l e r = z ä h l e r + 1
else
kara . move
end
end
Aufgaben
Fertigen Sie für die folgenden Aufgaben jeweils ein Struktogramm an, ehe Sie versuchen das
Programm zu erstellen. verwenden Sie wo möglich eigene Methoden.
1. Bearbeiten Sie die Aufgabe Tunnelsucher II und die drei Aufgaben zur Kleeblattsuche im
Wald.
2. Bearbeiten Sie die Aufgabe Pacman.
3. K ARA soll ein 4 × 5-Rechteck mit Blättern belegen.
4. K ARA soll ein Schachbrett (8 × 8) auslegen (weisse Felder leer, schwarze Felder mit Blatt).
Lösungen
Aufgabe 1:
Tunnelsucher:
Den zwei benötigten Zuständen für dieses Problem im Automatenmodell entsprechen hier
zwei while-Schleifen.
TunnelEnde
not(treeLeft and treeRight)
move
treeLeft and treeRight
move
Das zugehörige Programm:
1
2
kara . move while not ( kara . t r e e L e f t and kara . t r e e R i g h t )
kara . move while kara . t r e e L e f t and kara . t r e e R i g h t
Wald I
Das Struktogramm verwendet die Methode umBaum, die noch definiert werden muss.
Wald I
not onLeaf
XX
treeFront
X
T XXXX
F
X
XXX
umBaum
removeLeaf
move
7
Das zugehörige Programm:
1
2
3
4
5
6
7
8
9
10
@kara=kara
def umBaum
@kara . t u r n L e f t
@kara . move
@kara . t u r n R i g h t
2 . ti me s { @kara . move}
@kara . t u r n R i g h t
@kara . move
@kara . t u r n L e f t
end
11
12
13
14
15
16
17
18
19
while not kara . onLeaf
i f kara . t r e e F r o n t
umBaum
else
kara . move
end
end
kara . removeLeaf
Wald II
Hier muss lediglich die Methode umBaum angepasst werden. Der mittlere Teil mit den zwei
move-Befehlen muss ersetzt werden durch eine Schleife, um beliebig viele Bäume zu umgehen:
1
2
3
4
5
6
7
8
9
10
def umBaum
@kara . t u r n L e f t
@kara . move
@kara . t u r n R i g h t
@kara . move
@kara . move while @kara . t r e e R i g h t
@kara . t u r n R i g h t
@kara . move
@kara . t u r n L e f t
end
Wald III
Hier wird eine Unterscheidung von drei verschiedenen Fällen benötigt: Baum vorn, Baum links
und Baum rechts. Man erledigt das mit einer geschachtelten Fallunterscheidung. Man prüft
zunächst, ob vorn ein Baum steht. Ist dies nicht der Fall, prüft man, ob rechts oder links ein
Baum steht.
Man erhält folgendes Struktogramm:
8
Wald3
not onLeaf
PP
PP
P
treeFront
PP
P
PP
P
T
PP
PP
P
PP
PP
P
not treeLeft
PP
P
T
turnLeft
F
PP
PP
P
F
PP
P
∅
turnRight
move
removeLeaf
Dies führt zu folgendem Programm. Die geschachtelte Fallunterscheidung wird aus dem Struktogramm so übertragen, dass innerhalb der ersten Fallunterscheidung eine zweite eingefügt
wird. Dies lässt sich beliebig oft wiederholen.
1
2
3
4
5
6
7
8
9
10
11
while not kara . onLeaf
i f kara . t r e e F r o n t
i f not kara . t r e e L e f t
kara . t u r n L e f t
else
kara . t u r n R i g h t
end
end
kara . move
end
kara . removeLeaf
Aufgabe 2:
Pacman
Dieses Programm benötigt eine Schleife und innerhalb der Schleife eine geschachtelte Fallunterscheidung. Im ersten Fall ist nichts zu tun, falls K ARA auf einem Blatt ist, im anderen Fall
muss K ARA das nächste Blatt erst lokalisieren und sich auf das Feld mit dem Blatt begeben. Die
Schleife hat also als Invariante die Bedingung, dass K ARA auf einem Blatt stehen muss. Dieses
wird am Ende jedes Schleifendurchgangs entfernt.
9
Pacman
removeLeaf
not treeFront
hhh
hh
hhh
hhhh
move
not onLeaf
hhh
hh
T
hhhh
h
hhh
zurück
turnRight
move
hhhh
hhhh
not onLeaf
hhh
hhhh
hhh
T
F
h
zurück
∅
move
removeLeaf
F
∅
Um K ARA auf das nächste Blatt zu bewegen wird die Methode zurück verwendet. Das benötigte Struktogramm für die Methode zurück ist:
zurück
turnLeft()
turnLeft()
move()
Daraus erhält man das folgende Programm:
1
2
3
4
5
@kara=kara
def zur ück
# umdrehen
2 . ti me s { @kara . t u r n L e f t }
@kara . move
end
und
z u r ü c k
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
kara . removeLeaf
while not kara . t r e e F r o n t
kara . move
i f not kara . onLeaf # k e i n B l a t t v o r n
zur ück
kara . t u r n R i g h t
kara . move
i f not kara . onLeaf # n i c h t s l i n k s
zur ück
kara . move # n a c h r e c h t s !
end
end
kara . removeLeaf # h i e r m u s s w a s l i e g e n
end
10
Aufgabe 3:
Für das Rechteck erhält man zunächst eine Programmstruktur mit zwei ineinander geschachtelten Schleifen. Eine äußere Schleife ist für die aufeinanderfolgende Anordnung der Reihen
des Rechtecks zuständig, in einer inneren Schleife wird jeweils eine einzelne Reihe ausgegeben.
Rechteck
4 mal
reiheLegen()
nächsteReihe()
Das Legen einer Reihe wird als Methode formuliert und mit einer Schleife realisiert:
reiheLegen
nächsteReihe()
2 mal turnLeft()
5 mal
move()
turnLeft()
move()
turnLeft()
5 mal
putLeaf()
move()
Der Wechsel zur nächsten Reihe ist ebenfalls als Methode realisiert. K ARA geht zum Anfang der
vorigen Reihe und wechselt dann in die zu legende neue Reihe. Das scheint umständlich, aber
für abwechselnde Links- und Rechtsdurchgänge benötigt man jetzt noch nicht besprochene
Hilfsmittel. Das folgende Programm zeichnet das gewünschte Rechteck.
1
2
3
4
5
6
7
@kara=kara
def reiheLegen
5 . ti me s do
@kara . putLeaf
@kara . move
end
end
8
9
10
11
12
13
14
15
def n ä c h s t e R e i h e
2 . ti me s { @kara . t u r n L e f t }
5 . ti me s { @kara . move}
@kara . t u r n L e f t
@kara . move
@kara . t u r n L e f t
end
16
17
18
19
20
4 . time s do
reiheLegen
n ä c h s t e R e i h e
end
Aufgabe 4
11
Diese Aufgabe ist eine Variante des Rechteck-Problems. Allerdings müssen zwei aufeinander
folgende Reihen um eins gegeneinander versetzt gelegt werden. Die folgenden Struktogramme
beschreiben eine mögliche Lösung.
Schachbrett
4 mal
reiheLegen1()
nächsteReihe()
reiheLegen2()
nächsteReihe()
Die Methoden reiheLegen1() und reiheLegen2 unterscheiden sich nur geringfügig.
reiheLegen1
reiheLegen2
4 mal
4 mal
move
putLeaf
move
putLeaf
2 mal move
Die Methode nächsteReihe() wird durch folgendes Struktogramm beschrieben:
nächsteReihe()
2 mal turnLeft
8 mal move
turnLeft
move
turnLeft
Das fertige Programm sieht dann so aus:
1
2
3
4
5
6
7
@kara=kara
def reiheLegen1
4 . ti me s do
@kara . putLeaf
2 . ti me s { @kara . move}
end
end
8
9
10
11
12
13
14
15
def reiheLegen2
4 . ti me s do
@kara . move
@kara . putLeaf
@kara . move
end
end
16
17
18
19
20
def n ä c h s t e R e i h e
2 . ti me s { @kara . t u r n L e f t }
8 . ti me s { @kara . move}
@kara . t u r n L e f t
12
21
22
23
@kara . move
@kara . t u r n L e f t
end
24
25
26
27
28
29
30
31
#
Hauptprogramm
4 . time s do
reiheLegen1
n ä c h s t e R e i h e
reiheLegen2
n ä c h s t e R e i h e
end
5
Methoden mit Parametern und Rückgabewerten, Eingaben und
Ausgaben
Häufig will man einer Methode beim Aufruf Informationen übergeben, die den Ablauf der
Methode beeinflussen können. Bisher wurde nur das Objekt kara an Methoden übergeben. Oft
will man aber weitere Informationen zur Steuerung der Methode übergeben.
Als erstes Beispiel soll das erläutern. Es soll eine Methode reiheLegen(n) vorgestellt werden,
die K ARA veranlasst, n Schritte zu gehen und dabei auf nicht belegten Feldern ein Blatt abzulegen.
1
2
3
4
5
6
def reiheLegen ( n )
n . t im e s do
@kara . putLeaf i f not @kara . onLeaf
@kara . move
end
end
Ruft man diese Methode beispielsweise mit reiheLegen(7) auf, so bewegt sich K ARA 7 Felder
in seiner gegenwärtigen Richtung und belegt alle leeren Felder, die er passiert mit einem Blatt.
Das letzte Feld, auf dem K ARA stehen bleibt, wird nicht belegt. Das Ausgangsfeld hingegen
wird belegt.
Im Unterschied zu den bisherigen Definitionen von Methoden steht hier in der Klammer hinter
dem Methodennamen ein Parameter.
Beim Aufruf einer Methode mit Parametern müssen in die Klammern hinter dem Methodennamen Ausdrücke für die Parameter eingetragen werden (durch Kommata getrennt, falls es
mehrere Parameter gibt). Rechenausdrücke sind bei der Parameterübergabe erlaubt.
Aufgaben:
1.
1
2
3
4
5
6
def reiheLegen ( n )
n . ti me s do
@kara . move
@kara . putLeaf i f not @kara . onLeaf
end
end
13
Was ändert sich bei der Methode
reiheLegen(n), wenn sie wie neben stehend formuliert wird?
2. Schreiben Sie eine Methode turnLeft(int anzahl) und eine Methode turnRight(int
anzahl), die K ARA veranlasst, sich so oft nach links bzw. rechts zu drehen, wie anzahl
angibt. Programmieren Sie ein passendes Verhalten für negative Werte von anzahl.
3. Verändern Sie das Schachbrettprogramm (Seite 7) so, dass nur eine Methode zum Legen
der Reihen benötigt wird und die Größe des Schachbretts variabel gehalten wird.
In vielen Fällen möchte man erst zur Laufzeit Daten eingeben können. Dazu stellt R UBY K ARA
unter anderen (Seite 18) die Methode intInput(Titel) zur Verfügung. Diese Methode wird
nun verwendet, um das Rechteck-Programm zu verallgemeinern. intInput benötigt als Parameter eine Eingabeaufforderung in Form eines Strings (Zeichenkette). Strings werden eingegeben, indem man den gewünschten Text in Anführungszeichen einschließt. Außerdem hat
die Methode eine ganze Zahl als Rückgabewert. Dazu wird durch die Methode ein Fenster mit
dem Text als Eingabeaufforderung und einem Eingabefeld geöffnet. In dieses Feld wird die
gewünschte Zahl eingegeben.
Man kann deshalb Anweisungen wie
anzahl = tools.intInput("Geben Sie eine Anzahl an!")
schreiben. Dabei wird beim Lauf des Programms ein Fenster geöffnet und die dort eingegebene
Zahl der Variablen anzahl zugewiesen. Für das Rechteckprogramm benötigt man nur kleine
Änderungen:
1
2
3
4
5
6
7
@kara=kara
def reiheLegen ( n )
n . t im es do
@kara . putLeaf
@kara . move
end
end
8
9
10
11
12
13
14
15
def n ä c h s t e R e i h e ( n )
2 . ti me s { @kara . t u r n L e f t }
n . t im e s { @kara . move}
@kara . t u r n L e f t
@kara . move
@kara . t u r n L e f t
end
16
17
18
19
20
21
22
23
#
Hauptprogramm
hoehe = t o o l s . i n t I n p u t ( ”Höhe des R e ch t e c ks ? ” )
b r e i t e = t o o l s . i n t I n p u t ( ” B r e i t e des R e ch t e c ks ? ” )
hoehe . t im es do
reiheLegen ( b r e i t e )
n ä c h s t e R e i h e ( b r e i t e )
end
K ARA soll nun zu einem Baum laufen, die Anzahl der Schritte zählen, zu seinem Ausgangspunkt zurückkehren und die Anzahl der Schritte bis zum Baum ausgeben.
Für die Ausgabe verwenden wir die Methode (Seite 18) tools.showMessage(String Text).
Die Schritte werden in einer Variablen mitgezählt. Dies führt zu folgendem Programm.
14
1
2
3
4
5
6
7
8
9
s c h r i t t e =0
while not kara . t r e e F r o n t
kara . move
s c h r i t t e +=1
end
2 . time s { kara . t u r n L e f t }
1 . upto ( s c h r i t t e ) { kara . move}
2 . time s { kara . t u r n L e f t }
t o o l s . showMessage ( ”Zum Baum sind es #{ s c h r i t t e } S c h r i t t e . ” )
Besondere Beachtung verdient die Art, wie hier der Text der Meldung zusammengestellt wird.
In R UBY wird Text (Datentyp: String) durch Anführungszeichen begrenzt. Werte von Variablen
oder Ausdrücken können mit dem Konstrukt #{...} in Text eingefügt werden. R UBY wandelt
in diesem Fall die Ausdrücke mit der Methode to s in ihre Textdarstellung um.
Man kann auch Methoden schreiben, die Werte als Ergebnis liefern, das zu weiteren Berechnungen verwendet werden kann. In der Methode wird dann mit return Wert ein Wert passenden
Types zurückgegeben. Dazu ein einfaches Beispiel. Die Methode treeBack? liefert true , falls
hinter K ARA ein Baum ist.
1
2
3
4
5
6
7
@kara=kara
def t r e e B a c k ?
@kara . t u r n L e f t
erg=@kara . t r e e L e f t
@kara . t u r n R i g h t
r e t u r n erg
end
Aufgaben
3. Schreiben Sie eine Methode mushroomLeft? und eine Methode
mushroomRight? und testen Sie diese in geeigneter Umgebung.
6
Schleifen
Bisher sind Schleifen mit nicht vorherbestimmter Anzahl von Wiederholungen mit der whileAnweisung konstruiert worden
1
2
3
while Bedingung do
Anweisung ( en )
end
Der Anweisungsblock zwischen do und end wird dabei so lange wiederholt ausgeführt, wie die
Bedingung den Wert true liefert. Es kann also auch vorkommen, dass der Block gar nicht ausgeführt wird, weil die Bedingung schon zu Beginn nicht erfüllt ist. Man nennt solche Schleifen
deshalb auch abweisende Schleifen oder Schleifen mit Vorbedingung.
Für Schleifen mit einer festen Anzahl n von Wiederholungen wurde die Iteration
1
n . tim es {Anweisung}
15
oder
1
2
3
1 . upto {n} do | i |
# Anweisungen
die
den
jeweiligen
Wert
von
i
verwenden
end
verwendet. An Stelle der Konstruktion
while not Bedingung do ... kann man auch
until Bedingung do ... verwenden. Beide Schleifenkonstrukte können auch als Modifikatoren verwendet werden:
Beispiele:
kara.move until kara.treeFront
x=0
x=x+1 while x<100
eingabe=-1
eingabe=tools.intInput("Zahl eingeben") until (eingabe>0) and (eingabe<100)
A CHTUNG : Die Initialisierung von x und eingabe ist dabei notwendig!
Aufgaben
1. Fertigen Sie für das folgende Programm ein Struktogramm an und erläutern Sie seine
Funktion.
1
@kara = kara
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def reiheLegen ( r e c h t s )
4 . ti me s do
@kara . putLeaf
2 . ti me s { @kara . move}
end
i f r e c h t s then
@kara . t u r n R i g h t
@kara . move
@kara . t u r n R i g h t
@kara . move
else
@kara . t u r n L e f t
@kara . move
@kara . t u r n L e f t
@kara . move
end
end
20
21
22
23
24
4 . t im es do
reiheLegen ( t r u e )
reiheLegen ( f a l s e )
end
16
2. Schreiben Sie ein Programm, das vom Benutzer die Anzahl der Strecken und die Anzahl
der Blätter erfragt, die bei jeder Strecke im Vergleich zur vorigen mehr gelegt werden
sollen und dann entsprechend Blätter legt.
3. Schreiben Sie ein Programm das vom Benutzer die Anzahl der gewünschten Reihen erfragt
und dann ein entsprechendes (gefülltes) Dreieck aus Blättern legt.
4. Schreiben Sie ein Programm, das vom Benutzer die Anzahl der gewünschten Reihen erfragt und dann den Rand eines entsprechenden (gleichschenklig rechtwinkligen) Dreieck
aus Blättern legt.
5. Schreiben Sie ein Programm, das vom Benutzer die Anzahl der gewünschten Reihen erfragt
und dann den Rand eines entsprechenden Quadrats aus Blättern legt.
7
Vordefinierte Methoden in R UBY K ARA
In der Umgebung von R UBY K ARA stehen unterschiedliche Methoden zur Verfügung. Man
kann sich eine Kurzbeschreibung der Methoden mit der Hilfe-Schaltfläche des Welt-Fensters
anzeigen lassen. Die Methoden sind in verschiedene Gruppen eingeteilt.
Zunächst sind einige der Methoden dazu da, K ARA zu Aktionen zu veranlassen:
move
turnLeft
turnRight
putLeaf
removeLeaf
Ein Feld vorwärts
Vierteldrehung nach links
Vierteldrehung nach rechts
Blatt ablegen
Blatt aufnehmen
Die Sensoren werden über Methoden abgefragt, die als Ergebnis true oder false zurückgeben.
treeFront
treeLeft
treeRight
onLeaf
mushroomFront
Baum vorn?
Baum links?
Baum rechts?
auf Blatt?
vor Pilz?
Schließlich gibt es noch Methoden, K ARA direkt auf einem bestimmten Feld zu positionieren
und eine Methode, Karas Position abzufragen.
17
setPosition (x,y)
setDirection(d)
getPosition.getX
getPosition.getY
setzt K ARA auf das Feld mit den Koordinaten (x; y)
setzt K ARAS Richtung: 0: Nord, 1: West, 2: Süd, 3: Ost
liefert K ARAS x-Koordinate
liefert K ARAS y-Koordinate
Alle diese Methoden müssen mit dem Präfix kara aufgerufen werden, also z. B. nicht move,
sondern kara.move. Leider gibt es keine Methode zum Ermitteln von K ARAS Richtung.
Es gibt auch Methoden zur direkten Veränderung von K ARAS Welt. Sie müssen mit dem Prefix
world aufgerufen werden.
clearAll
setLeaf (x,y,legen?)
setTree (x,y,legen?)
setMushroom (x,y,legen?)
setSize (xMax,yMax)
getSizeX
getSizeY
isEmpty(x,y)
isTree(x,y)
isLeaf(x,y)
isMushroom(x,y)
Alles entfernen (auch K ARA ).
legt (legen? = true ) oder entfernt (legen? = false ) ein Blatt bei (x;y)
legt oder entfernt einen Baum bei (x;y)
legt oder entfernt einen Pilz bei (x;y)
Neue Größe der Welt festlegen
horizontale Weltgröße zurückgeben
vertikale Weltgröße zurückgeben
true , falls Feld (x;y) leer ist
true , falls ein Baum auf Feld (x;y) ist
true , falls ein Blatt auf Feld (x;y) ist
true , falls ein Pilz auf Feld (x;y) ist
Die nun folgenden Methoden sind in der Klasse tools definiert, müssen also mit dem Prefix
tools aufgerufen werden. Diese Methoden sind vor allem für Ein- und Ausgaben nützlich. Der
Typ string ist für Zeichenketten (also Texte) bestimmt.
showMessage(text)
stringInput(Titel)
intInput(Titel)
doubleInput(Titel)
random(Obergrenze)
sleep(ms)
checkState
gibt text in einem Dialogfenster aus
fordert die Eingabe eines Strings in einem Dialogfenster.
Titel liefert die Fensterüberschrift.
Die Rückgabe ist die Konstante null, falls
die Eingabe abgebrochen wird.
Eingabe einer ganzen Zahl in einem Dialogfenster
mit der Überschrift Titel. Das Ergebnis ist
Integer.MIN_VALUE, wenn die Eingabe abgebrochen
wird oder keine ganze Zahl eingegeben wird.
Eingabe einer Dezimalzahl in einem Dialogfenster
mit der Überschrift Titel. Das Ergebnis ist
Double.MIN_VALUE, wenn die Eingabe abgebrochen
wird oder keine Dezimalzahl eingeben wird.
liefert eine ganze Zufallszahl aus dem
Intervall [0; Obergrenze]
stoppt die Programmausführung für ms Millisekunden
überprüft die Bedienungsleiste.
Unter Verwendung dieser Methoden kann man viele interessante kleine Probleme behandeln.
Aufgaben
1. Erzeugen Sie eine 20 × 20-Welt, umranden Sie sie mit Bäumen und setzen Sie K ARA auf
das Feld (10|10) mit Richtung Norden.
18
2. Schreiben Sie ein Programm, das die Anzahl der Bäume einer Welt ermittelt und in einem
Message-Fenster ausgibt.
A NLEITUNG : Ermitteln Sie die Breite und Höhe der Welt und untersuchen Sie in einer
geschachtelten Schleife die Felder der Welt darauf, ob auf ihnen ein Baum steht. K ARA
wird dazu nicht benötigt!
3. Schreiben Sie ein Programm, das eine leere 15 × 15-Welt erzeugt und in ihr per Zufall 25
Bäume verteilt.
H INWEIS : Verwenden sie die world- und tools-Funktionen. Erzeugen Sie zwei Zufallszahlen, um die Zeile und Spalte eines Feldes für einen Baum auszuwählen. Beachten Sie,
dass kein Feld mehr als einmal ausgewählt werden darf.
4. Lassen Sie K ARA auf einem Feld beliebiger Größe eine Zufallswanderung durchführen.
Bei jedem Schritt geht K ARA zufallsgesteuert ein Feld nach vorn oder nach links oder
nach rechts und legt ein Blatt ab, falls dort keines liegt. Zählen Sie die Anzahl der Schritte,
bis die Welt gefüllt ist.
H INWEIS :
Verwenden Sie random.
5. Ändern Sie das vorige Programm so ab, dass K ARA bei belegten Feldern auf seinem Weg
das Blatt aufnimmt. Lassen Sie K ARA etwa so viele Schritte tun, wie in der vorigen Aufgabe zum Füllen der Welt benötigt wurden und prüfen Sie, wieviel der Welt dann mit
Blättern bedeckt ist.
6. Man kann mit world.setLeaf(x,y,true) auf dem Feld (x|y) ein Blatt ablegen. Man kann
dies verwenden, um Muster zu erzeugen. Schreiben Sie eine Klasse, die eine 20 × 20-Welt
erzeugt, im oberen linken Eck ein zufälliges 4 × 4-Muster aus Kleeblättern erzeugt und
dieses Muster dann auf die ganze Welt kopiert.
⇒
7. Die angezeigten Ziffern sollen jeweils mit einer eigenen Methode erzeugt werden.
Dabei gibt es folgende Vorgaben:
• Jede Ziffer beansprucht ein Rechteck mit 6 × 8 Feldern.
• In den seitlichen Rändern des Rechtecks liegen keine Blätter.
• die Methode erhält als Eingabe die Koordinaten des linken unteren Eckfelds des
Rechtecks.
Die Eins kann man beispielsweise wie folgt erzeugen:
19
1
2
3
4
5
def e i n s ( x , y )
2 . upto ( 4 ) { | i | @world . s e t L e a f ( x+ i , y , t r u e ) }
1 . upto ( 7 ) { | i | @world . s e t L e a f ( x +3 , y−i , t r u e ) }
1 . upto ( 2 ) { | i | @world . s e t L e a f ( x+ i , y−4−i , t r u e ) }
end
Analysieren Sie diese Methode und schreiben Sie Methoden für die restlichen Ziffern.
Schreiben Sie damit ein Programm, welches das oben gezeigte Bild erzeugt.
H INWEIS : Es empfiehlt sich, eine allgemeine Methode
public void schreibe(int ziffer,int x,int y)
zu schreiben, die je nach Ziffer die passende Schreibmethode wählt. Dazu sollte man sich
ansehen (Internet), wie man Mehrfachfallunterscheidungen in R UBY mit case realisiert.
8. Schreiben Sie unter Verwendung der vorigen Aufgabe ein Programm, das eine natürliche
Zahl vom Benutzer erfragt und diese Zahl dann in eine passende Welt schreibt.
H INWEIS : Den Divisionsrest von a:b erhält man durch a % b. Im Bereich der ganzen Zahlen liefert R UBY immer den ganzzahligen Quotienten ohne Rest, wenn man a/b berechnet. anders ausgedrückt: Gilt a = q · b + r mit r < b, so ist a/b = q und a % b = r.
9. Von Manfred Eigen (http://de.wikipedia.
org/wiki/Manfred_Eigen) stammt die folgende Simulation zum Thema Evolution.
Man nehme ein 8 × 8-Feld und belege es in jedem der vier Teilquadrate mit einem Symbol,
wie rechts gezeigt (das vierte Symbol“ ist hier
”
ganz einfach ein leeres Feld). Nun wird folgendes Verfahren wiederholt, bis nur noch eine
Symbolsorte auf dem Feld ist: Man wählt per
Zufall zwei verschiedene Felder des Quadrats.
Das Symbol auf dem ersten Feld wird entfernt
und durch ein Symbol von der Art des auf dem
zweiten Feld befindlichen ersetzt.
Startbelegung
Schreiben Sie ein Programm, das diese Simulation durchführt.
8
Erweiterungen
Eine Reihe von Problemen wären einfacher lösbar, wenn K ARA mehr Sensoren hätte. Man kann
eigene Sensoren als Methoden leicht zusätzlich definieren.
Beispiel: Es soll ein Sensor definiert werden, der einen Pilz links von K ARA entdeckt.
Dazu muss sich K ARA nach links drehen, sich merken, ob jetzt vor ihm ein Pilz steht und sich
dann zurück nach rechts drehen.
1
2
3
4
def p i l z L i n k s
@kara . t u r n L e f t
p i l z =@kara . mushroomFront
@kara . t u r n R i g h t
20
5
6
return p i l z
end
Nach diesem Verfahren kann man natürlich auch einen Sensor für rechts von K ARA stehende
Pilze definieren. Allerdings hat das Verfahren den Nachteil, dass K ARA bewegt werden muss,
was die Programmausführung verlangsamt. Will man entsprechend Sensoren definieren, die
erkennen, ob K ARA links von, rechts von oder vor einem Blatt steht, so wird der Aufwand
noch höher, weil der vorhandene Sensor Blätter nur erkennt, wenn K ARA auf ihnen steht. Um
zu erkennen, ob K ARA vor einem Blatt steht, muss K ARA daher einen Schritt vorwärts machen.
Das geht aber nicht immer. K ARA könnte vor einem Baum stehen, dann kann er keinen Schritt
vorwärts machen. Steht er vor einem Pilz, so kann er vielleicht einen Schritt vorwärts machen,
verschiebt aber dabei den Pilz.
Wenn man die Methoden der Klasse world verwendet, kann man alternativ etwa folgenden
Sensor erzeugen:
1
2
3
4
5
6
7
8
9
10
def l e a f S o u t h
y=@kara . g e t P o s i t i o n . getY
x=@kara . g e t P o s i t i o n . getX
ymax= @world . getSizeY −1
i f y==ymax
r e t u r n @world . i s L e a f ( x , 0 )
else
r e t u r n @world . i s L e a f ( x , y + 1 ) ;
end
end
Hier wird zunächst K ARAS Position abgefragt. Dabei muss berücksichtigt werden, dass die
Welt in form einer Torus vorliegt. Wenn K ARA auf der untersten Zeile steht, so ist die oberste
Zeile südlich“ von K ARA . Dem wird mit der Fallunterscheidung Rechnung getragen.
”
Entsprechend kann man auch Methoden schreiben, die K ARA in eine bestimmte Himmelsrichtung drehen. Natürlich kann das auch direkt mit kara.setDirection bewerkstelligt werden,
aber eine passende Neudefinition ist einprägsamer:
1
2
3
def t u r n E a s t
@kara . s e t D i r e c t i o n ( 3 )
end
Wenn man eine Reihe solcher Definitionen häufiger verwenden will, kann man sie in einer eigenen von JavaKaraProgram abgeleiteten Klasse definieren. Die ganzen Definitionen der Sensoren und die Drehungen sind beispielsweise in der Datei Kepi.rb zusammengestellt. Die Datei
enthält nur Definitionen und kein Programm. Speichert man diese Datei im Verzeichnis der
Datei rubykara.jar, so kann man danach mit dem Befehl require ’Kepi.rb’ zu Beginn einer
Programmdatei diese Definitionen einschließen und verwenden.
Das folgende Programm verwendet diese Methode, um eine vereinfachte Fassung von Pacman
zu erzeugen:
1
r e q u i r e ’ Kepi . rb ’
2
3
4
@kara . removeLeaf i f @kara . onLeaf
while not @kara . t r e e F r o n t
21
5
6
7
8
9
10
11
12
13
14
15
16
17
if leafEast
turnEast
e l s i f leafWest
turnWest
e l s i f leafSouth
turnSouth
e l s i f leafNorth
turnNorth
e l s e r a i s e ” Kein B l a t t weit und b r e i t : −(
end
@kara . move
@kara . removeLeaf
end
”
Aufgaben
1. Schreiben Sie nach den Beispielen in diesem Abschnitt die vollständige Datei Kepi.rb
und testen Sie das pacman-Programm.
9
Arrays
Wir wollen den verwendeten Zufallszahlengenerator tools.random(n) benutzen, um einen
Würfel zu simulieren. Dazu sollen hundert Zufallsszahlen im Bereich von 1 bis 6 erzeugt werden. Wir wollen die Anzahlen festhalten, mit denen jede der Zahlen erzeugt wird.
Die Zufallszahlen werden mit folgender Methode erzeugt (random(5) erzeugt Zahlen von 0 bis
5):
1
2
3
def w ürfel
r e t u r n (1+ @ t o o l s . random ( 5 ) ) ;
}
Zum Mitzählen der einzelnen Ergebnisse könnte man jetzt sechs Variablen definieren und diese
mit einer Fallunterscheidung bei jedem Ergebnis um eins erhöhen. Dies wäre äußerst umständlich. Da solche und ähnliche Probleme häufig auftauchen, sehen die meisten Programmiersprachen auch Datenstrukturen vor, die solche Aufgaben vereinfachen.
Will man mehrere Daten gleichen Typs gruppieren, verwendet man einen Array (deutsch:
Feld). In einem Array werden eine feste Anzahl von Daten gleichen Typs gespeichert. Auf die
einzelnen Daten kann man über einen Index zugreifen. In R UBY werden Arrays beispielsweise
wie folgt vereinbart:
zahlen=[1,2,3,4,5,6];
Diese Anweisung definiert eine Array-Variable zahlen mit 6 Einträge vom Typ int, die mit
den in den eckigen Klammern stehenden Zahlen initialisiert werden. Auf das dritte Element
des Arrays wird mit dem Ausdruck zahlen[2] zugegriffen (die Zählung der Array-Elemente
beginnt mit 0).
Alternativ kann man ein Array auch ohne Initialisierung definieren und die Zuweisung von
Werten später vornehmen. Mit der folgenden Methode wird die Anzahl der Elemente des Arrays festgelegt. Dabei wird automatisch jedes Element mit einem Standardwert nil initialisiert.
22
werte=Array.new(100);
erzeugt einen Array mit 100 Elementen , die alle mit nil initialisiert werden. Man kann auch
Blöcke zur Initialisierung verwenden:
zahlen=Array.new(6) {|i| i+1}
erzeugt ebenfalls den Array [1,2,3,4,5,6]. Der Variablen i des Blocks wird dabei für jedes
neue Arrayelement dessen Index übergeben. Das Ergebnis der Berechnung des Blocks wird
dann diesem Element zugewiesen. Mit
zahlen=Array.new(10){|i| [i,i*i]}
erhält man einen Array von 10 zweielementigen Arrays die jeweil eine der Zahlen von 0 bis 9
und ihr Quadrat enthalten.
Das Programm für die 100 Würfe des Würfels könnte so aussehen:
1
2
3
e r g e b n i s =Array . new ( 6 , 0 )
1 0 0 . t im e s { e r g e b n i s [ @ t o o l s . random ( 5 ) ] +=1}
@ t o o l s . showMessage ( e r g e b n i s . i n s p e c t )
Der Array ergebnis wird mit 6 Elementen definiert, die alle den Wert 0 erhalten. So kann man
für jede Zahl i des Würfels ganz einfach das ite Element von ergebnis als Zähler verwenden.
Da die Array-Indizes von 0 bis 5 gehen, verwendet man einen Würfel“ mit den Augenzahlen
”
von 0 bis 5. Man kann natürlich leicht auch größere Anzahlen von Würfen simulieren. Mit
inspect wird der Array für die Ausgabe in einen String umgewandelt.
Aufgaben
1. Schreiben Sie ein Programm, das vom Benutzer den Funktionsterm einer Funktion (Funktionsvariable: x) anfordert und für diesen Funktionsterm eine Wertetabelle für die ganzzahligen Werte von x von -5 bis 5 ausgibt.
H INWEIS : @tools.stringInput kann zur Eingabe des Terms verwendet werden. Die Methode eval kann einen String auswerten, wenn dieser einen gültigen R UBY -Ausdruck
darstellt.
B EISPIEL x=3; eval("x**2+2*x+1") liefert als Ergebnis 16.
2. Schreiben Sie ein Programm, welches die Bäume, Pilze und Blätter einer Welt mit einem
Array zählt und das Ergebnis anzeigt.
3. Simulieren Sie ein Galtonbrett mit Kara als Kugel“ und Bäumen“ als Stifte.
”
”
(Informationen dazu unter http://de.wikipedia.org/wiki/Galtonbrett)
23