6.3 Tic Tac Toe

6.3.1 Beschreibung
6.3.2 Definition der Objekte
6.3.3 Besonderheiten

6.3.1 Beschreibung

Das TicTacToe-Spiel basiert auf dem Käsekästchenspiel, bei dem zwei Spieler versuchen, auf einem quadratischen, in Kästchen unterteilten Spielfeld, durch das Setzen von Symbolen eine Reihe zu bilden. Das Spiel kann wahlweise gegen den Computer gespielt werden, der Computer kann auch alleine gegen sich selbst spielen. Die Größe des Spielfeldes kann vom klassischen 3×3 Spielfeld bis zu einer Größe von 7×7 Feldern variiert werden, ebenso kann die Zahl der Symbole, die für eine Reihe benötigt werden, zwischen 3 und 7 Felder eingestellt werden. Bei einem größeren Spielfeldes ist es sinnvoll, nicht bis zur vollständigen Belegung aller Felder zu spielen, sondern das Spiel vorzeitig abzubrechen, wenn kein Spieler mehr in der Lage ist, eine Reihe zu bilden. Diese Option kann über ein Auswahlfeld angewählt werden.

<img src="../grafik/bilder/tictactoe.jpg" width="360" height="360">

6.3.2 Definition der Objekte

Das Spielfeld-Objekt ist die logische Repräsentation des Spielfeldes. Sein new-Handler erhält als einziges Argument ein Spielfeldraster (eine verschachtelte Liste), das sozusagen vom Spielfeldobjekt "eingewickelt" wird. Es bietet Methoden für das Auslesen und Setzen von Feldern und das Bestimmen der Größe des Rasters. Außerdem bietet es Methoden, um die Zahl der belegten und freien Felder zu bestimmen und um zu ermitteln, welcher Spieler gewonnen hat und welche Felder eine Lösungsreihe bilden. Zusätzlich gibt es einige Methoden, die vom Computergegner zur Ermittlung des besten Zuges benötigt werden. Diese ermitteln, wie viele Reihen unter bestimmten Bedingungen gebildet werden können. Damit die Gesamtzahl an möglichen Reihen nicht ständig neu berechnet werden muss, werden die möglichen Reihen pro Feld in der Eigenschaft mMoeglicheReihen festgehalten und die Ergebnisse wiederbenutzt. Beim Verändern des Spielfeldes wird diese Eigenschaft zurückgesetzt.
Um eine Kopie des Spielfeldes zu erstellen, bietet das Spielfeld-Objekt die Methode getCopy(). Die Kopie des Spielfeldes kann verändert werden, ohne dass sich die Originalwerte ändern. Der new-Handler des Spielfeld-Objektes wird übrigens nie direkt, sondern entweder beim erstellen einer Kopie oder durch das Objekt ZeichenbaresSpielfeldObjekt aufgerufen.

Das Objekt ZeichenbaresSpielfeldObjekt erweitert das Spielfeld-Objekt um die Eigenschaft, das Spielfeld auf dem Bildschirm darzustellen; es erbt alle Eigenschaften des Spielfeldes und fügt Methoden zum Zeichnen des Spielfeldes, zum Hervorheben der Gewinnerreihen und zum Aufblinkenlassen des letzten Computerzuges. Ferner speichert es die tatsächliche Größe des Spielfeldes auf dem Bildschirm und kann dadurch aus der Mausposition den entsprechenden Rasterindex des Spielfeldes berechnen.

Das Schiedsrichter-Objekt startet ein Spiel, hält fest, welcher Spieler am Zug ist und ob ein Spieler ziehen darf. Nach jedem Spielzug wird die Methode zugBeendet() aufgerufen, die prüft, ob ein Spieler gewonnen hat oder das Spiel unentschieden ausgegangen ist. Das Schiedsrichterobjekt fügt sich außerdem der actorlist hinzu und entscheidet im stepFrame-Handler, ob der Computer am Zug ist und führt gegebenenfalls dessen ziehen()-Methode aus.

Das Computergegner-Objekt bietet als einzige öffentliche Methode die Methode ziehen(), die den nächsten Zug ausführt. Diese führt daraufhin Berechnungen aus, um den bestmöglichen Zug zu ermitteln. Dabei wird nach einer bestimmten Reihenfolge jede freie Feldposition auf dem Spielfeld einer Wertung zugeordnet. In mehreren Berechnungsschritten werden jeweils die höchsten Wertungen weiter untersucht, bis am Ende eine Menge gleichwertiger Züge übrigbleibt. Aus dieser wird zufällig ein Zug ausgewählt ausgeführt. Leider war es aus Geschwindigkeitsgründen nicht möglich, den Computergegner einige Züge im Voraus denken zu lassen, da die zu bearbeitende Datenmenge sehr schnell ansteigt und Lingo wenig Möglichkeiten zur Optimierung des Codes lässt. Der Computer spielt also kein perfektes Spiel. Die genaue Vorgehensweise ist dem Lingo-Code zu entnehmen, der gut kommentiert ist.
Es wäre theoretisch möglich, vom Computergegner zwei Instanzen zu erzeugen, wenn der Computer gegen sich selbst spielt. Für unser Spiel ist dies jedoch nicht notwendig, da im Computergegner-Objekt keine über mehrere Züge relevanten Eigenschaften gespeichert werden. Dies kann in anderen Spielen durchaus der Fall sein, z.B. wenn der Computergegner eine bestimmte Taktik verfolgt. Bei einem Kartenspiel beispielsweise wird der Computer für jeden Spieler, je nach eigenem Blatt und ausgespielten Karten, eine eigene Wahrscheinlichkeit für bestimmte Karten von einem verdeckten Stapel berechnen. Der Vorteil an der objektorientierten Programmierung ist, dass das Verhalten eines Spielers nur einmal programmiert werden muss und für jeden neuen Spieler einfach eine Instanz erzeugt wird.

