singe/docs/de/lessons/02-numbers.adoc

403 lines
16 KiB
Text

[#_lesson_2_numbers_that_change]
== Lektion 2: Zahlen, die sich ändern
image::learn/02-numbers.png[Die fertige Lektion, 480]
In Lektion eins hast du ein Wort auf den Bildschirm gebracht, und da saß es dann. Am Ende dieser
Lektion gleitet dasselbe Wort durchs Fenster, kehrt um, wenn es an einen Rand kommt, und das tut
es wegen vier Zahlen, die das Programm sechzigmal pro Sekunde ändert.
Unterwegs triffst du auf die eine Idee, über die mehr Anfänger stolpern als über jede andere: Wo
du etwas hinschreibst, entscheidet, wie lange es lebt.
[#_start_a_new_folder]
=== Leg einen neuen Ordner an
Leg neben deinem Ordner `hello` einen Ordner namens `numbers` an und darin eine Datei namens
`numbers.singe`. Tipp das Programm aus Lektion eins hinein, oder kopier `hello.singe` herüber und
benenne sie um -- beides ist in Ordnung, und das Abtippen ist keine verschwendete Zeit.
[source,lua]
----
function onOverlayUpdate()
overlayClear()
overlayPrint(2, 2, "Hello!")
return OVERLAY_UPDATED
end
----
Starte es mit `Singe -R numbers` und lass es laufen. Dank dem `-R` vom Ende der Lektion eins
musst du es für den Rest dieser Lektion nicht wieder starten; speichere die Datei, und das Fenster
zieht von selbst nach.
[#_count_the_frames]
=== Zähl die Frames
In Lektion eins hieß es, dass Singe `onOverlayUpdate` etwa sechzigmal pro Sekunde aufruft. Das ist
leicht gesagt und schwer zu glauben, also zähl nach.
[source,lua]
----
function onOverlayUpdate()
frames = 0
frames = frames + 1
overlayClear()
overlayPrint(2, 2, "Hello!")
overlayPrint(0, 0, "frame " .. frames)
return OVERLAY_UPDATED
end
----
Speichere. Oben links in der Ecke steht `frame 1`, und es bleibt für immer bei `frame 1`.
Das ist enttäuschend, und es ist zugleich das Nützlichste in dieser Lektion, also reparier es noch
nicht. Sieh dir an, was du geschrieben hast, und denk über die Reihenfolge der Zeilen nach, die laut
Lektion eins das Wichtigste an einem Programm ist.
Singe ruft `onOverlayUpdate` auf. Die erste Zeile setzt `frames` auf null. Die zweite zählt eins
dazu, macht eins. Du gibst es aus, du kehrst zurück, und die Funktion ist vorbei. Dann ruft Singe
sie wieder auf, und als Erstes wird `frames` wieder auf null gesetzt.
Du zählst keine Frames. Du zählst bis eins, sechzigmal pro Sekunde.
[#_move_the_line_out]
=== Hol die Zeile heraus
Nimm die Zeile `frames = 0` aus der Funktion heraus und setz sie darüber, für sich allein, vor das
Wort `function`.
[source,lua]
----
frames = 0
function onOverlayUpdate()
frames = frames + 1
overlayClear()
overlayPrint(2, 2, "Hello!")
overlayPrint(0, 0, "frame " .. frames)
return OVERLAY_UPDATED
end
----
Speichere. Die Zahl steigt, schnell, und hört nicht auf.
Sonst hat sich nichts geändert. Der einzige Unterschied ist, *wo* die Zeile steht, und das ist
schon die ganze Idee. Zeilen in einer Funktion passieren jedes einzelne Mal, wenn die Funktion
aufgerufen wird. Zeilen außerhalb jeder Funktion passieren einmal, wenn Singe deine Datei liest,
bevor das Spiel anfängt.
`frames = 0` musste einmal passieren. Es passierte sechzigmal pro Sekunde und machte die Arbeit der
Zeile darunter zunichte.
Alles, was sich bewegt und was du je schreibst, hat diese Form. Die Zahl draußen anlegen, drinnen
ändern.
[#_what_just_happened_2]
=== Was gerade passiert ist
Da drin stecken drei neue Stücke Code. Hier sind sie einzeln.
[source,lua]
----
frames = 0
----
Das denkt sich einen Namen aus, `frames`, und legt die Zahl null dahinter. Ein Name mit einem Wert
dahinter ist eine *Variable*, und sie heißt so, weil der Wert variieren darf -- sich ändern,
während das Programm läuft.
Das `=` ist nicht das Gleichheitszeichen aus der Schule. Es sagt nicht, dass `frames` gleich null
ist; es sagt *leg null in `frames`*, und es arbeitet immer in diese Richtung, von rechts nach links.
Programmierer nennen das *Zuweisung*, und es lohnt sich, die Zeile beim Schreiben so laut
auszusprechen: „frames bekommt null“.
Den Namen wählst du. Er kann fast alles sein, was mit einem Buchstaben anfängt und keine
Leerzeichen enthält, und er sollte sagen, was das Ding ist. `frames` ist ein guter Name. `f` ist
ein schlechter, und in einer Woche wirst du dir dafür nicht dankbar sein.
[source,lua]
----
frames = frames + 1
----
Das ist die Zeile, bei der die Leute innehalten und starren, denn als Aussage übers Rechnen ist sie
Unsinn: Nichts ist gleich sich selbst plus eins.
Sie ist keine Aussage übers Rechnen. Denk daran, dass `=` die rechte Seite in den Namen links legt,
und lies sie in dieser Reihenfolge, rechts zuerst. Rechne `frames + 1` aus, mit dem, was gerade in
`frames` steht. Dann leg das Ergebnis zurück in `frames`. Stand in `frames` eine Sechs, ergibt die
rechte Seite sieben, und sieben kommt in `frames`.
Eins zu einer Variablen dazuzuzählen wie hier wirst du tausende Male schreiben.
Das `+` tut, was du erwartest, und die anderen drei auch: `-` subtrahiert, `*` multipliziert und
`/` dividiert. Es gibt keine Taste `x` zum Malnehmen, darum übernimmt das der Stern.
[source,lua]
----
overlayPrint(0, 0, "frame " .. frames)
----
`overlayPrint` will Text, und `frames` ist eine Zahl, kein Text. Die zwei Punkte fügen die beiden
zusammen und übergeben das Ergebnis als einen String: die fünf Zeichen `frame`, ein Leerzeichen
und dann so viele Ziffern, wie die Zahl braucht.
Diese zwei Punkte sind der Verbindungsoperator, und es lohnt sich, seinen richtigen Namen zu
kennen, weil die Fehlermeldungen ihn benutzen: Strings aneinanderzuhängen heißt *Konkatenation*.
Beachte das Leerzeichen in den Anführungszeichen, vor dem schließenden. Ohne es bekommst du
`frame6`. Lua hängt genau das aneinander, was du ihm gibst, und tut nichts Eigenes dazu.
Lua macht für dich aus einer Zahl Text, wenn du sie so an einen String hängst, und darum musstest
du nichts Besonderes tun. Das macht es nicht mit allem. Wenn du je versuchst, etwas anzuhängen, das
weder Text noch Zahl ist, hält Lua mit einem Fehler an, und der Weg drumherum ist `tostring`, das
aus allem überhaupt Text macht:
[source,lua]
----
overlayPrint(0, 0, "frame " .. tostring(frames))
----
Diese Zeile tut genau dasselbe wie die davor. Für Zahlen brauchst du `tostring` nicht. In Lektion
drei wirst du es brauchen.
[#_make_it_move]
=== Bring es in Bewegung
Jetzt hast du alles, was du brauchst, um das Wort zu bewegen. Das Wort wird gezeichnet, wo du
willst, du hast eine Möglichkeit, eine Zahl von einem Frame zum nächsten aufzubewahren, und du
hast eine Möglichkeit, sie zu ändern.
Gib der Spalte eine eigene Variable außerhalb der Funktion und zähl drinnen etwas dazu.
[source,lua]
----
frames = 0
wordX = 0
function onOverlayUpdate()
frames = frames + 1
wordX = wordX + 1
overlayClear()
overlayPrint(wordX, 2, "Hello!")
overlayPrint(0, 0, "frame " .. frames)
return OVERLAY_UPDATED
end
----
Speichere. Das Wort schießt rechts hinaus und kommt nie zurück.
Es ist in jedem Frame eine Spalte weitergerückt, sechzigmal pro Sekunde, also hat es das ganze
Fenster in etwa einer Sekunde durchquert. Bremse es, indem du weniger als eine ganze Spalte
dazuzählst:
[source,lua]
----
wordX = wordX + 0.25
----
Jetzt braucht es vier Frames für eine Spalte, und das Wort kriecht. `wordX` ist meistens eine Zahl
wie `12.75`, und das ist überhaupt keine Spalte; Singe löst das, indem es beim Zeichnen alles nach
dem Punkt wegwirft: `12.75` wird in Spalte zwölf gezeichnet. Genau weil der Bruchteil in der
Variablen bleibt, kannst du dich langsamer als eine Spalte pro Frame bewegen. Die Zahl ist feiner
als der Bildschirm.
[#_where_is_the_edge]
=== Wo ist der Rand?
Das Wort haut immer noch ab. Um es umkehren zu lassen, musst du wissen, wo der rechte Rand ist, und
das könntest du durch Zählen herausfinden: Ein Spiel ohne Video bekommt standardmäßig eine Welt von
720 mal 480, das Overlay, auf das du zeichnest, ist halb so groß, 360 mal 240, und die Buchstaben
der eingebauten Konsolenschrift sind sechs Pixel breit, also gibt es sechzig Spalten.
Mach das nicht. Frag die Engine.
[source,lua]
----
lastColumn = overlayGetWidth() // CELL_WIDTH - string.len(word)
----
`overlayGetWidth` und `overlayGetHeight` sind zwei weitere Funktionen, die Singe bereitstellt, wie
`overlayPrint`. Sie bekommen nichts und geben eine Zahl zurück: wie breit und wie hoch die
Zeichenfläche ist. In Lektion eins hast du gesehen, wie `return` einen Wert zurückgibt, und das
hier ist das andere Ende davon -- eine Funktion, die du aufrufst, kann dir etwas geben, und hier
rechnest du mit dem, was sie dir gegeben hat.
Die Zahlen, die sie dir geben, sind in Pixeln, und `overlayPrint` zählt in Zeichenzellen, also
musst du teilen. `overlayGetFontWidth` sagt dir, wie viele Pixel breit eine Zelle ist, und
`overlayGetFontHeight`, wie hoch, sodass du die Antwort nie selbst kennen musst. Die zwei
Schrägstriche heißen *teilen und den Bruchteil wegwerfen*, denn ein Drittel einer Zeichenzelle ist
kein Ort, an dem du etwas ausgeben kannst. Der gewöhnliche einfache Schrägstrich würde dir einen
Bruch geben, und ein Bruchteil einer Zelle ist auch kein Ort.
`string.len` zählt die Zeichen in einem String, und es steht da, damit das Wort umkehrt, wenn sein
*letzter* Buchstabe den Rand erreicht, und nicht sein erster. Probier es ohne, und du siehst sechs
Zeichen ins Nichts gleiten.
Die Großbuchstaben in `CELL_WIDTH`, das festhält, was dir `overlayGetFontWidth` gibt, sind eine
Gewohnheit, keine Regel. Lua ist das egal, aber mit einem Namen in Großbuchstaben sagen Programmierer „das ist eine Zahl, die ich einmal oben festlege
und nie wieder anfasse“, und das ist besser als eine nackte `6` mitten in einer Datei, die einen
Monat später keiner erklären kann.
[#_turn_it_round]
=== Lass es umkehren
In die andere Richtung zu laufen ist überhaupt keine Arbeit: Statt in jedem Frame `0.25`
dazuzuzählen, zähl `-0.25` dazu. Bewahr den Betrag also in einer eigenen Variablen auf und dreh
sein Vorzeichen um, wenn das Wort an einem Rand ankommt.
[source,lua]
----
if wordX > lastColumn then
wordX = lastColumn
speedX = -speedX
end
----
Das ist eine Entscheidung, und um Entscheidungen geht es in der ganzen Lektion drei, also nimm es
für eine Lektion einfach hin. Lies es laut: *wenn* `wordX` über `lastColumn` hinaus ist, *dann*
mach die zwei eingerückten Zeilen; sonst überspring sie. Das `end` schließt es ab, so wie `end`
eine Funktion abschließt.
Die erste Zeile heftet das Wort genau an den Rand, damit ein schnelles Wort nicht darüber
hinaussegeln kann. Die zweite ist das Umdrehen. `-speedX` ist minus das, was in `speedX` steht;
stand dort `0.25`, steht jetzt `-0.25` darin, und wenn du in jedem Frame `-0.25` dazuzählst,
läuft das Wort den Weg zurück, den es gekommen ist. Erreicht es den linken Rand, dreht das
passende `if` es wieder um, und es wird wieder zu `0.25`.
Noch zwei davon für den oberen und den unteren Rand, und das Wort springt im ganzen Fenster herum.
[#_the_whole_thing]
=== Das Ganze
[source,lua]
----
CELL_WIDTH = overlayGetFontWidth()
CELL_HEIGHT = overlayGetFontHeight()
TOP_ROW = 2
word = "Hello!"
wordX = 0
wordY = TOP_ROW
speedX = 0.25
speedY = 0.125
frames = 0
lastColumn = overlayGetWidth() // CELL_WIDTH - string.len(word)
lastRow = overlayGetHeight() // CELL_HEIGHT - 1
function onOverlayUpdate()
frames = frames + 1
wordX = wordX + speedX
wordY = wordY + speedY
if wordX < 0 then
wordX = 0
speedX = -speedX
end
if wordX > lastColumn then
wordX = lastColumn
speedX = -speedX
end
if wordY < TOP_ROW then
wordY = TOP_ROW
speedY = -speedY
end
if wordY > lastRow then
wordY = lastRow
speedY = -speedY
end
overlayClear()
overlayPrint(0, 0, "frame " .. frames)
overlayPrint(wordX, wordY, word)
return OVERLAY_UPDATED
end
----
`TOP_ROW` hält das Wort aus den obersten zwei Zeilen heraus, damit es nie über den Frame-Zähler
kritzelt. In `lastRow` steht ein `- 1` statt eines `string.len`, weil ein Wort viele Zeichen breit,
aber immer nur eine Zeile hoch ist.
Beachte, dass die ganze obere Hälfte der Datei einmal läuft und die ganze untere Hälfte sechzigmal
pro Sekunde, und dass du an der Einrückung erkennst, welche welche ist. Dafür waren die Leerzeichen
da.
[#_try_it_2]
=== Probier es aus
. *Ändere die Geschwindigkeiten.* Probier `speedX = 1` und `speedY = 1`. Dann probier `0.05` für
beide. Dann mach eine viel größer als die andere.
. *Ändere das Wort.* Schreib ein viel längeres Wort zwischen die Anführungszeichen -- lang genug,
um ein Drittel des Fensters zu füllen -- und sieh zu, wie das Abprallen rechts immer noch an der
richtigen Stelle passiert. Das ist `string.len`, das seine Arbeit tut.
. *Nimm das Löschen raus.* Lösch die Zeile `overlayClear()`. Lektion eins hat versprochen, dass das
eines Tages wichtig wird, und heute ist der Tag: Das Wort schmiert eine Spur quer durchs Fenster
und radiert nichts davon weg, weil Zeichnen schon immer über das gezeichnet hat, was da war. Setz
sie wieder ein.
. *Halt die Seitwärtsbewegung an.* Setz `speedX = 0` und starte es. Finde heraus, warum das Wort
jetzt nie Spalte null verlässt und warum das `if` für den linken Rand nie anspringt.
. *Mach den Frame-Zähler absichtlich kaputt.* Schieb die Zeile `frames = 0` zurück in die
Funktion, über `frames = frames + 1`. Sieh zu, wie der Zähler bei eins hängen bleibt, während
sich das Wort tadellos weiterbewegt. Dann finde heraus, warum das Wort nichts davon merkt.
[#_break_it_on_purpose_2]
=== Mach es absichtlich kaputt
Ersetze die zwei Punkte in der Zeile mit dem Frame-Zähler durch ein Pluszeichen, sodass sie so
lautet:
[source,lua]
----
overlayPrint(0, 0, "frame " + frames)
----
Speichere. Das Fenster geht zu, Singe hält an, und im Terminal steht:
----
Error executing function 'onOverlayUpdate': numbers/numbers.singe:42: attempt to add a 'string' with a 'number'
----
gefolgt von mehreren Zeilen, die mit `stack traceback:` anfangen und die du vorerst ignorieren
kannst.
Lies es so, wie Lektion eins es dir beigebracht hat: die Datei, die Zeile, dann die Beschwerde.
Zeile 42 ist die Zeile, die du geändert hast. Die Beschwerde sagt, Lua sollte einen String zu einer
Zahl *addieren*, und Addieren ist etwas für Zahlen. `+` und `..` sehen auf dem Papier ähnlich aus
und erledigen völlig verschiedene Aufgaben: Plus ist Rechnen, `..` ist Kleber.
Diesen Fehler lohnt es sich kennenzulernen, wegen der Stelle, an der er auftaucht. Der Fehler aus
Lektion eins hat Singe angehalten, bevor dein Spiel überhaupt anfing, weil Lua die Datei gar
nicht lesen konnte. Dieser hier steckt in einer völlig lesbaren Zeile, die erst schiefgeht, wenn sie
läuft; also ging das Fenster auf, das Spiel lief, und es starb im ersten Frame. Beide Arten nennen
dir die Datei und die Zeile. Fang so oder so dort an.
[#_what_you_learned_2]
=== Was du gelernt hast
* Eine Variable ist ein Name mit einem Wert dahinter, und der Wert kann sich ändern.
* `=` heißt „leg die rechte Seite in den Namen links“, nicht „ist gleich“.
* `frames = frames + 1` rechnet zuerst die rechte Seite aus und speichert sie dann.
* `+`, `-`, `*` und `/` rechnen, wie du es erwartest; `//` teilt und wirft den Bruchteil weg.
* `..` hängt Text aneinander und macht dabei aus einer Zahl Text.
* Eine Zeile in `onOverlayUpdate` läuft in jedem Frame. Eine Zeile außerhalb jeder Funktion läuft
einmal, bevor das Spiel anfängt.
* Alles, was sich von einem Frame zum nächsten etwas merken muss, muss draußen wohnen.
* `overlayGetWidth` und `overlayGetHeight` sagen dir die Größe der Zeichenfläche, damit du sie nie
raten musst.
* `overlayClear` wird wichtig, sobald sich irgendetwas bewegt.
[#_next_time_2]
=== Beim nächsten Mal
Das Wort bewegt sich, aber es bewegt sich von selbst, und nichts, was du tust, ändert etwas daran.
In Lektion drei nimmst du dir die vier `if`-Blöcke vor, die du auf Treu und Glauben benutzt hast,
lernst, was sie wirklich sagen, und richtest sie auf die Pfeiltasten statt auf die Ränder des
Bildschirms. Das Kästchen, das du am Ende durchs Fenster steuerst, ist die Spielfigur des Spiels,
das du in Lektion sieben baust.