Labels

Posts mit dem Label Wherigo für Experten werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Wherigo für Experten werden angezeigt. Alle Posts anzeigen

Sonntag, 9. November 2014

Nachrichten asynchron anzeigen mit einer Messag-Queue

Voraussetzung: "Der Einstieg in lua“

Abbildung 1: Wenn die Nachricht unpassend ist, wird sie später angezeigt

In diesem Beitrag geht es um asynchrone Nachrichtenverarbeitung. Klingt komplizierter als es ist, deswegen will ich es mal an einem einfachen Beispiel erklären:

Stellt euch vor in eurem Wherigo-Spiel geschieht gerade ein längerer Dialog. Der Spieler muss 3-4 Fragen beantworten auf die er jeweils direkt eine Antwort bekommt.

Dazu habt ihr einen Timer der im Hintergrund einmal pro Minute etwas prüft und gegebenenfalls eine Nachricht anzeigt "Die Löwen sind wieder hungrig und möchten gefüttert werden"
Nicht nur dass die Nachricht Umlaute enthält, nein sie würde auch im aktuellen schwierigen Dialog stören bzw. wichtige Informationen überlagern.

Stellt euch vor, der Spieler erhält gerade die dringende Nachricht "Gehe schnell zum Affengehege" als der Timer anspringt und sofort mit der wenig dringenden Löwennachricht überlagert. Da wüsste der Spieler gar nicht, dass er zuerst du den Affen soll.

Um dass zu verhindern nutze ich eine Mesage-Queue in die ich Nachrichten einstelle (Zeile 174), wenn sie gerade unpassend sind. Wenn sie gerade nicht stören, also hideMessage auf false steht können sie auch direkt angezeigt werden.
Die msgQueue besteht aus einer Tabelle in die mit table.insert ein Text sowie ein Bild eingefügt wird, um später die Nachricht darzustellen.
Zusätzlich hält das Array-Objekt noch einen Pointer, der auf die nächste darzustellende Nachricht zeigt.

Wenn dann die kritische Situation vorbei ist (Der Dialog ist beendet, oder immer wenn der Spieler eine Zone verlässt) wird printMessageFromQueue aufgerufen. Diese Methode prüft ob neue Nachrichten vorhanden sind (dann wenn mehr Nachrichten im Array sind als der Pointer groß ist) und gibt die Nachricht aus auf die der Pointer zeigt.
Nach Drücken von "OK" durch den Spieler wird der Pointer einen weitergesetzt und printMessageFromQueue rekursiv aufgerufen. Solange bis alle Nachricht abgearbeitet, also angezeigt wurden.


Abbildung 2: Während der Dialog mit dem Spieler etwas länger dauert, springt der Timer an und schiebt die Nachricht in die Queue
Abb. 2 zeigt uns die Anwendung im Code. Die Methode doLongDialog soll den komplizierten Frage-Antwort-Dialog simulieren. Bevor der Dialog startet wird hideMessage auf true gesetzt. Ab jetzt gehen alle Nachrichten in die Queue.
Zeile 206 bis 208 zeigen uns den Timerfall. Während der Timer anspringt ist hideMessage positiv und die Nachricht wird eingereiht.

Nachdem der Dialog vorbei ist, wird (!!! Im Callback des letzten Dialogs / MessageBox !!!) hideMessage wieder auf false gesetzt, damit ab jetzt wieder live angezeigt werden kann, da die kritische Situation vorbei ist.

Wenn es in eurem Spiel keinen festdefinierten Zeitpunkt gibt, wann die Nachrichten nachgeholt werden können, könnt ihr mit einem weiteren Timer arbeiten, der alle 60 Sekunden prüft ob hideMessage auf false steht und dann printMessageFromQueue aufruft.


Abbildung 3: Egal ob direkt oder später, die Nachricht erscheint als MessageBox

Dienstag, 10. Juni 2014

Finalkoors und Hint

Voraussetzung: Globaler Code - Teil II

Abbildung 1: Glückwunsch du hast gewonnen und bekommst die Koors auch angezeigt
In diesem Tutorial geht es darum, dem Spieler das Finden des Finals zu erleichtern. Es soll ja vorkommen, dass zwischen der letzten Aufgabe im klasischen "Multi-WIG" und dem Final mehr als 100 Meter und eventuell auch noch 4 x auf dem Wanderweg abbiegen liegen.
Auch bei einem dynamischen WIG wo die Reihenfolge der Aufgabe flexible ist, kann es recht schnell vorkommen, dass der Pfeil der "Final"-Zone nicht ausreicht um dorthin zu navigieren. Das zweite Problem stellt sich wenn dein WIG an mehreren Ort mit verschiedenen GC-Listings läuft und jede Dose nen anderen Hint hat.
Bei diesen Problemen haben sich folgende Schritte bewährt:
1) Gebt die Koors mit aus, damit der Spieler sie abschreiben kann ...

Abbildung 2: An der Zone Final kann man Koors und Hint jederzeit ablesen
... und 2) schreibt die Koors nochmal als Beschreibung der Zone Final rein. Für all die Spieler die es verpeilt haben die Koors beim ersten Mal abzuschreiben weil die Finger wieder schneller als die Augen waren.
Damit der Spoiler fürs Final genau zum richtigen Zeitpunkt bekannt gegeben wird könnt ihr ihn ebenfalls in die Beschreibung der Zone platzieren.
All dies kann man natürlich klassisch mit Urwigo machen. Es spricht auch nichts dagegen....
... es sei denn die Finallocation ändert sich (eine der beiden Stelle wo die Koors angepasst werden müssen vergesst ihr garantiert) oder die Geschichte mit verschiedenen Dosen / Hints kommt ins Spiel.
Wie können wir es also dynamisch mit lua lösen?


Abbildung 3: dreigeteilter Code für Initialisierung, Setzen der Werte nach Wahl der Location und Gewinnmeldung

Vorbereitung


Als erstes definiert ihr ein Array von locations und anschließend für jede reale Dose einen Eintrag.
locations[1] für die Dose in Hamburg, locations[2] für die in Berlin, locations[3] für München usw.
Was kommt in einen location-Eintrag? Bei mir sind es zwei Informationen:
  • der Wegpunkt mit dem die Final-Zone versehen werden soll
  • Eine Beschreibung des Finals bzw. der Hint

Spielstart

