[#_lesson_6_lists_of_things] == Lektion 6: Listen von Dingen image::learn/06-tables.png[Die fertige Lektion, 480] Du hast hundertzwanzig Sterne, und keiner davon existiert. Nirgends in deinem Programm gibt es einen Stern: Es gibt eine Schleife, die hundertzwanzigmal läuft, und bei jedem Durchlauf rechnet sie aus der Nummer des Sterns und seiner Ebene zwei Zahlen aus, setzt ein Pixel und vergisst alles. Die drei Ebenen sind da, um das eine vorzutäuschen, was diese Anordnung nicht kann: einen Stern anders sein zu lassen als seinen Nachbarn. Diese Lektion gibt dir, was fehlt. Du lernst, wie du eine Liste von Dingen aufbewahrst, wie du ein Ding machst, das mehrere benannte Werte auf einmal hält, und dann, wie du beides zu einer Liste von Dingen zusammensetzt, von denen sich jedes seine eigene Position, Geschwindigkeit und Helligkeit merkt. Es ist die längste Lektion in diesem Teil des Buchs und die, auf die sich alles Spätere stützt. Lass dir Zeit damit. [#_start_a_new_folder_5] === Leg einen neuen Ordner an Ein Ordner namens `tables`, darin eine Datei namens `tables.singe`, und in einem Terminal läuft `Singe -R tables`. Kopier das fertige Programm aus Lektion fünf hinein: das Sternenfeld, das Raumschiff und alles andere. [#_a_list] === Eine Liste Füg diese Zeile oben in `tables.singe` ein, unter den anderen Werten, und die drei Aufrufe von `overlayPrint` in `onOverlayUpdate`, über dem `return`. [source,lua] ---- local names = { "Ada", "Grace", "Alan" } ---- [source,lua] ---- overlayPrint(2, 2, names[1]) overlayPrint(2, 3, names[3]) overlayPrint(2, 4, "There are " .. #names .. " names.") ---- Starte es. `Ada` in einer Zeile, `Alan` in der nächsten und `There are 3 names.` darunter. Die geschweiften Klammern in der ersten Zeile machen eine *Tabelle*. Eine Tabelle ist der eine Behälter, den Lua hat, und alles, wovon du je mehr als eins aufbewahrst, kommt in eine. Hier hält sie drei Strings hintereinander, und diese Form nennt man eine *Liste*. Die eckigen Klammern in den nächsten Zeilen holen einen Eintrag heraus. `names[1]` ist der erste Eintrag, `names[3]` der dritte, und die Zahl in den Klammern heißt *Index*. Bei dieser Zahl trennen sich die Wege von Lua und fast jeder anderen Sprache, der du begegnen wirst. *Lua zählt ab eins.* C, Java, Python, JavaScript und die meisten anderen zählen ab null, also ist ihr erster Eintrag Eintrag null und ihr dritter Eintrag Index zwei. Der erste Eintrag von Lua ist Eintrag eins, und das ist genau das eine, worüber Programmierer stolpern, die von woanders herkommen. Du kommst von nirgendwo, also hast du es leichter: Der erste ist Nummer eins. `names[0]` ist übrigens kein Fehler. Dort ist nichts gespeichert, also ist es `nil` -- der Nichts-Wert aus Lektion drei. Ein Index, der nie gefüllt wurde, liefert beim Lesen immer `nil`, und das ist eine Freundlichkeit, bis zu dem Moment, in dem du versuchst, es zu benutzen. Das `#` vor dem Namen einer Tabelle ist für eine Weile das letzte neue Satzzeichen. Es gibt dir, wie viele Einträge in der Liste sind, also ist `#names` gleich `3`. Weil die Anzahl und der letzte Index dieselbe Zahl sind, ist `names[#names]` immer der letzte Eintrag, egal wie lang die Liste wird. [#_adding_and_taking_away] === Hinzufügen und Wegnehmen Eine Liste, die du komplett ausschreiben musst, nützt einem Spiel nicht viel. Mit diesen beiden Aufrufen ändert sich eine Liste, während das Programm läuft. [source,lua] ---- table.insert(names, "Katherine") ---- Das hängt `Katherine` hinten an, und `#names` ist jetzt `4`. `table.insert` ist eine Funktion wie jede andere, und der Punkt in der Mitte ihres Namens bedeutet, dass sie in einer Tabelle namens `table` wohnt, die Lua mitbringt -- einer Tabelle voller Funktionen, die mit Tabellen arbeiten. Du musst sie weder anlegen noch laden. Sie ist da. Gib `table.insert` drei Argumente, und das mittlere sagt, wohin der neue Eintrag kommt. [source,lua] ---- table.insert(names, 1, "Edsger") ---- `Edsger` kommt vorne hinein, und alles, was schon da war, rückt auf, um Platz zu machen: `Ada` war Eintrag eins und ist jetzt Eintrag zwei. Nichts wird überschrieben, und nichts geht verloren. Einen Eintrag herauszunehmen ist das Spiegelbild. [source,lua] ---- table.remove(names, 2) ---- Eintrag zwei verschwindet, und alles dahinter rückt nach, um die Lücke zu schließen, sodass die Liste nie ein Loch in der Mitte hat. `table.remove` gibt außerdem den Eintrag zurück, den es herausgenommen hat, also wirft `local gone = table.remove(names, 2)` ihn weg und sagt dir in einer Zeile, was er war. Lass die Zahl ganz weg -- `table.remove(names)` -- und es nimmt den letzten Eintrag, und so benutzt du eine Liste am schnellsten als Stapel. Setz diese drei Aufrufe nacheinander in dein Skript, starte es nach jedem und sieh zu, wie sich die Anzahl in der dritten Zeile ändert. [#_walking_a_list] === Eine Liste durchlaufen Eine Möglichkeit, jeden Eintrag zu besuchen, kennst du schon, denn du benutzt sie seit Lektion vier: [source,lua] ---- for i = 1, #names do overlayPrint(2, i + 1, names[i]) end ---- Das funktioniert. Bei eins anfangen, bei der Anzahl aufhören, den Zähler als Index benutzen. Es gibt einen besseren Weg, und so wirst du es ab jetzt schreiben: [source,lua] ---- for i, name in ipairs(names) do overlayPrint(2, i + 1, name) end ---- `ipairs` läuft eine Liste ab Eintrag eins durch, bis sie zu Ende ist, und gibt deiner Schleife bei jedem Durchlauf zwei Dinge: den Index und den Eintrag selbst. Auf das zweite kommt es an. Mit dem zählenden `for` bekommst du eine Zahl und musst mit `names[i]` zurück zur Tabelle, um herauszufinden, was dort steht; mit `ipairs` hast du den Eintrag schon in der Hand, unter dem Namen, den du als zweiten hinschreibst. Außerdem kann man es schwerer falsch machen. `for i = 1, #names` ist eine Stelle, an der man sich leicht um eins verzählt -- fang bei null an, und du gibst ein `nil` aus, hör bei `#names - 1` auf, und dir fehlt der letzte -- und in `ipairs` steht überhaupt keine Zahl, die man falsch machen könnte. Meistens willst du gar nicht den Index, nur den Eintrag. Schreib den Index als einzelnen Unterstrich, und fertig: [source,lua] ---- for _, name in ipairs(names) do ---- `_` ist ein ganz gewöhnlicher Name, und nichts in Lua behandelt ihn besonders. Es ist eine Gewohnheit, mit der Programmierer sagen: „Hier kommt etwas an, und ich werde es nicht benutzen.“ Wer deine Schleife liest, weiß auf einen Blick, dass der Index darin keine Rolle spielt. Lösch jetzt die Versuche mit `names` und mit ihnen die Zeilen mit `overlayPrint`. Du weißt, was eine Liste ist. Zeit, etwas Besseres als einen String hineinzulegen. [#_a_table_can_also_be_a_record] === Eine Tabelle kann auch ein Datensatz sein Ein Stern muss sich vier Dinge merken: wo er waagerecht ist, wo er senkrecht ist, wie schnell er fällt und wie hell er ist. Vier getrennte Listen, die du alle von Hand im Gleichschritt halten musst, wären furchtbar. Mach stattdessen eine Tabelle und leg Namen hinein. [source,lua] ---- local star = {} star.x = 40 star.y = 0 star.speed = 2 star.shade = 180 ---- Die leeren geschweiften Klammern machen eine leere Tabelle. Die vier Zeilen danach legen Werte hinein, unter Namen statt unter Nummern, und mit dem Punkt sagst du, welchen Namen du meinst. `star.x` liest man als das `x` von `star`, und es verhält sich genau wie jede andere Variable: Lies es, weise ihm etwas zu, zähl etwas dazu. [source,lua] ---- star.y = star.y + star.speed ---- Eine so benutzte Tabelle -- eine feste Menge benannter Werte, die ein Ding beschreiben -- ist ein *Datensatz*, und die Namen darin sind seine *Felder*. Es ist dieselbe Art Tabelle wie die Liste oben. Der einzige Unterschied ist, dass man die Einträge einer Liste über eine Nummer findet und die Felder eines Datensatzes über einen Namen, und eine Tabelle kann beides gleichzeitig, falls du das je willst. Der Punkt ist eine Kurzschreibweise. `star.x` und `star["x"]` bedeuten genau dasselbe, und das solltest du einmal gesehen haben: Die eckigen Klammern nehmen einen Namen genauso gern wie eine Zahl, also sind eine Liste und ein Datensatz wirklich eine Idee mit zwei Hüten. Benutz den Punkt. Er liest sich besser. Ein Feld, das du nie gesetzt hast, ist `nil`, genau wie ein Index, den du nie gefüllt hast. Es gibt nirgends eine Liste erlaubter Feldnamen, und nichts prüft deine Rechtschreibung, also ist `star.sped` kein Fehler -- es ist `nil`, und was dann passiert, lernst du am Ende dieser Lektion kennen. Vier Zeilen zu schreiben, um vier Felder zu füllen, wird schnell langweilig. Du kannst die Felder gleich beim Anlegen der Tabelle in die geschweiften Klammern schreiben: [source,lua] ---- local star = { x = 40, y = 0, speed = 2, shade = 180 } ---- Dieselbe Tabelle, eine Anweisung. Die Kommas trennen die Felder, hinter dem letzten braucht es kein Komma, und die Zeilen sind nur so verteilt, damit man sie leicht lesen kann. [#_a_list_of_records] === Eine Liste von Datensätzen Hier ist die ganze Idee dieser Lektion in zwei Zeilen: [source,lua] ---- local stars = {} table.insert(stars, star) ---- Eine Liste kann alles halten, auch eine Tabelle. Eine Liste von Datensätzen ist also eine Liste von Dingen, von denen sich jedes alles über sich selbst merkt. `stars[1]` ist ein ganzer Stern, und `stars[1].y` ist, wie weit unten auf dem Bildschirm dieser Stern steht. Fast jedes Spiel, das du je schreibst, ist eine Handvoll solcher Listen: die Gegner, die Schüsse, die Extras und die fallenden Blöcke. Eine Sache über Tabellen musst du wissen, bevor du den Code schreibst, und sie erwischt jeden einmal. Wenn du einer Funktion eine Tabelle übergibst, wird sie nicht kopiert. Eine Zahl wird kopiert -- darum konnte `half` in Lektion fünf ihre `number` zerhacken, ohne `drift` zu stören -- aber eine Tabelle wird geteilt. Beide Namen verweisen auf dieselbe Tabelle, und eine Änderung über den einen ist über den anderen zu sehen. [source,lua] ---- local star = stars[3] star.y = star.y + 1 ---- Das bewegt den dritten Stern in der Liste. Keine Kopie davon: ihn selbst. Genau das willst du hier, und genau das lässt den nächsten Abschnitt funktionieren, aber denk daran, wenn eine Funktion, die du geschrieben hast, etwas ändert, bei dem du das nicht erwartet hast. [#_rebuild_the_star_field] === Bau das Sternenfeld neu Die Rechnerei fliegt raus. Ein Stern wird gleich zu einem Ding. Lösch `starAt`, `shadeFor`, `drawLayer` und das alte `drawStar` und mit ihnen die Konstanten, die sie benutzt haben: `LAYERS`, `STARS_PER_LAYER`, `ACROSS_STEP`, `DOWN_STEP`, `LAYER_SHIFT` und `LAYER_SPEED`. Die Variable `drift` fliegt auch raus, und ebenso die Zeile mit `drift` oben in `onOverlayUpdate`. `LAYER_BRIGHT` bleibt, unter einem Namen, der keine Ebenen mehr erwähnt, und zwei Werte kommen dazu: [source,lua] ---- local STAR_COUNT = 120 local STAR_SHADE = 55 local BURST_COUNT = 10 ---- [source,lua] ---- local stars = {} ---- Hundertzwanzig hattest du vorher auch: drei Ebenen zu je vierzig. Die Liste fängt leer an, und das hier füllt sie: [source,lua] ---- local function newStar(y) local speed = math.random(1, 3) local star = { x = math.random(0, width - 1), y = y, speed = speed, shade = math.random(60, 90) + speed * STAR_SHADE } return star end ---- `newStar` macht einen Stern und gibt ihn zurück. Sieh dir das letzte Feld an und vergleich es mit `shadeFor` aus Lektion fünf: dieselbe Rechnung, mit der eigenen Geschwindigkeit des Sterns dort, wo früher die Nummer der Ebene stand. Die Ebenen sind nicht so sehr verschwunden als vielmehr aufgelöst. Jeder Stern sucht sich jetzt seine eigene Geschwindigkeit aus, ein, zwei oder drei Pixel pro Frame, und ein Stern, der schneller fällt, ist heller; das ist die ganze Illusion von Tiefe, und sie kostet eine Zeile. Das anfängliche `y` ist ein Parameter, weil die beiden Aufrufer Verschiedenes wollen. Die Sterne, die beim Start des Spiels entstehen, sollen über den ganzen Bildschirm verstreut sein; die, die später entstehen, kommen oben herein. [source,lua] ---- local function makeStars() for number = 1, STAR_COUNT do table.insert(stars, newStar(math.random(0, height - 1))) end end ---- Noch ruft nichts `makeStars` auf. Füg ganz unten in der Datei eine Zeile hinzu, nach allem anderen und in überhaupt keiner Funktion: [source,lua] ---- makeStars() ---- Über diese Zeile gibt es noch mehr zu sagen, und das wartet bis zum Ende der Lektion. Jetzt das Zeichnen, das jedes Mal kürzer wird, wenn du es anfasst: [source,lua] ---- local function drawStar(star) colorForeground(star.shade, star.shade, star.shade) overlayPlot(star.x, star.y) end local function drawStars() for _, star in ipairs(stars) do drawStar(star) end end ---- `drawStar` hat in Lektion fünf drei Zahlen bekommen und bekommt jetzt einen Stern. Alles, was es wissen muss, steckt im Stern, und genau darum gibt es überhaupt Datensätze: ein Ding zum Herumreichen statt vier, und keine Gefahr, sie in der falschen Reihenfolge zu übergeben. Und `drawStars` hat seine Schleife über die Ebenen verloren, zusammen mit jedem Interesse daran, wie viele Sterne es gibt oder woher sie kommen. [#_stars_that_come_and_go] === Sterne, die kommen und gehen Jetzt der Teil, den das alte Sternenfeld überhaupt nicht konnte. Sterne fallen mit ihrer eigenen Geschwindigkeit, verschwinden unten und werden oben ersetzt. [source,lua] ---- local function moveStars() for i = #stars, 1, -1 do local star = stars[i] star.y = star.y + star.speed if star.y >= height then table.remove(stars, i) end end while #stars < STAR_COUNT do table.insert(stars, newStar(0)) end end ---- Ruf sie in `onOverlayUpdate` auf, in der Zeile, in der früher `drift = drift + 1` stand. Jeder Stern bewegt sich um seine eigene `speed` nach unten, und das ist die Zeile, die Lektion fünf nicht schreiben konnte. Das `while` unten ist das Auffüllen: Solange es weniger als hundertzwanzig Sterne gibt, mach oben auf dem Bildschirm einen neuen. Es läuft so oft, wie es muss, und hört dann auf, und das ist das richtige Werkzeug, wenn du nicht weißt, wie viele fehlen. Die `for`-Zeile oben zählt rückwärts, mit der Schrittweite, die du in Lektion vier kennengelernt hast. `for i = #stars, 1, -1` besucht zuerst den letzten Eintrag und arbeitet sich zurück bis Eintrag eins. Das ist keine Verzierung. Es ist der einzige sichere Weg, das zu tun, was diese Schleife tut, und der nächste Abschnitt erklärt, warum. [#_never_remove_going_forwards] === Nie vorwärts entfernen Schreib dieselbe Schleife vorwärts, und sie ist kaputt: [source,lua] ---- for i = 1, #stars do local star = stars[i] star.y = star.y + star.speed if star.y >= height then table.remove(stars, i) end end ---- Geh sie von Hand mit vier Sternen durch, und nimm an, dass die Sterne zwei und drei beide unten angekommen sind. * `i` ist `1`. Stern eins ist in Ordnung. * `i` ist `2`. Stern zwei ist unten hinaus, also fliegt er raus. Alles rückt nach: Der Stern, der drei war, ist jetzt zwei, und der Stern, der vier war, ist jetzt drei. * `i` ist `3`. Das ist der Stern, der früher vier war. *Stern drei wurde nie angesehen.* Das Entfernen von Eintrag zwei hat ihn auf Platz zwei rücken lassen, und die Schleife war schon bei Platz zwei gewesen und weitergezogen. * `i` ist `4`. Es gibt keinen Eintrag vier mehr. `stars[4]` ist `nil`. Zwei getrennte Fehler aus einem kleinen Versehen. Einträge werden übersprungen, also wird ein Stern, der unten angekommen war, diesmal nicht einmal angesehen. Ein Stern, der einen Frame länger überlebt, schadet niemandem, aber dieselbe Schleife über eine Liste von Gegnern, die erst das Ansehen tötet, ist ein Fehler, dem du eine Stunde lang hinterherjagst. Der zweite Fehler ist lauter. Die Liste ist jetzt kürzer als die Anzahl, mit der die Schleife angefangen hat, weil `#stars` einmal ausgerechnet wurde, vor jedem Entfernen, und so greifen die letzten Durchläufe der Schleife über das Ende der Liste hinaus: ---- Error executing function 'onOverlayUpdate': tables/tables.singe:60: attempt to index a nil value (local 'star') stack traceback: tables/tables.singe:60: in upvalue 'moveStars' tables/tables.singe:124: in function 'onOverlayUpdate' ---- „Attempt to index a nil value“ heißt, dass du einen Punkt hinter etwas gesetzt hast, in dem nichts war. Das `(local 'star')` am Ende nennt es beim Namen: `star` war `nil`, also hatte `star.y` keinen Ort, an dem es nachsehen konnte. Rückwärts zu laufen behebt beide Fehler auf einmal, und es lohnt sich zu sehen, warum, statt es einfach zu glauben. Wenn du Eintrag `i` entfernst, rückt alles hinter `i` einen Platz nach unten -- und wenn du rückwärts läufst, ist alles hinter `i` genau das, was du schon besucht hast. Was noch kommt, die Einträge `i - 1` bis hinunter zu `1`, hat sich überhaupt nicht bewegt. Dass die Liste hinter dir schrumpft, kann dir auch nicht schaden, denn du läufst auf Eintrag eins zu, und Eintrag eins ist immer da. Die Regel, die du für den Rest deines Lebens benutzen wirst: *Wenn eine Schleife Einträge aus der Liste entfernen könnte, die sie durchläuft, lauf sie rückwärts durch.* `ipairs` ist keine Ausnahme davon. Einträge mitten in einer `ipairs`-Schleife zu entfernen geht auf dieselbe Weise schief. `ipairs` ist zum Ansehen da, und das rückwärts laufende `for` zum Ändern. [#_add_some_yourself] === Füg selbst welche hinzu Noch ein Zweig in `onInputPressed`, und du kannst zusehen, wie die Liste wächst, während das Spiel läuft. Das kommt zu den anderen beiden: [source,lua] ---- elseif what == SWITCH_BUTTON1 then addBurst() ---- [source,lua] ---- local function addBurst() for number = 1, BURST_COUNT do table.insert(stars, newStar(0)) end end ---- `addBurst` kommt über die Callbacks, zu deinen anderen Funktionen. Und damit du siehst, wie sich die Länge der Liste ändert, bring die Anzahl in `onOverlayUpdate` auf den Bildschirm: [source,lua] ---- overlayPrint(0, 0, "Stars: " .. #stars) ---- Drück den Knopf -- die Leertaste, außer du hast geändert, woher `SWITCH_BUTTON1` kommt -- ein Dutzend Mal. Die Anzahl steigt mit jedem Drücken um zehn, und der Himmel wird dichter, und solange du ihn gedrückt hältst, wiederholt sich nichts: `onInputPressed` erfährt es einmal, wenn der Knopf nach unten geht. Dann sieh zu, wie die Anzahl von selbst auf hundertzwanzig zurückgeht, während die zusätzlichen Sterne unten ankommen und entfernt werden, denn das Auffüllen füllt die Liste immer nur bis `STAR_COUNT` auf und kürzt sie nie. Weder dem Zeichnen noch dem Bewegen noch dem Entfernen musste jemand sagen, dass sich die Zahl der Sterne geändert hat. Sie arbeiten alle mit der Liste. [#_the_whole_thing_5] === Das Ganze [source,lua] ---- local STAR_COUNT = 120 local STAR_SHADE = 55 local BURST_COUNT = 10 local PLAYER_WIDTH = 20 local PLAYER_HEIGHT = 6 local PLAYER_SPEED = 3 local PLAYER_MARGIN = 6 local width = overlayGetWidth() local height = overlayGetHeight() local stars = {} local playerX = width // 2 local playerY = height - PLAYER_HEIGHT - PLAYER_MARGIN local movingLeft = false local movingRight = false local function clamp(value, low, high) if value < low then return low end if value > high then return high end return value end local function newStar(y) local speed = math.random(1, 3) local star = { x = math.random(0, width - 1), y = y, speed = speed, shade = math.random(60, 90) + speed * STAR_SHADE } return star end local function makeStars() for number = 1, STAR_COUNT do table.insert(stars, newStar(math.random(0, height - 1))) end end local function addBurst() for number = 1, BURST_COUNT do table.insert(stars, newStar(0)) end end local function moveStars() for i = #stars, 1, -1 do local star = stars[i] star.y = star.y + star.speed if star.y >= height then table.remove(stars, i) end end while #stars < STAR_COUNT do table.insert(stars, newStar(0)) end end local function drawStar(star) colorForeground(star.shade, star.shade, star.shade) overlayPlot(star.x, star.y) end local function drawStars() for _, star in ipairs(stars) do drawStar(star) end end local function drawPlayer() colorForeground(80, 255, 120) overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT) end local function movePlayer() if movingLeft then playerX = playerX - PLAYER_SPEED end if movingRight then playerX = playerX + PLAYER_SPEED end playerX = clamp(playerX, 0, width - PLAYER_WIDTH - 1) end function onInputPressed(what) if what == SWITCH_LEFT then movingLeft = true elseif what == SWITCH_RIGHT then movingRight = true elseif what == SWITCH_BUTTON1 then addBurst() end end function onInputReleased(what) if what == SWITCH_LEFT then movingLeft = false elseif what == SWITCH_RIGHT then movingRight = false end end function onOverlayUpdate() overlayClear() movePlayer() moveStars() drawStars() drawPlayer() overlayPrint(0, 0, "Stars: " .. #stars) return OVERLAY_UPDATED end makeStars() ---- [#_what_just_happened_3] === Was gerade passiert ist Das Programm ist kürzer als das aus Lektion fünf, und es tut etwas, woran Lektion fünf nicht einmal denken konnte. Drei Stücke sind einen zweiten Blick wert. [source,lua] ---- makeStars() ---- Diese einzelne Zeile ganz unten in der Datei steht in keiner Funktion, und man übersieht sie leicht. Lua liest deine Datei von oben nach unten, wenn Singe sie lädt, und eine Zeile ganz außen in der Datei läuft sofort an Ort und Stelle, einmal, bevor die Engine irgendetwas aufgerufen hat. Alles darüber sind Definitionen -- hier ist ein Wert, hier ist eine Funktion -- und das hier ist die eine Anweisung, die beim Laden passiert. Sie muss ganz unten stehen, weil es `makeStars` erst gibt, wenn die Zeile gelesen wurde, die es erzeugt. [source,lua] ---- local star = stars[i] star.y = star.y + star.speed ---- `star` ist eine lokale Variable, die bei jedem Durchlauf der Schleife neu entsteht, und sie hält dieselbe Tabelle, die in der Liste liegt -- keine Kopie davon. `star.y` zu ändern bewegt den Stern, der in der Liste steht. Würden Tabellen kopiert, würde diese Schleife pro Frame hundertzwanzig Kopien bewegen, und auf dem Bildschirm würde sich nie etwas ändern. [source,lua] ---- for _, star in ipairs(stars) do drawStar(star) end ---- Vergleich das mit Lektion vier: zwei verschachtelte Schleifen, zwei Rechnungen pro Stern, ein Rest, damit jeder auf dem Bildschirm bleibt, und drei Ebenen, um die Vielfalt vorzutäuschen, die du jetzt umsonst bekommst. Das Zeichnen weiß nicht mehr und kümmert sich nicht mehr darum, wie die Sterne dorthin gekommen sind, wo sie sind, wie viele es gibt oder was als Nächstes mit ihnen passiert. Jeder Teil des Programms erledigt eine Aufgabe, und die Liste ist das, worauf sich alle einigen. [#_try_it_6] === Probier es aus . *Mehr Tiefe.* Ändere `math.random(1, 3)` in `math.random(1, 6)` und `STAR_SHADE` in `25`. Mehr Geschwindigkeiten, mehr Helligkeitsstufen, ein tieferer Himmel. . *Weniger Sterne, größerer Schwung.* Setz `STAR_COUNT` auf `10` und `BURST_COUNT` auf `100`, dann drück den Knopf drei- oder viermal. Sieh zu, wie die Anzahl auf dem Bildschirm steigt, und miss, wie lange es dauert, bis sie wieder abgeflossen ist. . *Gib einem Stern ein weiteres Feld.* Füg `age = 0` in `newStar` hinzu, zähl in `moveStars` eins zu `star.age` dazu und bring `stars[1].age` neben der Anzahl auf den Bildschirm. Sonst muss sich nichts im Programm ändern, damit es eine neue Tatsache über jeden Stern mitträgt. . *Hör auf, sie zu entfernen.* Lösch die drei Zeilen des Blocks `if star.y >= height` aus `moveStars` und starte es. Warte zehn Sekunden, dann lies die Anzahl auf dem Bildschirm und sieh dir den Himmel an. Finde heraus, wie beides gleichzeitig wahr sein kann. . *Mach es vorwärts.* Ändere `for i = #stars, 1, -1` in `for i = 1, #stars` und lass es laufen, bis es umfällt. Dann lies den Abschnitt oben noch einmal, mit dem echten Fehler vor Augen. [#_break_it_on_purpose_6] === Mach es absichtlich kaputt Schreib einen Feldnamen falsch. Ändere in `moveStars` das `star.speed` in `star.sped`: [source,lua] ---- star.y = star.y + star.sped ---- Starte es: ---- Error executing function 'onOverlayUpdate': tables/tables.singe:60: attempt to perform arithmetic on a nil value (field 'sped') stack traceback: tables/tables.singe:60: in upvalue 'moveStars' tables/tables.singe:124: in function 'onOverlayUpdate' ---- Deine Zeilennummern werden nicht genau diese sein, außer deine Datei stimmt Zeile für Zeile mit der oben überein. Was du liest, sind der Name der Datei, die Zahl dahinter und die Beschwerde. Lies zuerst das Ende der ersten Zeile. Mit `field 'sped'` sagt dir Lua, welcher Name leer ausging, und es sagt dir, dass es ein Feld war -- etwas hinter einem Punkt -- und keine eigene Variable von dir. „Attempt to perform arithmetic on a nil value“ ist das, was es gerade tat, als es das merkte: `nil` zu einer Zahl addieren, und das geht nicht. Es gab keine Beschwerde, als die Tabelle ohne ein Feld `sped` gemacht wurde, und keine in dem Moment, in dem du danach gefragt hast. Eine Tabelle gibt für jeden Namen, den sie nicht hat, kommentarlos `nil` heraus. Der Fehler kommt später, in der ersten Zeile, die versucht, das Nichts, das du bekommen hast, zu *benutzen*, und die kann weit von dem Tippfehler entfernt sein. Wenn du `nil value (field 'something')` siehst, such die Stelle, an der dieser Name anders geschrieben wurde. [#_what_you_learned_6] === Was du gelernt hast * Geschweifte Klammern machen eine Tabelle, den einen Behälter, den Lua hat. Eine Tabelle, die als Liste benutzt wird, hält Einträge hintereinander, und eckige Klammern mit einem Index holen einen heraus. * Lua zählt ab eins. Die meisten anderen Sprachen zählen ab null. * `#list` ist die Anzahl der Einträge darin, und `list[#list]` ist der letzte. * `table.insert` fügt einen Eintrag hinzu, am Ende oder an einer Stelle, die du nennst; `table.remove` nimmt einen heraus und schließt die Lücke. * Ein Index oder ein Feld, das nie gefüllt wurde, ist `nil`, und nichts warnt dich, bis du es benutzt. * `ipairs` läuft eine Liste durch und gibt dir den Index und den Eintrag; `_` ist die übliche Schreibweise für einen Index, den du nicht willst. * Eine Tabelle, die als Datensatz benutzt wird, hält benannte Felder, die du mit einem Punkt erreichst. * In einer Liste von Datensätzen bewahrt ein Spiel seine Gegner auf, seine Schüsse und alles andere, wovon es mehr als eins gibt. * Tabellen werden geteilt, nicht kopiert, wenn du sie herumreichst. * Wenn eine Schleife Einträge aus der Liste entfernen kann, die sie durchläuft, lauf sie rückwärts durch. [#_next_time_6] === Beim nächsten Mal Du hast jetzt alle Teile eines Spiels: Dinge, die sich bewegen, ein Raumschiff, das auf die Steuerung hört, Entscheidungen, Schleifen, Funktionen und eine Liste, in die Dinge hinein- und aus der sie wieder hinausgehen können, während das Spiel läuft. Lektion sieben setzt sie zu einem Spiel zusammen, das du verlieren kannst. Du steuerst am unteren Rand des Bildschirms entlang, von oben fallen Blöcke, und du gehst ihnen aus dem Weg -- und die fallenden Blöcke sind eine Liste von Datensätzen, gemacht, bewegt und entfernt genau wie deine Sterne.