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.

Montag, 15. Oktober 2012

Automatisches Speichern: Wie und Wann?

Voraussetzung: "Einsteigerkurs“

Abbildung 1: Beim Erreichen der Zone 'Rathaus' wird automatisch gespeichert.
Eine der entscheidenen Fragen, ob der Spielablauf vom Cacher als angenehm empfunden wurde, oder ob er sich schwarz ärgert und damit seinem Frust in den Logs Luft lässt ist der automatische Speichervorgang und wann er ausgeführt wird.
Es geht dabei nicht um das expliziete Speichern, dass der Spieler veranlassen kann, wenn er das Cartrigde verlässt, sondern darum, dass während des Spieles immer wieder automatisch gespeichert wird, ohne dass der WIG-Läufer davon Wind bekommt.
Abb. 1 zeigt eine einfache Möglichkeit wie dies geschehen kann: Wir gehen zum 'Actions' Bereich und ziehen den Baustein 'Save game' an die entsprechende Stelle im Flussdiagramm. Das war es schon.

Abbildung 2: Egal welche Zone wir verlassen, es wird immer gespeichert.
Nachdem wir gesehen haben, WIE es geht, stellen wir uns die Frage WANN wir speichern. Dabei ist entscheidend was nach dem Fortsetzen geschieht. Speichern wir beim Zoneneintritt, so muss der Spieler beim Wiederherstellen des Cartrigdes die Aufgabe der Zone (z.B: "Tippe den langen lateinischen Satz ab") nochmals lösen. Das kann schonmal schwierig werden, wenn ihr 800 Meter weiter kurz vor der nächsten Station seid und das Cartridge abstürzt. Dieses Problem können wir damit umgehen, dass wir beim Verlassen der Zone speichern. Tritt dann unterwegs ein Problem auf, so ist die Aufgabe beim Wiederherstellen das Erreichen der nächsten Zone. Und auf dem Weg dahin, sind wir ohnehin.
Abb. 2 zeigt uns aber nicht das Speichern beim Verlassen einer Zone (das müssten wir ja bei jeder Zone einstellen) sondern wir nutzen das Global Zone Event 'On zone exit'. Dieses wird beim Verlassen jeder Zone ausgeführt und zwar bevor der inidivuelle Teil einer bestimmten OnExit Implementierung ausgeführt wird.

Die Variante mit dem Speichern beim Verlassen soll aber nicht der Weisheit letzter Schluss sein. Mich interessiert eure Meinung: Wann lasst ihr eure Cartridges automatisch speichern und was sind die Bewegründe, Vor- und Nachteile. Lasst es mich und die restliche WherIBlog-Communiction durch einen Kommantar wissen.