Datenstrukturen
Voraussetzung: "Einstieg in Lua“Definition von Variablen
Wir beginnen dieses Tutorial mit der Definition von einfachen Variablen. Abb. 1 zeigt die verschiedenen Möglichkeiten Platzhalter zu definieren.In den ersten vier Zeilen sehen wir global definierte Variablen vom Typ Zahl, Boolean, String und Array. Die ersten drei Typen sind uns von den graphischen Bausteinen her bekannt. Ein Array, oft auch als Tabelle bezeichnet, ist ein sehr flexibler und nützlicher Datentyp. Hier sehen wir ein Beispiel in Form einer einfachen Aufzählung. Der Zugriff auf Arrays erfolgt über einen Index, der die Stelle im Array definiert, indem man hinter den Arraynamen den Index in eckigen Klammern schreibt. An erster Stelle mit Index 1 wurde der String "Michael" gesetzt. Wir können diesen Wert mit
names[1]
abfragen. ( ! Während in vielen Programmiersprachen
mit der 0 zu zählen begonnen wird, startet die Sequence bei lua mit der 1)
Wie man am dritten Wert des Array erkennen kann, können auch andere Platzhalter zur Definition herangezogen werden.
names[3]
würde folglich "Krolock" liefern.
Entgegen anderen Sprachen muss man bei der Definition weder ein Keywort "var" oder "obj" vor die Variable schreiben, noch den Datentyp explizit dabei schreiben. Dieser ergibt sich aus dem Wert, der ihr zugeordnet wird und kann sogar gewechselt werden. (Auch wenn das im Normalfall nicht empfehlenswert ist).
Lokale Variablen
In der siebten Zeile steht das Wörtchen "local" vor dem Variablen delta. Dies bedeutet, dass sie nur in der Methode nextRound() bekannt ist. Nachdem die Funktion abgearbeitet wurde existieren "delta" und "message" nicht mehr und der von ihnen belegte Speicher wird vom CarbageCollector wieder freigeben. "roundCount" ist aber global definiert und besitzt nicht mehr den Initialwert 0, sondern nunmehr 1.Wie bereits im lua Einstieg erwähnt werden Strings nicht durch das Pluszeichen, sondern durch zwei Punkte konkateniert.
Ein Array als Tabelle und als Dictionary nutzen
Bisher haben wir das Array lediglich als eine einfache Tabelle genutzt. Das wahre Potentional ist aber viel größer. Wir wollen uns folgende Aufgabenstellung geben:In einem Wherigo soll der Spiel eine Quizrunde durchlaufen. Dabei gibt es pro Runde 3 Fragen, mit jeweils 3 möglichen Antworten von denen natürlich nur jeweils eine richtig ist.
Abb. 2 zeigt uns eine solche Datenstruktur. In Zeile 13 sehen wir, dass der Index nicht automatisch eine Zahl sein muss, sondern auch ein String sein kann.
game["round"]
oder auch kurz game.round
liefert den Wert 1.
Es geht knifflig weiter mit Zeile 14. Dort definieren wir als Wert für Index 1 ein Subarray, dem wir für den Key
game[1].trial
den Wert 1 zuordnen. Wir haben somit den ersten Versuch in Runde Eins
vorbereitet.
Um diesen mit Werten zu füllen wird in den nächsten beiden Zeilen (Zeilenumbruch dient nur der
Übersichtlichkeit) der Inhalt von game[1][1]
mit der Frage question
, den
möglichen Antworten answers
und der richtigen Antwortnummer correctAnswerNr
aufgefüllt.
Zeile 18 und 19 definieren dieses Trippel für die zweite Frage und
game[1][3]
dementsprechend
die dritte Frage der ersten Runde.
Wie man an Zeile 23 erkennen kann, muss man nicht den Umweg über
game[2][1]
,
game[2][2]
, game[2][3]
gehen sondern kann die drei Fragentrippel auch als "Einzeiler"
definieren. (Damit es lesbar bleibt, hab ich Fragen und Antworten nicht ganz ausformuliert )
Den Zugriff vereinfachen
Da der Zugriff auf die Datenstruktur nicht mehr der 08/15 MethodemyArray[2]
entspricht, schreiben
wir uns ein paar Helferlein:
Wir beginnen mit dem Abruf der aktuellen Frage: Zuerst fragen wir die Rundennummer ab, sobald wir diese haben
fragen wir nach der Nummer des aktuellen Versuches. Somit haben wir die beiden Indizes für den Zugriff auf
das Fragentrippel, von dem wir die question auswählen und zurückgeben. Der Rückgabewert ist hierbei ein String.
Praktischerweise verwaltet die Datenstruktur selbstständig welche Runde und welcher Versuch gerade akutell ist, sodass wir im späteren Programmablauf nicht mit lokalen Variablen diese Werte merken und aktualisieren müssen.
Die Abfrage der möglichen Antworten funktioniert auf die gleiche Weise. An Zeile 37 sieht man, dass man den Wert nicht erst zwischenspeichern muss, sondern ihn direkt zurückgeben kann.
Noch weniger Code ist bei der Ermittlung der korrekten Antwortnummer verwendet. Man sieht aber auch, dass sich der Code hart an der Grenze der Lesbarkeit befindet bzw. diesen Bereich eigentlich schon verlassen hat. Wir wollen den Vorteil, dass die Datenstruktur ihre "Pointer" Runde und Versuch selbst pflegt, weiter ausbauen, indem wir Getter für den Zugriff auf die aktuelle Round und Trail erstellen.
Während
getRoundNr()
noch sehr unspektakulär game.round
zurückliefert, wird es bei
getRoundArray()
, getTrialNr()
und getTrialArray()
schon komplizierter,
zumal sich die Getter auch gegenseitig aufrufen.
Den Vorteil dieses Verfahrens sehen wir in Zeile 45. Für das Ermittlung der korrekten Antwort brauchen wir nur
getTrialArray()
aufzurufen, um das Fragentrippel zu erhalten.
Es wäre sogar noch eine weiter Optimierung möglich indem man getAnswers()[getCorrectAnswertNr]
verwendet.
Bleibt noch eine Frage zu klären: Wie erreiche ich die nächste Runde, bzw. den nächsten Versuch?
Auch hier soll im laufenden Programmcode nicht auf die Variablen
round
und trial
zugegriffen werden, sondern abstrahiert durch zwei increment (erhöhe) Methoden. incrementRound()
und incrementTrial()
zeigen uns wie es geht.
Die Logik der Datenstruktur ist erledigt, jetzt wollen wir sie bedienen. Der Dialgog in Abb. 5 zeigt uns wie
einfach dies geschehen kann. Dieser Dialog ist natürlich nur zu Testzwecken sinnvoll.
Im Spiel wäre ein MultipeChoice Dialog geeigneter.
Während
getQuestion()
uns die Frage "Wo wurde der erste deutsche Cache
gelegt ?" liefert, liefert getAnswers(1)
den String "Hamburg" zurück.getAnswer(2)
liefert genauso wie getCorrectAnswer()
"Berlin", da getCorrectAnswerNr()
als Ergebnis 2 zurück gibt.
updateInput()
auf, um Frage und potentielle Antworten im Input MyInput
auf erste Runde, erster Versuch zu setzen. Anschließend starten wir den Input. Wie simple updateInput()
implementier ist, werden wir weiter unten sehen.
Nachdem der Spieler eine Lösung der MultipleChoice Frage ausgewählt hat, kann die Antwort mit getCorrectAnswer()
verglichen werden.
Ist die Lösung korrekt, wird die Frage der nächsten Runde vorgelegt, ansonsten die des nächsten Versuchs. Dabei fungieren prepareNextRound()
und
prepareNextTrial()
wieder als Hilfsmethoden um Frage und Antworten anzupassen.
Natürlich sollte man noch ein wenig Output in Form von MessageBoxen
an den Player geben, die fallen der Übersichtlichkeit halber hier recht kurz aus. Man muss übrigens nicht im If und im Else Zweig eine MessageBox plazieren.
Alternativ könnte
man in jedem Zweig in der Lua User code
Box eine Variable message definieren, die man vor dem Input in einer MessageBox über Lua user
expression
als Outputwert wieder aufruft.
prepareNextRound()
und prepareNextTrial()
eigentlich? Die Frage ist sehr einfach zu beantworten
Beim Vorbereiten der nächsten Runde, wird der Rundezähler erhöht. Mit objMyInput.Text
wird die Frage und mit objMyInput.Choices
die möglichen Antworten im Input überschrieben.
Und schon kann die nächsten Frage vorgelegt werden
Persistenz
Ein Problem bleibt aber noch übrig: Wie werden meine Variablen persistiert und überleben ein Abspeichern und Wiederherstellen (Laden) des Cartrigdes. Es werden nämlich nicht automatisch alle Variablen gespeichert. Die von Uriwgo angelegten Variablen werden in einem Feld namensZVariables
gespeichert.
Variablen, die man im lua Code anlegt werden natürlich nicht automatisch in dieses Feld aufgenommen. Möchte man also die eigenen Objekte sichern -für unser
Array game sehr empfehlenswert-, so gibt es verschiedene Möglichkeiten:
- Man legt eine Variable namens game in der graphischen Oberfläche von Urwigo an. Entweder setzt man den Identifier ebenfalls auf game, oder man spricht sie mit objGame an
- Man legt eine Variable namens storage in der graphischen Oberfläche von Urwigo an. Im OnSave Block kann man dann storage als Array umdeklarieren und alle eigenen Variablen dort einfügen. ! In OnRestore muss das Array wieder aufgedröselt werden
- Man fügt beim Anlegen der Variable dieselbige dem Feld
ZVariables
hinzu (Zeile 2 in Abb. 10). Dies kann in einer Zeile geschehen und man muss sich nicht mehr um die Serialisierung kümmern. Dabei ist zu beachten, dass das FeldZVariables
an der CartridgevariablenobjDatenstrukturen
(obj + Cartridgename) hängt.
Keine Kommentare:
Kommentar veröffentlichen