503 lines
20 KiB
Text
503 lines
20 KiB
Text
[#_lesson_3_making_decisions]
|
|
== Lektion 3: Entscheidungen treffen
|
|
|
|
image::learn/03-decisions.png[Die fertige Lektion, 480]
|
|
|
|
Alles, was du bisher geschrieben hast, tut in jedem Frame dasselbe. Das Wort aus Lektion zwei hat
|
|
sich bewegt, ob du wolltest oder nicht, und umgekehrt ist es nur wegen vier Blöcken Code, die du
|
|
auf Treu und Glauben hinnehmen solltest.
|
|
|
|
In dieser Lektion geht es um diese vier Blöcke, und zwar richtig. Am Ende stellt dein Programm
|
|
Fragen, handelt nach den Antworten, und -- weil die interessanteste Frage, die ein Spiel stellen
|
|
kann, „Was macht der Spieler?“ lautet -- du steuerst ein Kästchen mit den Pfeiltasten durchs
|
|
Fenster. Heb dieses Kästchen gut auf. Es ist die Spielfigur des Spiels, das du in Lektion sieben
|
|
baust.
|
|
|
|
[#_start_a_new_folder_2]
|
|
=== Leg einen neuen Ordner an
|
|
|
|
Ein Ordner namens `decisions`, darin eine Datei namens `decisions.singe`, und in einem Terminal
|
|
läuft `Singe -R decisions`. Wie beim letzten Mal.
|
|
|
|
[#_what_an_if_really_says]
|
|
=== Was ein `if` wirklich sagt
|
|
|
|
Hier ist der Block aus Lektion zwei für sich allein.
|
|
|
|
[source,lua]
|
|
----
|
|
if wordX > lastColumn then
|
|
wordX = lastColumn
|
|
end
|
|
----
|
|
|
|
Vier Teile. Das Wort `if`. Eine Frage. Das Wort `then`. Und ein Rumpf aus Zeilen, die nur laufen,
|
|
wenn die Antwort auf die Frage Ja ist, abgeschlossen mit `end`.
|
|
|
|
Die Frage ist `wordX > lastColumn`, und die eigentliche Arbeit macht das `>`. Es vergleicht die
|
|
beiden Zahlen links und rechts davon und liefert eine Antwort, die entweder *wahr* oder *falsch*
|
|
ist. Diese beiden Wörter sind kein Text und keine Zahlen; sie sind eigene Werte, *Booleans*
|
|
genannt, und ein Boolean ist das Einzige, wofür sich ein `if` interessiert.
|
|
|
|
Die Einrückung ist dieselbe Idee wie in Lektion eins: Die Zeilen darin sind eingerückt, damit du
|
|
auf einen Blick siehst, was zum `if` gehört, und das `end` steht genau unter dem `if`, das den
|
|
Block geöffnet hat. Lua würde es auch ohne die Leerzeichen ausführen. Du könntest es dann nicht
|
|
lesen.
|
|
|
|
Es gibt sechs solche Vergleiche, und du wirst alle benutzen.
|
|
|
|
[cols="1,4"]
|
|
|===
|
|
| `a < b` | a ist kleiner als b
|
|
| `a > b` | a ist größer als b
|
|
| `a <= b` | a ist kleiner oder gleich b
|
|
| `a >= b` | a ist größer oder gleich b
|
|
| `a == b` | a ist dasselbe wie b
|
|
| `a ~= b` | a ist nicht dasselbe wie b
|
|
|===
|
|
|
|
Bei den letzten beiden musst du aufpassen, aus verschiedenen Gründen.
|
|
|
|
`~=` heißt „ist nicht gleich“. Die meisten Sprachen schreiben das `!=`, und wenn du schon einmal
|
|
ein Programm gesehen hast, tippen deine Finger `!=`, und Lua lehnt es ab. Die Schlangenlinie liegt
|
|
auf deiner Tastatur neben der `1` oder unter dem `#`, je nachdem, wo du wohnst.
|
|
|
|
`==` sind zwei Gleichheitszeichen, und das ist kein Tippfehler für eins. Das ist einer der
|
|
häufigsten Fehler beim Programmieren, und es lohnt sich, kurz dabei stehen zu bleiben.
|
|
|
|
In Lektion zwei hast du gelernt, dass ein Gleichheitszeichen *leg die rechte Seite in den Namen
|
|
links* bedeutet. Das ist etwas, das du tust. Es ändert etwas.
|
|
|
|
Zwei Gleichheitszeichen bedeuten *sind die beiden gleich?* Das ist eine Frage, die du stellst. Sie
|
|
ändert nichts, und sie gibt wahr oder falsch zurück.
|
|
|
|
[source,lua]
|
|
----
|
|
lives = 3
|
|
if lives == 3 then
|
|
----
|
|
|
|
Die erste Zeile setzt `lives` auf drei. Die zweite fragt, ob es drei ist. Verschiedene Aufgaben,
|
|
verschiedene Symbole, ein Zeichen Unterschied. Lua erwischt dich, wenn du sie verwechselst, und den
|
|
Fehler, den es dann meldet, lernst du am Ende dieser Lektion kennen.
|
|
|
|
[#_when_the_answer_is_no]
|
|
=== Wenn die Antwort Nein ist
|
|
|
|
Ein `if` allein tut überhaupt nichts, wenn die Antwort falsch ist. Sehr oft willst du, dass
|
|
stattdessen das andere passiert, und dafür gibt es `else`.
|
|
|
|
[source,lua]
|
|
----
|
|
if lives > 0 then
|
|
message = "Ready"
|
|
else
|
|
message = "Game Over"
|
|
end
|
|
----
|
|
|
|
Genau einer der beiden Rümpfe läuft, nie beide, nie keiner, also steht in `message` am Ende immer
|
|
einer der beiden Strings und nie gar nichts.
|
|
|
|
Wenn es mehr als zwei Möglichkeiten gibt, hängt `elseif` sie aneinander.
|
|
|
|
[source,lua]
|
|
----
|
|
if score >= 10000 then
|
|
rank = "ace"
|
|
elseif score >= 5000 then
|
|
rank = "pilot"
|
|
elseif score >= 1000 then
|
|
rank = "rookie"
|
|
else
|
|
rank = "passenger"
|
|
end
|
|
----
|
|
|
|
Lua probiert die Fragen von oben und hört bei der ersten auf, die wahr ist. Eine Punktzahl von 6000
|
|
ist nicht größer als 10000, also probiert es die nächste, und die ist wahr, also wird `rank` zu
|
|
`pilot`, und die anderen beiden Fragen werden nie gestellt. Dieser letzte Punkt ist wichtig: Das
|
|
`elseif` für 1000 ist bei einer Punktzahl von 6000 auch wahr, aber es wird nie erreicht, und darum
|
|
geht die Reihenfolge vom Größten zum Kleinsten.
|
|
|
|
Die ganze Kette braucht genau ein `end`, ganz egal, wie viele `elseif`-Teile darin stecken. Es ist
|
|
eine Entscheidung, nicht vier.
|
|
|
|
[#_true_false_and_nothing_at_all]
|
|
=== Wahr, falsch und gar nichts
|
|
|
|
Du musst nicht zwei Dinge vergleichen, um einen Boolean zu bekommen. Du kannst einen aufbewahren.
|
|
|
|
[source,lua]
|
|
----
|
|
paused = false
|
|
----
|
|
|
|
Das ist eine Variable wie jede andere, und der Wert darin ist der Boolean `false` selbst, ohne
|
|
Anführungszeichen geschrieben, weil es nicht das Wort „false“ ist, sondern die Idee. Dasselbe gilt
|
|
für `true`. Eine Variable, die einen von beiden enthält, kann direkt in ein `if`, ganz ohne
|
|
Vergleich:
|
|
|
|
[source,lua]
|
|
----
|
|
if paused then
|
|
----
|
|
|
|
Lies es als „if paused“. Das ist besseres Englisch als `if paused == true`, das dasselbe auf dem
|
|
Umweg tut, und erfahrene Programmierer schauen dich seltsam an, wenn du das schreibst.
|
|
|
|
Drei Wörter verbinden diese Antworten und drehen sie um.
|
|
|
|
[source,lua]
|
|
----
|
|
if alive and not shielded then
|
|
if coins > 0 or freePlay then
|
|
----
|
|
|
|
`and` ist wahr, wenn beide Seiten wahr sind. `or` ist wahr, wenn eine der beiden Seiten wahr ist,
|
|
oder beide. `not` macht aus wahr falsch und aus falsch wahr. Sie lesen sich wie Englisch, und,
|
|
ungewöhnlich fürs Programmieren, sie bedeuten auch, was sie im Englischen bedeuten.
|
|
|
|
Es gibt noch einen Wert, den du kennen solltest, und er ist der, der die verwirrendsten Fehler
|
|
deines ersten Jahres erzeugt.
|
|
|
|
[source,lua]
|
|
----
|
|
overlayPrint(0, 0, "lives " .. tostring(lives))
|
|
----
|
|
|
|
Wenn du nie etwas in `lives` gelegt hast, gibt das `lives nil` aus. Lua beschwert sich nicht über
|
|
einen Namen, den es nie gesehen hat; es gibt dir `nil`, und das ist sein Wort für *hier ist
|
|
nichts*. Es ist nicht die Null, und es ist kein leerer String. Die Null ist eine Zahl, die du
|
|
speichern wolltest. `nil` ist das Fehlen eines Werts.
|
|
|
|
Du bist `nil` schon zweimal begegnet, ohne dass es dir jemand gesagt hat. `frames = frames + 1`
|
|
ohne die Zeile `frames = 0` darüber schlägt fehl, weil du Lua gebeten hast, eins zu nichts zu
|
|
addieren. Und ein Callback, den du falsch schreibst, ist ebenfalls `nil`, und darum bringt dir ein
|
|
falsch geschriebenes `onOverlayUpdate` ein schwarzes Fenster und überhaupt keinen Fehler: Singe
|
|
sucht nach dem Namen, findet dort nichts und macht still mit seinem Tag weiter.
|
|
|
|
`tostring` steht in dieser Zeile, weil `..` einen Boolean oder ein `nil` an nichts anhängt. Zahlen
|
|
wandelt es für dich um; diese nicht. Das war das Versprechen am Ende von Lektion zwei, und jetzt
|
|
hast du eine Verwendung dafür.
|
|
|
|
Zum Schluss die Regel, die den ganzen Abschnitt zusammenhält, denn `if` nimmt alles, was du ihm
|
|
gibst, nicht nur wahr und falsch:
|
|
|
|
[quote]
|
|
In Lua ist alles wahr außer `false` und `nil`.
|
|
|
|
Null ist wahr. Ein leerer String ist wahr. Nur diese beiden sind es nicht, und wenn du das einmal
|
|
weißt, liest sich `if movingLeft then` tadellos, ob in `movingLeft` nun `true` oder
|
|
`false` steht oder ob es nie gesetzt wurde.
|
|
|
|
[#_reading_the_controls]
|
|
=== Die Steuerung abfragen
|
|
|
|
Zeit, den Spieler etwas zu fragen. Dafür braucht es einen neuen Callback.
|
|
|
|
[source,lua]
|
|
----
|
|
function onInputPressed(what)
|
|
if what == SWITCH_RIGHT then
|
|
boxX = boxX + 10
|
|
end
|
|
end
|
|
----
|
|
|
|
`onInputPressed` ist der zweite Callback, den du geschrieben hast, und er funktioniert genau so, wie
|
|
Lektion eins es beschrieben hat: Du schreibst ihn, Singe ruft ihn auf. `onOverlayUpdate` wird in
|
|
einem festen Takt aufgerufen. Dieser hier wird aufgerufen, wenn etwas passiert -- wenn der Spieler
|
|
irgendetwas drückt, das die Engine erkennt.
|
|
|
|
Der Unterschied zu `onOverlayUpdate` ist das Wort in den Klammern. Singe muss dir sagen, *welches*
|
|
Steuerelement gedrückt wurde, also übergibt es der Funktion einen Wert, und `what` ist der Name,
|
|
unter dem du ihn entgegennehmen willst. In Lektion eins hießen die Dinge, die du einer Funktion
|
|
übergibst, Argumente; von innen gesehen ist der Name, der eines auffängt, ein *Parameter*. Der Name
|
|
gehört dir: Nenn ihn `what`, nenn ihn `button`, nenn ihn `k`. Singe füllt ihn so oder so.
|
|
|
|
`SWITCH_RIGHT` ist ein Name, den die Engine schon einem Wert gegeben hat, wie `OVERLAY_UPDATED` in
|
|
Lektion eins. Du musst nichts laden, um ihn zu benutzen; Singe legt sie alle fest, bevor dein Skript
|
|
läuft. Es gibt einen für alles, was ein Automat hat:
|
|
`SWITCH_LEFT`, `SWITCH_RIGHT`, `SWITCH_UP`, `SWITCH_DOWN`, `SWITCH_BUTTON1`
|
|
bis `SWITCH_BUTTON4`, `SWITCH_START1`, `SWITCH_COIN1` und mehr. Die Tabelle
|
|
Engine-Konstanten in der Referenz deckt die ganze Familie ab, die von `SWITCH_UP` bis
|
|
`SWITCH_RECORD` reicht. Sie heißen absichtlich nicht „die rechte Pfeiltaste“, denn dasselbe
|
|
`SWITCH_RIGHT` kommt an, ob der Spieler eine Pfeiltaste gedrückt hat, einen Joystick oder den
|
|
Stick eines Gamepads. Du schreibst das Spiel einmal, und es funktioniert mit allen dreien.
|
|
|
|
Füg diesen Callback hinzu, gib `boxX` außerhalb der Funktionen einen Startwert und zeichne etwas
|
|
bei `boxX`. Drück die Pfeiltaste nach rechts. Das Kästchen springt bei jedem Drücken zehn Pixel
|
|
weiter.
|
|
|
|
[#_it_happened_and_it_is_happening]
|
|
=== Es ist passiert, und es passiert gerade
|
|
|
|
Halt die Pfeiltaste gedrückt. Mehr passiert nicht.
|
|
|
|
`onInputPressed` wird einmal aufgerufen, beim Hinunterdrücken, und erst wieder, wenn du loslässt
|
|
und noch einmal drückst. Es gibt keine Tastenwiederholung. Für manche Dinge ist das genau richtig
|
|
-- einen Schuss abfeuern, eine Münze einwerfen, einen Menüpunkt wählen -- und fürs Laufen genau
|
|
falsch, denn das muss so lange weitergehen, wie die Taste gehalten wird.
|
|
|
|
Das sind wirklich zwei verschiedene Fragen. *Es ist passiert* ist ein Ereignis, und davon hörst du
|
|
einmal. *Es passiert gerade* ist ein Zustand, und der dauert an. Singe erzählt dir von Ereignissen,
|
|
und aus Ereignissen machst du einen Zustand, indem du sie dir selbst merkst.
|
|
|
|
Zwei Callbacks, eine Variable dazwischen.
|
|
|
|
[source,lua]
|
|
----
|
|
movingRight = false
|
|
|
|
|
|
function onInputPressed(what)
|
|
if what == SWITCH_RIGHT then
|
|
movingRight = true
|
|
end
|
|
end
|
|
|
|
|
|
function onInputReleased(what)
|
|
if what == SWITCH_RIGHT then
|
|
movingRight = false
|
|
end
|
|
end
|
|
----
|
|
|
|
`onInputReleased` ist der dritte Callback, und er ist das Spiegelbild des zweiten: Singe ruft ihn
|
|
auf dem Weg zurück nach oben auf, mit demselben `SWITCH_`-Wert. Zusammen sorgen die beiden dafür,
|
|
dass `movingRight` die Wahrheit darüber sagt, was der Finger des Spielers gerade tut.
|
|
|
|
Bewegt hat sich noch nichts, und genau darum geht es. Das Drücken und das Bewegen sind jetzt zwei
|
|
getrennte Aufgaben. Das Bewegen kommt dorthin, wo alle Arbeit hinkommt, die in jedem Frame
|
|
passiert.
|
|
|
|
[source,lua]
|
|
----
|
|
function onOverlayUpdate()
|
|
if movingRight then
|
|
boxX = boxX + BOX_SPEED
|
|
end
|
|
...
|
|
----
|
|
|
|
Das Kästchen gleitet jetzt gleichmäßig, solange die Taste unten ist, mit einer Geschwindigkeit, die
|
|
du bestimmst, und das Rechnen ist wieder in der einen Funktion, die sechzigmal pro Sekunde läuft,
|
|
wo es hingehört. Diese Form -- Ereignisse setzen Flags, der Frame handelt nach den Flags -- ist die
|
|
Art, wie fast jedes Spiel seine Steuerung behandelt, und du wirst sie in jeder Lektion wieder
|
|
benutzen, in der es eine Spielfigur gibt.
|
|
|
|
[#_drawing_a_box]
|
|
=== Ein Kästchen zeichnen
|
|
|
|
`overlayBox` bekommt zwei Ecken: links und oben am Rechteck, dann rechts und unten.
|
|
|
|
[source,lua]
|
|
----
|
|
colorForeground(255, 220, 0)
|
|
overlayBox(boxX, boxY, boxX + BOX_SIZE, boxY + BOX_SIZE)
|
|
----
|
|
|
|
Es zeichnet einen *Umriss*, vier Linien, ein Pixel dick, und kein gefülltes Rechteck. Unter den
|
|
Zeichenaufrufen fürs Overlay gibt es überhaupt kein gefülltes Rechteck. Wenn du später einen
|
|
massiven Block willst, gibt es Wege dahin, und Lektion neun zeigt den üblichen.
|
|
|
|
Seine Zahlen sind Pixel, keine Zeichenzellen. Darauf fallen Leute herein, denn `overlayPrint` hat
|
|
in Lektion zwei in Zellen von sechs mal dreizehn Pixeln gezählt, und alles andere in Singe zählt in
|
|
Pixeln. `overlayPrint` ist der Sonderling; die übrigen Zeichenaufrufe -- `overlayBox`,
|
|
`overlayLine`, `overlayCircle`, `overlayEllipse` und `overlayPlot` -- arbeiten alle in
|
|
denselben Pixeln, die `overlayGetWidth` meldet.
|
|
|
|
`colorForeground` legt die Farbe fest, als drei Zahlen von 0 bis 255: wie viel Rot, wie viel Grün,
|
|
wie viel Blau. Alle drei auf 255 ist Weiß, alle auf 0 ist Schwarz, und `(255, 220, 0)` ist ein
|
|
warmes Gelb. Beachte, dass es kein Argument von `overlayBox` ist. Es ist eine Einstellung: Du
|
|
änderst sie, und alles, was du danach zeichnest, kommt in der neuen Farbe heraus, bis du sie wieder
|
|
änderst. `overlayPrint` ist wieder einmal die Ausnahme -- die Konsolenschrift bringt ihre eigenen
|
|
Farben fest eingebacken mit und ignoriert dich.
|
|
|
|
[#_the_whole_thing_2]
|
|
=== Das Ganze
|
|
|
|
[source,lua]
|
|
----
|
|
BOX_SIZE = 20
|
|
BOX_SPEED = 2
|
|
|
|
width = overlayGetWidth()
|
|
height = overlayGetHeight()
|
|
maxX = width - BOX_SIZE - 1
|
|
maxY = height - BOX_SIZE - 1
|
|
|
|
boxX = width // 2
|
|
boxY = height // 2
|
|
|
|
movingLeft = false
|
|
movingRight = false
|
|
movingUp = false
|
|
movingDown = false
|
|
|
|
|
|
function onInputPressed(what)
|
|
if what == SWITCH_LEFT then
|
|
movingLeft = true
|
|
elseif what == SWITCH_RIGHT then
|
|
movingRight = true
|
|
elseif what == SWITCH_UP then
|
|
movingUp = true
|
|
elseif what == SWITCH_DOWN then
|
|
movingDown = true
|
|
end
|
|
end
|
|
|
|
|
|
function onInputReleased(what)
|
|
if what == SWITCH_LEFT then
|
|
movingLeft = false
|
|
elseif what == SWITCH_RIGHT then
|
|
movingRight = false
|
|
elseif what == SWITCH_UP then
|
|
movingUp = false
|
|
elseif what == SWITCH_DOWN then
|
|
movingDown = false
|
|
end
|
|
end
|
|
|
|
|
|
function onOverlayUpdate()
|
|
if movingLeft and not movingRight then
|
|
boxX = boxX - BOX_SPEED
|
|
elseif movingRight and not movingLeft then
|
|
boxX = boxX + BOX_SPEED
|
|
end
|
|
|
|
if movingUp and not movingDown then
|
|
boxY = boxY - BOX_SPEED
|
|
elseif movingDown and not movingUp then
|
|
boxY = boxY + BOX_SPEED
|
|
end
|
|
|
|
if boxX < 0 then
|
|
boxX = 0
|
|
elseif boxX > maxX then
|
|
boxX = maxX
|
|
end
|
|
|
|
if boxY < 0 then
|
|
boxY = 0
|
|
elseif boxY > maxY then
|
|
boxY = maxY
|
|
end
|
|
|
|
overlayClear()
|
|
|
|
if movingLeft or movingRight or movingUp or movingDown then
|
|
colorForeground(255, 220, 0)
|
|
else
|
|
colorForeground(80, 160, 255)
|
|
end
|
|
|
|
overlayBox(boxX, boxY, boxX + BOX_SIZE, boxY + BOX_SIZE)
|
|
overlayPrint(0, 0, "x " .. boxX .. " y " .. boxY)
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Vier Dinge darin sind einen zweiten Blick wert.
|
|
|
|
`movingLeft and not movingRight` steht da, weil ein Spieler beide Pfeiltasten gleichzeitig halten
|
|
kann. Ohne das `not` würden die beiden gegeneinander kämpfen, und das Kästchen würde zittern oder
|
|
wegdriften; mit ihm gewinnt keine, wenn beide gehalten werden, und das Kästchen steht still, und
|
|
genau das erwartet ein Spieler.
|
|
|
|
`maxX` wird einmal oben ausgerechnet, mit `overlayGetWidth`, so wie in Lektion zwei, und das `- 1`
|
|
ist da, weil das letzte Pixel der Fläche um eins kleiner ist als ihre Breite. Nimm das `- 1`
|
|
heraus, und die rechte Kante des Kästchens fällt von der Fläche und verschwindet, weil Singe alles,
|
|
was du außerhalb des Overlays zeichnest, wortlos wegwirft.
|
|
|
|
Die Begrenzungsblöcke benutzen `elseif` statt zweier getrennter `if`-Blöcke. Das Kästchen kann
|
|
nicht gleichzeitig links und rechts über den Rand hinaus sein, also ist es verschwendete Arbeit,
|
|
die zweite Frage zu stellen, wenn die erste wahr war.
|
|
|
|
Und die Farbentscheidung ist ein `if`, dessen einzige Aufgabe es ist, einen Wert auszuwählen. Gelb,
|
|
solange der Spieler etwas tut, blau, solange nicht, und so siehst du gratis, dass deine
|
|
Eingabeverarbeitung wirklich funktioniert.
|
|
|
|
[#_try_it_3]
|
|
=== Probier es aus
|
|
|
|
. *Ändere die Geschwindigkeit.* `BOX_SPEED = 1` und `BOX_SPEED = 8`. Sieh dir bei acht an, was die
|
|
Begrenzung macht, wenn du das Kästchen in eine Ecke schiebst.
|
|
. *Zeig deine Flags.* Füg `onOverlayUpdate` eine Zeile hinzu:
|
|
`overlayPrint(0, 1, "left " .. tostring(movingLeft))`. Halte die linke Pfeiltaste, lass sie
|
|
los und sieh zu, wie der Wert umspringt. Dann nimm das `tostring` weg und lies den Fehler, den
|
|
Lua dir gibt.
|
|
. *Mach es schneller, wenn ein Knopf gedrückt ist.* Füg ein Flag `running` hinzu, das in den
|
|
beiden Eingabe-Callbacks von `SWITCH_BUTTON1` gesteuert wird, und benutze ein `if` in
|
|
`onOverlayUpdate`, um dich um das Doppelte von `BOX_SPEED` zu bewegen, solange es wahr ist.
|
|
. *Markier die Mitte.* Zeichne ein zweites, kleines Kästchen in die Mitte des Fensters, das die
|
|
Farbe wechselt, wenn das Kästchen des Spielers es überlappt. Du brauchst `and`, um vier
|
|
Vergleiche zu verbinden, und es ist schwieriger, als es klingt. Es ein bisschen falsch
|
|
hinzubekommen ist normal; Lektion elf macht es richtig.
|
|
. *Nimm das Loslassen raus.* Lösch die ganze Funktion `onInputReleased` und starte es. Das
|
|
Kästchen macht einen Schritt und hält dann nie wieder an. Finde heraus, welche Zeile jetzt nie
|
|
erreicht wird.
|
|
|
|
[#_break_it_on_purpose_3]
|
|
=== Mach es absichtlich kaputt
|
|
|
|
Such den Begrenzungsblock und mach aus dem `<` in seinem ersten Vergleich ein einzelnes
|
|
Gleichheitszeichen, sodass er so lautet:
|
|
|
|
[source,lua]
|
|
----
|
|
if boxX = 0 then
|
|
----
|
|
|
|
Speichere. Das Fenster bleibt leer, und im Terminal steht:
|
|
|
|
----
|
|
Error running script: decisions/decisions.singe:57: 'then' expected near '='
|
|
----
|
|
|
|
Das ist der Fehler, vor dem der ganze Abschnitt über `==` gewarnt hat, und die Meldung ist ein
|
|
gutes Beispiel für einen Fehler, der völlig zutreffend und völlig nutzlos ist, wenn du ihn wörtlich
|
|
nimmst. Lua will nicht, dass du das Wort `then` hinzufügst. Lua ist bis `if boxX` gekommen, hat
|
|
als Nächstes einen Vergleich oder ein `then` erwartet und `=` gefunden, und das kann keins von
|
|
beiden sein.
|
|
|
|
Immer wenn du `'then' expected` nach einem `if` siehst, such ein einfaches `=`, das ein doppeltes
|
|
sein sollte. Fast immer ist es das.
|
|
|
|
Achte darauf, wo dieser hier aufgetaucht ist: Das Spiel ist nie gestartet, genau wie beim
|
|
unvollständigen String in Lektion eins, weil Lua die Datei nicht lesen konnte. Der Fehler mit dem
|
|
Frame-Zähler in Lektion zwei passierte während des Laufens. Fehler in der Form deines Codes halten
|
|
dich auf, bevor das Spiel anfängt. Fehler in dem, was dein Code tut, warten, bis er läuft.
|
|
|
|
[#_what_you_learned_3]
|
|
=== Was du gelernt hast
|
|
|
|
* `if question then ... end` führt einen Rumpf nur aus, wenn die Antwort wahr ist.
|
|
* `else` liefert den anderen Fall, und `elseif` hängt weitere an; die erste wahre Frage gewinnt, und
|
|
die übrigen werden nie gestellt.
|
|
* Die Vergleiche sind `<`, `>`, `<=`, `>=`, `==` und `~=`.
|
|
* `=` legt einen Wert in einen Namen. `==` fragt, ob zwei Dinge gleich sind.
|
|
* `true` und `false` sind Werte, und du kannst sie in Variablen aufbewahren.
|
|
* `and`, `or` und `not` verbinden diese Antworten und drehen sie um.
|
|
* `nil` bekommst du von einem Namen, den du nie gesetzt hast, und es ist nicht die Null.
|
|
* Alles ist wahr außer `false` und `nil`.
|
|
* `onInputPressed` und `onInputReleased` sind Callbacks, die Singe mit einem `SWITCH_`-Wert
|
|
aufruft, wenn der Spieler ein Steuerelement drückt oder loslässt.
|
|
* Ein Drücken ist ein Ereignis und passiert einmal; halte den Zustand in einer Variablen fest und
|
|
handle in jedem Frame danach.
|
|
* `overlayBox` zeichnet einen Umriss in Pixeln, und `colorForeground` ist eine Einstellung, kein
|
|
Argument.
|
|
|
|
[#_next_time_3]
|
|
=== Beim nächsten Mal
|
|
|
|
Ein Kästchen ist leicht. Hundert Sterne sind es nicht, zumindest nicht so, wie du bisher
|
|
geschrieben hast, denn hundert Zeilen `overlayPlot` abzutippen ist niemandes Vorstellung von einem
|
|
schönen Abend. In Lektion vier geht es um die Schleife: dem Computer zu sagen, dass er dasselbe
|
|
viele Male tun soll, jedes Mal mit etwas anderem, und erst das macht einen Bildschirm voll mit
|
|
irgendwas möglich.
|