Montag, 16. April 2012

Fortschrittskurs - Dynamische Programmierung

Fortschrittskurs - Dynamische Programmierung

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


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

Konzept

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

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

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

Zoneneintritt

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

Funktionen

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


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

Mister X wird sichtbar

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

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

Mister X ist am Zug

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

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

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

U-Bahn, Bus oder doch nur Taxi ?

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

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

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

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

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

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

Sammelfunktion „afterTransportChoice“

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

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

Mister X in Gefangenschaft

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

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

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

Simulationsschutz

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

Hausnummernsuche? Bitte nur beim ersten Stationsbesuch

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

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

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

Spoiler, Complete-Flag und Unlock-Code beim Final

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

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

Etwas mehr Übersichtlichkeit bitte

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

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

Wo kann ich diesen Wherigo spielen?

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

Keine Kommentare:

Kommentar veröffentlichen