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.
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
Außerdem wird die Variable
Um das zu verstehen muss man wissen, dass die Methode
Wenn wir die obigen Vergleiche nicht einbauen, wird die
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
Am Ende des Listing beginnt die Implementierung von
Bei der weiteren Abarbeitung des Feld bitte nicht vergessen ob die Siegbedingung (s.o.) erfüllt ist und das Spiel beendet werden kann.
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
Die Variable
Wer es genau wissen möchte, dem kann folgende Debug Zeile helfen:
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.
Danach wird die Ecke ObenLinks aka
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
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
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
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
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.
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"
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).
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 vonactiveField
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 MethodeendGame()
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 mitrows
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 alsPoints
der wirklichen Zone Spielfeld
und schalten
es aktiv und sichtbar.
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.
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.
Keine Kommentare:
Kommentar veröffentlichen