====== Schleifen ====== Schleifen werden dazu benutzt, einen bestimmten Teil eines Skripts wiederholt auszuführen. Ähnlich zu [[ scripting:tutorials:level1:branches |Verzweigungen]] wird der Inhalt einer Schleife so lange ausgeführt, wie eine bestimmte Bedingung zutrifft. Auch bei Schleifen wird diese Bedingung mittels eines [[ scripting:tutorials:level1:variables#booleans |Booleans]] ausgedrückt. Es gibt in Lua meherere Möglichkeiten, eine Schleife zu formulieren. Die wollen wir im Folgenden nacheinander durchgehen. ---- ===== while-Schleife ===== Die **while**-Schleife ist die intuitivste der Schleifen in Lua. Als Satz formuliert setzt sie folgende Logik um:\\ "Solange eine bestimmte Bedingung erfüllt ist, führe den folgenden Block aus." Eine **while**-Schleife beginnt mit dem Schlüsselwort ''while''. Darauf folgt die Bedingung, die geprüft werden soll. Mit ''do'' wird die Definition der Bedingung abgeschlossen und der auszuführende Block beginnt. ''end'' schließt wie aus Verzweigungen oder Funktionen gewohnt die Schleife ab. Dieses Prinzip zeigt das folgende einfache Beispiel: MyNumber = 1 while MyNumber < 42 do MyNumber = MyNumber + 1 end Als Satz formuliert: "Solange MyNumber kleiner als 42 ist, erhöhe MyNumber um 1." Die Schleifenbedingung kann dabei jeder mögliche Boolean sein, zum Beispiel auch das Ergebnis einer Funktion: -- Dieser Code hat genau die gleiche Funktion wie unser Beispiel oben function IsNumberLargeEnough(_Number) return _Number >= 42 end MyNumber = 1 while not IsNumberLargeEnough(MyNumber) do MyNumber = MyNumber + 1 end Die Bedingung, ob der Inhalt der Schleife ausgeführt werden soll, wird **immer im Voraus** geprüft. Das heißt, wenn im Beispiel oben ''MyNumber'' zu Beginn mit 42 definiert wird, wird die Zahl kein einziges Mal um 1 erhöht werden. ---- **Hinweis**: Unsere Beispiele in diesem und den folgenden Kapiteln können besser nachvollzogen werden, wenn man die Programmzeilen nebenbei ausprobieren kann. Dazu kann man diese Webseite hier verwenden: [[ https://www.lua.org/demo.html ]] Um dabei bei der Ausgabe ein Ergebnis zu sehen, kann man die Fnuktion ''print'' verwenden. Will man beispielsweise die Variable ''MyNumber'' ansehen, kann man print(MyNumber) schreiben. Wenn die Variable einen anderen Namen hat, muss man diesen dementsprechend austauschen. ---- Um die Möglichkeiten der **while**-Schleife besser zeigen zu können, wollen wir den Rest einer ganzzahligen Division berechnen. Diese Operation wird auch //Modulo// genannt. Ziel ist also für zwei Zahlen A und B herauszufinden, wie viel Rest beim Berechnen von ''A/B'' übrig bleibt. Die Strategie unseres Algorithmus ist es, B so lange von A abzuziehen, bis B nicht mehr in A "passt". Der Rest muss folglich der Rest der Division sein: function Modulo(_A, _B) while _A > _B do _A = _A - _B end return _A end Ein klassisches Beispiel für die Verwendung einer **while**-Schleife ist die Berechnung des größten gemeinsamen Teilers zweier Zahlen, sodass beispielsweise ein Bruch gekürzt werden kann. Dazu verwenden wir den [[ https://de.wikipedia.org/wiki/Euklidischer_Algorithmus |Euklidischen Algorithmus]]: function GreatestCommonDivisor(_A, _B) if _A == 0 then return _B else while _B ~= 0 do if _A > _B then _A = _A - _B else _B = _B - _A end end end return _A end Wer Lust hat, kann sich auf der verlinkten Wikipediaseite anschauen, warum dieser Algorithmus funktioniert. **Achtung**: Stelle bei jeder Formulierung einer **while**-Schleife sicher, dass die Schleifenbedingung auch wirklich irgendwann **false** wird! Andernfalls wird dein Programm die Schleife nie verlassen und darin "gefangen" sein (**Endlosschleife**). Leicht modifiziert, wird das Beispiel von oben so zu einer Endlosschleife: MyNumber = 1 while MyNumber < 42 do MyNumber = MyNumber - 1 end Jetzt wird ''MyNumber'' nicht mehr um 1 erhöht, sondern reduziert. Dadurch entfernt sich die Zahl immer weiter von der 42, weshalb die Schleife niemals enden kann. Die Lua-Demo wird das Programm abbrechen, aber das Siedler-Spiel wird sich aufhängen, sodass es nur mit dem Task Manager beendet werden kann! ---- ===== for-Schleife ===== Die **for**-Schleife wird dazu benutzt, den eingesetzten Code-Block für eine feste Anzahl an Wiederholungen auszuführen. Die Anzahl der Wiederholungen (**Iterationen**) wird durch eine Zähler angegeben, der einen Startwert, einen Zielwert und eine Schrittweite hat. Beim Startwert beginnend, geht der Zähler gemäß der Schrittweite auf den Zielwert zu. Bei jedem Schritt wird der Block in der Schleife ausgeführt. Eingeleitet wird die **for**-Schleife mit dem Schlüsselwort ''for''. Danach wird der Name einer **lokalen** Zählvariable angegeben, die innerhalb der Schleife benutzt werden kann. Nach ''do'' beginnt wie gewohnt der Schleifenblock und endet mit ''end''. -- Dieser Code addiert alle ungeraden Zahlen von 1 bis 10 Sum = 0 -- Counter ist der Name der Zählvariable -- Die erste Zahl nach dem = ist der Startwert -- Die zweite Zahl ist der Zielwert, getrennt durch ein Komma -- Die dritte Zahl ist die Schrittweite, getrennt durch ein Komma for Counter = 1, 10, 2 do -- In jeder Iteration wird der aktuelle Wert der Zählvariable addiert Sum = Sum + Counter end Wenn die Schrittweite der Zählvarable 1 sein soll, kann man sie einfach weglassen: -- Dieser Code erstellt einen String aller Zahlen von 1 bis 10 NumberString = "" -- Die Schrittweite soll hier 1 sein, also brauchen wir sie nicht extra aufzuschreiben for Counter = 1, 10 do NumberString = NumberString..Counter -- In allen außer der letzten Iteration wollen wir ein Komma, um die Zahlen zu trennen if Counter < 10 then NumberString = NumberString..", " end end Der Variablenname ''Counter'' ist selbstverständlich willkürlich gewählt und kann durch jeden güligen Variablennamen ersetzt werden. Viele Programmierer benutzen als Zählvariable schlicht ''i''. Wir geben noch ein Beispiel, um zu zeigen, dass **for**-Schleifen selbstverständlich auch rückwärts zählen können, wenn die Schrittweite negativ ist: PerfectlyBalanced = 0 -- Hier werden wieder alle Zahlen in der Zählschleife addiert -- Da die gleichen positiven und negativen Zahlen addiert werden, -- wird das Ergebnis 0 sein for i = 10, -10, -1 do PerfectlyBalanced = PerfectlyBalanced + i end Es können auch mehrere Schleifen ineinander geschachtelt werden. Dabei ist es **wichtig**, dass die Zählvariablen alle unterschiedliche Namen haben! -- Dieser Code addiert alle Zahlen a und b, wenn a und b zwischen 1 und 10 liegen -- Die Summen werden als String angezeigt for a = 1, 10 do -- Jede "Reihe" muss mit einem leeren String beginnen NumberString = "" for b = 1, 10 do -- Die einzelnen Zahlen werden mit einem Leerzeichen getrennt NumberString = NumberString..(a + b).." " end print(NumberString) end **Hinweis**: Innerhalb der Schleife solltest du die Zählvariable niemals verändern: for i = 1, 10 do i = i + 1 end Es ist zwar prinzipiell möglich, das zu tun, führt aber meistens nur zu Problemen. Besser ist es, einfach eine andere Schrittweite zu setzen. Falls die Schrittweite sich verändern soll, bietet sich eine **while**-Schleife an, die eine maßgeschneiderte Zählvariable führt. ---- ===== repeat-until-Schleife ===== Die **repeat-until**-Schleife ist im Prinzip nur eine "umgekehrte" **while**-Schleife und findet nur selten Verwendung. Der Vollständigkeit halber wollen wir sie aber hier erklären. Der Schleifenblock wird mit ''repeat'' eingeleitet und mit ''until'' beendet. Auf ''until'' folgt die Bedingung, mit der die Schleife abbricht: MyNumber = 1 repeat MyNumber = MyNumber + 1 until MyNumber >= 42 Wir sehen, dass diese Art der Schleife auf zwei verschiedene Arten die **while**-Schleife umkehrt: - Die angegebene Bedingung in der **repeat-until** Schleife gibt nicht an, unter welcher Bedingung die Schleife fortgesetzt wird, sondern unter welcher Bedingung sie beendet wird. Will man eine **while**-Schleife in eine **repeat-until**-Schleife umbauen, muss man die Bedingung also genau umkehren - Der Schleifenblock wird **immer mindestens 1 mal** ausgeführt. Diese Eigenschaft wird in der Syntax dadurch ausgedrückt, dass die Bedingung **unten** statt **oben** in der Schleife steht. Das Programm führt also zuerst den Block aus und prüft dann, ob eine weitere Iteration stattfinden soll. Der letzte Punkt kann zu subtilen Unterschieden im Codeverhalten führen. Vergleiche: MyNumber = 42 while MyNumber < 42 do MyNumber = MyNumber + 1 end und MyNumber = 42 repeat MyNumber = MyNumber + 1 until MyNumber >= 42 In der ersten Variante hat ''MyNumber'' zum Schluss den Wert ''42'', in der zweiten den Wert ''43''! Dies ist das Resultat davon, dass die **repeat-until**-Schleife immer mindestens 1 mal ausgeführt wird. Obwohl die beiden Schleifenbedingungen äquivalent sind, sind die Ergebnisse unterschiedlich. Es gibt einige Fälle, in denen eine **repeat-until**-Schleife sinnvoll ist. In den meisten Fällen kannst du aber davon ausgehen, dass eine **while**-Schleife ausreicht. Auch mit **repeat-until** sind Endlosschleifen möglch. Stelle deshalb sicher, dass die Schleifenbedingung immer **true** werden kann! ---- ===== Abgrenzung zu Funktionen ===== Im Artikel zu [[ scripting:tutorials:level1:functions_blocks |Funktionen]] haben wir beschrieben, dass Funktionen dazu da sind, Codeteile wiederverwertbar und wiederholt ausführbar zu machen. Schleifen scheinen eine ähnliche Funktion zu erfüllen, da auch hier kleine Codeteile wiederholt ausgeführt werden. Warum braucht man also beides? Funktionen dienen primär dazu, Teilen von Code einen **Namen** zu geben. Dadurch erhält der Code Struktur und wird besser lesbar. Funktionen können außerdem mit Parametern arbeiten und ein Ergebnis zurückgeben (''return''), was mit Schleifen nur über Umwege geht.\\ Schleifen hingegen ersparen dem Programmierer Schreibarbeit, da der Inhalt der Schleife nicht mehrmals, sondern nur einmal aufgeschrieben werden muss. Auch laufen viele Berechnungsvorschriften, wie das Beispiel oben mit dem größten gemeinsamen Teiler, **bis sie fertig sind** und keine feste Anzahl an Schritten. Tatsächlich haben Schleifen und Funktionen aber einen gemeinsamen historischen Ursprung. Wer möchte, kann sich dazu den [[ https://en.wikipedia.org/wiki/Goto |Wikipedia-Artikel zu Goto]] anschauen. Lua bietet keine Funktionalität für Goto (und das ist auch gut so). ---- Mit sogenannten //Tables// (= Tabellen) werden im nächsten Kapitel eine mächtige und flexible Datenstruktur vorgestellt. [[ scripting:tutorials:level1:variable_scope | Voriges Kapitel: Lokale und Globale Variablen ]]\\ [[ scripting:tutorials:level1:tables | Nächstes Kapitel: Tables ]]\\ [[ scripting:tutorials:level1:loops | Zurück nach oben ]]