RGSS/Tutorials/Rubykurs 1 - Grundlagen
| Rubykurs | |
|---|---|
| Autor | Kai D |
| Thematik | Ruby |
| Vorraussetzungen | Programme: RPG-Maker XP Fähigkeiten: Grundlagen des Eventscripting im RPG-Maker XP |
| Andere Teile | |
Inhaltsverzeichnis
Vorwort
In Bearbeitung...
Umgang mit dem Scripteditor
Im Scripteditor kann man fast alle Rubyscripte, die das Projekt verwendet, einsehen, sowie eigene hinzufügen. Man öffnet den Editor mit F11 oder mit dem entsprechenden Symbol in der Werkzeugleiste. Das Fenster besteht aus einer Scriptliste links und einem Eingabefenster rechts. Durch Anklicken eines Scripts in der Scriptliste wird dessen Inhalt in dem Eingabefenster angezeigt. Mit der rechten Maustaste auf die Scriptliste öffnet sich ein Dialog, in dem ihr neue leere Scripte der Scriptliste hinzufügen (Insert), bestehende Scripte ausschneiden und/oder in die Zwischenablage kopieren (Cut und Copy), bestehende Scripte löschen (Delete) oder nach einem Begriff innerhalb aller Scripte suchen (Find) könnt. Auch im Eingabefenster kann mit Rechtsklick ein Dialog aufgerufen werden. Die Befehle hier beziehen sich auf die Textzeichen und funktionieren im Grunde genauso wie in jedem Texteditor. Mit dem Find-Befehlen kann man nach einem Begriff innerhalb des ausgewählten Scripts suchen. Über Jump springt der Editor zu der eingegebenen Zeile des Scripts.
Die Scripte im Scripteditor haben für den Code keinerlei Bedeutung und dienen nur zur Strukturierung des Codes. Theoretisch könnte also der gesamte Code auch in ein Script geschrieben werden, was jedoch die Übersichtlichkeit erschwert. Wichtig ist, dass der Code von oben nach unten eingelesen wird. Das Script Main wird also als letztes ausgeführt. Neue Scripts sollten stets über dem Main Script und unter den anderen Standard-Scripts eingefügt werden. Da die Anzahl der Scripte und deren Namen für den Code keine Bedeutung haben, kann man auch leere Scripte als Trennwände nutzen.
Die ersten Schritte
Die Scripte dieses Tutorials sollten in ein neues Script über Main eingefügt werden. Wählt in der Scriptliste also mit der rechten Maustaste das Main Script an und klickt dann auf Insert. Ihr könnt diesem neuen Script nun einen Namen geben, beispielsweise Tutorialscript. Wählt dann das neue Script aus. Unsere Aufmerksamkeit widmen wir ab nun dem Eingabeeditor rechts. Aus traditionellen Gründen wird das erste Programm, das wir programmieren, ein Hallo Welt-Programm werden. Beim Ausführen soll auf dem Bildschirm der Text Hallo Welt ausgegeben werden. Der Code hierfür lautet simpel:
print("Hallo Welt")
Beim Starten eures Projektes sollte sich nun ein Windowsfenster öffnen, welches diesen Text anzeigt. Der Befehl print (engl. ausdrucken) gibt also etwas auf dem Bildschirm aus. Dies können Zahlen, Wörter oder andere Dinge sein. Damit print weiß, was es ausgeben soll, muss man ihm sogenannte Parameter, manchmal auch Argumente genannt, übergeben. Diese Parameter werden in Klammern neben dem Befehl geschrieben. Will man mehrere Parameter übergeben, so trennt man diese mit Kommata ab.
print("Die Welt ist schön. ", "Die Welt ist groß. ", "Hallo Welt!")
Bei diesem Code haben wir drei Mal das Wort Welt verwendet. Für den Programmierer ist es praktisch, wenn er eine bestimmte Information, die mehrmals im Code vorkommt, nur einmal schreiben muss. Dies realisiert man über Variablen. Dies sind Bezeichner für ein Objekt/Information. Sie wirken wie ein Etikett, das einem Objekt einen Namen gibt. Variablen schreibt man als einfache Wörter, die mit einem Kleinbuchstaben oder _ beginnen und keine Sonderzeichen (dafür aber Zahlen) enthalten dürfen. Um einer Variable ein Objekt zuzuweisen, benutzt man den = Operator.
wort = "Welt" print("Die ", wort, " ist schön. Die ", wort, " ist groß. Hallo ", wort, "!")
In diesem Script wird der Variable wort das Objekt "Welt" zugewiesen. Überall im Script, wo die Variable steht, wird stattdessen das Objekt eingesetzt, zu dem die Variable verweist. Interessanterweise kann eine Variable ihre Zuweisung jederzeit ändern.
wort = "Welt" print("Schöne ", wort) wort = "Stadt" print("Schöne ", wort)
In diesem Fall verweist die Variable wort zuerst auf "Welt" und dann auf "Stadt".
Damit Ruby ein Wort von einer Variable unterscheiden kann, muss das Wort in Anführungszeichen geschrieben sein. Der Computer macht aber auch Unterschiede zwischen Zeichen und Zahlen. Ebenso zwischen Ganzzahlen und Kommazahlen. So werden Zeichen und Wörter in Anführungszeichen geschrieben, Zahlen hingegen nicht. Für den Computer sind also "1" und 1 völlig unterschiedliche Informationen. "1" stellt ein Zeichen auf der Tastatur dar. 1 hingegen eine mathematische ganzstellige Zahl. Ein Text, also eine Zeichenkette, bezeichnet man als String. Eine ganzstellige Zahl ist ein Integer, wobei es kleine ganzstellige Zahlen, sogenannte Fixnum, und große ganzstellige Zahlen Bignum gibt. Mit Integer Objekten kann man rechnen, Strings eignen sich hingegen zum Darstellen von Informationen auf dem Bildschirm. Denn damit etwas auf dem Bildschirm als Text ausgegeben werden kann, muss es in Form von Zeichen vorliegen. print wandelt jeden Parameter selbstständig in einen String um. Die folgenden zwei Befehle werden also identisch ablaufen:
print(123) print("123")
Im folgenden werden wir die wichtigsten Datentypen durcharbeiten und einige ihrer Funktionen kennenlernen. Dies mag für Einige vielleicht ermüdend sein, da dies wohl mehr mit Informatik und Mathematik, statt mit dem Programmieren von Spielen zu tun zu haben scheint, doch sind diese grundlegenden Informationen für alle späteren Schritte notwendig.
Kommentare
Kommentare gibt es ja auch im Maker, über den Event Command Comment. Kommentare sind Bemerkungen zum Code, die auf diesen jedoch keinerlei Auswirkungen haben. Was in einem Kommentar geschrieben wird, ist also nur für denjenigen bestimmt, der den Code liest. Das können Hinweise sein, was der Code macht, oder Überschriften, Trennwände etc. um den Code übersichtlicher zu gestalten. Es gibt in Ruby zwei Formen von Kommentaren. Einzeilige und Mehrzeilige. Einzeilige Kommentare beginnen mit einem Rautezeichen #, Mehrzeilige haben ein =begin am Anfang und ein =end am Ende stehen (wobei beide Begriffe immer am Anfang einer neuen Zeile stehen müssen!). Normalerweise verwendet man zur Dokumentation des Codes aber nur einzeilige Kommentare.
#Einzeiliger KommentareIm Tutorial werden oftmals Kommentare am Ende einer Zeile geschrieben, die folgende Form haben:
3 + 3 # => 6
Dies ist ein Kommentar wie jedes andere auch. Aber das => soll verdeutlichen, dass das nachfolgende das Ergebnis dieser Codezeile ist (in diesem Fall eben soll es verdeutlichen, dass 3 + 3 = 6 ist).
Rechnen mit Ruby
Fixnum und Bignum
Programmieren besteht eigentlich nur aus Logik und Mathematik. Deshalb ist das Rechnen mit Zahlen besonders wichtig. Glücklicherweise laufen mathematische Gleichungen in Ruby ähnlich wie im Mathehefter eines jeden Schülers ab.
print(4+2)
In diesem Beispiel soll das Ergebnis aus 4+2 auf dem Bildschirm ausgegeben werden. Man kann eine Variable auch auf ein Rechenergebnis verweisen lassen und mit diesem weiterrechnen.
x = 3*2 y = 8/2 print(x+y)
x verweist auf das Ergebnis von 3*2, also 6. y verweist auf das Ergebnis 8/2, also 4. Nun soll die Summe aus beiden Zahlen auf dem Bildschirm ausgegeben werden. Im folgenden ein kurzer Überblick über die verschiedenen Rechenoperationen in Ruby:
| Syntax | Operation |
| a+b | Addition aus a plus b. |
| a-b | Subtraktion aus a minus b. |
| a*b | Multiplikation aus a mal b. |
| a/b | Division aus a durch b. |
| a%b | Modulo, also teilen mit Rest. |
| a**b | Potenz aus a hoch b. |
Die Modulo-Operation dürfte jenen, die sich mit dem Maker auskennen ja bereits bekannt sein. Vollständigkeitshalber sei sie noch einmal kurz erklärt. Modulo ist Teilen mit Rest. Das Ergebnis aus 13%3 ist beispielsweise 1, da die 3 vier Mal in die 13 reinpasst, wonach nur noch ein Rest von 1 übrig bleibt.
Einige haben vielleicht auch bemerkt, dass es kein Radizieren, also keine Wurzel gibt. Dies liegt daran, dass eine Wurzel nur eine Potenz mit gebrochenen Exponenten ist. Die dritte Wurzel aus 2 ist gleich 2 hoch 1/3. Genauso kann man also für die Wurzel aus 36 schreiben: 36**(1.0/2).
Beim Dividieren und Radizieren sollte man sich immer vergegenwärtigen, dass wir gerade mit Integer-Werten, also ganzstelligen Zahlen arbeiten. Aus diesem Grund ist das Ergebnis aus 3/2 nicht etwa 1.5, sondern 1. Da ganzstellige Zahlen keine Dezimalstellen haben, rundet Ruby den Wert einfach ab.
Ebenfalls zu beachten ist, dass Ruby eure mathematischen Formeln genauso betrachtet wie ein pingeliger Mathelehrer - zumindest was die Klammersetzung angeht. Auch bei Ruby gilt: Punkt- vor Strichrechung! Ihr könnt es ja im Scripteditor einmal ausprobieren:
a = 3+2*5 b = (3+2)*5 print(a) print(b)
Darum lieber zu viele Klammern setzen, als zu wenig!
Will man einen Wert zu einer Variable hinzuaddieren/subtrahieren/dividieren o.ä. so muss der Code folgendermaßen lauten:
a = 10 a = a + 3
Es ist natürlich nervig jedes Mal das a in den Term dazuzuschreiben. Darum kennt Ruby, wie auch die meisten anderen Programmiersprachen, eine praktische Abkürzung. a = a + b kann man abkürzen mit a += b. Das geht übrigens auch mit jeder anderen Rechenoperation:
a = 10 a += 2 #=> 12 a /= 2 #=> 6 a -= 3 #=> 3 a %= 2 #=> 1 a *= 10 #=> 10 a **= 2 #=> 100
Große Zahlen werden übrigens automatisch in Bignums umgewandelt. Man braucht sich also nicht darum zu kümmern, ob eine Zahl nun Fixnum oder Bignum ist.
Eigentlich sollte Ruby nicht anders arbeiten als euer Taschenrechner. Dennoch ist es sinnvoll, ein wenig herumzuexperimentieren und einige Formeln in den Scripteditor einzugeben, auf diese mit Variablen zu verweisen und die Ergebnisse letztendlich auszugeben. Wer mehr über ganzstellige Zahlen wissen will, kann den Artikel Integer anschauen. Doch dort wird keine Rücksicht darauf genommen, welches Wissen bereits in diesem Kurs vermittelt wurde. Wundert euch also nicht, wenn ihr nicht alles, was in anderen Rubyartikeln steht, auf Anhieb versteht.
Float
Der zweite Datentyp, den wir behandeln werden, ist Float. Er ist für Kommazahlen zuständig. Wie bereits oben erwähnt, ergibt die Rechnung 3 / 2 nicht etwa 1,5 wie man es erwarten würde, sondern 1. Gerade in einem Kampfsystem können Rundungswerte aber katastrophale Folgen auf das Endergebnis haben. Aus diesem Grund sollte man auch mit Kommazahlen umgehen können, auch wenn diese wesentlich seltener gebraucht werden. Eine Kommazahl erzeugt man folgenderweise:
kommazahl = 5.364Im gewissen Sinne also nicht anders als wir es in unserem Mathehefter machen - mit dem Unterschied, das hier statt Kommata Punkte verwendet werden. Jede Zahl, die einen Punkt enthält, ist automatisch vom Typ Float. Selbst folgende Zahlen sind Floats:
a = 1.0 b = 2.0 c = a + b print(c) #=> 3.0
Mit Kommazahlen rechnet man eigentlich genauso wie mit Integers. Nur das hier nicht abgerundet wird.
print(3.0 / 2.0) #=> 1.5
Nun stellt sich natürlich die Frage, was passiert, wenn Integer und Floats gleichzeitig in einer Rechnung auftauchen. Probieren wir es aus!
print(3/2.0) #=> 1.5 print(3.0/2) #=> 1.5 print(5 + 1.0) #=> 6.0
Ihr seht: Egal was am Ende rauskommt, wenn ein Float an der Rechnung beteiligt war, wird das Ergebnis auch immer vom Typ Float. Will man aus einem Integer einen Float machen, müsste man also theoretisch nur schreiben:
x = 5 x = x + 0.0
Es geht aber auch einfacher, denn Ruby bietet eine Funktion zum Umwandeln von Integers in Floats an. Diese funktioniert folgendermaßen:
x = 5 x = x.to_f print(x) #=> 5.0 print(4 + 5.to_f) #=> 9.0 print((3 + 2).to_f) #=> 5.0
Ebenso gibt es auch Methoden, um aus einem Float ein Integer zu machen. Da wäre einmal die Methode to_i, welche beim Umwandeln stets abrundet, sowie die Methode round, welche nach den altbekannten Rechenregeln aufrunden (>=5 -> aufrunden).
print(3.0.to_i) #=> 3 print(3.9.to_i) #=> 3 print(3.9.round) #=> 4
Nun solltet ihr also in der Lage sein, mit Ruby eure Mathehausaufgaben auszurechnen. Leider machen Matheaufgaben keinen Spaß - einen Award für das beste "Spiel" wird man damit also nicht gewinnen. Also lassen wir die Mathematik beiseite und wenden uns den anderen Bereichen von Ruby zu.
Damit ihr selbst testen könnt, ob ihr den eben behandelten Stoff verstanden habt, folgt nach jedem Kapitel eine (oder mehrere) Übungsaufgaben. Ihr erkennt sie an dem charakteristischen "Übungszettel"-Symbol über der Aufgabe. Wenn ihr auf dieses Symbol klickt, gelangt ihr zur Auflösung der Aufgabe. Nachdem ihr eine Aufgabe gelöst habt, könnt ihr so vergleichen, ob eure Lösung richtig war. In manchen Fällen gibt es aber auch mehrere Lösungsmöglichkeiten. Solltet ihr eine Aufgabe einmal nicht lösen können, ist das auch kein Problem. In der Auflösung wird die Vorgehensweise genau erklärt, so dass ihr auch auf diese Weise dazulernt.
Im Folgenden seht ihr Aufgaben, die aus jeweils drei Rechnungen bestehen. Jeweils zwei Rechnungen einer Aufgabe haben das gleiche Ergebnis, doch eine Rechnung weicht immer vom Ergebnis ab. Könnt ihr herausfinden welche von den jeweils drei Rechnungen im Ergebnis abweicht (Anmerkung: 1 und 1.0 werden als unterschiedliche Ergebnisse gewertet)? Ihr könnt eure Antwort überprüfen, in dem ihr das Ergebnis der Rechnungen mit print ausgeben lasst, oder auf euch die Lösungen der Aufgabe in der Wiki anschaut.
#Aufgabe 1 4 / 2 (4.0 / 2).round (4 / 2).to_f #Aufgabe 2 3 / 2 (3.0 / 2.0).round (3 / 2.to_f).to_i #Aufgabe 3 4.0**(1/2) 4**(1.0/2.0) 4**(1/2.0)
Methoden
Nun haben wir schon oft genug über Methoden und dergleichen gesprochen, aber noch nie erklärt, was sich hinter diesem Namen verbirgt. Nun, das ist auch gar nicht so einfach, denn die Methoden sind Bestandteile eines objektorientierten Systems, welches recht kompliziert ist und erst an späterer Stelle erklärt werden soll. Aber auch nicht-objektorientierte Sprachen wie Pascal oder C kennen Methoden - nur eben unter einem anderen Namen, nämlich Funktionen.
Methoden sind im Prinzip bestimmte Codeblöcke, die die häufiger ausgeführt werden müssen und die eine bestimmte Herangehensweise beschreiben. So ist zum Beispiel das Zähneputzen eine Methode unseres Körpers. Sie beschreibt, wie der Mensch mit seinen Armen rumfuchtelt, um seine Zähne wieder blank zu kriegen. Jede Armbewegung ist wiederum eine eigene Methode. Und jedes Zucken unseres Muskels auch. Nur stellt euch vor, ihr würdet auf die Frage eurer Mutter "Was machst du gerade?" antworten: "Ich kontrahiere den Muskelfaser XYZ, um damit eine Armbewegung um 3° zu erzeugen, damit die Zahnbürste in meiner Hand..." - nun, ihr würdet wohl nie fertig werden. Da ist es einfacher zu sagen "Ich putze Zähne!". Der Computer sieht das auch so. Deswegen arbeitet er ebenfalls mit solchen Methoden. Man erklärt ihm einmal, wie ein bestimmter Vorgang abläuft, und gibt diesem Vorgang dann einen Namen. Wenn der Vorgang dann irgendwann erneut ablaufen soll, dann braucht man nur noch seinen Namen aufzurufen. Erinnert irgendwie an Variablen, bei denen man auch nur noch ihren Namen rufen muss, um ihren Inhalt herbeizurufen, oder?
Wir haben ja bereits verschiedene Methoden kennengelernt. print ist zum Beispiel eine solche Methode. Sie zeigt Zeichen auf dem Bildschirm an. Aber +, -, /, ** etc. sind auch Methoden, sie rechnen mit einem Wert. Auch unser round und to_i sind Methoden, sie verändern einen Datentyp. Wie ruft man nun allgemein eine Methode auf? Dafür gibt es eine Syntax. Eine Syntax ist praktisch die Grammatikregel einer Programmiersprache. Wie im Englischen das Subjekt vor dem Prädikat kommt, muss es in Ruby heißen:
objektname.methodenname(parameter1, parameter2, parameter3,...)
objektname ist der Name des Objektes, welcher diese Methode ausführt. Denn in Ruby muss jede Methode von einem Objekt erlernt und ausgeführt werden. methodenname ist der Name der Methode - logisch. Und in Klammern stehen dann die Parameter, welche der Methode zugeschickt werden. Den scharfsinnigen unter euch sollte nun auffallen, das meine tolle Syntax nicht gerade mit dem übereinstimmt, was ich zuvor an Beispielen genannt habe. Wo ist bei print("Hallo", "Wie geht es dir?") bitte sehr der Objektname? Wir haben einen Methodennamen, print, wir haben Parameter, ("Hallo", "Wie geht es dir?"), aber wo steckt der Objektname? Das stimmt doch was nicht... Korrekt! Ruby bescheißt uns nämlich. print ist eine Methode, die jedem Objekt gelehrt wird. Das heißt, sie kann in jedem Objekt ausgeführt werden. Ihr Ergebnis ist dabei immer das Gleiche. Wenn kein Objektname hingeschrieben wird, dann wird die Methode automatisch vom aktuellen Objekt ausgeführt. Welches das ist, kann uns egal sein. Denn letztlich beherrscht ja doch jedes Objekt diese Fähigkeit. Warum das so ist, dazu kommen wir erst später.
Leider sind auch unsere Rechenoperationen kein Vorbild. Wieder hält niemand sich an die Syntax. Wo ist denn bitte hier der Objektname und die Parameterliste? Richtig müsste es doch heißen:
x = 5.+(4)
Probiert es aus. Es funktioniert. Nun, Ruby ist mit der Grammatik halt immer etwas nachlässig. Genauso wie die deutsche Sprache. Man könnte unzählige Beispiele nennen... öhm... wie hieß das Buch? "Der Dativ ist dem Genitiv sein Tod?". Nun, jedenfalls sind auch wir Deutschen in unserer Grammatik immer etwas oberflächlich. Da brauchen wir Ruby nicht zu kritisieren. Ruby hat nämlich zwei Ausnahmeregelungen eingeführt:
1. Methoden, deren Namen aus einem Sonderzeichen besteht (sowie einige andere), bei denen muss Objektname und Methodenname nicht durch ein . getrennt werden. Es ist also möglich zu schreiben 5 + statt 5.+
2. Parameter müssen nicht zwangsläufig von einer Klammer umgeben sein. Sie erhöht nur die Übersichtlichkeit. Es ist also möglich zu schreiben:
print "Hallo Welt" statt print("Hallo Welt").
Ebenso kann man auch bei Rechenoperationen 3 + 2 statt 3 +(2) schreiben. Vor allem mit der letzten Regel sollte man aber behutsam umgehen. Klammern bessern die Übersicht. Also sollte man sie wirklich nur dann weglassen, wenn die Übersicht mit Klammern noch stärker gefährdet wäre. Gebt eurem Mathelehrer doch mal die Formel: (5.+(3))./(2.**(3)) und ihr werdet verstehen was ich meine.
Kommen wir zum letzten Beispiel. 5.3.to_i Wo ist hier der Parameter? 5.3 ist der Objektname, to_i ist der Methodenname. Nur der Parameter fehlt. Nun, das kann man sich eigentlich mittlerweile schon selbst herleiten. Jede Methode braucht unterschiedlich viele Parameter. die Methode + z.B. will einen haben. print nimmt beliebig viele Parameter an. Es gibt auch Methoden, die brauchen keinen Parameter. to_i ist eine solche Methode. Die Syntax müsste also lauten:
5.3.to_i()
Und da wir ja bereits besprochen haben, dass man Klammern weglassen darf, sind die () überflüssig. Nun, nicht immer. Manchmal sind auch bei parameterlosen Methoden Klammern recht nützlich. Nämlich bei Methoden, die wie print keinen Objektnamen brauchen. Stellt euch print ohne Parameter vor. Man könnte ihn doch glatt für eine Variable halten. Daher doch lieber Klammern nutzen, wenn man glaubt, es könnte zu Verwechslungen führen.
Nun haben wir gelernt, wie man Methoden aufruft. Jetzt fehlt noch, was Methoden eigentlich sind (und was wir wohl auf die Nachhilfestunde Objektorientierte Programmierung verschieben müssen), und wie man Methoden selbst erstellt. Nun, letzteres können wir schnell durchgehen. Ist auch nicht viel Arbeit. Auch hier gibt es wieder eine Syntaxregel:
def methodenname(parameter1, parameter2, ...) #Methodeninhalt end
Nun, statt methodenname setzen wir natürlich den Namen unserer Methode ein. Hier gelten ähnliche Regeln wie bei Variablen. Methodennamen sollten möglichst klein geschrieben werden und bis auf wenige Ausnahmen (z.B. +, ?, !...) keine Sonderzeichen enthalten! Schreiben wir doch einmal eine Methode, welche den Satz "Hallo Welt" auf dem Bildschirm ausgibt!
def schreibe print("Hallo Welt!") end
Nun rufen wir die Methode an anderer Stelle auf:
schreibe #=>Hallo WeltWollen wir der Methode noch einige Parameter verpassen? Na klar!
def wetterbericht(sender, wetter, tag) print("Hallo! Willkommen beim Wetterbericht von ", sender, ". Am ", tag, " wird es voraussichtlich ", wetter, " geben!") end wetterbericht("ARD", "Regen", "Donnerstag") #=> Hallo! Willkommen beim Wetterbericht von ARD. Am Donnerstag wird es voraussichtlich Regen geben!
Bisher haben wir die Methoden nur als etwas betrachtet, dass einen Code ausführt. Wichtig ist aber auch, dass Methoden immer einen Rückgabewert haben. Man kann sie mit Beamten vergleichen, denen man erst einen Auftrag samt undurchsichtiger Dokumente gibt, die diesen Auftrag dann durchführen und am Ende einen langen Statusbericht verfassen, den sie an den Auftraggeber zurückfaxen. Der Rückgabewert einer Methode ist dabei immer das zuletzt genannte Objekt.
def test print "Hallo!" 5 end
In diesem Beispiel wäre die Zahl 5 der Rückgabewert der Funktion. Probieren wir es doch einmal aus. Schreibt den Code in den Scripteditor und schreibt dann darunter:
zahl = test
print zahlUnd ausgegeben wird "5". Man kann sich das so vorstellen, dass Methoden im Code durch ihren Rückgabewert ersetzt werden.
#Folgender Code... a = test + test #...ist also nix anderes als a = 5 + 5
Methoden lassen sich also addieren, so verrückt das klingt. Nun eigentlich stimmt das nicht ganz. Nur der Rückgabewert von Methoden lässt sich addieren, insofern er ein Fixnum ist. Übrigens hat jede Methode einen Rückgabewert. Ob sie es will oder nicht. Auch print hat einen Rückgabewert. Wollt ihr ihn sehen?
print(print("Hallo"))
In diesem Fall wird Ruby folgendermaßen vorgehen:
- Das innerste Element der Klammer wird ausgeführt, also
print("Hallo"). Die print Methode lässt das Wort "Hallo" auf dem Bildschirm ausgeben - Nachdem print ausgeführt wurde, gibt es seinen Wert zurück: nil. Der code heißt nun also
print(nil) - Nun wird er äußere print Befehl ausgeführt. Das Wort "nil" wird auf dem Bildschirm angezeigt.
- Auch dieses print hat einen Rückgabewert. Übrig bleibt also nur noch der Code
nil
Man kann also den Rückgabewert von Methoden abfangen und in Variablen speichern, oder mit diesem Rückgabewert andere Methode ausführen, oder ihn als Parameter für andere Methoden nutzen. An sich ist das nichts neues. Schauen wir doch mal bei den Rechnungen vorbei:
x = 5 + 3 * 2
Zum besseren Verständnis hier der Code nochmal in strengster Rubyschreibweise:
x = 5.+(3.*(2))
Erneut die selbe Abfolge:
- Zuerst wird die innere Klammer ausgeführt:
3.*(2) - Diese gibt den Rückgabewert 6 zurück. Der Code lautet jetzt also:
5.+(6) - Nun wird die äußere Methode ausgeführt:
5.+(6) - Die äußere Methode gibt den Rückgabewert 11 aus. Der Code heißt jetzt also
x = 11.
Im Scriteditor werden wir auch öfters Methoden sehen, die folgendermaßen aufgebaut sind:
def addiere(a, b, c, d) return(a+b+c+d) end addiere(5,4,1,2) #=> 12
Was hat es mit diesem return auf sich? Nun, dieser Befehl beendet die Methode und gibt ihr als Rückgabewert automatisch die Parameter, die hinter return stehen. Wir könnten unter return noch weiteren Code schreiben, dies ist völlig egal, da die Methode ab dem Schlüsselwort return abbricht und beendet wird.
def mittelwert(a, b, c, d) wert = (a+b+c+d).to_f wert /= 4 return(wert) print("Diese Methode hat soeben den Mittelwert von vier Zahlen errechnet!") end mittelwert(5,4,1,2) #=> 3.0
Wir sehen, obwohl nach dem return noch ein print kommt, wird dieses nicht ausgeführt, da die Methode schon bei return aufhört. Oftmals wird das return auch einfach nur gesetzt, um die Rückgabewerte zu verdeutlichen. return(wert) ist auffälliger, als wenn man nur ein einzelnes wert hinschreibt.
Es gibt noch einige weitere Besonderheiten bei Methoden. Aber die werden wir erst später behandeln, wenn wir die übrigen Grundlagen dafür haben. Bis dahin kommen wir erst einmal zu den übrigen, wichtigen Datentypen. Auch dort werdet ihr eine Menge neuer Methoden lernen.
Doch bitte fangt nicht an diese auswendig zu lernen. Mein Informatiklehrer würde zwar sagen: "Eine Programmiersprache lernen ist wie eine Sprache lernen, also reines Auswendiglernen von Vokabeln.". Doch derartigen Ansichten kann ich nicht zustimmen.
Programmieren lernt man nicht, in dem man tausende Begriffe auswendig lernt. Sondern in dem man sie anwendet. Nunja, eigentlich auch nicht viel anders als Sprachen.
Ich selbst gehe immer nach der Faustformel vor: "Methoden, die ich häufig brauche, lerne ich schon durch den häufigen Gebrauch auswendig. Methoden, die ich selten brauche, muss ich auch nicht auswendig lernen - ich brauche sie ja ohnehin so gut wie nie.".
Im Maker berechnet sich der Schadenswert mit folgender Formel:
Schaden = (Power des Angreifers - (defense des Verteidigers / 2)) * (20 + Stärke des Angreifers )/20
<p>
Schreibe eine Methode die anhand der Parameter defense, power und strength den Schadenswert berechnet.
Strings
Wir haben in diesem Tutorial bereits mehrfach mit Strings gearbeitet, daher wird es höchste Zeit, sie näher zu behandeln. Strings sind Zeichenketten - auf gut Deutsch: Zeichen, Wörter, Sätze, Texte, kryptisches Geschwafel. Damit Ruby ein Zeichen von anderen Elementen wie Integers, Floats oder Variablen unterscheiden kann, werden sie in Anführungszeichen geschrieben.
wort = "Hallo" satz = 'Wie geht es dir?'
Will man Anführungszeichen innerhalb eines Strings verwenden, so darf man nicht die nutzen, mit denen man den String einleitet und beendet. Will man dennoch diese Zeichen nutzen, so schreibt man einfach ein \ davor.
satz = 'Hast du mich "Angsthase" genannt?' #oder satz = "Hast du mich \"Angsthase\" genannt?"
Ansonsten unterscheidet sich "" von '' nur darin, dass er bestimmte Sonderfunktionen zulässt. Diese erinnern in ihrer Art an die Messagebefehle des Makers. Hier die wichtigsten:
- #{code} Dieser Teil des Strings wird durch das Ergebnis des Codes (normalerweise wird einfach nur ein Variablenname hingeschrieben) ersetzt.
- \n Zeilenumbruch.
- \t Tab
a = 3 b = 4 c = (a**2 + b**2)**(1.0/2.0) satz = "Laut dem Satz des Pythagoras ist c² = a² + b²\nDemnach ist bei a=#{a} und b=#{b} das c=#{c}" print satz #Laut dem Satz des Pythagoras ist c² = a² + b² #Demnach ist bei a=3 und b=4 das c=5
Nun noch einige Methoden, die das Arbeiten mit Strings erleichtern:
Strings kann man addieren. Natürlich nicht wie Zahlen. "5" + "1" ist also keinesfalls "6". Stattdessen ist die Summe zweier Strings einfach beide Zeichenketten aneinandergereiht.
a = "5" + "1" print(a) #=> 51 b = "Hallo " + "Welt!" print(b) #=> Hallo Welt! c = "Ich werde heute " a = a + "." #=> 51. satz = b + c + a #=> Hallo Welt! Ich werde heute 51.
Es gibt auch eine Methode, die zwei Strings miteinander addiert:
a = "Hallo".concat(" Welt!") print a #=> Hallo Welt!
Die concat Methode arbeitet im Grunde genommen nicht viel anders als die + Methode (die feinen Unterschiede lernen wir an anderer Stelle). Manchmal gibt es in Ruby mehrere Methoden, die das gleiche machen. Man nennt so etwas alias. Es sind sozusagen verschiedene Namen für ein und dieselbe Methode. Ein solcher alias für concat ist z.B. <<.
"Hallo " << "Welt!" #ist das selbe wie "Hallo ".concat("Welt")
Außerdem ist << wieder ein Beispiel für die Sonderzeichenregel bei Methodennamen. Wie auch bei + müssen wir hier keinen . nach dem Objektnamen setzen.
concat, + und << ermöglichen es uns also Zeichenketten aneinanderzureihen. Doch Vorsicht! Man kann nicht Strings mit Integers oder Floats addieren!
Allerdings lassen sich Strings in Integers umwandeln - insofern der String einer Zahl entspricht. Dies wird mit der bereits bekannten to_i Methode realisiert. Auch die to_f Methode für das Umwandeln in Floats lässt sich für Strings anwenden.
"55".to_i #=> 55 print("33".to_i + 2) #=> 35 print("5.5".to_i + 1) #=> 6 print("4.2".to_f * 2) #=> 8.4
Will man ein Zeichen innerhalb eines Strings austauschen, kann man auf dieses Zeichen einfach mit string[zeichennummer] zugreifen. Vorsicht, das erste Zeichen hat die Nummer 0! Das Zweite dann die Nummer 1 usw.
a = "Wild" a[1] = "a" print(a) #=> Wald
Strings werden benötigt um Zeichen auf den Bildschirm auszugeben, oder Texte in eine Datei zu schreiben oder aus einer Datei einzulesen. Die print Methode beispielsweise wandelt alle Parameter automatisch in Strings um. Dadurch kann man also auch Integers als Parameter übergeben, die dennoch als Zeichen auf dem Bildschirm eingeblendet werden. Um dies zu bewerkstelligen, nutzt Ruby die Methode to_s. Diese Methode beherrscht jedes Objekt! Sie wandelt ein Objekt in einen String um.
5.2.to_s #=> "5.2" "Hallo!".to_s #=> "Hallo!" 4.to_s #=> "4"
Schreibe eine Methode gruss die einen Parameter name entgegennimmt und einen Grußspruch für diesen Namen ausgibt. Auf die Eingabe "Valnar" sollte die Methode z.B. die Ausgabe "Hallo Valnar, wie geht es dir?" zurückgeben. Es gibt mehrere Möglichkeiten diese Methode zu schreiben. Versucht so viele wie möglich zu finden.
Arrays
Arrays sind Reihungen von Objekten. Man könnte auch Aufzählungen dazu sagen. Denn so wird ein Array auch erstellt, in dem man verschiedene Objekte, mit Komma getrennt, aufzählt.
verben = "schreiben", "laufen", "hören", "sehen", "spielen", "schlafen" substantive = "Die Schrift", "Der Lauf", "Das Gehör", "Die Sicht", "Das Spiel", "Der Schlaf"
Damit Ruby einen Array aber eindeutig erkennt und ihn nicht mit einer Parameterliste oder ähnlichem verwechselt, umschließt man die Aufzählung noch mit eckigen Klammern.
adjektive = ["sauber", "schnell", "aufmerksam", "scharf", "vergnügt", "müde"]
Das besondere an diesen Reihungen ist, dass jedes Objekt eine Nummer, einen sogenannten Index, erhält. Von 0 anfangend bis zum letzten Element des Arrays.
| 0 | 1 | 2 | 3 | 4 | 5 |
| "schreiben" | "laufen" | "hören" | "sehen" | "spielen" | "schlafen" |
| "Die Schrift" | "Der Lauf" | "Das Gehör" | "Die Sicht" | "Das Spiel" | "Der Schlaf" |
| "sauber" | "schnell" | "aufmerksam" | "scharf" | "vergnügt" | "müde" |
Das kommt einem doch irgendwo bekannt vor... Genau! Die Variables des Makers. Auch bei ihnen hat jede Variable eine Nummer, nur das sie bei 1 anfängt. Im Grunde genommen sind aber Makervariablen nichts weiter als ein Array namens $games_variables.
Will man auf ein Element des Arrays zugreifen, so schreibt man
arrayobjekt[index]
Als Beispiel:
adjektive = ["sauber", "schnell", "aufmerksam", "scharf", "vergnügt", "müde"] verben = ["schreiben", "laufen", "hören", "sehen", "spielen", "schlafen"] print("Wenn wir ", verben[4], " bin ich ", adjektive[2]) #=>Wenn wir spielen bin ich aufmerksam
Genauso kann man auch Elemente des Arrays verändern.
verben = ["schreiben", "laufen", "hören", "sehen", "spielen", "schlafen"] verben[1] = "rennen" p(verben) #=> ["schreiben", "rennen", "hören", "sehen", "spielen", "schlafen"
Statt print eignet sich zum Ausgeben von Arrays auch wunderbar die Methode p. Sie macht eigentlich dasselbe wie print, nur dass sie die Elemente mit Kommas abtrennt.
Um ein Element an einen Array dranzuhängen, gibt es zwei mögliche Methoden: push und <<. Welche ihr verwendet, ist völlig egal, sie bewirken beide das gleiche. Allerdings kann man mit push() beliebig viele Elemente an einen Array dranhängen, während man mit << immer nur ein Element dranhängen kann. Der Rückgabewert der Methoden push() und << ist der veränderte Array selbst.
obst = ["Apfel", "Birne", "Pflaume"] obst << "Kirsche" p(obst) #=> ["Apfel", "Birne", "Pflaume", "Kirsche"] obst.push("Apfelsine") p(obst) #=> ["Apfel", "Birne", "Pflaume", "Kirsche", "Apfelsine"] # und nun mehrere Elemente dranhängen obst << "Zitrone" << "Mandarine" << "Pfirsich" # oder mit push obst.push("Erdbeere", "Weintraube", "Banane")
Mit der Methode delete kann man ein Element aus dem Array löschen. Mit delete_at kann man ein Element an einem bestimmten Index löschen.
gemuese = ["Apfel", "Gurke", "Kartoffel", "Zitrone"] gemuese.delete("Zitrone") p gemuese #=> ["Apfel", "Gurke", "Kartoffel"] gemuese.delete_at(0) p gemuese #=> ["Gurke", "Kartoffel"]
Will man Arrays in einen String umwandeln, so gibt es neben der typischen to_s Methode auch die Methode join. Sie zeigt jedes Element eines Arrays nacheinander an. Als Parameter akzeptiert sie einen String, der die Arrayelemente voneinander abtrennt.
array = ["Apfel", "Gurke", "Banane"] array.join(", ") #=> Apfel, Gurke, Banane array.join(" und ") #=> Apfel und Gurke und Banane array.join(" oder ") #=> Apfel oder Gurke oder Banane
Arrays kann man auch wie Mengen aus der Mathematik nutzen. Vielleicht erinnert sich noch jemand an diese scheußlichen Mengengesetze:
- Menge A geschnitten Menge B ist eine Schnittmenge C, die alle Elemente enthält, die sowohl A als auch B enthalten
- Menge A vereinigt mit Menge B ist eine Vereinigungsmenge C, die sowohl die Elemente von A, als auch die Elemente von B, aber nix doppelt enthält
- Menge A minus Menge B ist eine Differenzmenge C, die jene Elemente von A enthält, die nicht in B vorkommen.
Sowas geht auch mit Arrays. Nämlich mit den Methoden & (Schnittmenge), | (Vereinigungsmenge) und - (Differenzmenge).
| Grafik |
|
|
|
| Die grüne Fläche steht für das Ergebnis der jeweiligen Mengenoperation | |||
| Mathematische Operation | Schnittmenge | Vereinigungsmenge | Differenzmenge |
| Methode | a & b | a | b | a - b |
Außerdem kann man Arrays auch addieren. Dann wird ein Array einfach an den anderen drangehängt. Der Unterschied zur Vereinigungsmenge liegt darin, dass addierte Arrays auch doppelte Elemente enthalten können. Beispiel: [1,2,3] + [2,4] ist [1,2,3,2,4]. Dagegen: [1,2,3] | [2,4] ist [1, 2, 3, 4].
obst = ["Apfel", "Birne", "Pflaume", "Kirsche", "Apfelsine"] gemuese = ["Gurke", "Kartoffel", "Salat"] #Karl mag Obst und Gemüse, aber keinen Salat und keine Apfelsinen karl_mag = (obst | gemuese) - ["Salat", "Apfelsine"] #Tina mag Gemüse und Äpfel. Gegen anderes Obst ist sie allergisch tina_mag = gemuese | ["Apfel"] #Tinas Mutter will zum Nachtisch etwas machen, dass Karl und Tina schmeckt. nachtisch = karl_mag & tina_mag #Was schmeckt den beiden? Probiert es aus: p nachtisch
Zuletzt noch etwas zur Methode size. Sie gibt die Länge eines Arrays zurück. Doch beachtet, dass ein Array stets mit 0 beginnt. Wenn er also fünf Elemente hat, so gibt die Methode size die Länge 5 zurück. Das letzte Element hat aber trotzdem den Index 4, da es ja mit 0 anfängt.
postkartensammlung = ["Portugal", "Italien", "Frankreich", "Russland", "Bulgarien", "USA", "Ägypten", "Japan"] print("Ich habe von meinen Freunden ", postkartensammlung.size, " Postkarten bekommen!") #=> Ich habe von meinen Freunden 8 Postkarten bekommen!
Arrays spielen eine enorm wichtige Rolle in Ruby, sowie auch in allen anderen Programmiersprachen. Ebenso wie Strings und Fixnums.
Wie bereits gesagt liegen die Makervariablen ebenfalls in Form eines Arrays vor. Die Variable für ihn heißt $game_variables. Schreibt für jede der Variablenoperationen der EventCommands eine entsprechende Rubymethode, welche diese Operation umsetzt. z.B. eine Rubymethode add_variable(nummer, wert) welche die Variable mit dem Index nummer um wert erhöht und der Variablenaddition des Makers entspricht. Achtung: Um eure Methode zu testet, müsst ihr sie im Script-Befehl eines Events aufrufen. Ruft ihr sie im Scripteditor auf, kommt es zu einem Fehler, da die Makervariablen erst während des Titelbildschirms erzeugt werden, die Scripte im Scripteditor aber schon davor ausgeführt werden.
Im Rm2k/3 gab es eine Operation mit der man Variablenpointer realisieren konnte. Das heißt das eine Variable als Wert den Index einer anderen Variable hatte. Im XP wurde diese Funktion entfernt. Schreibt einen Rubycode der die Variable, mit dem Index des Wertes der Variable #100, um 1 erhöht.
Hashs
Über Hashs werden wir jetzt nicht viel Worte verlieren. Sie sind ungemein praktisch, aber für die Theorie nicht wichtig. Wir werden sie richtig kennenlernen, wenn wir die ersten Scripte schreiben.
Hashs sind im Grunde genommen das Gleiche wie Arrays, jedoch mit einem wichtigen Unterschied: sie haben keinen Index, sondern einen sogenannten Key (englisch für Schlüssel). Dieser muss nicht ein Integer sein, sondern kann jedes beliebige Objekt darstellen.
| "verb" | "substantiv" | "adjektiv" |
| "schreiben" | "Die Schrift" | "sauber" |
In diesem Beispiel haben wir einen Hash mit drei Elementen. Wie ihr seht, haben wir keinen Index, sondern drei Strings. Jeder String weist auf einen anderen String. Genausogut könnte der String auch auf eine Zahl zeigen. Wir müssen also nicht immer den gleichen Objekttyp verwenden.
Man definiert Hashs mit der Syntax
hash = {key1 => value1, key2 => value2, key3 => value3, ...} #Beispiele woerter = {"verb" => "schreiben", "substantiv" => "Die Schrift", "adjektiv" => "sauber"} zahlen = {"eins" => 1, 1.5 => "Einskommafünf", 3 => "Drei", 5 => 5.0}
Achtung: Wir verwenden bei Hashs statt eckigen Klammern [] die geschweiften Klammern {}!
Auf Hashs greift man genauso zu wie auf Arrays, in dem man ihren Key in eckige Klammern setzt.
print(woerter["substantiv"]) #=> Die Schrift zahlen["sechs"] = zahlen["eins"] + zahlen[5] print(zahlen["sechs"]) #=> 6.0
Warum überhaupt Arrays benutzen, wenn Hashs sowohl Zahlen, als auch jedes andere Objekt als Key nutzen können? Nun, Hashs sind unsortiert. Es gibt in einem Hash kein erstes Element. In einem Array dagegen schon (nämlich das Element 0). Aus diesem Grund können wir Arrays in vielen Situationen wesentlich effektiver einsetzen.
Aus diesem Grund werden wir die Hashs auch erstmal beiseite schieben. Arrays sind wichtiger und werden in Zukunft auch häufiger Verwendung finden, als Hashs.
Booleans, nil und Aussagenlogik
Booleans... wieder so ein kompliziertes Wort. Namensvater dieses Begriffes war der Mathematiker George Boole. Booleans sind der wichtigste Datentyp in allen Programmiersprachen. Alles andere basiert auf ihnen. Dabei gibt es nur zwei Formen dieser tüchtigen Burschen: true (wahr) und false (falsch). Sie erinnern euch vielleicht etwas an die Switchs im Maker, denn auch sie können nur zwei Werte annehmen: ON und OFF. Mit dieser Vermutung (falls ihr sie denn gehabt habt) liegt ihr auch richtig, denn die Switchs des Makers sind in dem Array $game_switches gespeichert und liegen in Form von Booleans vor. ON entspricht also true, OFF ist false. Genau wie die Switchs im Maker kann man auch mit Booleans bestimmte Prozesse steuern, aktivieren und deaktivieren. Aber das ist nicht das eigentliche Aufgabenfeld. Booleans sind viel mächtiger. Sie dienen der Aussagenlogik.
Was ist ein Aussage? Nun, eine Aussage wäre zum Beispiel: "Karl ist größer als Tina!". In Ruby würde sich so eine Aussage folgendermaßen gestalten:
karl = 1.8 tina = 1.7 karl > tina
> ist eine Methode, die, genau wie <, >=, <= usw. in allen Datentypen, die vergleichbar sind, definiert ist. Sie vergleicht in diesem Fall den Float 1.8 mit dem Float 1.7 Und wie es bei Methoden üblich ist, gibt sie auch einen Rückgabewert ab. Und dieser, ich hoffe ihr habt es schon erraten, ist ein Boolean.
Da Karl größer als Tina ist, also 1.8 größer als 1.7, würde die Methode also true zurückgeben. Die Aussage ist also wahr. Würde man schreiben karl < tina, so würde die Methode false (falsch/unwahr) zurückgeben, denn dann wäre die Aussage ja falsch. Was wäre, wenn man schreibt:
karl = 1.8 tina = 1.8 print(karl > tina)
Quizfrage: Was kommt raus? Ich hoffe alle werden einstimmig auf false getippt haben. Es wird aber vielleicht auch einige geben, die sich unsicher waren, manche, die sich vielleicht sogar fragten, ob der bisher unerwähnte Typ nil etwas damit zu tun hat (bevor ich jetzt Verwirrung stifte: Nein, nil hat absolut nix damit zu tun). Eine Faustregel der Aussagenlogik ist: Eine Aussage kann immer nur wahr oder falsch sein. Ist eine Aussage nicht wahr, so ist sie falsch. Ist eine Aussage nicht falsch, so ist sie wahr.
Karl und Tina sind gleich groß. Die Aussage heißt 1.8 > 1.8. Ist 1,8 Meter größer als 1,8 Meter? Nein, ist es nicht. Und damit ist die Antwort false. Das beide Zahlen gleichgroß sind spielt keine Rolle, denn das wurde ja gar nicht gefragt. In der Informatik gibt es kein "jein", "teilweise wahr, teilweise falsch" und auch kein "ich tippe mal auf... na irgendwie beides, oder?". In der Informatik gibt es nur true (wahr) und false (falsch). Würde die Frage dagegen lauten "Ist Karl genauso groß wie Tina", dann würde die Aussage, insofern beide 1,8 Meter groß sind, true sein.
karl = 1.8 tina = 1.8 print(karl == tina)
Vorsicht! == ist etwas anderes als =. Das doppelte Gleichheitszeichen ist eine Methode, die zwei Objekte miteinander vergleicht. Das einzelne Gleichheitszeichen ist eine Zuweisung eines Objekts zu einer Variable. karl = tina würde beispielsweise bedeuten, dass die Variable karl auf das Objekt verwiesen wird, auf das tina zeigt. Also eine völlig andere Bedeutung. Ihr solltet diese beiden Gleichheitszeichenformen niemals miteinander verwechseln, sonst kommt es zu bösen Fehlermeldungen.
Machen wir kurz einen Ausflug in die Mengenlehre (jaja, schon wieder Mathe). Wir haben folgende Mengen:
obst = ["Apfel", "Birne", "Pflaume", "Kirsche", "Apfelsine"] gemuese = ["Gurke", "Kartoffel", "Salat"] karl_mag = ["Apfel", "Birne", "Pflaume", "Kirsche", "Gurke", "Kartoffel"] tina_mag = ["Gurke", "Kartoffel", "Salat", "Apfel"]
Eine Methode der Arrays, die wir bisher noch nicht behandelt haben, ist die Methode include?. Sie überprüft, ob der Parameter im Array enthalten ist. Ist dies der Fall, so gibt sie true zurück, ansonsten false. Probieren wir es einmal aus:
#Mag Karl Birnen? karl_mag.include?("Birne") #=> true
Und Karl mag Birnen. In der Informatik (und logischerweise auch in der Mengenlehre) gibt es sogenannte logische Gatter. Das sind drei Stück an der Zahl: and, or, not (oder auf Deutsch: Und, Oder, Nicht). Aus diesen logischen Gattern und einigen Booleans ist der ganze Rechner aufgebaut. Die Funktion dieser drei logischen Gatter lassen sich schnell erläutern:
- a or b gibt true zurück, wenn a true ist, oder wenn b true ist, oder wenn beide true sind
- a and b gibt true zurück, wenn sowohl a als auch b true sind
- not a gibt true zurück, wenn a nicht true ist (also wenn a false ist)
Das klingt im Grunde genommen komplizierter als es ist. Probieren wir es einfach mal aus:
#Karl mag Birnen und Pflaumen und er mag keine Apfelsinen print((karl_mag.include?("Birne") and karl_mag.include?("Pflaume") and not karl_mag.include?("Apfelsine"))) #Ich glaube, Karl mag Salat und Äpfel, oder er mag Gurken und Kirschen print(((karl_mag.include?("Salat") and karl_mag.include?("Apfel")) or (karl_mag.include?("Gurke") and karl_mag.include?("Kirsche")))) #Ich denke, Karl mag mehr Speisen als Tina und Tina mag mehr Gemüse als Karl print(((karl_mag.size > tina_mag.size) and ((tina_mag & gemuese).size > (karl_mag & gemuese).size)))
Brrr, was für ein Kauderwelsch. Besonders grauenhaft sind die vielen Klammern. Tja, jetzt stellt sich die Frage: Welche Aussagen sind wahr? Wenn ihr den Code in den Scripteditor eingebt, erkennt ihr sofort, dass alle drei Aussagen wahr sind. Schauen wir uns doch einmal die letzte Aussage an und nehmen sie etwas auseinander. Dabei arbeiten wir auch einmal mit Zeilenumbrüchen, um das ganze übersichtlicher zu gestalten:
print( ( (karl_mag.size > tina_mag.size) and ((tina_mag & gemuese).size > (karl_mag & gemuese).size))) )
Im Code würde ich diese Zeilenumbrüche lieber nicht verwenden, hier aber sollen sie uns helfen das Gewirr auseinander zu pflücken. Im Grunde genommen arbeiten diese logischen Gatter wie Methoden - was sie eigentlich auch sind. Wir haben zuerst die Aussage (karl_mag.size > tina_mag.size). Die Größe des Arrays von karl_mag wird also mit der Größe des Arrays von tina_mag verglichen. Karl hat 6 Elemente, Tina nur 4. Die Methode karl_mag.size gibt also 6 zurück, die Methode tina_mag.size hingegen 4. Der Code sieht nun also folgendermaßen aus: (6 > 4). Erneut haben wir eine Methode >. Diese überprüft ob 6 größer als 4 ist. Da dies der Fall ist, gibt die Methode true zurück. Aus der ganzen Codezeile bleibt also nur noch true übrig.
Nehmen wir uns nun die Zeile ((tina_mag & gemuese).size > (karl_mag & gemuese).size)) vor:
Zuerst werden wieder die inneren Klammern aufgelöst. tina_mag & gemuese gibt einen neuen Array zurück, der die Elemente enthält, die sowohl der Array tina_mag, als auch der Array gemuese enthält. Oder anders gesagt: Der die Gemüse-Elemente von tina_mag enthält. Das wären bis auf "Apfel" alle. Übrig bleibt also ,["Gurke", "Kartoffel", "Salat"]. Dieser Array führt nun die Methode size aus und gibt die Zahl seiner Elemente, also 3, zurück. Das gleiche geschieht nochmal mit dem Array karl_mag, bei dem letztlich die Zahl 2 übrig bleibt. Aus der ganzen Zeile wird also 3 > 2. Und das gibt bekanntlich true zurück.
Wir haben jetzt also stehen: true and true. Vergleichen wir das mit unserer obigen Definition von and: a and b sind true, wenn a true ist und b true ist. Dies ist bei uns der Fall. true and true gibt also true zurück.
Übrig bleibt: print(true).
Woah, dass war ein Wirrwarr. Zum Glück sind nur wenige Aussagen derart komplex. Außerdem ist die Aussagenlogik, wie ihr Name schon sagt, äußerst logisch. Was komplex aussieht, muss nicht gleich kompliziert sein. Wenn man Schritt für Schritt eine Aussage auseinander nimmt, stellt sich schnell heraus, wie einfach diese Aussagen eigentlich sind. Denn logisches Denken beherrscht jeder Mensch mindestens genauso gut wie die besten Rechenmaschinen.
Wofür braucht man solche Aussagen überhaupt? Nun, das behandeln wir im nächsten Kapitel. Vorerst noch ein kurzer Crashkurs zum ominösen nil.
Wer jetzt befürchtet, dass dieses nil die gesamte Aussagenlogik wieder durcheinander wirft, sei entwarnt. nil hat mit Aussagen eigentlich nichts zu tun. In Ruby gibt es nur die Regel: Globale Variablen und Instanzvariablen sind nil, wenn sie noch keinem Objekt zugewiesen wurden..
Globale Variablen und Instanzvariablen sind eigentlich nur Variablen, die ein @ oder ein $ vor ihrem Namen tragen. So zum Beispiel unsere $game_variables.
print($firlefanz) $firlefanz = 10 print($firlefanz)
In der ersten Zeile geben wir die Variable $firlefanz der Methode print als Parameter. Nun stellt sich natürlich die Frage: Was für einen Wert hat $firlefanz? Wir wissen, eine Variable zeigt auf ein Objekt. Man kann ihr ein Objekt zuweisen, in dem man schreibt:
variablenname = objekt
Was passiert aber, wenn man eine Variable benutzt, der noch nie ein Objekt zugewiesen wurde? Wenn es sich um eine lokale Variable oder eine Konstante handelt, so stürzt das Programm mit einer Fehlermeldung ab. Ansonsten wird der Variable einfach das Objekt nil zugewiesen. nil kommt aus dem Englischen und bedeutet "nichts". Also die Variable hat "nichts" als Objekt, oder anders ausgedrückt, sie hat kein Objekt (was aber nur halb richtig ist, da nil ja ein Objekt ist). nil ist außerdem die Abkürzung für "not in list", also für eine noch nicht zugewiesene Variable. Natürlich kann man auch eine Variable manuell auf nil setzen:
a = nil print(a)
Außerdem gibt es Methoden, die nil zurückgeben. print zum Beispiel. Nun stellt sich mal wieder die Frage, warum eine Methode als Rückgabewert nil haben sollte. Die Antwort ist einfach: nil hat symbolische Bedeutung. Wenn eine Methode nil zurückgibt, heißt das praktisch, dass sie nichts zum zurückgeben hat. Man könnte natürlich auch false oder 5 oder "Ich habe nix!" zurückgeben. Aber traditionell wird eben das gute nil verwendet. Im Grunde genommen ist das nil weniger wichtig als die anderen Themen, die in diesem Kapitel behandelt wurden. Es hat eher symbolischen Charakter und dient sonst nur dazu, um herauszufinden, ob eine Variable bereits ein Objekt hat, oder nicht. Wie man so etwas herausfindet, das wird Thema des nächsten Kapitels sein.
In dem EventCommand conditional branch kann man nicht nur Variablen und Switchs, sondern auch Rubycode abfragen (über die Schaltfläche "Script"). Ist der Rubycode wahr, wird der conditional branch ausgeführt, ansonsten wird (falls vorhanden) der else case des conditionals ausgeführt. Im folgenden seht ihr ein EventCommand-Script, in welchem sehr viele Abfragen über conditional branchs verschachtelt sind. Könnt ihr mit einem einzigen Conditional Branch und einem Script-Befehl die Abfragen vereinfachen?
@>Conditional Branch: Switch [0015] == ON @>Conditional Branch: Variable [0009] > 15 @>Conditional Branch: Switch [0001] == OFF @>Conditional Branch: [Potion] in inventory @> : Else @> Text: Ihr habt noch keinen Heiltrank also gebe ich euch einen. @> : Branch End : Branch End : Branch End : Branch End
$game_party.item_number(1) > 0 durchgeführt.
Bedingungen
If
Bedingungen kennen wir schon vom Maker. Dort sind es die Conditional Branchs. Eine Bedingung ist eine Fallunterscheidung. Sie führt einen Code je nach Wahrheitsgehalt einer Aussage aus.
Die typischste Bedingung, die man auch in allen anderen Programmiersprachen wiederfindet, ist die If-Bedingung:
if aussage then #wenn eine Aussage wahr ist... #...dann mache etwas... elsif andere_aussage #...wenn hingegen eine andere Aussage wahr ist... #...dann mache dies... else #...ansonsten mache das end
Hierzu muss gesagt werden, dass das then optional ist. Es muss also nicht dazugeschrieben werden. Auch else ist optional. Man fügt es hinzu, wenn bei falscher Aussage ebenfalls ein besonderer Code ausgeführt werden soll.
karl = 1.8 tina = 1.7 if karl > tina then print("Karl ist größer als Tina!") elsif karl < tina then print("Tina ist größer als Karl!") else print("Karl und Tina sind gleichgroß!") end
In diesem Fall wird der Satz "Karl ist größer als Tina!" angezeigt, wenn der Float karl größer ist als der Float tina. Wenn dies nicht der Fall ist, so wird überprüft, ob der Float karl kleiner als der Float tina ist. Wenn dem so ist, so wird der Satz "Tina ist größer als Karl!" angezeigt. Wenn aber auch diese Aussage nicht true zurückgibt, so wird der Satz "Karl und Tina sind gleichgroß" ausgegeben (wenn etwas weder größer, noch kleiner ist, muss es gleich groß sein). Mit elsif stellt man also eine alternative Bedingung auf, else fängt alles ab, was die obigen Aussagen nicht erfüllt.
An der If-Bedingung sieht man auch, warum wir zuvor diese Aussagenlogik behandelt haben. Hier kommt sie zum Einsatz.
karl_groesse = 1.8 karl_alter = 16 karl_krank = false #Achterbahn Regeln: #Kinder unter 16 Jahre dürfen die Achterbahn nicht betreten #Kinder, die kürzlich krank waren, sollen die Achterbahn ebenfalls meiden #Kinder, die unter 1,7 Meter groß sind, dürfen die Achterbahn nicht betreten #Bei Erwachsenen (mindestens 18 Jahre alt) gelten diese Sicherheitsvorschriften nicht if (karl_alter >= 18) or ((karl_groesse >= 1.7) and (karl_alter >= 16) and not (karl_krank)) then print("Karl darf die Achterbahn betreten!") else print("Karl darf die Achterbahn nicht betreten") end
Aussagen können wie gewohnt mit logischen Gattern verknüpft werden. Das macht If-Sätze auch wesentlich mächtiger als Conditional Branchs im Maker. Denn hier kann ein einziger If-Satz mehrere Aussagen gleichzeitig abfragen (im Grunde genommen ist es ja letztlich nur eine Aussage, die sich aus mehreren Booleans verknüpft mit logischen Gattern zusammensetzt).
Dennoch funktionieren If-Sätze etwas anders, als man es erwarten würde. Bei der Aussagenlogik von Ruby gilt nämlich:
[]Ein Wert wird dann als true angesehen, wenn er weder false noch nil ist
Huch, jetzt kommt also doch plötzlich dieses nil vor, obwohl ich vorhin gesagt habe, das nil mit der Aussagenlogik nichts zu tun hat. Im Prinzip hat es das auch nicht. Nur Ruby arbeitet pragmatisch. Für Ruby ist alles wahr, was nicht false und nicht nil ist. Damit erfüllen If-Sätze gleich zwei Funktionen auf einmal.
- Sie unterscheiden zwischen true und false
- Sie unterscheiden zwischen Objekt und nil
#Wenn $firlefanz noch nicht existiert, soll es auf 0 gesetzt werden if not $firlefanz then $firlefanz = 0 end #andersrum: wenn a einen Wert ungleich false/nil hat, wird die Bedingung ausgeführt a = 50 if a then print("Dieser Text sollte bei allen angezeigt werden") end if 7 < 3 then print("Dieser hingegen nicht ^^") end
Mit If-Sätzen kann man also abfragen, ob eine variable eine Referenz zu einem Objekt hat, oder ob sie noch auf nil verweist. Ebenso kann man abfragen, ob eine Aussage true ist.
if aussage then #tue etwas end #...ist also das gleiche wie... if (aussage != nil) and (aussage != false) then #tue etwas end
Man kann übrigens auch If-Sätze an eine Zeile hinten dran hängen:
karl_krank = true #Dieser If-Satz macht das gleiche... if karl_krank then print("Karl ist krank") end #...wie dieser If-Satz print("Karl ist krank") if karl_krank
Auf diese Weise kann man den Code schlanker gestalten. Allerdings kann man auf diese Weise keine Else-Fälle einbaun.
If-Sätze sind übrigens auch eine Art Methode. Das heißt, sie geben ebenfalls einen Wert zurück. Es ist also möglich zu schreiben:
ergebnis = if karl_krank then achterbahn_erlaubt = false end
Der If-Satz gibt immer den Wert zurück, der als letztes innerhalb seines Codeblocks ausgeführt wird. Im obigen Beispiel wäre das false, da dies der letzte Wert zwischen dem if und dem end ist. Was für ein Rückgabewert hätte die Bedingung gehabt, wenn karl_krank false wäre? Da wir keinen else-Fall geschrieben haben, in dem der Rückgabewert ermittelt werden könnte, ist dieser einfach nil.
ergebnis = if true then 100 end print(ergebnis) #=> 100 ergebnis = if false then 100 end print(ergebnis) #=> nil ergebnis = if false then 100 else 50 end print(ergebnis) #=> 50 # und über mehrere Zeilen hinweg: ergebnis = if true then achterbahn_fahrt = true karl_gluecklich = true 333 end print(ergebnis) #=> 333
Unless
unless ist im Grunde genommen das gleiche wie if, nur umgekehrt. Er überprüft, ob eine Aussage nil oder false ist.
unless achterbahn_erlaubt then print("Karl darf nicht auf die Achterbahn.") end
unless ist damit das Gleiche wie
if not aussage then #tue_etwas end
Allerdings gibt es bei unless keinen Else- oder Elsif-Fall. Dafür ist es aber wieder möglich, das unless ans Ende der Zeile zu hängen.
#Gib $firlefanz den Wert 5, falls dieser Variable noch kein Wert zugewiesen wurde $firlefanz = 5 unless $firlefanz
Ternärer Operator
In Programmiersprachen wie C oder Java gibt es noch ein weiteres Bedingungskonstrukt namens ternärer Operator. Er hat die Syntax (bedingung) ? (dann-Fall) : (ansonsten-Fall). Auch in Ruby wurde dieser ternäre Operator eingeführt, obwohl er dort eigentlich keinerlei Nutzen hat und genauso funktioniert wie ein If-Satz.
# bestimme das Maximum beider Variablen def maximum(x, y) if x > y then x else y end end #ist dasselbe wie def maximum(x, y) x > y ? x : y end
Wichtig beim ternären Operator ist nur, dass man bei ihm einen Else-Fall (also das :) angeben MUSS. Ansonsten unterscheidet sich der ternäre Operator nur vom If-Operator insofern, dass er etwas kürzer zu schreiben ist und dass in ihm pro Fall nur ein Ausdruck stehen darf (wir können also nicht mehrere print-Zeilen hintereinander schreiben).
Der ternäre Operator sollte möglichst nur für verzweigte Ausdrücke innerhalb von Formeln o.ä. genutzt werden, wo das längere If-Konstrukt vom eigentlichen Inhalt der Formel ablenkt.
Case
Kommen wir zur letzten Bedinung: Case. Sie ist eigentlich eine Art If-Satz, der mehrere mögliche Zustände einer Variable unterscheidet. Die Syntax sieht folgendermaßen aus:
case variable when vergleichswert1 then #mache_etwas when vergleichswert2 then #mache etwas #... else #mache etwas end
Beispiel:
tinas_geschwister = 2 case tinas_geschwister when 0 then print("Es gibt keinen Rabatt!") when 1 print("Ihr beide müsst nur 80% des Preises bezahlen.") when 2 print("Ihr drei müsst nur 70% des Preises bezahlen.") else print("Ihr bekommt 60% Rabatt auf alle Karten.") end
Case-Anweisungen arbeiten nicht mit der Vergleichsmethode ==, sondern mit der Methode ===. Diese funktioniert eigentlich fast immer genauso. Bei einigen Datentypen arbeitet sie aber anders. So zum Beispiel bei Ranges. Diesen Datentyp haben wir bisher nicht besprochen. Er wird mit der Syntax
range = (anfangswert..endwert) #Beispiel: range = 1..5
erzeugt. Ein Range ist ein Bereich zwischen zwei Zahlen. Vergleicht man einen Range mit einer anderen Zahl über die Methode ===, so wird überprüft, ob die andere Zahl sich innerhalb des Bereiches befindet. Dies kann man sich bei Case-Anweisungen zu Nutze machen.
tinas_alter = 15 case tinas_alter when 1..10 print("Die darf auf keinen Fall mit auf die Achterbahn!") when 11..15 print("Die darf nur in Begleitung mit einem Erwachsenen auf die Achterbahn!") when 16..17 print("Die darf alleine auf die Achterbahn, insofern sie groß genug ist!") else print("Nun gut, sie darf auf die Achterbahn." end
Bei Vergleichen zwischen zwei Zahlen haben wir bisher nur mit den Methoden >, >=, <, <= sowie == gearbeitet. Es gibt aber noch eine weitere: <=>. Sie kann alle drei Möglichkeiten (größer, gleich, kleiner) gleichzeitig abfragen. Bei c = a <=> b ist c gleich 0, wenn a und b gleich sind. c ist 1, wenn a größer als b ist. Und c ist -1, wenn a kleiner als b ist. Diese Methode eignet sich nicht bei If-Sätzen, da sie einen Fixnum, statt einen Boolean als Rückgabewert hat. Bei einem Case hingegen ist das ideal. So kann man nämlich alle drei Möglichkeiten schnell abfragen:
karl_groesse = 1.8 tina_groesse = 1.7 case (karl_groesse <=> tina_groesse) when 0 print("Karl und Tina sind gleich groß!") when 1 print("Karl ist größr als Tina!") when -1 print("Karl ist kleiner als Tina.") end
Tricks und Kniffe mit logischen Verknüpfungen
In diesem Zusammenhang wollen wir noch einige Tricks lernen, mit denen wir viele If-Bedingungen einsparen können. Grundlegend erst einmal der Hinweis, das wir statt and auch && und statt or auch || schreiben können. not lässt sich auch mit ! ausdrücken.
true and not true #ist das gleiche wie true && !true not false or true #ist das gleiche wie !false || true
Die englischen Begriffe haben eine schwächere Bindung als die Zeichen.
true and true || false # wird als true && (true || false) gewertet variable = false or true # wird als (variable = false)|| true gewertet
Die letzte Zeile dürfte überraschen. Englische Begriffe führen schnell zu unerwarteten Ergebnissen, daher ist es sicherer die Zeichenschreibweise zu benutzen. Wem die englischen Begriffe lieber sind, der sollte zumindest jedes logische Gatter in eine Klammer setzen, dann kann auch nichts passieren.
Im Tutorial werden wir das so handhaben, da die englischen Wörter besser zu sehen sind als die Zeichen.
Nun sollten wir wissen, dass Ruby beim vergleichen logischer Verknüpfungen (oder logischer Gatter, wie die Informatiker sie nennen) sehr faul ist.
Nehmen wir das Beispiel: false and true. Wir wissen, dass eine and-Verknüpfung immer dann true zurückgibt, wenn sowohl das erste, als auch das zweite Element nicht false und nicht nil sind.
In unserem Fall jedoch ist das erste Element false. Egal wie das zweite Element aussieht, die Verknüpfung würde IMMER false zurückgeben.
Aus diesem Grund sieht sich Ruby den zweiten Wert erst gar nicht an. Es überprüft nur den ersten, sieht das er false ist, und bricht ab.
Was nützt uns das? Eine ganze Menge. Nehmen wir an wir haben eine globale Variable $wort, der wir irgendwo im Programm einen String zuweisen. Wenn dieser String mehr als 8 Zeichen hat, soll der Text "Dies ist ein langes Wort" ausgegeben werden.
if $wort.size > 8 then print("Dies ist ein langes Wort.") end
Wir wissen aber, dass globale Variablen (auch wenn wir ihre genaue Funktion noch nicht behandelt haben) immer nil sind, wenn man ihnen noch keinen Wert zugewiesen hat. Es könnte also passieren, dass wir vergessen der Variable einen Wert zuzuweisen und das dann nil.size ausgeführt wird. Dies erzeugt eine Fehlermeldung, da nil die Methode size nicht kennt.
Wir müssen also zwei Abfragen machen. Einmal, ob die Variable bereits einen Wert hat und dann ob ihre Länge größer als 8 ist. Mit Hilfe der logischen Verknüpfungen können wir beide Aussagen verbinden:
if $wort and ($wort.size > 5) then print("Dies ist ein langes Wort.") end
Was wäre, wenn wir die Verknüpfung umdrehen würden? if ($wort.size > 5) and $wort then Dies würde eine Fehlermeldung ergeben, wenn $wort nil ist. Andersherum hingegen funktioniert es problemlos, weil zuerst der linke Ausdruck überprüft wird, also ob $wort nicht nil und nicht false ist, und erst wenn dies der Fall ist wird der rechte Ausdruck verglichen.
Im Zusammenhang mit diesen faulen Überprüfungen gibt es noch mehr Tricks. So haben wir gelernt, dass eine Methode immer ihren letzten Wert zurückgibt. Verknüpfungen tun dies interessanterweise auch. false or 5 beispielsweise gibt den Wert 5 zurück. Warum? Weil 5 nicht false und nicht nil ist, damit also die Aussage korrekt ist und 5 der zuletzt geprüfte Wert ist.
nil and true gibt beispielsweise nil zurück. Ruby sieht den linken Wert, merkt, dass dieser nil ist und das die Aussage damit falsch ist. Es bricht ab ohne sich den rechten Wert anzusehen. nil wird zurückgegeben, da es der zuletzt geprüfte Wert ist.
Dies können wir uns zu Nutze machen. z.B. wollen wir einer Variable den Inhalt einer globalen Variable übergeben. Wenn diese Variable allerdings nil ist, soll ein Standardwert gesetzt werden:
variable = ($wort or "")
In diesem Beispiel erhält variable den Inhalt von $wort. Wenn $wort aber false oder nil ist, erhält variable stattdessen den Wert "".
Diesmal wollen wir der globalen Variable $wort einen Wert zuweisen. Aber nur, wenn sie noch keinen hat. Wir haben das bisher folgendermaßen gelöst:
$wort = "wert" unless $wort
Mittlerweile kennen wir noch eine andere Schreibweise:
$wort = ($wort or "wert")
Diese Schreibweise ist kürzer und es geht sogar noch besser:
$wort ||= "wert"
$wort ||= "wert" ist einfach nur die Abkürzung von $wort = $wort || "wert". Wir kennen diese Schreibweise ja schon bei mathematischen Operationen.
Schleifen
Auch Schleifen sollten bereits vom Makern unter dem Namen "Loop" oder "Cycle" bekannt sein. Dies sind Codeblöcke, die wiederholt werden. In vielen Programmiersprachen sind Schleifen von großer Bedeutung. In Ruby hingegen muss der Benutzer sich nicht mit ihnen rumärgern, da es durch die Iteratoren eine viel effektivere Form der Codewiederholung gibt. Dennoch seien die zwei wichtigsten Schleifen von Ruby hier kurz angerissen.
while
Die while-Schleife, im Deutschen auch als Solange-Schleife bezeichnet, wiederholt einen Code solange, wie eine Aussage wahr ist.
while aussage #wiederhole etwas end
Beispiel:
alter = 11 while alter < 16 print("Karl ist nun ", alter, "Jahre alt. Er darf noch nicht auf die große Achterbahn.") print("Ein Jahr vergeht...") alter += 1 end print("Karl darf nun auf die große Achterbahn!")
Die Aussage dieser Schleife wäre alter < 16. Da alter anfangs 11 ist, und 11 < 16 wahr ist, gibt diese Aussage true zurück. Nun wird das innere der Schleife ausgeführt. Der Text wird angezeigt und das Fixnum-Objekt alter wird um 1 erhöht. Nachdem der innere Block ausgeführt wurde, springt Ruby zum While zurück und überprüft erneut die Aussage. Gibt diese immer noch true zurück, so wiederholt sich der ganze Vorgang. Dies geht so lange, bis die Aussage false zurückgibt, das alter also 16 erreicht hat. Nun überspringt Ruby das Innere der Schleife und führt den Code hinter der Schleife aus, also wieder eine Textausgabe, die dem Benutzer mitteilt, dass Karl nun endlich die heißersehnte Achterbahn benutzen darf.
Genau wie auch beim if kann man das While hinter eine Zeile schreiben.
alter = 10 print("Nach einem Jahr ist Karl ", (alter+=1), " Jahre alt.") while alter < 16
until
Die until-Schleife ist das Gegenteil der While-Schleife, genauso wie auch unless das Gegenteil von if ist. Das heißt, diese Schleife wiederholt ihren Inhalt solange, wie ihre Aussage false oder nil ist, bzw. bis ihre Aussage true wird. Aus diesem Grund wird die Schleife im Deutschen auch als Wiederhole-bis-Schleife bezeichnet, denn sie wiederholt so oft, bis eine Aussage wahr wird.
naschen = 0 until naschen > 3 naschen += 1 print("Wag es nicht nochmal vom Kuchen zu naschen!") end print("Jetzt reicht es! ", naschen, " Mal ist zu viel des Guten!")
In diesem Beispiel nascht Tina so oft vom Kuchen, bis der Mutter der Kragen platzt und sie dem Töchterlein eine Standpauke hält.
Auch hier es es möglich das until hinter eine Zeile zu schreiben:
zaehler = 0 print("Karl geht nun zum ", (zaehler+=1), ". Mal in der Woche zur Schule.") until zaehler > 4
Rekursion
Ruft eine Methode sich selbst auf, bezeichnet man dies als Rekursion. Man kann jede Schleife als Rekursion darstellen:
def while_schleife platz = 8 while platz > 0 do print "Es passen noch #{platz} Personen in die Achterbahn" platz -= 1 end print "Es passen keine Personen mehr in die Achterbahn" end def rekursive_schleife(platz=8) if platz > 0 then print "Es passen noch #{platz} Personen in die Achterbahn" rekursive_schleife(platz - 1) else print "Es passen keine Personen mehr in die Achterbahn" end end
Beide Formen, die while-Schleife, wie auch die Rekursion, schreiben nacheinander die Anzahl der freien Plätze in der Achterbahn auf, bis diese voll besetzt ist. Die while-Schleife haben wir ja bereits kennengelernt.
Eine Rekursion beginnt normalerweise mit einer If-Abfrage. Es werden zwei Fälle unterschieden: Der Rekursionsfall und der Trivialfall. Der Trivialfall wäre platz <= 0, also es ist kein Platz mehr da. In diesem Fall bricht die Rekursion ab. Im Rekursionsfall dagegen, hier platz > 0, ruft die Methode sich selbst mit veränderten Parametern auf.
Eine rekursive Methode ruft also solange sich selbst auf, bis irgendwann der Trivialfall eintritt.
Die Idee hinter der Rekursion ist, ein schweres Problem durch ein leichteres zu ersetzen. Beispielsweise kann man das schwere Problem der Potenzberechnung a hoch b vereinfachen zu einem leichteren Problem a mal (a hoch (b minus 1)). Dieses leichtere Problem lässt sich wieder vereinfachen usw. Bis irgendwann der Trivialfall eintritt. Das ist ein Problem, welches ohne umständliche Berechnung sofort lösbar ist. So können wir z.B. sofort sagen: a hoch 0 ist 1. Das gilt immer. Im Folgenden eine rekursive Potenzmethode:
def potenz(a, b) if b == 0 then 1 #a hoch 0 ist immer 1! else a * potenz(a, b-1) end end #wie viel ist 2 hoch 3 p potenz(2, 3) #=> 8 p 2**3 #=> 8, stimmt also
Wie läuft die Methode ab?
potenz(2, 3) = 2 * potenz(2, 2) = 2 * potenz(2, 1) = 2 * potenz(2, 0) = Trivialfall: 1
Die Methode wird also 5 Mal aufgerufen. Das 5te Mal wird sie mit dem Trivialfall b==0 aufgerufen. Sie ruft sich danach nicht mehr selbst auf, sondern gibt die Zahl 1 zurück. Danach wird die Rekursion wieder Schritt für Schritt aufgelöst:
2 * 2 * 2 * 1 = 8
Bleibt am Ende übrig.
Im Folgenden noch eine Rekursionsmethode für die Division:
def division(a,b) if a >= b then 1 + division(a-b, b) else 0 #Trivialfall end end
Die Division 12 / 3 sieht also folgendermaßen aus:
division(12, 3) = 1 + division(9, 3) = 1 + division(6, 3) = 1 + division(3, 3) = 1 + division(0, 3) = Trivialfall: 0
Und die Auflösung:
1 + 1 + 1 + 1 + 0 = 4
Rekursionen lassen sich manchmal leichter formulieren als Schleifen, da sie näher an der mathematischen Definition stehen. Das heißt: Mathematiker lieben Rekursionen, manch anderer wird sie vielleicht sogar schwerer als Schleifen empfinden,
Es gibt aber Algorithmen, in denen die Rekursion viel leichter vonstatten geht als die gewöhnlichen Schleifen. Aus dem Grund sollte man Rekursionen stets im Hinterkopf behalten.
Schreibe eine Methode, welche den Rest der Division aus den Parametern a und b errechnet, also eine eigene Modulomethode a%b. Versuche diese Methode einmal mit einer while-Schleife, dann mit einer until-Schleife und schließlich mit einer Rekursion aufzuschreiben.
Eine Methode duell(hp1, ang1, hp2, ang2) soll ein Duell zwischen zwei Kontrahenten simulieren. In jeder Kampfrunde soll der Kontrahent 1 soviel hp verlieren, wie der ang-Wert des Kämpfers 2. Der Kämpfer 2 soll wiederum soviel hp verlieren wie der ang-Wert des Angreifers. Die Methode soll ausgeben nach wie vielen Runden der Kampf beendet ist, unter der Voraussetzung, dass ein Kampf endet, sobald die HP eines Kämpfers unter 1 sinkt.
Beispiel für eine Ausgabe:
duell(100, 10, 50, 15) #=> 5
Der Kampf endet nach 5 Runden, da der erste Kämpfer 5 Mal je 10 Schaden zugefügt und der zweite Kämpfer mit nur 50 HP damit besiegt wurde.
Module
Okay, zum letzten Teil, ehe wir ins OOP gehen: Module. Im Grunde genommen verbirgt sich hinter diesem Namen nichts weiter als ein Namensraum, ein Paket, in das man Methoden, Variablen und dergleichen verpacken kann. Man erzeugt ein Modul mit dem Code:
module Name #Modulinhalt end
Wichtig ist, dass der Modulname mit Großbuchstaben anfängt. In das Modul können wir nun Methoden schreiben. Hier ist zu bemerken, dass der Methodenname nun den Modulnamen vor sich tragen muss. Beispiel:
module Schreiber def Schreiber.gruss print("Hallo Welt!") end end
Es ist sogar möglich Module ineinander zu verpacken:
module Schreiber module Neue_Deutsche_Rechtschreibung def Neue_Deutsche_Rechtschreibung.gruss print("Der Fluss ist lang!") end end def Schreiber.gruss print("Der Fluß ist lang!") end end
Nun haben wir zwei Methoden mit den gleichen Namen. Dennoch sind es unterschiedliche Methoden, da sie in unterschiedliche Module gepackt sind. Statt immer den Modulnamen davor zuschreiben, kann man auch self hinschreiben.
module Schreiber def self.gruss print("Hallo!") end end
Juhuu! Nun können wir Module erzeugen. Jetzt stellt sich nur die Frage, was diese Module überhaupt bringen. Die Antwort lautet schlicht und ergreifend: Übersicht. Wir können Code nun in Kategorien verpacken. Alle Methoden die einen Text ausgeben, kommen in das Modul Schreiber. Alle Methoden die mit Variablen rechnen, kommen in das Modul Mathematik usw. Ihr seht nicht so recht den Sinn darin? Nun gut, Module haben noch einige andere tolle Fertigkeiten drauf. Aber die sind vorerst noch zu kompliziert. Module ermöglichen es euch, Code sinnvoll zu verpacken, um ihn dann bei Bedarf auszuführen. Bisher haben wir den Code immer direkt in den Scripteditor geschrieben. Er wurde dadurch zu Beginn des Spiels ausgeführt. Verpackt man ihn in eine Methode eines Moduls, so wird die Methode erst ausgeführt, wenn man sie irgendwo im Script, oder über den Script Event Command mitsamt ihres Moduls aufruft.
Nachwort
Damit wären wir auch schon am Ende des ersten Kurses angelangt. Bisher war leider noch nichts dabei, was man wirklich für den Maker gebrauchen konnte. Die Grundlagen die hier besprochen wurden, sind allerdings in ähnlicher Form in fast jeder Programmiersprache gültig. Es ist also keine Zeitverschwendung sich damit zu beschäftigen. Im Übrigen bauen die späteren Kurse alle auf dieses Grundwissen auf. Der nächste Kurs handelt erst einmal über Objektorientierung und ist damit wohl der wichtigste Kursabschnitt. Sobald das OOP-Konzept durchgenommen wurde, werden die Kurse auch mit praktischen Beispielen und Scripten am Maker arbeiten.
Sollte es Unklarheiten, Anregungen oder Kritiken geben, so teilt mir diese bitte über die Diskussionsseite dieses Artikels mit.



