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