Sonntag, 29. April 2012

Datenstrukturen in lua

Datenstrukturen

Voraussetzung: "Einstieg in Lua“

Abbildung 1: Variablen können lokal oder global definiert werden, es gibt Zahlvariablen, Strings, Booleanwerte und Arrays

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.

Abbildung 2: Definition eines Arrays als Tabelle und Dictionary

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 )

Abbildung 3: Hilfsmethoden für den Zugriff

Den Zugriff vereinfachen

Da der Zugriff auf die Datenstruktur nicht mehr der 08/15 Methode myArray[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.

Abbildung 4: Der Runden- und Versuchszugriff wird durch weitere Getter abstrahiert.
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.

Abbildung 5: Die Bedienung unserer game Datenstruktur ist recht einfach.
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.

Abbildung 6: Vor dem Aufruf des Inputs müssen dessen Fragen und MultipleChoice Möglichkeiten aktualisiert werden
Diese Abfragen sind nützlich um einen Input damit aufzubauen. Beim Start des Spiels (alternativ beim Erreichen der ersten Fragezone) rufen wir 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.

Abbildung 7: Im Dialog MyInput wird die Antwort verglichen und Rounde / Versuch erhöht.
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.

Abbildung 8: Der Dialog wird ausgebaut
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.

Abbildung 9: Während objMyInput.Text Zugriff auf die Frage bietet, können die möglichen Antworten mit objMyInput.Choices überschrieben werden
Wie funktionieren 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 namens ZVariables gespeichert.

Abbildung 10: Die einfachste Art eigene Variablen zu persistieren ist sie an ZVariables anzuhängen
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 Feld ZVariables an der Cartridgevariablen objDatenstrukturen (obj + Cartridgename) hängt.

Montag, 16. April 2012

Fortschrittskurs - Dynamische Programmierung

Fortschrittskurs - Dynamische Programmierung

Voraussetzung: "Einsteigerkurs“
Im Anfängerkurs haben wir gelernt, wie man ein statisches Wherigo-Projekt erstellt.
Im Fortschrittskurs wollen wir nun einen Schritt weiter gehen und lernen wie man diese beliebte Cacheart durch dynamischen Programmablauf noch interessanter machen kann.
Dynamisch bedeutet hier, dass der Spieler nicht einer festgelegten Route (Parkplatz – Kirche – Rathaus – Marktplatz) folgen muss, sondern während des Spiels selbst entscheidet, welche Station er als nächstes angehen möchte.
Außerdem werden wir keine Stadtrundführung programmieren, sondern eine Mini-Version des Brettspieles Scotland Yard, bei dem der Spieler den mysteriösen Mister X jagen muss.


Abbildung 1: Mini-Streckenfahrplan mit Taxi-, Bus- und U-Bahn-Verbindungen. Im fertigen Wherigo sind natürlich mehr Stationen vorhanden. Damit es nicht zu kompliziert wird begnügen wir uns mit vier Stationen

Konzept

Beginnen wir wieder mit einem Konzept:
Das Spielfeld besteht aus 4 Zonen - nachfolgend als Stationen bezeichnet -, die jeweils einen Taxistand darstellen. Zusätzlich zu der Taxiverbindung kann man zwischen Station 1 und Station 3 mit der U-Bahn und zwischen Station 2 und Station 3 mit dem Bus fahren.
Als Spieler steht man beim Erreichen einer Station vor zwei Entscheidungen: Mit welchem Verkehrsmittel möchte ich mich fortbewegen und wohin möchte ich fahren. Ihr seht schon, dass bereits an dieser Stelle das Spiel dynamisch verläuft.
Entgegen des Brettspieles gibt es Mister X nur virtuell, das einzige was wir im Programm für ihn benötigen ist eine Variable, die seine Position speichert. Mister X ist in der Regel unsichtbar, es wird nur offenbart, welches Verkehrsmittel er benutzt. Wir werden dies mit einer Message darstellen, die folgendermaßen aussehen könnte: “Mister X fährt Taxi“. Bei jedem dritten Zug zeigt er sich und teilt uns mit wo er ist, bzw. besser gesagt wo er war, bevor er das nächste Verkehrsmittel benutzt. Damit es nicht zu lange dauert, zeigt sich Mister X ab der zweiten Runde. Eine Offenbarung ist also mit Runde 2, 5, 8, 11, 14 … fällig.

Abbildung 2: Der Spielablauf verläuft rundenbasiert. Nachdem man eine Station erreicht hat, zeigt sich Mister X (ab und zu) und fährt sofort weiter. Wir entscheiden uns für ein Transportmittel und eine Station, laufen diese an und die nächste Runde beginnt.
Der Ablauf soll also folgendermaßen aussehen (siehe auch Abb.2):
  • Der Spieler erreicht eine Station.
  • Wenn sich Mister X ebenfalls in dieser Station befindet, ist das Spiel gewonnen und per Message teil Mister X mit wo sich der Final befindet.
  • Ansonsten ist Mister X an der Reihe und fährt zur nächsten Station
  • Abhängig von der Station wählt der Spieler zwischen Taxistand, Bushaltestelle oder U-Bahnstation
  • Abhängig von Station und Verkehrsmittel wählt der Spieler die nächste Station aus.
  • Der Spieler läuft, Verzeihung fährt zur nächsten Station
  • Der Spieler erreicht die nächste Station. Da wir uns in Runde 14 befindet teilt uns Mister X mit wo er sich befindet.
Um dieses Szenario abzubilden, benötigten wir folgende Bausteine:
  • 5 Zonen, „Station 1“ – „Station 4“ sowie „Final“
  • 4 Gegenstände Taxistand, Bushaltestelle und U-Bahnstation sowie Spoiler
  • pro Gegenstand ein Kommando („Taxi fahren“, „Bus fahren“ bzw. „U-Bahn fahren“)
  • Variablen für Position Mister-X „PosX“, Position Spieler „PosPlayer“ und Rundenzähler „RoundCount“
  • Bilder für Taxi, Bus und U-Bahn, Mister X, Stationen, Final und Spoiler

Abbildung 3: Beim Annähern an die Station wird der Programmablauf an eine Funktion weitergeleitet.

Zoneneintritt

Zu Beginn richtigen wir unser Augenmerk auf die "On proximity" Funktion der Zonen. Im Einsteiger-Tutorial haben wir unsere Zonen mit 4 Punkten abgesteckt und "On enter" implementiert. Bei dieser Vorgehensweise kommt es immer wieder zu Problemen im Spielablauf indem der Wherigo-Player entweder eine Ecke der Zone anvisiert um seinen Richtungspfeil auszurichten, oder der Wherigo-Builder aktualisiert den OriginalPoint der Zone, an dem sich viele Player orientieren, nicht wenn die Zone nachträglich verschoben wird. Wenn man die Zone nicht genau abstecken muss und auch gut mit einem Kreis leben kann, so ist "On proximity" mit dreimal Kreismittelpunkt als Koordinaten eine gute Alternative. Über "In proximity" lässt sich der Radius des Kreises bestimmen. Der Vorteil der Methode liegt auf der Hand: Der Spieler navigiert genau auf den Zonenmittelpunkt und als Cacheowner reicht es einen Punkt zu messen. Denn meistens möchte man den Spieler an einem bestimmten Punkt führen. Anstatt sich „On enter“ zu bedienen wird der Programmablauf beim Erreichen einer Station in „On proximity“ definiert. Ob ihr euren Wherigo mit Rechteck- oder Kreiszonen ausstattet, ist letztendlich euch überlassen und kann auch von der Situation vor Ort abhängen.

Funktionen

Da wir bei jeder Station beim Eintritt, bzw. nun bei Annäherung die gleichen Arbeitsschritte durchführen und wir diesen Ablauf nicht jedes Mal neu programmieren wollen, bedienen wir uns der Funktionen. Wie in Abb. 3 dargestellt, setzen wir beim Erreichen der Zone die aktuelle Position des Spielers und rufen anschließend die Funktion „enterZone“ auf.


Abbildung 4: Funktion „enterZone“ – Während die eigene Position und das Transportmittel von Mister X immer ausgegeben werden, wird seine Position nur jeden dritten Zug offenbart.
Die Funktion „enterZone“ bildet das Kernstück unserer Programmierung. Mit ihrer Hilfe wird ermittelt ob wir Mister X gefangen haben oder ob das Spiel weitergeht. Weiterhin werden hier die verfügbaren Verkehrsmittel freigeschaltet, Mister X zeigt sich (ab und zu) und plant dann seinen nächsten Zug.

Mister X wird sichtbar

Sobald ein Spieler eine Station erreicht erhöhen wir den Rundenzähler. Diesen benötigten wir um bei jeder dritten Spielrunde die Position von Mister X anzuzeigen. Da der Text der Message von dieser Bedingung abhängig ist, bauen wir erst den Text zusammen indem wir verschieden Textbausteine aneinander reihen und in der Variable „Message“ speichern. Im unteren Teil von Abb. 4 sehen wir die Abfrage der Rundennummer und die bedingte Erweiterung der Variable „Message“. Die beiden langen Sätze sind mit einem Zeilenumbruch versehen um die Lesbarkeit während des Spieles zu verbessern.

Abbildung 5: Funktion „enterZone“ – Spielende. Wenn die Position von Mister X mit der unseren übereinstimmt, haben wir ihn gefangen und können ihn verhören.
Darunter erkennen wir im Ansatz den Message-Baustein (fortgesetzt in Abb. 5), in dessen "On clicked" Bereich zunächst alle Station deaktiviert werden und danach überprüft wird, ob Mister X die gleiche Position hat wir wir selbst und sich damit in unser Zone befindet. Sollte dem so sein, ist Mister X geschlagen und stellt sich einem Verhör zur Verfügung. Dazu später mehr.

Mister X ist am Zug

Wenn wir Mister X nicht gefangen haben, ist nun die Stelle erreicht wo er seinen Zug ausführt. Dazu berechnet er abhängig von der Position des Spielers die nächste freie Station.
Mister X erhält also künstliche Intelligenz, auch wenn es sich hier nur um ein Minimum handelt. In den demnächst folgenden lua Tutorials werden wir erfahren wie wir mit Hilfe von direkter lua Programmierung und einem Path-Finding-Algorithmus ihn etwas schlauer machen, für diesen Kurs reicht uns folgender Ansatz :
  • Bestimme abhängig von seiner aktuellen Position eine durch Verkehrsmittel zu erreichende Station für Mister X
  • Befindet sich der Spieler auf der potentiellen Station?
    • Wenn nein, wähle die potentielle Station als aktuelle Station für Mister X
    • Wenn ja, wähle eine alternative Station
In beiden Fällen wird die Textvariable „Transport“ gesetzt, mit deren Hilfe der Text „Mister X fährt Taxi | Bus | U-Bahn“ zusammengesetzt wird. Anschließend wird in einer Message der gesamte Text ausgegeben.

Abbildung 6: Funktion „enterZone“ – Abhängig von unserem Standort sowie seiner eigenen Position plant Mister X seinen nächsten Zug.

Abbildung 7: Funktion „enterZone“ - Taxi fahren darf man an jeder Station. Welche Alternative zur Fortbewegung zusätzlich zur Verfügung steht, hängt von der Station ab.

U-Bahn, Bus oder doch nur Taxi ?

Während der Taxistand in jedem Fall aktiviert wird (Taxi fahren darf der Spieler von jeder Station aus) verzweigen wir für jeden möglichen Aufenthaltsort des Spielers um gegebenenfalls die Bushaltestelle oder die U-Bahnstation freizuschalten. Wie man in Abb. 7 erkennt, gibt es keine Fallunterscheidung für Station 4. Dies liegt daran, dass dort außer dem bereits aktivierten Taxi kein Transportmittel zur Verfügung steht.
In den ersten drei Runden geben wir noch den Hinweis aus, was der Spieler nun unternehmen soll. Nach den drei Runden wird er (hoffentlich) verstandenen haben wie der Hase läuft und wir ersparen ihm die nun lästige Nachricht.

Abbildung 8: Abhängig von der Station, in der wir einsteigen, dürfen wir mit dem Taxi unterschiedliche Station anfahren

Türen zu und los – Aber wohin dürfen wir mit Taxi & Co fahren?

Im nächsten Schritt schauen wir uns die Taxifahrt ein wenig genauer an, soll heißen wir implementieren das Kommando „Taxi fahren“ für den Gegenstand „Taxistand“.
In Abb. 8 erkennen wir, dass wir nichts weiter machen müssen als die Stationen aktiv zu schalten, die von der aktuellen Station des Spielers aus mit dem Taxi erreichbar sind.
Da jede Station einen Taxistand beinhaltet, müssen wir auch für jede eine Fallunterscheidung einbauen.

Abbildung 9: Bus fahren gestaltet sich recht übersichtlich, da lediglich zwei Stationen mit Bushaltestellen ausgestattet sind.
Dies ist beim Busfahren schon ein wenig übersichtlicher. Ein Blick auf den Spielplan zeigt, dass lediglich Station 2 und 3 über eine Bushaltestelle verfügen. Wie wir Abb. 9 erkennen, müssen wir auch nur diese beiden Station in die Fallunterscheidung einbeziehen. Für die U-Bahnfahrt sieht es ähnlich übersichtlich aus.

Abbildung 10: Nachdem wir uns für eine Art des Fortkommens entschieden haben, werden alle Transportmittel deaktiviert

Sammelfunktion „afterTransportChoice“

Bleibt nur noch zu klären, was es mit der Funktion „afterTransportChoice“ auf sich hat. In dieser Funktion werden alle Aktivitäten zusammengefasst, die nach der Wahl des Transportmittels ausgeführt werden müssen. In unserem Fall setzen wir die Sichtbarkeit von Taxistand, Bushaltestelle und U-Bahnstation auf "False", denn nachdem der Spieler sich für ein Transportmittel entschieden hat, soll er dies nutzen und zur nächsten Station laufen. Eine erneute Wahl macht also wenig Sinn. Wer beim Blick auf Abb. 10 denkt „Wozu soll ich diese drei mickrigen Anweisungen in eine extra Funktion auslagern?“, dem sei gesagt, dass es nicht nur darum geht sich das Wiederholen der drei Anweisungen zu sparen. Nein, spätestens wenn wir uns in Zukunft dazu entscheiden zusätzlich einen Flughafen oder eine Bootsanlegestelle anzubieten oder einen Text „Gute Wahl. Viel Spaß bei der Fahrt“ auszugeben, spätestens dann zeigt sich ein entscheidender Vorteil der Auslagerung des Codeteils in eine Funktion: Die Wartbarkeit. Es gibt nichts schlimmeres bei der Programmierung als Änderungen oder Fehlerbehebungen an gefühlten tausend Stellen durchführen zu müssen.

Abbildung 11: Das Verhör mit Mister X endet ziemlich schnell. Wer möchte, kann es zu einem Dialog ausbauen, bei dem Mister X überführt werden muss.

Mister X in Gefangenschaft

Abhängig von Größe der Spielfeldes und Spielablauf haben wir Mister X nach mehr oder weniger Zügen gefangen. Damit haben wir aber noch keine Dose in der Hand. Um den Bezug vom Spiel zum Final zu realisieren bauen wir ein Verhör mit Mister X ein, der uns somit die Finalzone freischaltet. Dazu erstellen wir ein Kommando „Mister X verhören“, deren Implementierung aus Message und zwei Set-Baustein besteht, mit deren Hilfe wir die Zone „Final“ sichtbar und aktiv schalten.

Damit ist der dynamische Ablauf unseres Wherigos komplett und wir können uns um weitere Details kümmern.

Abbildung 12: Lediglich beim ersten Erreichen der Station wird die Frage nach der Hausnummer vorgelegt.

Simulationsschutz

Als erstes betrachten wir den Aspekt des Simulationsschutzes. Über die Frage, ob man dem Spieler freistellt den schön gemachten Wherigo am Simulator auf der Couch durchzuspielen oder ob man ihn zwingt mehr Spielspaß vor Ort zu erleben, kann man ewig diskutieren und die Antwort hängt unter anderem vom Wherigo und der Örtlichkeit ab. Jetzt mag sich der ein oder andere denken „Moment, Urwigo bietet doch dieses nette Häkchen im Hauptfenster an“, der Praxistest zeigt aber einige Probleme des vorgefertigten Simulationsschutzes auf. Dieser verlässt sich auf die Messungenauigkeit der Geräte und meint einen echten von einem simulierten Programmablauf daran zu erkennen, dass sich innerhalb bestimmter Zeiten Höhen- und Positionsangaben ändern müssen. Nun lassen sich auf der einen Seite diese Schwankungen durch weiterentwickelte Emulatoren vortäuschen, auf der anderen Seite, und dies ist der entscheidende Part, kommt es bei bestimmten Geräten zu der Situation, dass „ehrliche“ Cacher auf der Wiese stehen und zu Unrecht eine Fehlermeldung à la „Du sollst nicht simulieren“ erhalten. Die einzig wirksame Maßnahmen den Couch-Potato in die Natur zu kriegen, bleibt die Abfragen von Ortsmerkmalen wie Hausnummern, Farben, Anzahl von Schrauben usw. Dies ist bisher auch noch nichts neues. Jeder Multi und die meisten statischen Wherigos arbeiten mit diesen Ortsmerkmalen.

Hausnummernsuche? Bitte nur beim ersten Stationsbesuch

Bei unserem dynamischen Spiel haben wir allerdings das Problem, dass wir Stationen mehr als einmal erreichen und jedes Mal die Hausnummer „85a“ einzugeben führt nicht zwangsweise zur Attraktivitätssteigerung. Wir wollen dieses Problem mit einer Booleanvariablen lösen. Dies Variable kann lediglich zwei Werte annehmen: "True" oder "False". Abb. 12 zeigt die Verwendung für die erste Frage. Nach dem Erreichen der Station wird abgefragt, ob die erste Frage (noch) aktiv ist. Wenn dem so ist –wir erreichen diese Station zum ersten Mal– wird die Frage „Question1“ vorgelegt. Wenn wir die Station das nächste Mal anfahren wird die Funktion „enterZone“ sofort ausgeführt.

Abbildung 13: Wenn die Antwort korrekt ist, wird das Vorlegen der Frage beim nächsten Erreichen der Station deaktiviert.
Wie aber erreichen wir nun dass die Variable „Question1Active“ beim nächsten Mal auf "False" steht. Die Antwort zeigt uns Abb. 13. Die Rekursion bei falscher Antwort kennen wir ja bereits aus dem Anfängerkurs. Neu ist nun, dass wir die Booleanvariable mittels Set-Baustein auf "False" setzen und anschließend unsere bekannte Funktion für das Erreichen einer Zone aufrufen. Hier zeigt es sich wieder vorteilhaft, dass wir diesen Ablauf in eine Funktion ausgelagert haben.

Abbildung 14: Spiel beenden. Anstatt mühsam mehrere Message-Bausteine über „On clicked“ zu synchronisieren nutzen wir den komfortablen Weg des Dialogs. Der „Completition code“ wird von wherigo.com erzeugt wenn das Cartrigde vor dem Download online kompiliert wird.

Spoiler, Complete-Flag und Unlock-Code beim Final

Beim Annähern an den Final verwenden wir die "On proximity" Logik aus dem Einsteigerkurs. Zusätzlich zum Aktivieren des Spoilers und dem Complete-Flag wollen wir aber noch mehr durchführen. Als erstes neues Element sehen wir den Dialog. Mit dessen Hilfen lassen sich mehrere Bildschirmausgaben hintereinander schalten, ohne dass wir uns selbst um die Synchronisation via „On clicked“ kümmern müssen, wie wir das bei mehreren Message Boxen hätten tun müssen. Beim Dialog lassen sich einfach mehrere Dialog Entries untereinander platzieren, die nacheinander aufgerufen werden, sobald ein Dialog Entry mit „OK“ bestätigt wird. Im mittleren Entry sehen wir, wie wir den Unlock-Code ausgeben können, damit das Cartridge auch ohne gws Datei auf wherigo.com als gefunden markiert werden kann.

Abbildung 15: Durch den breiten Set-Baustein braucht die gesamte Anweisung viel Platz

Etwas mehr Übersichtlichkeit bitte

Sobald man die erste komplexe Idee implementiert wird man schnell feststellen, dass der Bildschirm gar nicht groß genug sein kann um alles darstellen zu können. Besonders bei verketteten if/else Bedingungen stößt man schnell an die Grenze der Lesbarkeit. Eine Form der Abhilfe ist die Zoomfunktion, die man benutzen kann indem man bei gedrückter Strg-Taste mit dem Mausrad vor und zurück scrollt. Der angezeigte Ausschnitt des Flussdiagramms wird somit heran bzw. heraus gezoomt, wobei zu viel herauszoomen dazu führt, dass man die Schrift nicht mehr lesen kann. Eine Alternative zum Zoomen ist das Ausblenden von Bausteinen, die gerade nicht bearbeitet werden indem man auf das schwarze Dreieck neben dem Bausteinnamen klickt (kleiner Rahmen in Abb. 15).

Abbildung 16: Nachdem wir den Set-Baustein minimiert haben erscheint die Anweisung übersichtlicher
Somit bleiben lediglich die Bausteine sichtbar, die bearbeitet werden sollen bzw. zur Bearbeitung benötigt werden. Ein weiterer Klick auf das Dreieck blendet den Baustein dann wieder ein.

Wo kann ich diesen Wherigo spielen?

Wer Scotland Yard nicht nur in der Mini-Version, sondern als kompletten Wherigo erleben möchte, sollte nach Scotland Yard in Heide Ausschau halten. Hier wurde die Intelligenz von Mister X verbessert, sodass er zu Beginn recht schwierig zu fangen ist. Wie man so etwas direkt in lua programmiert und wie man Mister X im Laufe des Spieles dümmer werden lässt, könnt ihr in der Kategorie Wherigo für Experten herausfinden.

Der Einstieg in Lua

Einstieg in Lua

Voraussetzung: "Einsteigerkurs“

Der Urwigo builder ist ein recht nettes Tool, aber eines Tages entstehen durch immer neue „dümmere“ Ideen Anforderungen, die man mit einem reinen Kicki-Bunti-Baustein-Geschubse nicht mehr abbilden kann.
Spätestens jetzt wird es Zeit die reine Baustein-Lösung zu verlassen und seinen Horizont Richtung Programmierung zu lenken. Das soll nicht heißen, dass wir auf Urwigo und die graphischen Bausteine ganz verzichten. Nein, wir erweitern unser Projekt lediglich um neue Funktionen. (Wobei ich aus eigener Erfahrung sagen kann, je mehr man lua kennt, desto weniger klickt man die Bausteine zusammen. Aber das kommt von selbst nach und nach und muss nicht auf einen Schlag passieren).

Was ist lua?

Lua ist eine Scriptsprache, die sich dadurch auszeichnet, dass sie wenig Hardware und Speicher benötigt um einfache Abläufe zu realisieren. Klingt also ideal für „Steinzeitgeräte“ wie dem Garmin Oregon (Sorry, aber was Prozessorleistung betrifft, merkt man schon dass das Gerät einige Jahre auf dem Markt ist).
Wenn man sich den Urwigo builder genauer anschaut und eine erstellte gwz Datei entpackt (WinZip, WinRar oder 7zip) so erkennt man, dass die gesamte Programmierung in einer _cartridge.lua Datei steckt, die Urwigo aus den Bausteinen baut. Beim Erlernen der verschiedenen lua Befehl kann es hilfreich sein, einen Baustein ins Projekt einzufügen, das Projekt als gwz abzuspeichern, zu entpacken und sich dann den resultierenden lua Code anzuschauen.


Abb. 1: Gerade mal drei Tabellen bilden das Datenmodell der Straßen: Name, Anzahl der Häuser und Miete pro Haus.
Die Funktion calcRent() berechnet daraus die fällige Miete

Aufgabenstellung für unseren Wherigo

Als Beispiel für dieses Tutorial möchte ich einen Teil aus dem Cachopoly Projekt erläutern. Es gibt Straßen, auf die Häuser gebaut werden können und wo abhängig von der Straße pro Haus Miete fällig wird. (der Einfachheit halber gilt Miete = Anzahl Häuser * Miete pro Haus).

Als erstes definieren ein Zahlvariable namens „RentToPay“.
Danach legen wir je ein Feld (Array) an für den Namen der Straße, die Anzahl der Häuser, die es pro Straße gibt sowie für die Miete, die auf der Straße pro Haus fällig wird. (siehe Zeile 1 bis 3 in Abb. 1). Arrays werden mit geschweiften Klammern angegeben. Pro Straße gibt es einen Wert in dem Array, wobei es auf die Position im Array, Index genannt, ankommt.
Straße 1 (! Badstrasse, da keine Umlaute) hat folglich 1 Haus und pro Haus sind 200 Eur fällig.

Nun definieren wir eine Funktion, die für eine Straße -dargestellt durch den Index- die Anzahl der Häuser mit der Miete pro Haus multipliziert und das Ergebnis der Variablen „objRentToPay“ zuordnet. Der Zugriff auf das Array erfolgt über den Index, den man in eckigen Klammern hinter den Arraynamen schreibt. rentPerHouse[3] liefert das dritte Element des Arrays, nämlich 500.

„objRentToPay“ oder „RentToPay“ ?

Wenn wir uns den rechten Teil von Abb. 1 genauer anschauen, sehen wir, dass bei der Variablen „RentToPay“ ein Feld Identifier vorhanden ist. Dies ist quasi die Id der Variablen in lua. Trägt man hier nichts ein und lässt das Feld auf „Automatic“ so bilden lua die Id indem es „obj“ vor den Namen schreibt. Aus dem Namen „RentToPay“ wird die Id„objRentToPay“. Möchte man dies nicht, so kann man den Identifier selbst vergeben, z.B indem man den Namen „RentToPay“ nochmals ins Feld Identifier schreibt.

Abb2. : Im Flussdiagramm kann lua code mit den Bausteinen "Lua User code" und "Lua user expression" eingebaut werden

Die eigene Funktion aufrufen

Unsere (vereinfachten) Straßen sind durch die drei Arrays definiert und die Funktion für die Mietberechnung ist auch aufgestellt. Wie aber können wir diese Funktion auch nutzen?
Die Lösung zeigt uns Abb. 2. Beim Start des Cartridge verwenden wir einen „Lua User code“ Baustein um die Funktion mit der zweiten Straße aufzurufen. Die Variable „RentToPay“ besitzt nach dem Aufruf den Wert. 3 * 300 = 900. In der anschließenden MessageBox wird diese Variable nun für die Ausgabe mit anderen Textbausteinen konkateniert.
Interessant hierbei ist auch der Baustein „Lua user expression“, der einen direkte Zugriff auf unser streetNames Array ermöglicht und eine Zeichenkette zurückliefert. Im Gegensatz zu „Lua User code“ wird hier also etwas zurückgegeben.
Die Ausgabe im Gerät würde für diesen Fall lauten: „Du erreichst die Strasse E-Werk. Zahle 900 EUR“.

Geben und Nehmen

Apropos Rückgabe, in der zweiten MessageBox wird die Funktion „buildPaymentMessage“ aufgerufen, die dem „Lua user expression“ Baustein einen Wert (Zahl oder Zeichenkette zurückliefern muss. Dazu habe ich den lua Code in Abb. 3 um die Funktion „ buildPaymentMessage“ erweitert, der die gleichen Textelemente zusammenbaut wie unser Concatenate Baustein in Abb. 2. Auffällig ist, dass Zeichenketten -genannt Strings- nicht wie in anderen Programmiersprachen mit + sondern mit .. verbunden werden. Wie man sieht, ist der Zugriff auf das Array streetNames schneller als im Concatenate Baustein. Nachdem der String zusammenbaut ist, wird er durch den return Befehl dahin zurückgegeben wo die Funktion aufgerufen wird. Auch die Funktion „calcRent“ wurde um einen return erweitert, damit wir sie beim Zusammenbauen der PaymentMessage verwenden können.

Abb3. : Während auf der linken Seite buildPaymentMessage ein Beispiel die Konkatenation eines Strings sowie der Rückgabe eines Wertes dargestellt ist, zeigt der rechte Teil, wie man die Id eines lua Objektes anpassen kann.
Das urwigo Projekt zu diesem Tutorial als Download"

Donnerstag, 12. April 2012

Edit lua code in an external editor

Edit lua code in an external editor

This is the english version of the tutorial. It is also available in german
Dies ist die englische Version des Tutorials. Es ist ebenso auf deutsch verfügbar.

If you are familiar with programming wherigo cartridge in lua, you know the problem of too much code lines. Your program become confusing. The syntax highlighting of urwigo builder lua is a nice feature, but it is insufficient to handle code with a lot of lines.
During my programming of Cachopoly I have the problem to handle more than 1000 lines and so my main activity was to scroll to the line needed for debugging and testing.

After mailing with the developers of urwigo, they updates their editor. Know it is possible to link an external lua file which can be use in the urwigo project.
I decided to use the IntellijIDEA editor witch I extend with the lua plugin sylvanaar.
In Picture 1 you can see a simple "HelloWorld" Example, written with IDEA
Picture 1: Write lua code in IntellijIDEA

Top left you can see the storage location of the lua file. I decided to create a sub folder called lua which optimise the structure. But this decision is optional.
At the right side you can see the lua code. Beside syntax highlighting you can use features like parameter and function name competition and real-time syntax error and warning marking.
Bottom left we have the 'structure' element where I can see all the functions defined in my lua code. With the 'Autoscroll to source' feature you can jump to line 14 in code by clicking on the function 'helloWithName' in the structure frame. A nice feature if your code contains several hundred code lines.

It is not mandatory to use IntellijIDEA. You can edit the lua files with your favorite editor, but their are not many alternatives. If you found one, feel free to send me an email or make a comment for this post.
Pciture 2: all lua files in sub folder 'lua' are embedded
In which way can I embed the external files in my urwigo project? It seems to be tricky, but it will be very simple if you have see it once. All you have to do are two simple steps:
First you have to activate the checkbox "Inline Lua 'require'" and set the path to the lua files in the main screen of your urwigo project. The path 'lua\?.lua' contains all files in sub folder 'lua' (relative to your project folder) which ends with '.lua'.
The usage of a sub folder is optional. I think you will optimise your structure by dividing the lua files from your project files and picture.

Pay attention to the wildcard, that is not the asterisk '*' but the question mark '?'.
Picture 3: Embed the lua file with 'require'
The second step is very simple. All you have to do is write a require in your 'lua user directives' or 'lua user functions' of your project. The name of the embedded model must be the filename of the lua file
require "hello" must be equals to hello.lua.
Picture 4: Use the functions like you be used to
You can use the functions defined in the external file just as they were defined local in 'lua user directives' or 'lua user functions').

Dienstag, 10. April 2012

Lua in externen Editor bearbeiten

Lua in externen Editor bearbeiten

Dies ist die deutsche Version des Tutorials. Es ist ebenso auf englisch verfügbar.
This is the german version of the tutorial. It is also available in english

Voraussetzung: "Einstieg in Lua“

Wer einmal komplexere WIG-Projekte mit lua programmiert hat, wird recht schnell feststellen, dass das Syntaxhighlighting von Urwigo zwar recht nett, aber beileibe nicht ausreichend ist. Es fehlen Editorfunktionen wie Ersetzen von Befehlen, automatisches Schließen von Schachtelelementen, eine Übersicht der Variablen und Funktionen mit Möglichkeit direkt zur richtigen Zeile im Code springen zu können.
So wurde die Implementierung von Cachopoly bei über tausend Zeilen lua Code schnell zur nervigen Scrollpartie um beim Debuggen und Testen die richtigen Codestellen zu finden.

Nach längerer Diskusion und Austausch mit den Urwigo Entwicklern haben diese nun Abhilfe geschaffen und die Möglichkeit geboten den lua Code in eigene Dateien auszulagern, die man dann in einem externen Editor bearbeiten kann.
Ich habe mir dabei für den IntellijIDEA Editor entschieden, dem man mit sylvanaar ein brauchbares Plugin verpassen kann um lua Code zu implementieren.
Abbildung 1 zeigt den Editor mit einem "HelloWorld" Beispiel.
Abbildung 1: Lua in Intellij Idea bearbeiten

Im linken oberen Teil ist der Speicherort der lua Datei festgelegt. Der Übersichtlichkeit habe ich im Urwigo Ordner 'Test' einen Unterordner 'lua' angelegt, in den ich meine lua Dateien ablege. Soweit es sich dabei nur um eine einzige Datei handelt, könnte man sich den Aufwand des Unterordners auch sparen.
Im rechten Teil ist der lua Code zu sehen, den man mit vielen netten Feature wie Syntaxhighlighting, Befehl- und Variablenvervollständigung, Fehler- und Warnanzeigen recht komfortabel editieren kann.
Unten links ist die Funktionsübersicht zu sehen. Hier ist die vermisste Jumpfunktion realisiert, die sehr hilfreich ist, wenn man in über tausend Zeilen Code schnell zur Methode 'helloWithName' springen möchte.

Es muss übrigens nicht zwingend IntellijIDEA sein. Welchen Editor ihr verwendet ist euch überlassen. Viele brauchbare Alternativen gibt es aber nicht. Ich wäre für Feedback an dieser Stelle trotzdem sehr dankbar.
Abbildung 2: Einbinden aller lua Dateien im Unterordner 'lua'
Wie binde ich nun diesen Code in meinem Urwigo Projekt ein ? Dies scheint ein wenig tricky, ist aber sehr einfach, wenn man es einmal gesehen hat und besteht nur aus zwei kleinen Schritten:
Als erstes wählen wir auf der Hauptseite des Urwigo Projektes die Checkbox "Inline Lua 'require'" und setzten den Pfad in dem unsere lua Dateien liegen. Der Pfad 'lua\?.lua' erfasst alle Dateien die im Unterordner 'lua' liegen und die auf '.lua' enden. Zu beachten ist, dass das Jokerzeichen hier das Fragezeichen '?' und nicht wie gewöhnlich der Stern '*' ist.
Abbildung 3: Im directive Fenster müssen die lua Datein eingebunden werden
Der zweite Schritt ist noch einfacher und besteht lediglich aus einem require im 'lua user directives' oder 'lua user functions' Bereich des Projektes. Das dadurch eingebundene Modul muss den gleichen Namen tragen wie unsere lua Datei.
require "hello" muss mit hello.lua übereinstimmen.
Abbildung 4: Der Funktionsaufruf funktioniert wie gewohnt
Der Funktionsaufruf im Urwigo Projekt funktioniert genauso wie beim lokal (in 'lua user directives' bzw in 'lua user functions') definierten Code.