[#_lesson_4_doing_it_again] == Lektion 4: Noch einmal image::learn/04-repeating.png[Die fertige Lektion, 480] Du hast ein Wort und ein Kästchen gezeichnet. Ein Spiel braucht hundert Dinge: Sterne, Steine, Geschosse, die Zeilen eines Menüs. Hundert Zeilen abzutippen ist nicht die Antwort, und wenn es das wäre, müsstest du noch einmal hundert tippen, sobald du hunderteins willst. Diese Lektion ist die Antwort. Am Ende treibt ein Sternenfeld dein Fenster hinunter, und jeder einzelne Stern wird von denselben fünf Zeilen Code gezeichnet. [#_start_a_new_folder_3] === Leg einen neuen Ordner an Ein Ordner namens `repeating`, darin eine Datei namens `repeating.singe`, und in einem Terminal läuft `Singe -R repeating`. [#_forty_dots] === Vierzig Punkte Tipp das ein und starte es. [source,lua] ---- function onOverlayUpdate() overlayClear() colorForeground(255, 255, 255) for star = 1, 40 do overlayPlot(star * 8, 100) end return OVERLAY_UPDATED end ---- Eine Reihe aus vierzig weißen Punkten quer durch die Mitte des Fensters. `overlayPlot` ist der einfachste Zeichenaufruf, den es gibt: Er setzt ein Pixel auf die aktuelle Vordergrundfarbe, und genau das will ein Stern. Alles Interessante hier steckt in den drei Zeilen drumherum. [source,lua] ---- for star = 1, 40 do ---- Das sagt: Tu das Folgende vierzigmal, und bei jedem Durchlauf steht in `star` eine andere Zahl -- eins beim ersten Mal, zwei beim zweiten, vierzig beim letzten. Beide Enden zählen mit, also sind `1, 40` wirklich vierzig Durchläufe und nicht neununddreißig. Diese Form ist eine *Schleife*, und diese hier ist eine *numerische for-Schleife*. `star` ist ein Name, den du dir ausdenkst, so wie du dir in Lektion zwei `frames` ausgedacht hast, und die Schleife füllt ihn für dich. Du setzt ihn nicht, und du solltest ihn in der Schleife nicht ändern. Es gibt ihn nur zwischen dem `for` und seinem `end`; fragst du danach nach ihm, bekommst du `nil`. Das `do` ist Pflicht und leicht zu vergessen. Lässt du es weg, sagt Lua `'do' expected near`, gefolgt von dem, was es stattdessen gefunden hat, und das ist das Erste in der nächsten Zeile. [source,lua] ---- overlayPlot(star * 8, 100) ---- Der Rumpf. Das ist es, was vierzigmal läuft, und es ist jedes Mal dieselbe Zeile -- was sich ändert, ist `star`, also landet der erste Punkt bei x gleich 8, der zweite bei 16 und der letzte bei 320. Eine Schleife ist nur nützlich, wenn der Rumpf etwas mit der Nummer der Schleife macht, und das Erste, wozu du jedes Mal greifen wirst, ist, damit zu rechnen. Der Rumpf ist eine Stufe weiter eingerückt als das `for`, aus demselben Grund, aus dem bisher alles andere eingerückt war. [source,lua] ---- end ---- Schließt die Schleife. Jetzt kennst du drei Dinge, die ein `end` brauchen: `function`, `if` und `for`. Sie funktionieren alle gleich, sie lassen sich ineinander verschachteln, und an der Einrückung behältst du den Überblick, welches `end` wozu gehört. [#_counting_by_more_than_one] === In größeren Schritten zählen Die Schleife zählt in Einerschritten hoch, wenn du ihr nichts anderes sagst. Füg eine dritte Zahl hinzu, und sie zählt stattdessen in diesen Schritten. [source,lua] ---- for x = 0, overlayGetWidth() - 1, 10 do overlayPlot(x, 100) end ---- Lies es als *von, bis, Schrittweite*. Diese hier geht 0, 10, 20 und so weiter und hört beim letzten Pixel der Fläche oder davor auf, und so zeichnet sie eine gepunktete Linie ganz quer hinüber, egal wie groß das Fenster gerade ist. Es ist dieselbe Punktreihe wie vorher, beschrieben danach, wo die Punkte hinkommen, statt wie viele es sind. Die Schrittweite kann negativ sein, dann zählt die Schleife rückwärts. [source,lua] ---- for countdown = 10, 1, -1 do ---- Zehn, neun, acht, bis hinunter zu eins. Mit einer negativen Schrittweite ist die zweite Zahl der Boden statt der Decke, was du wohl auch erwartest, und ein falsches Vorzeichen ergibt eine Schleife, die nie läuft: `for i = 10, 1 do` ohne Schrittweite zählt von zehn aufwärts in Richtung eins, ist schon hinter dem Ende, bevor es losgeht, und tut nichts. Kein Fehler, keine Ausgabe. Wenn eine deiner Schleifen nie zu laufen scheint, prüf zuerst die Richtung. Die Schrittweite kann auch ein Bruch sein, aber das brauchst du seltener, als du denkst. [#_one_loop_inside_another] === Eine Schleife in einer anderen Der Rumpf einer Schleife ist gewöhnlicher Code, und gewöhnlicher Code kann eine Schleife enthalten. [source,lua] ---- for row = 0, overlayGetHeight() - 1, 20 do for column = 0, overlayGetWidth() - 1, 20 do overlayPlot(column, row) end end ---- Das zeichnet ein Punktraster über das ganze Fenster. Setz es in deinem `onOverlayUpdate` anstelle der Reihe ein und sieh es dir an, denn wie es abläuft, solltest du dir jetzt klarmachen und nicht erst in Lektion elf, wenn etwas davon abhängt. Die äußere Schleife läuft einmal mit `row` gleich 0. Darin läuft die ganze innere Schleife von Anfang bis Ende -- achtzehn Spalten, achtzehn Punkte, eine Zeile des Rasters. Erst wenn die innere Schleife fertig ist, geht die äußere weiter zu `row` gleich 20 und lässt die ganze innere Schleife noch einmal laufen. Der Rumpf der inneren Schleife läuft also achtzehn mal zwölf, das sind zweihundertsechzehn Mal, bei zwölf Durchläufen der äußeren. Wegen dieser Multiplikation füllst du mit verschachtelten Schleifen ein Rechteck mit was auch immer, und darum solltest du auch einen Blick auf die Zahlen werfen, bevor du drei davon verschachtelst. Beachte, dass die Position jedes Punkts beide Schleifenvariablen braucht: `column` für waagerecht und `row` für senkrecht. Das ist das Muster. Wenn der innere Rumpf nur die innere Variable benutzt, zeichnet die innere Schleife immer wieder dasselbe an dieselbe Stelle, und du hast eine langsame Art geschrieben, es einmal zu tun. [#_when_you_do_not_know_how_many] === Wenn du nicht weißt, wie viele Eine `for`-Schleife muss wissen, wie oft, bevor sie anfängt. Manchmal weißt du das nicht, und dann willst du die andere Sorte. [source,lua] ---- x = 0 while x < overlayGetWidth() do overlayPlot(x, 100) x = x + 10 end ---- Eine `while`-Schleife prüft vor jedem Durchlauf eine Frage, genau so eine Frage, wie ein `if` sie stellen würde, und macht weiter, solange die Antwort wahr ist. Ist die Antwort falsch, hört sie auf und macht mit der Zeile nach dem `end` weiter. Ist die Antwort gleich beim ersten Mal falsch, läuft der Rumpf überhaupt nie. Dieses Beispiel zeichnet dieselbe gepunktete Linie wie das `for` mit Schrittweite zehn, und es braucht drei Zeilen für das, was `for` in einer erledigt hat, also nimm dafür `for`. Hier ist der Unterschied, der es entscheidet: * Nimm `for`, wenn die Zahl der Durchläufe feststeht, bevor du anfängst. Vierzig Sterne. Jedes zehnte Pixel quer hinüber. Jede Zeile eines Rasters. * Nimm `while`, wenn du darauf wartest, dass etwas wahr wird, und nicht vorher sagen kannst, wie lange das dauert. Karten austeilen, bis der Stapel leer ist. Immer wieder fragen, bis der Spieler etwas Gültiges eintippt. Eine Liste durchgehen, bis du findest, was du gesucht hast. Ein `while` hat drei Teile, und alle drei musst du selbst schreiben: die Variable vor der Schleife anlegen, sie im `while` prüfen und sie im Rumpf ändern. Die `for`-Schleife erledigt alle drei für dich, und darum greifst du zu ihr, wenn sie passt. Vergiss im `while` den dritten Teil, und die Frage hört nie auf, wahr zu sein; genau das machst du am Ende dieser Lektion absichtlich. [#_a_star_made_out_of_its_own_number] === Ein Stern aus seiner eigenen Nummer Jetzt das Sternenfeld und ein Problem, das den Rest von Teil eins prägt. Jeder Stern braucht ein eigenes x und ein eigenes y, und du hast keine Möglichkeit, hundert getrennte Variablen aufzubewahren -- `star1X`, `star2X` und so weiter bis hundert zu schreiben ist schlimmer, als die hundert Zeilen abzutippen, um die du herumkommen wolltest. Also speichere sie nicht. Rechne jede Position aus dem Einzigen aus, was einen Stern von seinen Nachbarn unterscheidet: seiner Nummer. [source,lua] ---- starX = (star * 37) % width ---- Das `%` ist der letzte der Rechenoperatoren, und es ist der *Rest*: das, was beim Teilen übrig bleibt. Fünfzehn geteilt durch vier ist drei, Rest drei, also ist `15 % 4` gleich 3. Seine nützliche Eigenschaft ist, dass das Ergebnis nie so groß werden kann wie die Zahl rechts, also ist der Rest beim Teilen durch `width` immer eine Position irgendwo auf dem Bildschirm, ganz egal, wie groß die linke Seite wird. Stern 1 landet bei 37, Stern 5 bei 185, Stern 10 bei 10 -- 370 ist über 360 hinausgelaufen und links wieder hereingekommen. Siebenunddreißig ist gewählt, weil es keinen gemeinsamen Teiler mit 360 hat, und das verhindert, dass die Sterne in ordentlichen Streifen landen; probier stattdessen 36, und du siehst das Problem sofort. Das Hinabtreiben ist derselbe Trick mit einer Zahl, die mit der Zeit wächst. [source,lua] ---- drift = 0 function onOverlayUpdate() drift = drift + 1 ... starY = (star * 61 + math.floor(drift * layer * 0.5)) % height ---- `drift` wohnt außerhalb der Funktion und steigt in jedem Frame, genau wie `frames` in Lektion zwei. Wenn du es zum y jedes Sterns addierst, bewegt sich das ganze Feld gemeinsam nach unten, und das `%` holt einen Stern, der unten herausfällt, oben wieder herein, für immer, gratis. `math.floor` wirft den Bruchteil einer Zahl weg und gibt den ganzen Teil darunter zurück: `math.floor(7.9)` ist 7. In Lektion zwei hieß es, dass Singe das beim Zeichnen für dich tut, und das stimmt auch, aber wenn du es selbst tust, ist die Zahl, mit der du arbeitest, die Zahl, die auf dem Bildschirm erscheint. Das wird in Lektion sieben wichtig, wenn sich zwei Dinge einig sein müssen, ob sie sich berühren. Beachte, dass `layer` darin eine zweite Schleifenvariable ist. Die Sterne werden von einer Schleife in einer Schleife gezeichnet, und die äußere ist keine Zeile eines Rasters, sondern eine *Entfernung*: drei Ebenen von Sternen, jede mit eigener Geschwindigkeit und eigener Helligkeit, und das lässt ein flaches Punktfeld aussehen, als hätte es Tiefe. [#_chance] === Zufall Sterne funkeln. Funkeln heißt eine Helligkeit, die in jedem Frame ein bisschen anders ist, und „ein bisschen anders“ ist `math.random`. [source,lua] ---- shade = math.random(60, 90) + layer * 55 colorForeground(shade, shade, shade) ---- `math.random(60, 90)` gibt eine ganze Zahl von 60 bis 90 zurück, beide eingeschlossen, bei jedem Aufruf neu gewählt. Rufst du es ganz ohne Argumente auf, bekommst du stattdessen einen Bruch zwischen 0 und 1, und diese Form nimmst du, wenn etwas in einem von fünf Fällen passieren soll. Den Zufallsgenerator musst du nicht einrichten; das macht Lua, wenn Singe startet. Das Addieren von `layer * 55` hebt die nahen Ebenen aus dem trüben Bereich heraus, also bleiben die fernen Sterne schwach und die nahen sind fast weiß. Gleich viel Rot, Grün und Blau ist, wie Lektion drei erklärt hat, ein Grauton. Weil `shade` in jedem Frame für jeden Stern neu gewählt wird, flackert jeder Stern für sich, und bei sechzig Frames pro Sekunde wirkt das wie Funkeln und nicht wie ein Fehler. [#_the_whole_thing_3] === Das Ganze [source,lua] ---- LAYERS = 3 STARS_PER_LAYER = 40 ACROSS_STEP = 37 DOWN_STEP = 61 LAYER_SHIFT = 13 LAYER_SPEED = 0.5 LAYER_BRIGHT = 55 width = overlayGetWidth() height = overlayGetHeight() drift = 0 function onOverlayUpdate() drift = drift + 1 overlayClear() for layer = 1, LAYERS do for star = 1, STARS_PER_LAYER do starX = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width starY = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height shade = math.random(60, 90) + layer * LAYER_BRIGHT colorForeground(shade, shade, shade) overlayPlot(starX, starY) end end return OVERLAY_UPDATED end ---- Hundertzwanzig Sterne, gezeichnet von fünf Zeilen, und die Datei ist kürzer als die aus Lektion drei. Weiter kommst du mit diesem Ansatz aber auch nicht, und es lohnt sich, ehrlich zu sagen, warum, solange du es vor dir hast. Jeder Stern darin ist eine Formel. Nichts an einem Stern wird *gemerkt*: Du kannst keinen heller machen als seinen Nachbarn und ihn so bleiben lassen, du kannst keinen vom Himmel schießen, und du kannst keinen vom Spieler einsammeln lassen, weil es keinen Ort gibt, an dem du aufschreiben könntest, dass ihm etwas passiert ist. Stern siebzehn ist kein Ding. Er ist eine Zahl, die kurz durch eine Variable namens `star` wandert. Sobald du hundert Dinge willst, die sich jedes etwas Eigenes merken, brauchst du einen Ort für hundert Sätze von Fakten. Das ist Lektion sechs, und das Sternenfeld ist der Grund, warum es sie gibt. [#_try_it_4] === Probier es aus . *Mehr und weniger.* Probier `STARS_PER_LAYER = 200`, dann `4`. Dann `LAYERS = 8`, und sieh dir an, was `layer * LAYER_BRIGHT` mit `shade` macht, wenn die Nummer der Ebene groß wird. Die Zahlen, die `colorForeground` annimmt, hören bei 255 auf. . *Ändere die Abstände.* Setz `ACROSS_STEP` auf 36 und starte es. Die Sterne ordnen sich in Streifen, weil 36 genau zehnmal in 360 passt. Stell es wieder auf 37 und probier 71 und 90. . *Stopp das Funkeln.* Ersetze die Zeile mit `math.random` durch ein schlichtes `shade = layer * 70` und vergleiche. Entscheide, was dir besser gefällt; es gibt keine richtige Antwort, und zu bemerken, dass du einen Geschmack hast, gehört zur Arbeit. . *Lass sie nach oben fallen.* Ein Zeichen. . *Zeichne auch das Raster.* Setz die verschachtelte Rasterschleife von vorhin wieder ein, nach den Sternen, in einer gedämpften Farbe. Zwei verschachtelte Schleifen in einer Funktion, die zwei verschiedene Dinge zeichnen, und die zweite stört die erste nicht. [#_break_it_on_purpose_4] === Mach es absichtlich kaputt Jeder Fehler bisher war eine Meldung. Dieser hier nicht, und genau darum solltest du ihn kennenlernen, während du mit ihm rechnest. Setz das in dein `onOverlayUpdate`, direkt unter `overlayClear()`, starte es und lies den nächsten Absatz, bevor du irgendetwas anderes tust. [source,lua] ---- x = 0 while x < width do overlayPlot(x, 100) end ---- Die Zeile, die `x` weiterschiebt, fehlt, also bleibt `x` auf null, also hört `x < width` nie auf, wahr zu sein. Die Schleife dreht sich für immer und setzt immer dasselbe Pixel, und sie steckt in `onOverlayUpdate`, also kehrt `onOverlayUpdate` nie zurück. Singe wartet auf deine Funktion. Es kann nicht zeichnen, es kann die Tastatur nicht lesen, und es kann sich nicht beenden. Was du siehst, ist ein Fenster, in dem sich nichts mehr bewegt. Esc bewirkt nichts. Q bewirkt nichts. Ausgegeben wird auch nichts. Geh in das Terminal, in dem du Singe gestartet hast, und drück *Strg* und *C* zusammen. Singe hält deine Schleife an und sagt dir, wo sie gerade war: ---- Stopped: the script did not return, and Singe was asked to quit. stack traceback: repeating/repeating.singe:20: in function 'onOverlayUpdate' ---- Vielleicht sagt es das zweimal, ein paar Sekunden auseinander, bevor es sich beendet. Einmal reicht völlig. Zeile 20 ist das `end` deines `while`, und dort drehte sich die Schleife, als du sie angehalten hast. Nimm die Schleife heraus, speichere und starte Singe neu. Das ist ein Bericht, kein Fehler. Nichts hat sich beschwert, bis du gefragt hast, weil aus Sicht des Computers nichts schiefgegangen ist. Du hast eine Schleife verlangt, die läuft, solange `x` kleiner als die Breite ist, und genau die hast du bekommen. Das ist die eine Art von Fehler, bei der dir die Fehlermeldungen nicht helfen können, und das Gegenmittel ist eine Gewohnheit: Jedes Mal, wenn du ein `while` schreibst, schreib die Zeile, die die Variable ändert, bevor du irgendetwas anderes in den Rumpf schreibst. [#_what_you_learned_4] === Was du gelernt hast * `for name = first, last do ... end` führt seinen Rumpf einmal für jede Zahl vom ersten bis zum letzten Wert aus, beide eingeschlossen. * Eine dritte Zahl ist die Schrittweite, und sie darf negativ sein, um rückwärts zu zählen. * Die Schleifenvariable gibt es nur in der Schleife, und der Rumpf sollte sie benutzen. * `for` braucht ein `do`, und jede Schleife braucht ein `end`. * Eine Schleife in einer Schleife lässt bei jedem Durchlauf der äußeren die ganze innere laufen, und so füllst du ein Rechteck. * `while question do ... end` wiederholt, solange die Frage wahr ist, und du musst selbst im Rumpf etwas ändern. * Nimm `for`, wenn du weißt, wie oft, und `while`, wenn du darauf wartest, dass etwas wahr wird. * `%` ist der Rest, und er hält eine wachsende Zahl in einem Bereich. * `math.random(a, b)` liefert eine ganze Zahl von a bis b; `math.floor` wirft den Bruchteil weg. * Eine Schleife, die nie endet, friert das ganze Spiel ohne Fehlermeldung ein, und mit *Strg* und *C* im Terminal kommst du wieder heraus. [#_next_time_4] === Beim nächsten Mal Sieh dir dieses `onOverlayUpdate` noch einmal an. Es zeichnet Sterne, es rechnet Positionen aus, es wählt Farben, und es wird langsam zu einer Funktion, die vier Aufgaben erledigt statt einer. In Lektion fünf schreibst du eigene Funktionen, gibst ihnen Argumente, so wie Singes Funktionen sie bekommen, und holst Antworten aus ihnen heraus -- und das Sternenfeld wird zu einer Zeile, in der `drawStars()` steht.