singe/docs/de/lessons/05-functions.adoc

686 lines
25 KiB
Text

[#_lesson_5_your_own_functions]
== Lektion 5: Deine eigenen Funktionen
image::learn/05-functions.png[Die fertige Lektion, 480]
Lektion vier hat mit einer Warnung über dein eigenes Programm geendet: `onOverlayUpdate` rechnet
aus, wohin hundertzwanzig Sterne kommen, entscheidet, wie hell jeder ist, zeichnet sie und zählt
die Drift, und das ist eine Funktion, die vier Aufgaben erledigt. Gleich soll sie eine fünfte
übernehmen, denn ein Sternenfeld, vor dem nichts ist, ist kein Spiel.
In dieser Lektion nimmst du sie auseinander. Du schreibst eigene Funktionen -- Funktionen, von
denen Singe nie gehört hat und die es nie aufrufen wird -- und gibst die Arbeit an sie ab. Am Ende
ist das ganze Sternenfeld eine Zeile, in der `drawStars()` steht, unten auf dem Bildschirm ist
wieder ein Schiff, und das Programm ist leichter zu lesen als damals, als es weniger getan hat.
[#_start_a_new_folder_4]
=== Leg einen neuen Ordner an
Ein Ordner namens `functions`, darin eine Datei namens `functions.singe`, und in einem Terminal
läuft `Singe -R functions`.
Kopier das fertige Programm aus Lektion vier hinein. Hier ist es, unverändert, und es lohnt sich,
es einmal laufen zu lassen, bevor du irgendetwas anfasst:
[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
----
Drei Ebenen von Sternen, die mit drei Geschwindigkeiten das Fenster hinuntertreiben. Nichts in der
ersten Hälfte dieser Lektion ändert, was du siehst. Alles darin ändert, wie sich das Programm
liest.
[#_one_job_one_name]
=== Eine Aufgabe, ein Name
Fang mit den zwei Zeilen an, die einen Stern auf den Bildschirm bringen. Füg über
`onOverlayUpdate` das hier ein:
[source,lua]
----
function drawStar(x, y, shade)
colorForeground(shade, shade, shade)
overlayPlot(x, y)
end
----
Dann lösch in der inneren Schleife diese zwei Zeilen und ruf stattdessen die Funktion auf:
[source,lua]
----
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
drawStar(starX, starY, shade)
end
----
Speichere. Das Bild ist dasselbe.
Du hast schon früher Funktionen geschrieben -- `onOverlayUpdate` ist eine, `onInputPressed` und
`onInputReleased` aus Lektion drei ebenso. Aber das sind Callbacks. Du schreibst sie, und Singe
ruft sie auf. `drawStar` ist anders. Singe hat nie von ihr gehört. Sie läuft, weil *du* ihren
Namen und Klammern geschrieben hast, genauso wie du `overlayPlot` aufrufst, und nichts auf der Welt
führt sie aus, wenn du es nicht tust.
Das ist der ganze Trick. Wenn du ab jetzt denkst „und jetzt zeichne einen Stern“, kannst du
„zeichne einen Stern“ hinschreiben und es so meinen.
[#_a_parameter_is_a_name_not_a_thing]
=== Ein Parameter ist ein Name, kein Ding
Sieh dir die erste Zeile von `drawStar` noch einmal an.
[source,lua]
----
function drawStar(x, y, shade)
----
Die drei Namen in den Klammern sind *Parameter*. Ein Parameter ist ein Name, den die Funktion für
das benutzt, was der Aufrufer ihr übergeben hat. Wenn du `drawStar(starX, starY, shade)` schreibst,
setzt Lua `x` auf das, was in `starX` steht, `y` auf das, was in `starY` steht, `shade` auf das,
was in `shade` steht, und führt dann die Funktion aus.
Die Namen haben nichts miteinander zu tun. Die Zahl des Aufrufers heißt da draußen `starX` und hier
drinnen `x`, und es würde genauso funktionieren, wenn du sie `across` oder `banana` nennen würdest.
In der Funktion gibt es nur die Namen in den Klammern, und sie enthalten Kopien von dem, was
hereingekommen ist.
Hier ergeben die leeren Klammern aus Lektion eins endlich einen Sinn.
[source,lua]
----
function onOverlayUpdate()
----
Nichts in den Klammern heißt keine Parameter: Die Engine hat dieser Funktion nichts zu sagen, also
gibt es nichts zu benennen. Vergleich das mit dem Callback, den du in Lektion drei geschrieben
hast.
[source,lua]
----
function onInputPressed(what)
----
Ein Parameter namens `what`. Diesem hier hat die Engine etwas zu sagen -- welches Steuerelement
gedrückt wurde -- also gibt es einen Namen, der es auffängt. Du benutzt seit Lektion drei einen
Parameter.
Ein Parameter ist wirklich die eigene Kopie der Funktion, und es lohnt sich, dir das einmal selbst
zu beweisen. Tipp das ein, ruf es aus `onOverlayUpdate` auf und nimm es danach wieder heraus:
[source,lua]
----
function half(number)
number = number // 2
return number
end
----
Ruf sie als `half(drift)` auf. Die Funktion halbiert ihr `number` und gibt die Antwort zurück, und
`drift` da draußen in `onOverlayUpdate` zählt weiter, als wäre nichts passiert. Eine Funktion kann
nicht über einen Parameter hinausgreifen und die Variablen des Aufrufers ändern. Sie bekommt eine
Kopie, und was sie mit der Kopie macht, ist ihre Sache.
[#_handing_an_answer_back]
=== Eine Antwort zurückgeben
`half` hat etwas getan, was die Funktionen, die du bisher geschrieben hast, nicht getan haben: Sie
hat eine Antwort gegeben. Das ist die letzte Zeile.
[source,lua]
----
return number
----
`return` gibt einen Wert an den zurück, der die Funktion aufgerufen hat, und der Aufruf wird zu
diesem Wert. Schreib `smaller = half(drift)`, und Lua führt `half` aus, nimmt, was `half`
zurückgegeben hat, und legt es in `smaller`. Du hast `return` in Lektion eins kennengelernt, wo
`onOverlayUpdate` den Wert `OVERLAY_UPDATED` zurückgibt, um Singes Frage zu beantworten, ob sich
etwas geändert hat. Das ist dasselbe Wort mit derselben Aufgabe, nur dass jetzt du derjenige bist,
der die Frage stellt.
Die Zeile mit der Helligkeit bettelt geradezu darum, eine Funktion mit einer Antwort zu werden. Füg
diese hier hinzu:
[source,lua]
----
function shadeFor(layer)
return math.random(60, 90) + layer * LAYER_BRIGHT
end
----
Der ganze Rumpf ist ein `return`, und das ist bei einer kleinen Funktion normal. Jetzt kann die
innere Schleife die Antwort direkt an `drawStar` weitergeben, ohne einen Namen dazwischen:
[source,lua]
----
drawStar(starX, starY, shadeFor(layer))
----
Ein Aufruf in einem Aufruf. Lua rechnet zuerst den inneren aus, bekommt von `shadeFor` eine Zahl
zurück und übergibt diese Zahl an `drawStar` als drittes Argument. So etwas hast du schon
geschrieben, ohne es zu merken: Das `math.floor(drift * layer * LAYER_SPEED)` in Lektion vier
ist ein Aufruf, dessen Antwort sofort an Ort und Stelle benutzt wird, mitten in einer größeren
Rechnung.
[#_two_answers_at_once]
=== Zwei Antworten auf einmal
Die beiden Zeilen für die Position sind schwieriger, weil es zwei Zahlen sind, die zusammen
ausgerechnet werden, und du müsstest dir eine aussuchen, die du zurückgibst. In den meisten
Sprachen kann eine Funktion einen Wert zurückgeben, und zwei herauszubekommen ist lästig. Lua lässt
dich so viele zurückgeben, wie du willst.
[source,lua]
----
function starAt(star, layer)
local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
return x, y
end
----
Du fängst die Antworten auf, indem du links zwei Namen hinschreibst, durch ein Komma getrennt:
[source,lua]
----
local x, y = starAt(star, layer)
----
Der erste Wert kommt in den ersten Namen, der zweite in den zweiten. Gibst du weniger Namen an, als
die Funktion zurückgibt, werden die übrigen Werte weggeworfen:
`local x = starAt(star, layer)` gibt dir die waagerechte Zahl und verliert die senkrechte, ohne
Beschwerde. Gibst du mehr Namen an, als es Werte gibt, sind die überzähligen `nil`.
Ignorier das Wort `local` in diesen beiden Blöcken noch eine Minute. Es bekommt gleich einen
eigenen Abschnitt, und das ist der wichtige.
[#_the_star_field_in_one_line]
=== Das Sternenfeld in einer Zeile
Die innere Schleife besteht jetzt aus drei kurzen Zeilen, und alle drehen sich um eine Ebene von
Sternen. Das ist eine Aufgabe mit einem Namen.
[source,lua]
----
function drawLayer(layer)
for star = 1, STARS_PER_LAYER do
local x, y = starAt(star, layer)
drawStar(x, y, shadeFor(layer))
end
end
----
Und die äußere Schleife ist auch eine Aufgabe mit einem Namen:
[source,lua]
----
function drawStars()
for layer = 1, LAYERS do
drawLayer(layer)
end
end
----
Funktionen, die Funktionen aufrufen, die Funktionen aufrufen, drei Stufen tief, und jede ist drei
oder vier Zeilen lang, die du auf einmal im Kopf behalten kannst. `onOverlayUpdate` kann jetzt
sagen, was ein Frame *ist*:
[source,lua]
----
function onOverlayUpdate()
drift = drift + 1
overlayClear()
drawStars()
return OVERLAY_UPDATED
end
----
Das ist das Versprechen vom Ende der Lektion vier, eingelöst.
[#_put_the_ship_back]
=== Hol das Schiff zurück
Jetzt die fünfte Aufgabe, die, die aus dem alten `onOverlayUpdate` ein Durcheinander gemacht hätte.
Hol das Kästchen zurück, das du in Lektion drei gesteuert hast, diesmal als Schiff, das nahe am
unteren Bildschirmrand sitzt und sich nur nach links und rechts bewegt.
Es braucht ein paar eigene Werte, oben bei den anderen, und die beiden Eingabe-Callbacks aus
Lektion drei, ohne die Zweige für oben und unten:
[source,lua]
----
PLAYER_WIDTH = 20
PLAYER_HEIGHT = 6
PLAYER_SPEED = 3
PLAYER_MARGIN = 6
playerX = width // 2
playerY = height - PLAYER_HEIGHT - PLAYER_MARGIN
movingLeft = false
movingRight = false
----
[source,lua]
----
function onInputPressed(what)
if what == SWITCH_LEFT then
movingLeft = true
elseif what == SWITCH_RIGHT then
movingRight = true
end
end
function onInputReleased(what)
if what == SWITCH_LEFT then
movingLeft = false
elseif what == SWITCH_RIGHT then
movingRight = false
end
end
----
Es zu zeichnen sind zwei Zeilen, also ist es eine Funktion:
[source,lua]
----
function drawPlayer()
colorForeground(80, 255, 120)
overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT)
end
----
Beim Bewegen zahlt sich die Lektion aus. In Lektion drei hat es für jede Richtung vier Zeilen mit
`if` und `elseif` gebraucht, um das Kästchen auf dem Bildschirm zu halten. Diese Idee -- eine Zahl
zwischen einer Untergrenze und einer Obergrenze halten -- hat einen Namen, und der Name ist
*clamp*:
[source,lua]
----
function clamp(value, low, high)
if value < low then
return low
end
if value > high then
return high
end
return value
end
----
Drei Rückgaben, und höchstens eine davon passiert je, denn `return` gibt nicht nur eine Antwort
zurück: Es hält die Funktion auf der Stelle an. Nichts danach läuft. Liegt der Wert unter der
Untergrenze, gib die Untergrenze zurück und hör auf. Liegt er über der Obergrenze, gib die
Obergrenze zurück und hör auf. Sonst gib zurück, was hereingekommen ist.
[source,lua]
----
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
----
Eine Funktion, die ohne `return` endet, gibt nichts zurück, und das ist `nil`. Das ist richtig für
`drawPlayer` und `movePlayer`, die aufgerufen werden, um etwas zu tun, nicht um etwas zu
beantworten. In einer Funktion wie `clamp` ist es ein Fehler: Lass das letzte `return value` weg,
und jede Position mitten im Bereich kommt als `nil` zurück.
[#_where_names_live]
=== Wo Namen wohnen
Jeder Name, den du seit Lektion zwei angelegt hast, war eine *globale Variable*. Eine globale
Variable gehört dem ganzen Programm: `drift`, `playerX` und `LAYERS` können von jeder Funktion in
der Datei jederzeit gelesen und geschrieben werden, und sie bleiben da, solange das Spiel läuft.
Das klingt bequem, und bei sechs Namen ist es das auch. Der Ärger fängt bei sechzig an. Jede
globale Variable ist ein Name, den sich das ganze Programm reserviert hat; wenn also irgendeine
andere Funktion einen Platz für eine waagerechte Position braucht und ihn auch `starX` nennt, sind
die beiden dieselbe Schublade, und jede überschreibt die Zahl der anderen. Wenn in einer globalen
Variable etwas Falsches steht, kann es alles in der Datei dort hineingelegt haben, und um
herauszufinden, was, musst du die ganze Datei lesen.
Eine *lokale Variable* ist das Gegenmittel. Schreib das Wort `local` vor einen Namen, wenn du ihn
zum ersten Mal benutzt, und der Name existiert nur innerhalb der `function`, der Schleife oder des
`if`, in dem er geschrieben wurde.
[source,lua]
----
function starAt(star, layer)
local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
return x, y
end
----
`x` und `y` gehen hier niemanden sonst etwas an. Eine andere Funktion kann ihr eigenes `x` haben,
und die beiden begegnen sich nie. Wenn das `x` von `starAt` falsch ist, sind die drei Zeilen oben
die einzigen drei Zeilen, die das gewesen sein können. Ohne das Wort `local` würden diese beiden
Zeilen still zwei weitere globale Variablen anlegen, und genau das waren `starX` und `starY` die
ganze Zeit.
Jetzt der Teil, der alle überrascht. Tipp diese Funktion ein und setz eine Zeile in
`onOverlayUpdate`, die dir zeigt, was sie zurückgibt:
[source,lua]
----
function countUp()
local counted = 0
counted = counted + 1
return counted
end
----
[source,lua]
----
overlayPrint(0, 0, "Count: " .. countUp())
----
`Count: 1`, und es bleibt bei `Count: 1`, solange das Spiel läuft, obwohl die Funktion sechzigmal
pro Sekunde aufgerufen wird und jedes Mal eins dazuzählt. Eine lokale Variable wird frisch
angelegt, wenn die Funktion anfängt, und ist weg, wenn die Funktion endet; also wird `counted` als
`0` geboren, wird zu `1` und stirbt, sechzigmal pro Sekunde, für immer. Eine lokale Variable merkt
sich nichts. Nimm die beiden wieder heraus, wenn du es dir angesehen hast.
Das ist kein Mangel, sondern genau der Sinn: Eine lokale Variable kann nicht vom letzten Aufruf
vergiftet werden, weil vom letzten Aufruf nichts übrig ist. Wenn du dir doch etwas von einem Aufruf
zum nächsten merken musst (und `drift` und `playerX` müssen das), muss der Name außerhalb jeder
Funktion wohnen. Setz ihn mit `local` davor an den Anfang der Datei:
[source,lua]
----
local drift = 0
local playerX = width // 2
----
Ein `local` ganz oben in der Datei, außerhalb aller Funktionen, lebt so lange wie das Skript und
ist für jede Funktion darunter sichtbar. Auf das letzte Wort kommt es an: *darunter*. Eine lokale
Variable existiert von ihrer eigenen Zeile an abwärts, also kann eine Funktion, die über der Zeile
steht, die sie anlegt, sie nicht sehen. Dasselbe gilt für die Funktionen selbst, sobald du sie mit
`local` markierst, und darum hat das fertige Skript die Reihenfolge, die es hat: zuerst die Werte,
dann deine eigenen Funktionen, dann ganz zum Schluss die Callbacks.
Geh jetzt die ganze Datei durch und setz `local` vor jeden Namen, den du angelegt hast -- die
Konstanten in Großbuchstaben, `width` und `height`, `drift`, die vier Werte der Spielfigur und jede
deiner eigenen Funktionen. Drei Namen bekommen es nicht: `onInputPressed`, `onInputReleased` und
`onOverlayUpdate` müssen global bleiben, weil Singe sie über ihren Namen findet und ein lokaler
Name von außerhalb der Datei unsichtbar ist.
Sei ehrlich zu dir, was lokale Variablen nicht reparieren. Lua beschwert sich nicht, wenn du dich
bei einem Namen vertippst, dem du etwas zuweist -- `movinLeft = true` statt
`movingLeft = true`. Es legt eine brandneue globale Variable mit deinem Tippfehler als Namen an,
setzt sie und lässt die echte in Ruhe. Nichts bewegt sich, nichts meldet einen Fehler, und du
starrst zehn Minuten lang darauf. Davor retten dich lokale Variablen nicht. Den Namen zweimal zu
lesen schon.
[#_the_whole_thing_4]
=== Das Ganze
Hier ist das fertige Programm, mit jedem Stück der Lektion darin.
[source,lua]
----
local LAYERS = 3
local STARS_PER_LAYER = 40
local ACROSS_STEP = 37
local DOWN_STEP = 61
local LAYER_SHIFT = 13
local LAYER_SPEED = 0.5
local LAYER_BRIGHT = 55
local PLAYER_WIDTH = 20
local PLAYER_HEIGHT = 6
local PLAYER_SPEED = 3
local PLAYER_MARGIN = 6
local width = overlayGetWidth()
local height = overlayGetHeight()
local drift = 0
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 shadeFor(layer)
return math.random(60, 90) + layer * LAYER_BRIGHT
end
local function starAt(star, layer)
local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
return x, y
end
local function drawStar(x, y, shade)
colorForeground(shade, shade, shade)
overlayPlot(x, y)
end
local function drawLayer(layer)
for star = 1, STARS_PER_LAYER do
local x, y = starAt(star, layer)
drawStar(x, y, shadeFor(layer))
end
end
local function drawStars()
for layer = 1, LAYERS do
drawLayer(layer)
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
end
end
function onInputReleased(what)
if what == SWITCH_LEFT then
movingLeft = false
elseif what == SWITCH_RIGHT then
movingRight = false
end
end
function onOverlayUpdate()
drift = drift + 1
overlayClear()
movePlayer()
drawStars()
drawPlayer()
return OVERLAY_UPDATED
end
----
Auf zwei Dinge lohnt es sich hinzuweisen, bevor du weitermachst.
`local function drawStar(x, y, shade)` markiert die Funktion selbst als lokal. Deine eigenen
Funktionen sind Namen wie alle anderen, und sie gehören nur dieser Datei. Die drei Callbacks bleiben
global, damit Singe sie finden kann.
Und `onOverlayUpdate` besteht jetzt aus sechs Zeilen, die sagen, was ein Frame ist: die Drift
zählen, den Bildschirm löschen, die Spielfigur bewegen, den Himmel zeichnen, die Spielfigur
zeichnen, fertig. Es erledigt fünf Aufgaben, indem es fünf Dinge beim Namen nennt, die jeweils eine
Aufgabe erledigen, und das ist die Form jeder Spielschleife, die du je schreiben wirst.
[#_a_function_is_a_name_for_an_idea]
=== Eine Funktion ist ein Name für eine Idee
`clamp` wird einmal aufgerufen. Sie herauszuziehen hat überhaupt keine Tipparbeit gespart -- vier
Zeilen kamen heraus, eine ging hinein, und anderswo ist eine Funktion mit neun Zeilen aufgetaucht.
Es hat sich trotzdem gelohnt.
Eine Funktion ist nicht nur ein Weg, dich nicht zu wiederholen. Sie ist ein Weg, einer Idee einen
Namen zu geben, damit der Code, der die Idee benutzt, den Namen sagen und weitermachen kann.
„Halte die Spielfigur zwischen dem linken und dem rechten Rand“ ist ein Gedanke, den du einmal
hattest. `clamp(playerX, 0, width - PLAYER_WIDTH - 1)` ist dieser Gedanke, aufgeschrieben. Vier
`if`-Anweisungen sind derselbe Gedanke, zerlegt in Stücke, die du jedes Mal, wenn du sie liest, im
Kopf wieder zusammensetzen musst.
Wenn du entscheidest, ob etwas es verdient, eine Funktion zu sein, frag nicht, wie oft du sie
aufrufen wirst. Frag, ob es einen Namen hat.
[#_try_it_5]
=== Probier es aus
. *Benenne einen Parameter um.* Ändere in der Funktion `drawStar` überall `x` und `y` in `across`
und `down`. Fass nichts außerhalb davon an. Starte es. Finde heraus, warum nichts kaputtgegangen
ist.
. *Zeichne sie andersherum.* Vertausch `drawStars()` und `drawPlayer()` in `onOverlayUpdate` und
sieh dir das Schiff an. Dann stell sie zurück. Jede dieser fünf Zeilen ist eine Aufgabe, und ihre
Reihenfolge auf dem Bildschirm ist ihre Reihenfolge in der Funktion.
. *Benutz `clamp` für eine Helligkeit.* Ändere `shadeFor` zu
`return clamp(math.random(60, 90) + layer * 90, 0, 200)` und sieh dir die drei Ebenen an. Dann
nimm das `clamp` heraus und sieh noch einmal hin: `colorForeground` hat die Zahlen die ganze Zeit
still für dich gedeckelt, und jetzt siehst du, wo.
. *Ruf `drawStar` mit zwei Argumenten auf.* Schreib `drawStar(100, 100)` irgendwo in `drawLayer`
und starte es. Der Fehler nennt genau das, was gefehlt hat. Lies ihn, bevor du das dritte
Argument wieder einsetzt.
. *Verschieb eine lokale Variable.* Nimm die Zeile `local drift = 0` und schieb sie ans Ende der
Datei, unter alles andere. Starte es, lies den Fehler und denk über das Wort „darunter“ im
Abschnitt oben nach.
[#_break_it_on_purpose_5]
=== Mach es absichtlich kaputt
In einer Datei mit lokalen Variablen kommt es auf die Reihenfolge an, und der Fehler, den du
bekommst, wenn du diese Reihenfolge durcheinanderbringst, ist nicht der, den du erwartest.
Nimm die ganze Funktion `drawStar` und schieb sie ans *Ende* des Skripts, unter `onOverlayUpdate`.
Alles ist noch da, gleich geschrieben. Starte es:
----
Error executing function 'onOverlayUpdate': functions/functions.singe:50: attempt to call a nil value (global 'drawStar')
stack traceback:
functions/functions.singe:50: in upvalue 'drawLayer'
functions/functions.singe:57: in upvalue 'drawStars'
functions/functions.singe:101: in function 'onOverlayUpdate'
----
Das sieht anders aus als der Fehler in Lektion eins, weil es eine andere Art von Fehler ist. Der
Fehler aus Lektion eins wurde erwischt, bevor das Skript lief. Dieser hier wurde erwischt, während
es lief, und darum nennt Singe die Funktion, in der es gerade steckte, und darum steht darunter ein
*Traceback*: die Liste, wer wen aufgerufen hat, das Neueste zuerst. Zeile 50 liegt in `drawLayer`,
das von Zeile 57 in `drawStars` aufgerufen wurde, das von Zeile 101 in `onOverlayUpdate`
aufgerufen wurde. Deine Zeilennummern weichen ab, wenn deine Datei nicht genau mit der oben
übereinstimmt, und die Engine hängt darunter vielleicht noch eine oder zwei eigene Zeilen an. Das
Wort `upvalue` ist Luas Name für eine lokale Variable, die sich eine Funktion aus der Datei um sie
herum geliehen hat; lies vorerst darüber hinweg.
Die Beschwerde selbst lohnt es sich auswendig zu lernen. „Attempt to call a nil value“ heißt, dass
du Klammern hinter einen Namen gesetzt hast, in dem nichts stand. Das Wort `global` vor `drawStar`
ist der Teil, der alles verrät: Du hast `drawStar` als lokal markiert, und als Lua `drawLayer` gelesen und
nirgendwo darüber eine lokale Variable dieses Namens gefunden hat, blieb nur noch, es mit einer
globalen zu versuchen -- und eine globale namens `drawStar` gibt es auch nicht. Setz die Funktion
wieder über den Code, der sie aufruft, und der Fehler verschwindet.
[#_what_you_learned_5]
=== Was du gelernt hast
* Du kannst eigene Funktionen schreiben, und nichts führt sie aus außer deinen eigenen Aufrufen.
* Die Namen in den Klammern einer Funktion sind Parameter, und sie sind die eigenen Kopien der
Funktion von dem, was der Aufrufer übergeben hat.
* `return` gibt einen Wert zurück und hält die Funktion an der Stelle an, und eine Funktion, die
ohne ein solches endet, gibt `nil` zurück.
* Ein Aufruf kann ein Argument eines anderen Aufrufs sein, und der innere passiert zuerst.
* Lua kann mehr als einen Wert zurückgeben, und du fängst sie mit mehreren Namen auf, durch Kommas
getrennt.
* `local` legt einen Namen an, der nur dort existiert, wo er geschrieben wurde.
* Eine lokale Variable in einer Funktion wird bei jedem Aufruf frisch angelegt und merkt sich
nichts vom letzten.
* Eine lokale Variable ist nur unterhalb der Zeile sichtbar, die sie anlegt, also kommen Werte und
deine eigenen Funktionen über die Callbacks, die sie benutzen.
* Callbacks bleiben global, weil Singe sie über ihren Namen findet.
* Eine Funktion lohnt sich, wenn das, was sie tut, einen Namen hat, auch wenn du sie nur einmal
aufrufst.
[#_next_time_5]
=== Beim nächsten Mal
Deine hundertzwanzig Sterne sind immer noch hundertzwanzig Kopien von drei Ideen. Nichts im
Programm ist *ein Stern*: Es gibt eine Zahl und etwas Rechnerei, die aus der Zahl eine Position
macht. Du kannst nicht sagen „dieser hier ist näher, also fällt er schneller und leuchtet heller“,
weil es kein „dieser hier“ gibt, über das du es sagen könntest, und du kannst keinen Stern unten
herausfallen und durch einen neuen ersetzen lassen, weil da nichts ist, was man entfernen könnte.
Lektion sechs gibt dir das Ding, das einen Stern festhält, und dann einen Weg, hundertzwanzig davon
festzuhalten und einen hunderteinundzwanzigsten hinzuzufügen, während das Spiel läuft. Es ist die
nützlichste Lektion in diesem Teil des Buchs.