Das Objekt SpielsituationsObjekt wird nur vom Computergegner benutzt. Es wird für eine Was-Wäre-Wenn-Analyse benutzt. Sein new-Handler erhält Informationen über das zu untersuchende Spielfeld, den aktuellen Spieler und den zu untersuchenden Zug. Dieser wird auf einer Kopie des Spielfelds ausgeführt. Anschließend kann untersucht werden, ob der Zug zum Gewinn eines Spielers führt (spielerGewinnnt())oder den Gegner vom Gewinnen abhält (gegnerGewinntNicht()). Des weiteren bietet das Objekt noch einige Methoden, um eine Wertung für die neue Situation zu berechnen.

Das Utilities-Objekt enthält zwei Methoden, die von mehreren Objekten verwendet werden, nämlich die Ermittlung des Gegenspielers zu einem Spieler, und ob eine Zahl gerade oder ungerade ist. Die Objekte definieren das Utilities-Objekt als ancestor und können fortan diese Methoden benutzen, als wären es die eigenen.


Sequenzdiagramm des TicTacToe-Spiels

6.3.3 Besonderheiten

Dieses Spiel zeigt, dass sowohl der Computergegner wie auch der menschliche Spieler zum Setzen der Symbole auf dieselbe Methode zugreifen (gSpielfeldObj.setAtPos(indexPos, spieler)). Daher ist es sehr einfach, zwischen Computergegner und menschliche Gegner umzuschalten. Es wäre auch denkbar, dass das Spiel über ein Netzwerk gespielt wird und die Parteien sich über einen Multiuser-Server austauschen, an welche Stelle des Spielfeldes ein Symbol gesetzt werden soll.

Das Spiel zeigt ein Beispiel für die Kopie eines Objektes. Das Spielfeld wird vom Computergegner kopiert, um darauf alle Szenarien durchzuspielen, die entstehen, wenn ein Symbol auf ein freies Feld gesetzt wird. Dabei wird immer nur die logische Struktur des Spielfeldes kopiert, nicht die Eigenschaften, die das zeichenbare Spielfeldobjekt zur Darstellung benötigt. Durch diese Trennung wird der Speicherbedarf minimiert, da stets nur eine zeichenbare Instanz des Spielfeldes existiert. Durch das Ableiten eines Spielfeld-Objektes kann das zeichenbare Spielfeld dennoch auf dessen Methoden zugreifen, als wären es seine eigenen.

Die Ausführung der ziehen()-Methode des Computergegners aus dem stepFrame-Handler heraus hat folgenden Grund: Director prüft Benutzerereignisse wie z.B. Mausklicks immer nur ab, wenn der Abspielkopf in einen neuen Frame wechselt. Dies passiert nur, wenn alle Skripte eines Frames abgearbeitet sind. Solange einer der Spieler menschlich ist, ist dies auch der Fall, da der Rechner nach dem Computerzug auf eine Benutzereingabe wartet und sich in einer Schleife auf einem Frame befindet. Wenn jedoch beide Spieler vom Computer übernommen werden, würde ein Aufruf der ziehen()-Methode in der zugBeendet()-Methode des Schiedsrichter mit einem erneuten Aufruf von zugBeendet()-Methode abschließen. Diese würde wieder ziehen() aufrufen, u.s.w. Diese Verschachtelung an Methodenaufrufen würde Benutzereingaben blockieren, bis das Spiel beendet ist. Ein Aufruf der ziehen()-Methode aus einem stepFrame-Handler ist hingegen unproblematisch, da die Ausführung dieses Handlers voraussetzt, das ein Frame-Wechsel stattgefunden hat und somit Mausereignisse ausgewertet wurden.

In diesem Beispiel wurde für die Umsetzung gleichartiger Benutzerelemente Gebrauch von eigenen Verhalten und denen aus der Director-Verhaltensbibliothek gemacht. Die 4 Radio-Buttons zur Spielertyp-Auswahl benutzen alle das gleiche Verhalten mit unterschiedlichen Eigenschaften für die Spielernummer und den Spielertyp. Ebenso basiert die Funktion der Schieberegler auf demselben Verhalten. Für die eingeblendeten Hilfefenster wurde das Tooltip-Verhalten und das Display-Text-Verhalten aus der Library verwendet, letzteres in einer leicht modifizierten Form.

Quellcode des TicTacToe-Spiels

6.2 Sokoban 7 Anhang: Neue Befehle in Director 8