Das Spiel wird gestart, startGame (Zeile 7 ff) ausgeführt und eine der möglichen Locations ausgewählt. Dies kann per Multiple-Choice passieren oder voll automatisch, indem ihr die Location auswählt die am nächsten liegt.
Da dies in Dein WheriGo in Hamburg, München, Köln, Berlin .... behandelt wird, hab ich hier einfach die "1" gewählt.
Nun gilt es die Werte zu setzen. Dabei werden der Finalzone ihre benötigten Wegpunkte gegeben, nämlich dreimal loc.FinalPoint
Für die Ausgabe der Koors in einem lesbaren Format nutze ich die Helfermethode buildReadableCoorString (siehe Globaler Code - Teil II )
Danach wird die Beschreibung noch generiert (#1# wird mit N 50° 39.910, E 7° 28.092 ersetzt und an die Zone angeheftet.
Damit sind wir mit der Vorbereitung zu Spielstart fertig. Warum machen wir das nicht erst wenn das Spiel gewonnen ist und die Gewinnmeldung angezeigt bzw. der Final freigeschaltet wird?
Auch das wäre möglich, allerdings müsste man sich dann merken, welche location ausgewählt wurde. So können wir sie als local definieren und damit den Speicher entlasten. Es sind zwar nur geringfügige Performanceentlastungen aber wir wollen ja auch, dass die Geschichte auf den Garmingeräten läuft.

Spielende

Es sind alle Schiffe versenkt, alle Minen entdeckt oder alle Station gelöst und es geht nun zum Final. Sprich das Hauptspiel ist gewonnen und es folgt die Belohnung.
Das ganze hab ich in endGame() gekapselt. outWithMedia kommt wieder aus der global.lua und PFinal ist der Identifer des Schatzkistenbildes.
In der Erfolgsmeldung hab ich jetzt die Koors nochmals eingebaut. Da die Finalzone bereits fertig ist, muss sie nur noch aktiviert und sichbar gemacht werden.
(Achtung ! Damit die neue Description auf allen Playern übernommen wird muss sie an der Zone aktualisiert / gesetzt werden bevor Active und Visible aktiviert werden.

Das war's. Nun hat der Naturfreund beim Wherigospielen die Möglichkeit mit einem zweiten Gerät oder einer Unterbrechung des Wherigos auf der Karte nachzuschauen welchen Feldweg er am Besten nimmt um zur Dose zu gelangen.

Sonntag, 8. Juni 2014

Globaler Code - Teil II

Voraussetzung: "Globaler Code - Teil I“
Nachdem ich den ersten Teil des globalen Code hier im Blog vorgestellt habe, wollte ich mal eben schnell den nächsten Beitrag schreiben wo es um Convenience-Angebote für den Spieler geht. U.a sollen die Finalkoordinaten lesbar ausgegeben werden.
Es dürfte so gut wie jeder WIG-Owner sich schon desöftern über das für uns ungewohnte Dezimalgrad-Format (z.B 50.6651666666667N 7.4682E) bei der Koordinatenverarbeitung in Urwigo geärgert haben.
Als Geocacher ist man ja eher das Grad, Dezimalminuten-Format (N 50° 39.910, E 7° 28.092) gewöhnt sein.
Und wenn man dann noch Koordinaten in lua als Waypoint direkt definiert (Wherigo.ZonePoint(50.6651666666667, 7.4682, 0)) ist die manuelle Hin- und Herrechnerei ziemlich aufwändig. In manchen Fällen ist es empfehlenswert die Koordinaten lesbar auf dem Display auszugeben, z.B. wenn der Final etwas kompliziert zu erreichen ist. Damit man neben dem Wherigo-ZonePoint keine extra String-Version der Koordinaten im Dezimalminuten-Format im Code zu pflegen hat, empfiehlt es sich das ganze automatisch umrechnen zu lassen. Kommt vorallem gut, wenn sich die Location ändert, oder ne neue Location hinzukommt.

Abbildung 1: Koordinaten- bzw. Debug-Pretty-Print
Abb.1 zeigt uns mit buildReadableCoorString eine Helfermethode die genau dies macht. Zunächst wird in Zeile 71 und 72 der Waypoint in den Breiten- und Längengrad aufgesplittet.
Beginnend mit der latitude also dem Breitengrad werden mit math.floor der Vorkommateil, also die Gradzahlen abgeschnitten und abgezogen vom ganzen der Nachkommateil gebildet. (Zeile 74. lat - tmp).
Die Umrechnung des Nachkommateil der Dezimalgrad in Minuten und Dezimalminuten habe ich ausgeglieder da es später beim Längenanteil der Koordinaten wiederverwendet werden kann.
Dabei wird in Zeile 81 der Nachkommaanteil mit 60 multipliziert und auf drei Stellen hinterm Komma gerundet. N 50° 39.9100023347 sähe nicht so gelungen aus. Als Ergebnis würde N 50° 39.91 herauskommen was auch nicht zu 100% korrekt aussieht. Es fehlt die abschießende 0.
Dies geschieht in Zeile 82 wo mit string.format("%.3f", val) auf 3 Stellen hinterm Komma (bzw. Punkt) mit Nullen aufgefüllt wird.
Bleibt noch die Runden-Funktion, die gibt es in lua nämlich nicht fertig. Jedenfalls nicht für Dezimalzahlen. Man kann eine Dezimalzahl auf den Ganzteil abrunden (2.88484 => 2) oder aufrunden (4.127 => 5). Um mathematisch Runden zu können bedient man sich eines Trickes, indem man mit + 0.5 die Rundungsgrenze auf die ganze Zahl verschiebt und dann abrundet.
Bsp.: 2,6 + 0,5 ergäbe 3,1; abgerundet mit math.floor ergibt 3. Gegenbeispiel 2,3 + 0,5 ergibt 2,8; abgerundet auf den Ganzzahlanteil ergibt 2.
mult = 10 ^ (idp or 0) ergibt 1000, da idp mit 3 (Nachkommastellen) übergeben wird. In der Schule haben ja gelernt: Multiplikation mit 10 hoch x heißt das Komma um x Stellen nach rechts verschieben. Aus 0,9100023347 wird dann 910,0023347; das ganze addiert um 0,5 => 910,5023347; auf Ganzzahlanteil abgerundet => 910; Zum Schluss wird das Komma wieder um drei Stellen nach links verschoben, d.h. Division durch 10 hoch 3 = 1000 - ergibt 0,91

Wem jetzt der Schädel wegen zu viel Mathe qualt, der sei beruhigt, das war's mit Mathe, nun kommt nur noch eine Abkürzung für's Debug-Logging.
Wie ich bereits in Einen Wherigo debuggen beschrieben habe, ist es oft ziemlich hilfreich mit print den aktuellen Stand von Variablen auf die Simulatorkonsole zu schreiben.
Da der Informatiker faul ist, ergibt sich die "Notwendigkeit" das mit p abzukürzen.
pt hingegen bringt uns jetzt noch echten Mehrwert, indem vor die Logzeile die aktuelle Uhrzeit geschrieben wird. So kann man leichter zusammenhängende Aktionen erkennen und sich besser durch die Von-Oben-Nach-Unten Syntax des Logfiles wühlen. Es gibt übrigens keine Genauigkeit auf den Millisekundenbereich. Die Sekunde ist die kleinste Zeiteinheit, die ihr im Wherigo messen könnt.
Die Zeilen 95 - 99 beschäftigen sich mit dem Problem, dass man in lua keinen String mit nil oder nem Boolean verknüpfen kann. So ist gesichert, dass in Zeile 101 nur Zahlen und Strings konkateniert werden.

Samstag, 7. Juni 2014

global.lua - Helperfile für oft wiederkehrende Codestellen

Voraussetzung: "Der Einstieg in lua“
Es gibt so manche Aufgaben, die einem ziemlich oft bei der Programmierung begegnen, bzw. man denkt "Diesen Teil müsste man doch abkürzen" können.
So ist z.B die Ausgabe eines Textes mit oder ohne Bild eine recht komplexe Sache wenn man die MessageBox direkt in lua schreiben möchte.
Vorallem wenn nach dem Klick auf OK noch eine weitere Aktion folgen soll.

Auch das Ersetzen von Variablen in Strings (z.B. bei der Übersetzung deines Cartridges in verschiedene Sprachen ist eine Sache, die man nicht bei jedem neuen Wherigo neu schreiben möchte.

Abbildung 1: Einbinden des globales Codes
Zu diesem Zwecke habe ich mir angewöhnt eine global.lua Datei anzulegen, die ich im überordneten Ordner aller Wherigo liegen habe und die von jedem WIG-Projekt genutzt werden kann.
Ein Beispiel:
  • Im Ordner C:\Wherigos liegt die Datei global.lua
  • Im Ordner C:\Wherigo\MeinTestWherigo liegt dann die Projektdatei test.urwigo
  • Im Ordner C:\Wherigo\MeinZweiterWherigo liegt dann entsprechend zweiterWIG.urwigo
Beide Cartridges, sowohl test.urwigo als auch zweiterWIG.urwigo verwenden global.lua aus dem übergeordneten Ordner


Abbildung 2: Auszug aus _cartridge.lua (enthalten im gwz Archiv)
Beim "Bauen" des cartrigdes bzw. beim Erstellen des gwz Archives spielt der Pfad dann keine Rolle mehr, da der Inhalt der externen lua-Datei in die _cartridge.lua hineinkopiert wird

Abbildung 3: Boolean als Text oder Zahl
Nun aber zu den Methoden an sich. Den Anfang machen die Boolean Werte. Dabei ist es mir für debug Zwecke oft hilfreich, wenn der Wert als Zahl oder String zurückgegeben wird, da eine Konkatenation in lua mit verschiedenen Datentypen Probleme bereitet.
"Raetsel 1 geloest: " .. myBoolVar führt zu einem Laufzeitfehler da hier string und boolean nicht miteinander verknüpft werden kann.
"Raetsel 1 geloest: " .. bts(myBoolVar) hingegen funktioniert, da nun zwei Strings verknüpft werden. Die Ausgabe lautet dann "Raetsel 1 geloest: true"
Dass getBoolAsString als bts (boolToString) abgekürzt ist bzw. dass es mal value und mal condition heißt ist historisch gewachsen. Kein guter Stil, wie ich zugeben muss, aber eine Methode umbenennen wird im nachhinein schwierig, da man auch alle Aufrufe der Methode in zahlreichen Wherigo-Projekten anpassen muss.
Also lieber gleich sinnvolle Namen verwenden.

Die dritte Methode wählt zufällig true bzw. false, abhängig von einer Wahrscheinlichkeit.
Bsp: Bei meinem Fußball-Wherigo Heimspiel soll die gegnerische Mannschaft nur in einem von drei Fällen ein Tor schießen.
getRandomBool(33) löst diese Aufgabe. math.random(100) liefert eine Ganzzahl zwischen 1 und 100, also 5, 41, 87 ...
Ist diese Zahl kleiner als unsere Vorgabe 33 wird true zurück geliefert, ansonsten false. Korrekterweise müsste es kleiner gleich heißen, da nicht von 0 bis 99 sondern von 1 bis 100 gezählt wird.


Abbildung 4: Ausgaben vereinfachen
Im zweiten Block geht es um die Ausgabe in Form von MessageBoxen. Da die ersten drei Methoden nur Convenience-Wrapper für die vierte sind, beginnen wir auch bei letzerer in Zeile 38.
Hier wird eine MessageBox aufgebaut, die mit dem übergebenen Text m und einem optionalen Bildchen med gefüllt wird.
Als Callback wird der Teil bezeichnet, der ausgeführt werden soll, wenn der Spieler auf OK gedrückt hat.
Hier wird es jetzt spannend. Lua unterstützt nämlich hier das Konstrukt einer "closure". D.h. es wird eine Methode cf definiert (Zeile 35), die erst dann ausgeführt wird, wenn der MessageBox danach zumute ist.
Woraus besteht aber die Methode cf? Zunächst hat sie einen Parameter action. Ist dieser Parameter null, so geschieht ... rein gar nix. Ich konnte diesen Fall noch nie beobachten. Wahrscheinlich ein Sicherheitscheck, falls man die MessageBox nicht mit "OK", sondern mit dem Back-Button des Smartphones verlässt.
Ruft hingegen die MessageBox die Methode cf(anAction) mit einem Not-Null-Parameter auf, so wird die zweite innere Methode fun (wieder eine closure) ausgeführt.
Was aber ist denn fun ? fun wird von außen definiert, z.B. in Zeile 29, wo der Befehl "Zeige Mainscreen" übergeben wird. Auch hier gilt, in Zeile 29 wird Wherigo.ShowScreen(Wherigo.MAINSCREEN) nur definiert, aber noch nicht ausgeführt. Dies passiert erst, wenn der Spieler in der MessageBox (z.B. "Gehe nun zum Final") auf "OK" drückt.
Achtung: Vergesst nicht bei der Definition der Methode die Schlüsselwörter function() und end. Ansonsten habt ihr keine closure definiert, sondern es wird direkt der Mainscreen angezeigt!
Jetzt wird sich der gewiefte Leser/Entwickler denken "Wozu brauche ich den action Parameter?". Dieser dient theoretisch dazu weitere Informationen an die aufrufende Methode zu übergeben. Man könnte ne Zahl oder einen String reinschreiben. Trotz längerer Studie des von Urwigo kompilierten Codes konnte ich aber stets nur den action ~= nil Vergleich finden.
Man kann allerdings einer MessageBox mehrere Buttons geben und damit einen Quasi-Input generieren. Über action (enthält "Button1" bzw. "Button2") kann man dann unterscheiden welcher Button gedrückt wurde. Ein sinnvolles Szenario wäre "Du bist an der Zone Goldbergwerk angekommen. Die bereits abgebauten Goldklumpen wandern hiermit in dein Inventar", Button1 = "OK", Button2 = "Nachricht zukünftig nicht mehr anzeigen"

Abbildung 5: Debug-Ausgaben, Wegpunkt-Mittelung und String-Operationen
Nun aber genug der komplexen closure-Theorie. Zum Abschluss etwas leichtere Kost.
calcCenterZonePoint liefert euch die GPS-Koordinate, die genau zwischen point1 und point2 liegt. Ist ganz praktisch um die Hälfte der Laufstrecke oder den Mittelpunkt eines Fußballfeldes zu bestimmen.
printTable gibt beim debuggen den Wert einer Tabelle aus. Da jedes "Objekt" in lua als Tabelle definiert ist, kann das schon mal ganz hilfreich sein.
Die Werte einer Zone (Name, Koors, ob aktiv oder nicht...) sind ebenfalls als Tabelle gespeichert und können über getmetatable() abgerufen werden. Als Ergebnis seht ihr beim Debuggen alle Infos über eure Zone.

Zum Schluss noch ein Beispiel für Substitution. Hier wird in einem String "Gehe zur Zone #1# und warte", "Schmiede" der Wert "Gehe zur Zone Schmiede und warte" gebildet. Hilfreich bei Mehrsprachigkeit eures Wherigos.

Sonntag, 20. April 2014

Sprachausgaben übersetzten ohne vararg Parameter

Voraussetzung: "I18n - Einen Wherigo in verschiedenen Sprachen anbieten“


Abbildung 1: Die Übersetzungen mit mehreren Parametern ohne varargs
Im vergangenen Tutorial bzgl. der Übersetzung habe ich den vararg Parameter ... eingesetzt.
Es scheint aber so, dass manche WhereYouGo bzw. PiGo Player Version Probleme mit diesem Konstrukt haben.

Um dem vorzubeugen möchte ich euch folgende Lsg vorstellen, die mir schnuefelis als funktionstüchtig beschrieben hat.

Der Trick ist recht einfach. Anstatt mit ... eine unbestimmte Anzahl an Parameter zu definieren, baut ihr einfach mehrere Methoden, die einen, zwei, oder drei "replacements" akzeptieren.

Um nicht unnötig Code zu duplizieren, rufen diese Methoden alle die 4-Parameter Version auf, in der dann geprüft wird ob replacement1, 2 oder 3 nil sind und nur dann die Ersetzung starten.

Als Softwarentwickler tut eine solche Lösung zwar schon ein bißchen weh, aber bei der Vielzahl der Player mit all ihren Schwächen muss die Dynamik zugunsten der Stabilität leiden.

Freitag, 7. Februar 2014

To offer a wherigo in several languages

This is the english version of the article. It is also available in german

Good to know for this article: "Der Einstieg in lua“

In this tutorial you'll learn how to offer your wherigo cartridge in several languages.

Picture 1: Definition of the text outputs in three different languages
First we will define an array messages where we can define the outputs in different languages
Very important is to define the language parameter to to ZVariables because we want to save the value when we save and restore the cartrigde.
For each language (german, english and fresh in our example) we define a subarry like in row 5-7. After that we define the output strings.
How it works I can show you at the example for the key hello. Depending on the selected language we want to see "Herzlich Willkommen", "Welcome" oder "Bienvenue" in our display. Maybe you have noticed that for the key hi are no english value defined. For this case the program will use the defaultLanguage (see row 4) So if your language is set to en and no value for message.en.hi is defined the default value messages.de.hi => "Hi" (row 11) is used.
The next part is very important:
In row 13-18 you can see examples for using placeholders, e.g the name of the play inside the translated output string
At runtime the #1# will be replace with the name, e.g. 'Krolock'. You will have one of the following output:
  • de => Hallo Krolock, wie geht es dir?
  • en => Hi Krolock, how are you
  • fr => Salue Krolock, ca va?
Be careful with blanks! # 1# will not work, #1 # will not work and Hi#1#How are you will transformed to "HiKrolockHow are you".

During the start of your cartrigde you can offer an multiple-choice-input for the user where he can select the langauge. After the input you can set the variable language to the fitting value


Picture 2: Build the value fitting to the key and language
But how to use the resource strings. As shown in picture 2, row 21 we use an array access for the language in first and the key in second level. Using "de" as language and "hello" as key will deliver Herzlich Willkommen zum i18n Wherigo.
If no array value is available a new try with the default value is shown in row 23. So it is very import to define an entry for each key in the default value (de in our example)


Picture 3: Values for the placeholders
Now it is time to look to the parameter replacement. I is import to use a placeholder that will not be part of the "regular" strings. So I decided to use #1#.
As you can see in line 27 at picture 3 i use string.gsub to replace #1# with the first, #2# with the second parameter and so on. Picture 3 shows a updated version of getMessage with replacing the placeholders
The vararg argument "..." is a good way to be flexible.
The function getMessage can be called with only the key, with one or multiple parameters like the following examples show :
getMessage("hello")
getMessage("helloWithName","Krolock")
getMessage("openDoor","XYZ","A15N")

With #arg we can retrieve the number of the arguments stored in the array arg. With arg[1], arg[2] we can access to the first, the second parameter and so on

Picture 4: Example for calling the translation method
If you have more than one parameter you have to take care for the order. For the key openDoor you have different orders in the german and english version.
Picture 4 shows us an example how to call the translation method in Urwigo. He have an example for no, one and two parameters.

Last but not least I will show you how to seperate your code and your translations. In the concept of resource bundles you will define a own file for each language and each file hast the same key, excect the language part of the key.
You can use a file messages_de.lua containing the german, messages_en.lua for the english and messages_fr.lua for the french values.
All the three files can be bind with require "messages_de" and so on.
Development-Tool line eclipse or Intellij IDEA have plugins to manage your resource bundles.

To seperate the file is optional. It will also work if you write all the key-value entries in one file, but it is not a good practise.

Donnerstag, 7. November 2013

Fehlersuche und Codeanalyse mittels _cartrigde.lua

Voraussetzung: "Der Einstieg in lua“

Abbildung 1: Bei der Zonendefinition wird der Identifier des Steinbruchs mit kleinem s angelegt
In diesem Tutorial wollen wir uns mit Fehlersuche bzw. Codeanalyse beschäftigen. Genauer gesagt geht es darum sich anzuschauen welcher lua Code aus dem Urwigo Projekt generiert wird.
Dies kann uns in zweierlei Hinsicht nützlich sein.
  • Erweitern der lua-Kenntnisse sowie Analyse der Wherigo-API
  • Fehlersuche
Zunächst beschäftigen wir uns mit dem zweiten Punkt: Das Scenario ist folgendes:
Wir haben eine Zone Steinbruch definiert, die wir im Lua user functions Bereich ansprechen wollen bzw. der Aktivitätsstatus wir dort verändern möchten.
Abb. 1 zeigt das Anlegen der Zone. Erfahrene Wherigo-Entwickler werden auf den ersten Blick feststellen, dass wir neben Namen und Bild auch den Identifier festgelegt haben.

Abbildung 2: Im lua Teil wird das Objekt fälschlicherweise mit großem S angesprochen
Im Lua user functions Bereich nehmen wir unseren Steinbruch und setzen die Aktivät auf true.
Wer erkennt hier schon unseren Fehler? Kleiner Tipp: Bei diesem Code würde das Projekt gar nicht erst starten. Würden wir die Anweisung in einer Funktion verwenden, die erst während dem Spiel ausgeführt wird, z.B onStart würde die Fehlermeldung folgendermaßen lauten:
Lua method call failed. .../urwigo-src-23fd04e4-6177-45ca-a89a-2ffc8589ff08.lua:323: attempt to index global 'Steinbruch' (a nil value)

Abbildung 3: Wichitg für die Lesbarkeit im Testmodus ist Verschlüsselung sowie die Verschleierung der Identifier und Texte zu deaktivieren
Die Ursache der Fehlermeldung ist schnell erklärt: Während der Identifier in Abb.1 auf steinbruch mit kleinem s gesetzt wurde wird im lua Part versucht die Zone mit großem S anzusprechen.
Da der Code case-sensitive ausgewertet wird ist Groß- und Kleinschreibung wichtig. Schließlich befinden wir uns ja nicht im Windows-DOS-Modus.

Diesen Fehler erkennt man beim zweiten Mal sehr zügig. Was aber, wenn wir noch nicht so erfahren sind? Hier kann ein Blick in die lua Datei helfen, die von Urwigo aus dem Projekt erzeugt wird und neben den Bildern als zip Datei (oder besser gesagt als gwz Datei) zum wherigo.com Server hochgeladen wird.
Wir wollen also einen Blick in die generierte lua Datei werfen. Dazu ist es wichtig, dass wir wie in Abb.3 erkennbar ist, die Verschleierung und Verschlüsselung der Texte, Antworten und vorallem der Identifiers aufheben. Die Identifiers dürfen auf keinen Fall verscheiert werden, wenn wir eigenen lua Code schreiben. Die ersten beiden Kästchen dienen der besseren Lesbarkeit während der Testphase, sollten vor dem Hochladen zu wherigo.com aber wieder gesetzt werden.
(Nicht dass es euch so wie mir geht und ihr ein paar Minuten nach der Veröffentlichung des neuesten Wherigos eine eMail bekommt in der euch ein Cachingkollege mit breitestem Grinsen alle Antworten und Spoilerbild zuschickt, weil ihr vergessen habt die Verschlüsselung zu aktivieren...)

Abbildung 4: Das Bauen des package ist nicht neues und kennen wir schon als Vorbereitung für's Hochladen
Jetzt könnt ihr euch kurz entspannt zurücklehen, denn der Teil mit dem Erstellen eines Packets sollte euch wohlbekannt sein.
Für den weiteren Vorgang nehmen wir an, wir haben die gwz Datei ExtractLua.gwz genannt und in dem Ordner "C:\tmp\ExtractLua" abgespeichert.

Abbildung 5: Beispiel für das Entpacken der gwz-Datei mit 7zip
Um die lua Datei aus der gwz Archivdatei zu extrahieren, bedienen wir uns eines Zip-Programmes unserer Wahl. WinZip, WinRar sollten selbsterklärend sein. Ich habe 7zip verwendet, da es sich hierbei um ein kostenlos nutzbares Tool handelt, dass allen Ansprüchen der Extrahierung gerecht wird. Wenn man den Schritt "Packet bauen - auspacken - Lua Datei anschauen" mehrmals wiederholt (nach der Behebung des ersten Fehlers wartet der zweite) empfiehlt es sich den Extraktionsbefehl über die Kommandozeile abzusetzen, da er dort recht leicht wiederholt werden kann (z.B Pfeil aufwärts in der Konsole). Der Parameter e steht für Auspacken, während der Schalter y für das Überschreiben vorhandener Dateien steht. Deswegen sollte man die gwz Datei außerhalb des Projektordners auspacken.

Abbildung 6: In der von Urwigo erstellten lua Datei kann man gut sehen, woran der Fehler liegt: Groß- und Kleinschreibung des Identifiers stimmen nicht überein
Wie ihr eure gwz Datei entpackt ist aber euch überlassen. Wichtig ist, dass am Ende eine _cartridge.lua Datei herauskommt, die ihr dann mit einem Texteditor öffnen könnt. Ich habe dies mit dem vim Editor gemacht, da mir dann ein farbliches Syntax-Highlighting sicher ist. Wer bereits mit dem IntellijIDEA arbeitet (siehe Lua im externen Editor bearbeiten) kann natürlich sich dort die Datei anschauen.

Nun aber zur Fehleranalyse. In Abb. 6 erkennt ihr an dem oberen bzw. unteren roten Kreis, dass die Identifier sich in Groß- und Kleinschreibung unterscheiden und ihr habt den Fehler somit gefunden.

Ich hatte zu Beginn des Tutorial einen weiteren Nutzen der Codeanalyse angesprochen. Dies wird durch Betrachtung des mittleren roten Kreises deutlich:
Der Codeteil function objExtractLuaTest:OnStart() end enthält nämlich alle Codeanweisung, die beim Starten des Wherigo ausgeführt werden. Ich seht auch wie der Identifier des Cartridges gebildet wird, wenn er nicht von euch vorgegeben wird: Mit einem simplen Prefix obj der vor den Namen gesetzt wird.
Anstatt die Startanweisungen als lua Block aufwändig über Urwigo einzugeben, könnt ich auch einfach in euerer lua Datei diesen Codeteil verwenden.
Auf diese Weise hab ich mir nach und nach angeschaut, wir Textausgaben (MessageBoxen), Benutzeneingaben (Input), Timer, Zonen und Gegenstände verwendet werden, sodass ich heute nur noch die Objekte in Urwigo definiere (UUID-Erzeugung ist zwar möglich aber recht aufwändig) und alles andere direkt in lua schreibe, was sich bei aufwändigeren WherIGos wie Minesweeper, Cachopoly, Schiffe versenken... als wesentlich schneller herausgestellt hat und ihr habt die Codeanweisungen zentral in einer (später mehreren) Lua Dateien und könnt so euren Code besser nachvollziehen.

Donnerstag, 6. Juni 2013

Wherigo ohne Zonen

Voraussetzung: "Der Einstieg in lua“
Im heutigen Tutorial geht es darum einen Wherigo ohne Zonen zu programmieren. Dies kann hilfreich sein, wenn die benötigte WIG-Lösung viele Zonen bräuchte bzw. die Zonen nicht sichtbar sein sollen.
Die kann bei einem schachbrettartigen Spielfeld sinnvoll sein, z.B ein Memory-Spiel oder dem beim Projekt-Eck verfügbaren Minesweeper-Spiel. Ansonsten hätte wir selbst bei einem 4x4 Memory 16 aktive Felder. Mit dem Oregon undenkbar.

Ermittlung der aktuellen "Zone"


Abbildung 1: Bei der Ermittlung wo der Spieler gerade steht wird über alle Felder iteriert
Das Zauberwort heißt hier (mal wieder) Player.ObjectLocation. Damit kann festgestellt werden, wo sich der Spieler gerade befindet. Danach wird "lediglich" über alle vorher definierten Felder (z.B das Feld A2 auf unserem Schachbrett, Definition folgt weiter unten im Tutorial) iteriert und geprüft, ob der Abstand zum Feld einen definierten Mindestabstand distance entspricht. Dieses Attribut haben wir global festgelegt, z.B distance = 5 (Meter).

Abbildung 2: Die Klasse Field enthält alle benötigten Werte: Name, Index und vorallem die Location
Ein Feld wird dabei durch die Klasse Field definiert. Sie enthält neben Namen und Index(sinnvoll für Verwendung von Arrays) die Eigenschaft Location, die aus der GPS-Koordinate des Mittelpunktes besteht. Freunde des OnProximity Event (siehe Zone als Kreis) kennen diese Logik bereits. Der Radius der virtuellen Zone wird durch die obige Variable distance definiert.
Sobald das erste Feld diese Bedingung erfüllt (distance sollte passend zum Abstand zwischen den Feldern so gewählt sein, dass immer nur 1 Feld nahe genug ist), speichern wir es als activeField und führen die Aktionen aus, die wir normalerweise im onEnter oder onProximity definieren. Im Listing wird dazu die Methode handleField aufgerufen, die dann die gewünschten Anweisungen enthält.
Außerdem wird die Variable foundField auf true gesetzt und der Schleifendurchlauf mit break abgebrochen, denn schließlich haben wir unser aktives Feld ja gefunden. Wenn alle Felder durchlaufen sind, aber kein Feld gefunden wurde, so befindet sich der Spieler außerhalb des Spielfeldes (zur Sicherheit nochmals mit Spielfeld:Contains(Player) abgsichert, könnte auch weggelassen werden) so wird das activeField auf fieldOutside(definiert als Feld mit Namen "outside" und Index -1) gesetzt und eine MessageBox ausgegeben, dass man sich doch gefälligst wieder zurück zum Spielfeld begeben sollte. Diesen Teil habe ich in einer Methode out ausgelagert.
Spielfeld habe ich als wirkliche Zone definitert, damit der Spieler sich über den gewohnten Navigationpfeil leiten lassen kann um das Spiel zu beginnen / wieder aufzunehmen. Ganz ohne Zonen geht es also nicht, wenn man die Usabiltiy nicht außer Acht lassen möchte.

Nebenläufige Programmierung und ihre Tücken

Was aber sollen die Vergleich von activeField in Zeile 47 und 54?
Um das zu verstehen muss man wissen, dass die Methode calcActiveField regelmäßig angestoßen werden muss, da sich der Spieler dreisterweise bewegen möchte. Ein Timer mit Interval 1-5 Sekunden (je größer die Felder desto seltener, nicht zu oft, damit das Oregon mitkommt) bietet sich hier an.
Wenn wir die obigen Vergleiche nicht einbauen, wird die handleField Routine alle 5 Sekunden ausgeführt, was bei nem stattfindenen Dialog sehr unangenehm ist und dazu führt, dass der Spieler die gestellte Aufgabe womöglich nie durchführen kann. Also lieber handleField nur einmal ausführen und dann erst wieder wenn der Spieler das Feld wechselt. Das gleiche gilt für den Fall, dass er sich außerhalb des Spielfeldes verlaufen hat. Auch da reicht es wenn die MessageBox nur einmal angezeigt wird.
Bei sehr großer Anzahl von Felder kann es zu der Situation kommen, dass der aktuelle Schleifendurchlauf noch läuft, während der Timer schon wieder das Signal gibt den nächsten Durchlauf zu starten. Um Fehler oder Wechselwirkungen bei dieser Art der parallelen Abläufe zu verhindern bildet die boolean Variable runningActiveFieldCalculation, als Mutex-Variable eingesetzt, einen Schutz gegen gleichzeitiges Ausführen mehrerer Schleifendurchläufe. Vereinfacht gesagt: So lange ein Iterier-über-alle-Felder-Durchlauf läuft besitzt die Variable den Wert true und weist damit weitere Versuche ab. (! Ich rede bewußt von Abweisen, nicht von Blockieren). Erst wenn der kritische Programmteil abgarbeitet ist wird er mit runningActiveFieldCalculation = false wieder freigegeben.

Wann endet das Spiel?

Die ersten drei Zeilen steuern die Beendigung des Spieles. So soll die Methode endGame() aufgerufen werden, wenn das Spiel als beendet markiert wurde, die notwendige Benachrichtigung an der Spieler ("Gewonnen, gehe zum Final bei N 50° ....") noch nicht vollzogen ist. Die beiden boolean Variablen werden irgendwo in handleField gesetzt, z.B wenn alle Memory-Paare aufgedeckt sind. Zeile 32-35 sind aber nicht zwingend notwendig für die Implementierung des Wherigos ohne Zonen.
Am Ende des Listing beginnt die Implementierung von handleField. Besonders bei Schachbrett-Spielfeldern ist es für den Spieler sehr hilfreich gesagt zu bekommen, wo er sich befindet.
Bei der weiteren Abarbeitung des Feld bitte nicht vergessen ob die Siegbedingung (s.o.) erfüllt ist und das Spiel beendet werden kann.

Spielfeld definieren

Ich hoffe ihr habt noch ein wenig Saft im Aufmerksamkeitstank, denn jetzt wird es richtig knifflig.
Was haben wir?
Wir haben zwei Koordinaten für die Eckpunkte unseres Spielfeldes (ObenLinks und UntenRechts)
Was wollen wir berechnen?
Ein schachbrettartiges Spielfeld mit rows Zeilen und columns Spalten, z.B 3 x 5 Felder. Siehe Abb. 5 als Beispiel
Wie machen wir das
Vereinfacht gesagt ermitteln wir Breite und Höhe des Spielfeldes, teilen diese durch die Anzal der Spalten bzw. Zeilen und iterieren über alle Felder (bei 3 x 5 => 15 Felder) und setzen den Mittelpunkt des Feldes. Zum Schluss setzen wir noch die Ecken ObenLinks, ObenRechts, UntenLinks und UntenRechts als Points der wirklichen Zone Spielfeld und schalten es aktiv und sichtbar.

Abbildung 3: Ausgehend von den Ecken ObenLinks sowie UntenRechts wird das Spielfeld ermittelt

Nun im Detail von oben nach unten, beginnend mit den Zeilen 452 und 453. Dort werden die beiden gegenüberliegenden Eckpunkte unserer Rechteckes definiert. Mit Wherigo.VectorToPoint ermitteln wird den Abstand d sowie den Winkelb zwischen den beiden Punkten. Dies ist ähnlich dem Peilen von einer Koordianten zur anderen mittels Entfernung und Winkel. Ein Winkel b von 135° bedeutet, dass man von firstLoc schräg nach rechts unten gehen muss um zu lastLoc zu gelangen.
Die Variable dist hält den Abstand in Meter fest. Zeile 456 ermittelt den Anteil der Distanz in X-Richtung (also auf der genordeten Karte nach rechts). Wie wir an math.sqrt erkennen, kommt hier der Pythagoras-Klassiker a² + b² = c² zum Einsatz. Wer die Berechnung von distX verstanden hat, wird auch mit distY sowie degX und degY zurechtkommen. Die Modulo-Rechnung mit 360 soll verhindern, dass unser Peilwinkel größer als 360° wird.
Wer es genau wissen möchte, dem kann folgende Debug Zeile helfen:
print("b " .. b .. " dist " .. dist .. " distX ".. distX .. " distY " .. distY .." degX " .. degX .. " degY " .. degY)
In der Kurzversion wird zwischen 456 und 459 die Höhe und Breite sowie der Peilwinkel ermittelt um ein Rechteck aufzuziehen, dessen Verhältnis Höhe / Breite dem Verhältnis Zeilen / Spalten entspricht. Abb. 4 und 5 zeigen zwei Beispiele wie das resultierende Spielfeld aussehen kann. ObenLinks und UntenRechts sind auf beiden Abbildungen gleich, nämlich N 50.3691166666667 E 7.6164 bzw. N 50.3680333333333 E 7.61715.

Abbildung 4: Das Spielfeld mit 5 Zeilen und 3 Spalten ist höher als breit ...

Abbildung 5: während 3 Zeilen und 5 Spalten eher breit als hoch ist

Danach wird die Ecke ObenLinks aka firstLoc als Start der Zeile gesetzt. Dies entspricht in unserem Schachbrett A1. (Bitte entschuldigt den Unterschied zum realen Schachbrett, ich habe gerade erst gesehen, dass dort Zeile 1 die unterste Zeile ist. Bei mir ist es die oberste).
Ist der Rest der Division durch die Anzahl der Spalten 1 (tritt in unserem 3 x 5 Beispiel Abb. 5 bei 1, 6 und 11 auf) so wandert unsere Position nach unten, da wir eine neue Zeile starten. "Nach unten" ist aber nicht genau senkrecht (das wären 180°), sondern abhängig vom Peilwinkel den das Rechteck haben soll (im Beispiel SüdSüdWest bzw. 191,43°). Zur Erinnerung Wherigo.TranslatePoint berechnet den Punkt der von firstLoc aus distY Meter in Richtung degY geht. Vektorrechnung für Anfänger.
math.floor macht übrigens nichts anderes als das Ergenis des Bruches abzurunden, damit 0, 1 bzw. 2 herauskommt. (In der ersten Zeile müssen wir noch nicht nach unten verschieben)

Wenn wir nicht gerade das erste Element einer Zeile antreffen, so müssen wir vom Zeilenanfang nach rechts gehen. Genauer genommen in Richtung degX (OstSüdOst oder 101.43°, merkwürdigerweise genau 90° weniger als degY ). Auch hier gehen wir abhängig von der Spalte in der wir uns befinden 1/4, 2/4, 3/4, 4/4 nach rechts um unsere Position neu zu berechnen.

Zum Abschluss noch etwas entspanntes, um das Tutorial sanft ausklingen zu lassen. Um unsere Zone Spielfeld zu erzeugen setzen wir die Ecken ObenLinks, ObenRechts, UntenRechts und UntenLinks als Points, machen sie sichtbar und aktiv.
Die ganz aufmerksamen unter euch werden jetzt zurecht sagen: Moment, das Spielfeld ist kleiner als die Fläche die von den Feldern abgedeckt wird, denn die Eckpunkte von Spielfeld sind die Mittelpunkte der Eckkreise. Dies ist korrekt, aber Spielfeld dient lediglich dazu den Spieler zurück zur Spielfläche zu führen, wenn der diese früher erreicht als vom GPSr angezeigt so ist dies nicht wirklich schlimm, sollte aber bei der Messung der beiden Ecken ObenLinks und UntenRechts beachtet werden.
Vielen Dank an dieser Stelle an bodenseepingu, der mir seinen Quellcode von Geomemory zur Verfügung stellte und so die nötigen Denkanstöße gab ein Feld mit zwei gegenüberliegenden Eckpunkten aufzuziehen.

Freitag, 19. April 2013

Dein WheriGo in Hamburg, München, Köln, Berlin ....

Voraussetzung: "Der Einstieg in lua“
Was passiert, wenn man einen Wherigo veröffentlich hat, der gut ankommt? Richtig auf kurz oder lang kommen die Anfragen "Tolle Sache, können wir den in Leipzig, München, Hamburg, Berlin .... auch veröffentlichen?"
Wenn der Wherigo keine Ortmerkmale verlangt ist dies ja auch kein Problem. Vorallem wenn man ein Spielfeld braucht, das man an beliebiger Stelle spielen kann.
Möchte man aber keinen PlayAnywhere daraus bauen, bzw. dem Spieler das lästige und oft nicht verstandene Abstecken des Spielfeldes ersparen, so kann man mehrere Locations vordefinieren und dem Spieler eine MultipeChoice-Box vorlegen (die aber bei vielen Anfragen schon mal sehr lang ist und mit viel Scrollen verbunden ist)
Doch muss das sein? Wäre es nicht viel angenehmer, das Cartrigde entscheidet selbst wo gespielt werden soll? Wenn ich bei N 50° 56.000, E 6° 57.000 stehe macht es wenig Sinn die norddeutsche Version zu wählen. Nein einzig "Köln" ist die Option, die für mich Sinn macht.

Abbildung 1: Definition der möglichen Locations und Auswahl der nähesten

Wie soll aber das Cartrigde entscheiden welches die richtige Version ist? Natürlich über die Abstände zu den Spielfeldern. Dabei definiert man die möglichen Orte als Array wie in Zeile 227 - 229 zu sehen ist.
In diesem Beispiel geht es um ein rechteckiges Spielfeld bei dem ObenLinks und UntenRechts als Koordinaten definiert sind. Wie man daraus ein Rechteck berechnet seht ihr im nächsten Teil. (Wird die Tage folgen)
Zur Abstandsberechnung nehmen wir die Ecke ObenLinks.

Danach misst man mit Player.ObjectLocation seinen eigenen Standort. Der Rest ist ein ganz normaler Min/Max-Algorithmus:
  • Schaue für jede Location ob sie näher ist als das bisherige Minimum. (Oder ob es noch kein Minimum gab)
  • Wenn dem so ist, setze die Entfernung zur Location als neues Minimum und merke dir den Index. (Die Entfernung brauchen wir für den nächsten Vergleich und den Index als Pointer auf die Location)
Nach der Schleife über alle Locations greifen wir mit dem Index auf die näheste zu und setzten den die beiden Punkte für unser Rechteck.

Donnerstag, 28. März 2013

Die Auslösung von OnDistant kontrollieren

Voraussetzung: Zone als Kreis sowie "Der Einstieg in lua“
In früheren Tutorials berichte ich über die Möglichkeit bei einer punktförmigen Zone die Ereignisse OnProximity und OnDistant zu nutzen. Dabei kam es zu dem Problem, dass OnDistant bereits beim Start des Spieles ausgelöst wird.
Wie sich mittlerweile herausgestellt hat, wird OnDistant genaugenommen nicht beim Start sondern bei der ersten Aktvierung der Zone ausgelöst. (Beim vergangenen Tutorial waren alle betroffenen Zone von Start an aktiv, deswegen der Trugschluss mit OnStart).

Abbildung 1: Bei der ersten Auslösung wird das OnDistant-Events ignoriert
Eine einfache Abfrage ob wir uns in der Startphase befinden reicht also nicht mehr aus. Statt dessen müssen wir für jede Zone pflegen, ob das OnDistant-Event zum ersten Male ausgeführt wird (nämlich weil wir gerade die Zone zum ersten Mal aktivieren) oder ob der Spieler gerade den Proximity-Bereich verlässt, denn für diese Aktion wollen wir OnDistant ja nutzen. Dazu bauen wir uns ein Array namen OnDistantRegistry auf, dessen key aus dem Zonennamen besteht und als Value nil also nix oder der Booleanwert true möglich sind. Es könnte auch 1 oder "TradisSindDoof" als Wert genommen werden. Wichtig ist, dass der Vergleich OnDistantRegistry[zoneName] == nil fehlschlägt und da Boolean nur zwei Werte (true oder false) annehmen kann, verbrauchen wir auch nicht viel Speicher für unsere Registry.
In der Methode skipOnDistantEvent geben wir so true zurück, wenn die Methode zum ersten Mal mit diesem Zonennamen aufgerufen wird, ansonsten false.

Abbildung 2: Zu Beginn des OnDistant-Events fragen wir ab, ob wir abbrechen sollen oder ob der Code ausgeführt wird.
Sobald wir für eine Zone ein OnDistant-Event nutzen wollen, fragen wir zu Beginn ab, ob wir die Befehlsfolge des Events abarbeiten oder ob wir mit return abbrechen. 'Abbrechen' bezieht sich aber nur auf das OnDistant-Event nicht auf das Spiel selbst. Dieser Code muss auch nur in den OnDistant-Events eingesetzt werden, die auch wirklich genutzt werden.
Einen Hacken gibt es aber trotzdem bei dieser Lösung: Die fehlerhafte Auflösung des Events bei Aktivierung erfolgt nur, wenn der Spieler sich nicht in der Zone befindet. Sollte eine Zone aktiviert werden in deren onProximity Bereich der Spieler steht (Zufällig beim Start des Spieles, zwei Zonen überlappen sich, Zonenaktivierung durch Timer oder onClick-Events) so hat dies zur Folge, dass beim ersten Verlassen, besser gesagt Distanzieren von der Zone, die Abarbeitung ausfällt, da es für die Registry der erste Aufruf ist. Aber diese Fälle sind im Normalfall nicht zu erwarten und da dann auch das onProximity-Event nicht immer zuververlässig funktionert, kann der Fehler behoben werdene, indem der Spieler einmal raus und wieder rein in den Proximity-Bereich rennt.

Abbildung 3: Die globalen Events werden immer von den Zonen spezifischen ausgeführt
Diese Möglichkeit der Überprüfung ob das Event ausgeführt werden darf oder nicht, umfasst leider in dieser Form nicht die globalen Events, die automatisch für jede Zone ausgeführt werden sollen. Das liegt daran, dass globale Events ein Kunstgriff von Urwigo sind.
Abb. 3 zeigt die Umsetzung im von Uwrigo erzeugten lua Code. Dabei wird für jedes Event die currentZone definiert (deswegen können wir auch beim Aufruf der Prüfmethode auf die Variable zurückgreifen), dann die Methode mit der globalen Eventdefinition aufgerufen und zum Schluss der Zonenspezifische Code eingefügt. Zeile 352 und 353 sowie 362 und 363 werden automatisch von Urwigo erzeugt.
Man kann jedoch in der globalen Definition (da wo z.Zt lediglich "Globales OnDistant ausgeloest" steht) eine ähnliche Abhandlung wie in skipOnDistantEvent einbauen, nämlich if onDistantRegistry[currentZone] == nil then return end. Wichtig ist hier keine boolean Wert zu setzen ansonsten würde der nächste Aufruf (Zeile 354 bzw. 364) als zweiter Aufruf für die Zone erkannt und true zurückgeliefert.

Ich habe noch ein wenig mit Closures herum experimentiert um den skip-Check komplett in den global Event Teil zu schieben. Leider habe ich noch keine Möglichkeit gefunden, wie man in lua mehrere Methoden mittels return abbrechen kann. Also a() ruft b() auf und ein "return(2)" oder so ähnlich in b soll nicht nur die Methode b verlassen, sondern auch zum Abbruch der Methode a führen. Falls hier jemand weiter weiß, bin ich um einen Tipp sehr dankbar.

Samstag, 15. Dezember 2012

I18n - Einen Wherigo in verschiedenen Sprachen anbieten

This is the german version of the article. It is also available in english

Voraussetzung: "Der Einstieg in lua“
"Internationalisierung bedeutet in der Informatik beziehungsweise in der Softwareentwicklung, ein Programm so zu gestalten, dass es leicht (ohne den Quellcode ändern zu müssen) an andere Sprachen und Kulturen angepasst werden kann."
So startet der aktuelle Wikipedia-Artikel das Thema Internationalisierung.
In diesem Tutorial erfährst du wie man unter Nutzung des ResourceStrings-Ansatzes dein Cartrigde in mehreren Sprachen anbieten und dabei deinen eigentlichen Code vom Sprachproblem abkoppeln kannst.

Abbildung 1: Definition der Textausgaben in allen drei Sprachen
Wir beginnen mit der Definition einer Variablen language, die wir auch gleich als persistente Variable so ans Cartrigde anheften, dass es einen Save/Reload Vorgang übersteht.
Anschließend wird ein Array messages erzeugt indem die unterschiedlichen Textausgaben, sortiert nach Sprachen festgehalten werden.
Jetzt wird sich so mancher Entwickler fragen, warum speichert das messages-Array seine eingestellte Sprache nicht selbst sondern nutzt eine extra Variable. Der Grund hierzu liegt einzig in der Persistenzproblematik. Wir wollen ja auf keinen Fall das gesamte Array persistieren, denn an diesen Werten ändert sich zur Laufzeit nichts. Der einzig dynamische Teil ist die eingestellte Sprache, die nach dem Start z.B per Multiple-Choice ausgewählt werden kann. Um ein aufwendiges OnSave / OnRestore Prozedere zu verhindern, nutzen wir lieber den Workaround über die zweite Variable.
Für jede Sprache (in unserem Beispiel Deutsch, Englisch und Französich) wird ein Subarray wie in Zeile 5-7 definiert. Danach beginnt die eigentliche Stringdefinition.
Die Idee dabei ist, dass im Cartrigde an der Stelle wo für die Begrüßung des Spieler ein übersetzter String ausgegeben werden soll, der key hello verwendet wird und je nachdem welche Sprache eingestellt ist "Herzlich Willkommen", "Welcome" oder "Bienvenue" ausgegeben wird.
Bevor wir jedoch zum Auswerten des passenden String kommen, möchte ich noch auf ein paar Besonderheiten hinweisen. Aufmerksame Codestudenten werden bereits festgestellt haben, dass es für den key hi kein Wert für die englische Version definiert wurde. Wäre die Sprache auf en gesetzt, würde hier der Wert der defaultLanguage (siehe Zeile 4) gezogen. Wie man so etwas auflöst folgt weiter unten. In Zeile 13-18 stehen Beispiele für Verwendung von Platzhaltern, wenn z.B der Name des Spieler in den übersetzten String eingebaut werden soll.

Die Sprache ist aktuell auf Deutsch gestellt. Man kann aber bei Start des Cartrigdes durch eine einfache Multiple-Choice-Abfrage die Sprache wählbar machen und dann language auf en oder fr setzen.

Abbildung 2: Abfrage eines übersetzten Keys
Nach der Definition geht es zur Verwendung der ResourceStrings. Zeile 20 wählt aus dem messages-Array zunächst das für die Sprache passende Subarray (hier messages.de und daraus der Eintrag für unseren key. Wird diese Funktion mit dem key hello aufgerufen, so besitzt value den Wert von messages.de.hello also Herzlich Willkommen zum i18n Wherigo.
Was aber passiert, wenn die Sprache auf en eingestellt ist und der key hi ausgewählt wird. message.en.hi ist nämlich nicht definiert. Dieser Fall ist in Zeile 22-24 behandelt. Dort wird auf die defaultLanguage zurückgegriffen. Man sollte also darauf achten, dass für die defaultLanguage immer ein Wert definiert ist.
Im Idealfall definiert man immer alle Sprachen direkt, in der Praxis kann es aber vorkommen, dass man Erweiterungen (im Wherigo kommt eine Station hinzu und damit neue Sprachelemente) für Deutsch und Englisch direkt eintragen kann, für die spanische Version aber noch warten muss bis der passende Übersetzer (Der Austauschcacher aus Barcelona) Zeit hat die Strings zu vervollständigen.

Abbildung 3: Textausgabe mit Parametern
Bleibt noch die Sache mit den Parametern, z.B der Name des Cachers. Im definierten key können Platzhalter mit einer Zeichenkombi codiert werden. Es sollte eine Kombi sein, die man sonst nicht verwenden muss. Ich habe mich deswegen für #1# entschieden. Bei der Auflösung des ResourceStrings wird dieser Platzhalter mittels string.gsub durch den betreffenenden Wert ersetzt. Abb. 3 zeigt die überarbeitete getMessage Methode in der wir Platzhalter ersetzen können.
getMessage("helloWithName","Krolock") liefert für die deutsche Version "Hallo Krolock, wie geht es dir?"
Um die Anzahl der Parameter im String flexible zu halten, bedienen wir uns des variablen vararg Argumentes "...". Man kann die Funktion getMessage neben dem key mit keinen, einem oder mehreren weiteren Parametern aufrufen.
Beipspielaufrufe:
getMessage("hello")
getMessage("helloWithName","Krolock")
getMessage("openDoor","XYZ","A15N")

Mittels #arg werden die Anzahl der Argumente ermittelt, auf die dann mit arg[1], arg[2] usw zugegriffen wird

Abbildung 4: Aufrufbeispiel der Übersetzung
Bei mehreren Parametern muss aber auf die Reihenfolge achten, wie man an Zeile 16-18 erkennen kann. Für den key openDoor gibt es unterschiedliche Reihenfolgen in der deutschen und englischen Version
Abb. 4 zeigt uns wie man die Übersetzungen im Wherigo aufrufen und benutzen kann. Jeweils ein Beispiel für keinen, einen oder zwei Platzhalter.
Wie sich heute (10.03.14) erst gezeigt hat, zeigt der WhereYouGo-Player bei dem vararg Parameter ein etwas anderes Verhalten. Wenn die Methode getMessage ohne zweiten Parameter aufgerufen wird (also nur mit key) so ist arg kein leeres Array sondern nil. Um dem vorzubeugen fügen wir in Zeile 25 if arg ~= nil then ein, was wir in Zeile 29 mit end wieder schließen.

Eine Kleinigkeit fehlt noch, nämlich die strikte Trennung von Code und Übersetzung. Im Konzept der ResourceBundles trennt man diese Aspekte insofern, als dass für jede Sprache eine eigene Datei angelegt wird.
Man könnte also eine Datei messages_de.lua, messages_en.lua und messages_fr.lua erzeugen und diese dann mit require "messages_de" usw. einbetten. Software-Entwicklungsumgebungen wie eclipse oder Intellij IDEA bieten hier viel Unterstützung bei der Pflege der Übersetzungen.
Diese Trennung ist aber optional. Ihr könnt auch alles im lua-Teil des Urwigos direkt eingeben, wenn es nicht zu unübesichtlich wird.

Update 20.04.14: Da auch der PiGo-Player Probleme mit vararg zu haben scheint, schaut euch auch den Folgeartikel Sprachausgaben übersetzten ohne vararg an.

Donnerstag, 25. Oktober 2012

7 Zonen und das Oregon unmöglich! ... Oder doch?

Voraussetzung: "Mit lua alle Zonen des Wherigos erfassen“

Als Oregon Nutzer aber auch als Wherigo-Programmierer ist man bestimmt schon des Öfteren über das Problem gestolpert, dass die Garmin-Hardware einfach nicht mehr auf Höhe der Zeit und somit viel zu langsam ist. Recht deutlich wird dies, wenn in einem WIG recht viele Zonen gleichzeitig aktiv sein sollen. Im Geoclub liest man von maximal sieben Zonen, die das Oregon gerade noch so vertragen soll. Meine 300er Version funktionierte zwar auch noch mit 9 gleichzeitig aktiven Zonen, doch die Reaktionsfähigkeit inkrementierte in den Minutenbereich. Auf deutsch: Will man ne Zone, eine Person auswählen oder einfach nur den Bildschirmtext mit "OK" bestätigen muss man darauf gefasst sein, dass das Gerät erst nach 30 bis 60 Sekunden reagiert. Von Spielvergnügen ist da schon lange keine Rede mehr und die Gefahr eines Absturtzes steigt ebenso an.

Abbildung 1: Die Zonen nach Entfernung sortieren
Es muss also Abhilfe her. Die einfache Lösung lautet daher: Nie so viele Zonen gleichzeitig aktiv schalten.
Das ist natürlich meistens nicht so einfach möglich. Besonders bei dynamischen Spielen, wo der User selbst entscheiden kann, wo er hingehen möchte scheint dies erstmal unmöglich. Was aber möglich ist, dass nur die nächsten n (z.B 5) Zonen angezeigt werden. Die Idee dabei ist, dass man die Zonen nach Entfernung zum Spieler sortiert und die ersten n Zonen angezeigt werden. Ein Timer kann alle paar Sekunden überprüfen, welche Zonen am Nähesten liegen und entsprechend den Active-Status umsetzen. Einfach gesprochen, doch wie wird es umgesetzt.
Als Voraussetzung muss man auf alle Zonen zugreifen können. "Mit lua alle Zonen des Wherigos erfassen“ zeigt wie man automatisch alle Zonen erfasst und in ein Array namens zones steckt.
Haben wir alle Zonen greifbar, so messen wir unsere eigene Position mit Player.ObjectLocation. Anschließend iterieren wir über alle Zonen und messen die Entfernung des Spielers zur Zone (also zum Mittelpunkt zone.OriginalPoint).
Diese Entfernung wird in der neu geschaffene Eigenschaft DistanceToPlayer gespeichert (Lua ist hier sehr großzügig und erlaubt eine Erweiterung der Objekte um neue Attribute zur Laufzeit, da Objekte intern auf Tabellen umgesetzt werden) bevor wir die Zone in die distTable einfügen. Zeile 18 bis 20 definiert einen Komparator, der in Zeile 21 der Tabelle zum Sortieren mitgegeben wird.
Für die technisch versierten Leser sei erläutert, dass bei jedem table.insert das neue Element mit dem ersten Element der bestehenden Tabelle verglichen wird. Dazu wird die Vergleichsoperation aufgerufen, die wir oben definiert haben. Ist der Vergleich positiv (hier die Entfernung zum Spieler ist kleiner), so wird das neue Element an die aktuelle Stelle eingefügt und die nachfolgenden Elemente eins weiter nach hinten geschoben. Resultiert aus dem Boolean-Vergleich false, so wird das neue Element mit dem zweiten Element der Tabelle verglichen, u.s.w. bis entweder eine Stelle im Vergleich true liefert oder das Ende der Tabelle erreicht ist.
Beim Verlassen der Methode buildSortedActiveZoneMap() wird die nach Entfernung sortierte Tabelle der Zonen zurückgeliefert.
Bleibt noch die Frage warum in Zeile 11 zone.Active2 und nicht zone.Active verwendet wird. Die Antwort darauf ist relativ simple. Active2 signalisiert nicht, dass die Zone gerade aktiv ist, sondern dass sie zu den Zonen gehört, die aktiv sein dürfen. (Manche Zonen werden ja erst im Laufe des Spieles freigeschaltet). Auch Active2 gehört zu den Attributen, die wir zur Laufzeit einfach an das lua-Objekt Zone anhängen.

Abbildung 2: Die nähesten Zonen werden aktiviert
Nachdem die Zonen nach Entfernung sortiert sind, werden alle Zonen deaktiviert und anschließend die n nähesten Zone aktiviert. Damit aber nachher im Spiel nicht der Effekt auftritt, dass nacheinander die aktiven Zonen verschwinden um dann langsam wieder aufzutauchen, erweitern wir das Zonenobjekt um ein weiteres Attribute Active3. Dadurch bleibt für bereits aktive Zonen, die immer noch nahe genug sind der Status erhalten und wecheselt nicht von zone.Active = true auf false und wieder zurück auf true. Sieht nicht sehr schön aus im Oregon.
Zeile 30 bis 32 stellt sicher, dass es zu keinem Fehler kommt wenn mehr Zonen erlaubt sind als Zonen im Spiel freigeschaltet sind (beim Start darf der Spieler nur zwei Zonen sehen, das Oregon würde aber 5 verkraften)

Abbildung 3: Bei Initialisierung und Aktivierung darf Active2 nicht vergessen werden
Um die Sortierfunktion aufrufen zu können, bedarf es ein wenig Vorbereitung und Pflege der Zonen. So muss man beim Start des Spieles für alle Zonen den Wert Active2 initialisieren, da sonst der Vergleich in Zeile 11 fehlschlagen würde.
Außerdem darf beim Freischalten einer Zone dieses Attribute nicht vergessen werden, sonst wird dises bei dem timergesteuerten Nähecheck nie einbezogen.
Zum Schluss noch ein paar Worte zur Performance: Gerade die table.sort Funktion ist sehr rechenintensiv und kostet viel CPU. Man sollte auf Geräten wie Oregon also vorsichtig damit sein. Je weiter die Zonen voneinander entfernt sind, desto seltener muss der Timer den Vergleich auslösen. Wenn ich mindestens ne Minute brauche um die nächste Zone zu erreichen, so reicht es aus, alle 30 Sekunden zu aktualisieren. Sind dagegen meine Zonen nur 20 Meter entfernt, ist es besser das Interval auf 2 oder 3 Sekunden zu stellen.

Abbildung 2: Der Timer wird nur für die Garmin Geräte aktiviert.
Jetzt ist dieses Problem ja hauptsächlich ein Garmin Problem, da die meisten Nicht-Oregon-Cacher mit dem Smartphone unterwegs sind und da das Hardwareproblem nicht so entscheidend ist. Es wäre folglich schön, wenn man diesen Workaround auf die Garminserie einschränken können. Dies kann über die Abfrage des Environments geschehen. Dort sind viele nützliche Infos enthalten, u.a. die Geräteplatform. Diese lässt sich mit Env.Platform abfragen und lautet bei Garmin Geräte "Vendor 1 ARM9". Diese Abfrage lässt sich nutzen um zu entscheiden, ob man den ZoneChecker-Timer startet und nicht. Wird er nicht gestartet, so werden keine Zonen deaktiviert und es kommt wieder zum alten Verhalten. Ob das sinnvoll und übersichtlich ist, hängt natürlich von der Spielidee ab.
Weitere Infos zum Thema herstellerspezifische Spielsituationen findet ihr im Geoclub Thread Spielverlauf durch Geräteabfrage bestimmen und im WherigoBuilder-Wiki.

Dienstag, 16. Oktober 2012

Mit lua alle Zonen des Wherigos erfassen

Voraussetzung: "Einstieg in Lua“

Abbildung 1: Definition und Aufruf des lua Codes
Wenn man viel mit lua arbeitet kommt man auch desöfteren an den Punkt, an den man alle Zonen oder alle Character usw. durchiterieren möchte. (Demnächst folgt bereits das erste Anwendungsgebiet beim 7-Zonen-Problem). Um alle Zonen auf einmal zu erfassen, muss man sich ein wenig mit der von Wherigo aufgebauten Datenstruktur auskennen und die Kompatibilität mit allen WIG-Playern im Auge behalten.

Abb. 1 zeigt uns wie es gehen kann: Der Trick dabei ist, dass man über das Cartrigde (angesprochen über den Identifier auf das Feld AllZObjects zugreifen kann. Dort sind alle Objekte (Zonen, Character, Item, Media, Inputs ...) gespeichert, über die man mit einer for Schleife iterieren kann. Nachdem der erste Ansatz über den Klassennamen nicht auf allen Geräten funktioniert, habe ich den Ansatz aus der grünen Hölle umgesetzt und entscheide mittels der tostring Methode ob das Objekt eine Zone ist oder etwas anderes. Der Sicherheit halber wird beim String-Vergleich nicht nach Groß- und Kleinschreibung unterschieden.

Vergesst nicht die -per default nicht persistierete- Variable 'zones' den ZVariables (siehe Zeile 3) hinzu zu fügen, damit das Zonenarray auch nach einem Save/Restore noch zur Verfügung steht.

Der Aufruf der Methode getAllZones muss mit dem Identifier des Cartridges (nicht dem Namen) geschehen.

Noch eine Anmerkung zum Schluss: Das Cartrigde-Objekt besitzt zwar eine komfortable Methode GetAllOfType('ZZone'), diese funktioniert aber nicht auf allen Playern, weswegen der händische Weg, wie oben beschrieben empfehlenswerter ist.

Mittwoch, 18. Juli 2012

Tagescode

Voraussetzung: "Einstieg in Lua“
Wer kennt das nicht, man erstellt einen aufwendigen Cache, überlegt sich aufwendige Fragen und knifflige Aufgaben. Der FTF dauert auch einige Zeit, da es ja ein sehr aufwendiger Cache ist.
Cachefund Nr. 2 und 3 drudeln dann schon schneller ein und spätestens wenn Person X den Cache gefunden hat, steigt die Anzahl der Funde exponentiell an. Person X gehört nämlich zur lokalen Mystery-Tausch-Szene, die über eMail-Verteilerlisten Finalkoors von aufwändig gestalteten Döschen weiterverbreiten.
Wer diesen Misbrauch bei einem Wherigo verhindern oder besser gesagt erschweren möchte, der kann eine Art Checkcode einbauen, den man am Ende des Spiels erhält und in den Log schreiben bzw. per eMail senden muss. Es geht in diesem Beitrag nicht darum zu klären, ob dies bei einem WIG zulässig ist, bzw. ob man den Misbrauch kontrollieren sollte oder die Spielverderber einfach sich selber bescheißen lassen sollte. Diese Fragen kann man gerne in bekannten Foren diskutieren.

Abbildung 1: Generierung eines Tagescodes
Hier geht es lediglich um die technische Umsetzung eines Tagescodes. D.h ein Code, der nur für einen Tag lang gültig ist, der (relativ) eindeutig ist und was ganz wichtig ist, den man nicht zurückrechnen kann, um den Code für den nächsten Tag zu berechnen.
Man nennt so einen Vorgang eine Einwegfunktion. "Leicht" zu berechnen aber "schwer" umzukehren. (Siehe http://de.wikipedia.org/wiki/Einwegfunktion).
Abb. 1 zeigt die Erstellung einer Einwegfunktion mittels Multiplikation und ganz wichtig einer Modulo Division. Heute kommt als Ergebnis 5919, morgen 6466 und übermorgen 476. Wer kann daraus den Code für nächste Woche Montag berechnen? (Streng genommen wäre es möglich, wenn man nicht nur drei sondern hunderte Werte hätte, aber der zu betreibende Aufwand stände in keinem Verhältnis zum Nutzen)
Nachtrag vom 20.07: Leider hat sax (siehe Kommentar) völlig recht. Man kann relativ leicht a und b berechnen. Es muss nur die Formel bekannt sein. D.h. um die Sache etwas sicherer zu machen, darf die Formel nicht bekannt sein. Addiert z.B nach dieser Rechnung einen Wert c auf das Ergebnis multipliziert es mit d und rechnet anschließend wieder Modulo e. Das ist zwar immer noch nicht wasserdicht, aber wenn ihr nicht zwei Moduloschritte sondern 3, 4 oder 5 Mal den Rest nehmt, wird es immer schwieriger auf die Formel zu kommen. Ich werde demnächst ne Lösung mittels Hashfunktion, MD5 oder Public-Key als asymmetrisches Verschlüsselungsverfahren untersuchen und vorstellen. Immer vorausgesetzt, dass auch das langsame Oregon die Daten schnell genung berechnen kann.

Wenn man sich die Abbildung genauer betrachtet, wird man feststellen, dass auch Daten wie 31. April oder 30. Februar erfasst wird, aber das stört uns nicht. Wichtig ist nur, dass jeder Tag dabei ist und dass der Code eindeutig ist. Das mit der Eindeutigkeit stimmt nicht ganz hundertprozentig, aber je größer die beiden Zahlen für die Division und die Modulo Rechnung sind, desto unwahrscheinlicher ist es, dass im Log zweimal der gleiche Code auftaucht.

Abbildung 2: In drei Schleifen wird für jeden Tag, Monat und Jahr der Tagescode erzeugt und ausgegeben
Man sollte die beiden Zahlen auf jeden Zahlen aus dem Tutorial abändern, denn sonst kann jeder den Code berechnen.
Da wir eine Einwegfunktion benutzen, können wir natürlich anhand des vom User eingegebenen Codes nicht zurückrechnen, ob das Datum und damit der Code korrekt ist. Abhilfe schafft hier eine Tabelle, die wir uns erzeugen, damit können wir für den entsprechenden Tag nachschauen, ob der Code korrekt ist.
Abb. 2 zeigt drei ineinander geschachtelte Schleifen, die für die Jahre 2012, 2013 und 2014 für jede Kombination aus 31 Tagen und 12 Monaten den Code ausrechnen und mittels print ausgeben. Natürlich kann man die Funktion noch verfeinern, dass der 31. April nicht mit ausgegeben wird, es würde unsere Funktion aber unverhältnismäßig komplex werden lassen.
Da wir die print Ausgabe im WIG-Player nicht sehen können, bedienen wir uns des Urwigo-Simulator um die Tabelle anzuschauen. Einfach das Projekt CodeGenerator mit F5 starten und auf den Reiter "Lua Debug" klicken, um die Werte zu betrachten, zu markieren und in eine Textdatei zu sichern.

Abbildung 3: So gelangt man zur Print-Ausgabe im Simulator.
Wem das noch nicht genug Kontrolle ist -schließlich könnten so immer noch Rudelteilnehmer loggen, wenn nur einer die Aufgabe erfüllt hat- kann man noch den Username (Zugriff in lua mittels Player.Name) mit in den Code einkodieren. Dazu nutzen wir die string.byte(myString, position) Funktionalität, die die einzelnen Buchstaben in ihren ASCII-Code umwandeln. string.byte("ABCDE",1) liefert z.B 65, da dies der Code für das erste Zeichen im String, nämlich A ist. Um den Usernamen komplett zu erfassen, kann man über alle Buchstaben iterieren und die Summe der ASCII-Werte der Variablen days aus Abb. 1 hinzufügen. Doch Vorsicht: Hierdurch steigt der Testaufwand um ein Vielfaches, denn nicht nur Groß- und Kleinbuchstaben ("klein a" besitzt den ASCII-Code 97) müssen abgedeckt werden, auch Leerzeichen Umlaute, Zahlen und alle anderen Zeichen, die Groundspeak als Usernamen erlaubt, müssen durch die Funktion erfasst werden. Ebenso steigt der Wartungsaufwand für den Cache, da man bei jedem Log, manuell den Namen in den Codegenerator eingeben muss.
Wer sich hierfür interessiert, kann mir ne Mail zukommen lassen, damit er ein kleines Java-Progrämmchen erhält, mit dem Eingabe und Codeausgabe schneller gehandelt werden können. Zu bedenken ist auch, dass man die Cachesuche erheblich erschwert: Jeder WIG-Spieler muss sich das Cartridge selbst herunterladen und dabei auf wherigo.com mit dem Usernamen eingeloggt sein, mit dem er auf geocaching.com loggen möchte. Außerdem schließt man so auch alle Cacher aus, die keine WIG-fähiges Gerät besitzen und so nicht an den Code kommen.
Mir persönlich wäre es definitiv zu viel Kontrolle, aber das muss jeder selbst entscheiden. Ich möchte lediglich auf die technische Möglichkeit hinweisen.

Freitag, 15. Juni 2012

Wie man einen PlayAnywhere Wherigo schreibt

Voraussetzung: "Einstieg in Lua“

Abbildung 1. Aus zwei gemessenen Punkten lässt sich Abstand und Winkel bestimmen.
In diesem Tutorial wollen wir uns mit der PlayAnywhere (kurz PA) Variante der WIGs befassen. PlayAnywhere heißt hier, dass der Spieler festlegt, wo er das Cartridge spielen möchte.
Da wir die Zonen folglich nicht vorher bestimmen können, muss dies der Spieler tun.
Das Zauberwort lautet Player.ObjectLocation. Diese Funktion liefert uns ein ZonePoint Objekt, dass die Koors des aktuellen Spielstandortes Preis gibt.
Wir stecken uns folgendes Ziel: Der Spieler soll zwei Punkte festlegen, anhand deren wir dann ein Quadrat berechnen. Die Seitelänge des Quadrates ist der Abstand der beiden Koordinaten und die Verbindung zwischen Ihnen eine Seite des Vierecks.

Abbildung 2: Die erste Position wird gemessen
Wir geben eine MessageBox aus mit der Bitte zur ersten Position zu gehen. Sobald der OK-Button der MessageBox gedrückt wird, halten wir die aktuelle Spielerposition in der Variable position1 fest
So weit zu gut, eine Ecke des Quadrates ist bestimmt. Um die zweite Ecke zu bestimmen fügen wir, ebenfalls im "On clicked" Bereich der MessageBox eine zweite Nachricht ein und wiederholen so die Prozedur der Aufforderung und Messung.

Abbildung 3: Nachdem die zweite Position bestimmt wurde, werden die Ecken 3 und 4 errechent.
Nachdem die zweite Position in position2 gespeichert wurde beginnt die eigentliche Arbeit. Die vorgefertigte Funktion VectorToPoint, enthalten in der eingebundenen Wherigo-Bibliothek erwartet als Parameter zwei ZonePoint Objekte und berechnet Abstand und Winkel der beiden Positionen. Der Winkel wird in einem 360° Kreis gemessen (Osten 90°, Süden 180°, Westen 270° usw) und sollte uns von Peilungsaufgaben bekannt vorkommen. In Abb. 1 würde der Winkel bei etwa 340° liegen sollte.
Um es genau zu wissen, geben wir Abstand und Winkel in einer print Nachricht aus, die nur im Urwigo-Simulator, nicht auf dem WherigoPlayer (Oregon, WhereYouGo...) angezeigt wird. Da das Distance-Objekt d nicht automatisch in Meter angegeben wird, bestimmen wir den Wert mit d:GetValue"m".
Vorsicht: d:GetValue("m") ist zwar syntaktisch auch korrekt, funktioniert aber nicht auf allen WIG-Playern. Bei unserem Beispiel sieht die Ausgabe folgendermaßen aus:
Winkel 338.97394387323 Grad
Entfernung 49.227289103145 m
    
Als kleine Zwischenaufgabe wollen wir einen Wegpunkt bestimmen, der 5 Meter Richtung Osten von Position 1 entfernt ist. Dazu erzeugen wir mit Wherigo.Distance(5,'m') ein entsprechendes Distance-Object und mit Wherigo.TranslatePoint einen neuen ZonePoint, ausgehend von Position 1, 5 Meter in Richtung 90 ° gehend. Die Vektorrechnung aus der Schulzeit lässt grüßen.

Nun aber genug getestet, wir benötigen die dritte Ecke (oben rechts, siehe Abb. 4) unseres Quadrates. Was wir über diese Ecke wissen, ist, dass ihr Abstand zu Position 2 der gleiche ist, wie der zwischen 1 und 2. Beim Quadrat sind ja allen Seiten gleich lang. Für Wherigo.TranslatePoint haben wir also schon den Ausgangspunkt position2 und den Abstand d. Wie aber kommen wir zum Winkel b2 ? Diese Frage lässt sich recht schnell beantworten, wenn wir bedenken, dass die Innenwinkel eines Quadrates 90° sind. Winkel b2 ist also Winkel b zuzüglich 90°. Für den Fall, dass der Winkel größer als 360 wird, nehmen wir mittels Modulo den Wert zwischen 0 und 360. Mit dem so errechneten Winkel können wir Position 3 und von dort ausgehend wieder 90° weiter Position 4 errechnen. An der Zeile b3 = b2 + 90 sehen wir, dass wir die Modulo-Operation auch weglassen können, da die in lua genutzen Sinusfunktionen auch Winkel größer 360° vertragen.
Zuletzt setzen wir mit myZone.Points die vier bestimmten bzw. errechneten Position für unsere Zone myZone. Damit die Zonendarstellung im Wherigo-Player aktualisiert wird sollten wir die Sichtbarkeit der Zone mit myZone.Visible zuerst auf false und anschließend wieder auf true setzen.
!!! Damit ihr eure Zone mit myZone ansprechen könnt, müsst ihr bei der Definition der Zone in Urwigo nicht nur den Namen sondern auch den Identifier auf myZone setzen.

Abbildung 4: Unser Quadrat ist fertig und kann als Zone im PlayAnywhere-Cartridge genutzt werden.
Wir haben somit die Koordinaten der Zone auf den Standort gesetzt in dem der Spieler das Cartridge spielen möchte.
Das Handling mit Quadraten als Zone ist aber nicht zwingend erforderlich um einen PA zu erzeugen. Wenn die Zone ein Viereck sein soll, kann man auch die ersten 3 Ecken bestimmen lassen. Somit hat der Spieler auch eher eine Vorstellung wo sich das Spielfeld befindet und kann auch auf nicht rechtwinkligen Spielfeldern (wer weiß noch was ein Parallelogramm ist ?) die Zone definieren.

Abbildung 5: Bei der PA-Variante von Cachopoly werden alle 4 Ecken bestimmt und die Zwischenstationen ausgerechnet.
Ebenso denkbar ist, dass bei einem Spielfeld wie Cachopoly alle 4 Ecken gemessen werden. Hier werden die 4 Eckfelder (Homezone, Hochsitz, Parkplatz und Jäger) als Kreiszonen bestimmt und die Zwischenstationen (Muggelhausen, Filmdosenverleih, Stammtisch, Enigma...) berechnet, indem man den Winkel Homezone -> Hochsitz nimmt und von der Homezone aus 1/5, 2/5, 3/5 bzw. 4/5 der Entfernung für die Peilung mit Wherigo.TranslatePoint heranzieht. Grundgedanke ist aber immer, dass der Spieler genügend ZonePoints setzen muss, um das Spielfeld eindeutig aufzuziehen. Wer sich Spielfrust ersparen möchte, sollte im Cachelisting genau beschreiben was der Spieler gerade ausmisst. ("Setze mit Punkt A und B eine Gerade. Das Spielquadrat befindet sich anschließend rechts von dieser")