6.2 Sokoban

6.2.1 Beschreibung
6.2.2 Definition der Objekte
6.2.3 Besonderheiten

6.2.1 Beschreibung

Sokoban (japanisch für "Lagerarbeiter") ist ein klassisches Computerspiel aus den 1980er Jahren. Ziel des Spieles ist es, in einem Labyrinth verstreute Kisten auf vorgegebene Zielfelder zu schieben. Erschwert wird dies dadurch, das der Sokoban die Kiste nur schieben, nicht ziehen kann. Außerdem kann immer nur eine Kiste auf einmal bewegt werden. Im Gegensatz zum Originalspiel erfolgt die Spielsteuerung nicht mit der Tastatur, sondern mit der Maus. Wenn ein direkter Weg möglich ist, reicht es, auf eine freies Feld im Labyrinth zu klicken und die Spielfigur findet automatisch den kürzesten Weg. Ob ein Feld erreichbar ist oder eine Kiste verschoben werden kann, wir durch eine Veränderung des Mauszeigers dargestellt.

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

6.2.2 Definition der Objekte

Das Objekt, das für den gesamten Spielablauf zuständig ist, ist der Spielmanager. Seine Hauptaufgabe ist das Erzeugen eines neuen Spieles, wobei es das Sokoban-Objekt und den Kistenverwalter initialisiert. Es realisiert auch den Neustart eines Spieles sowie den Wechsel zu einem neuen Level. Dabei speichert es den Zustand des aktuellen Levels, damit bei der Rückkehr dieser Spielstand wieder geladen werden kann Außerdem delegiert es den Undo-Befehl und den Befehl, die aktuelle Position in einer History aufzuzeichnen, an die anderen Objekte.

Die Beschreibung der einzelnen Levels liest das LevelParser-Objekt aus einem Textdarsteller. Die Form dieser Beschreibung lehnt sich an die üblichen Konventionen zur Beschreibung eines Sokoban-Levels; es können also auch Levels aus dem Internet geladen werden und diesem Darsteller hinzugefügt werden. Level 2 sieht beispielsweise folgendermaßen aus:

"L e v e l 2"
#######
# $ .#
# @$ #
# #$.#
# . ##
######

Die genaue Beschreibung, wie ein Level definiert wird, befindet sich im Textdarsteller "Levels". Das LevelParser-Objekt wertet diesen Darsteller aus und ermittelt die Anzahl der Levels, ihren Aufbau und den Namen eines Levels.

Das Spielfeld-Objekt generiert aus der Levelbeschreibung in Textform ein Raster in Form einer verschachtelten Liste, an dessen Positionen es speichert, ob ein Feld leer ist, ein Zielfeld darstellt oder durch eine Wand verstellt wird. Aus dieser Information wird das Spielfeld generiert als Grafik in einer Variablen gespeichert. Das Spielfeldobjekt vereinigt alle Funktionen, die unmittelbar mit dem Aufbau und der Darstellung des Spielfeldes zu tun haben. So kann zu einem Punkt auf der Bühne die entsprechende Rasterposition ermittelt werden und umgekehrt. Außerdem wird die Erreichbarkeit eines Feldes und die Verschiebbarkeit einer Kiste berechnet. Eine rekursive Methode findet den jeweils kürzesten Weg zu einem angeklickten Feld.

Das Sokoban-Objekt repräsentiert die Lagerarbeiterfigur. Es zeichnet die Figur und bewegt sie gehend oder schiebend auf ein angegebenes Feld. Das Objekt kann außerdem aufgefordert werden, die aktuelle Position in einer Liste zu merken. Durch die undo()-Methode lässt sich der jeweils letzte Schritt rückgängig machen. Die reset()-Methode setzt die Figur auf den Anfangszustand zurück.

Das Kiste-Objekt ähnelt dem Sokoban-Objekt. Es fehlt ihm jedoch die gehen()- und schieben()-Methode. Stattdessen wird die bewegen()-Methode von der schieben()-Methode des Sokoban-Objekts aufgerufen, um die Kiste zu bewegen. Das Kiste-Objekt implementiert ebenfalls die gleichen Methoden wie das Sokoban-Objekt, um eine History seiner Positionen zu erstellen und die letzten Schritte rückgängig zu machen. Zusätzlich bietet es mit der Methode istImZiel() die Möglichkeit abzufragen, ob es sich über einem Zielfeld befindet. Die Kisten-Objekte werden nicht in einer Globalen gespeichert, sondern sind nur über das KistenVerwalter-Objekt zugänglich.

Das KistenVerwalter-Objekt ist die einzige Möglichkeit, eine Kiste zu erzeugen und auf sie zuzugreifen. Mit ihm kann überprüft werden, ob an einer bestimmten Position auf dem Spielfeld eine Kiste steht und sich gegebenenfalls eine Referenz auf diese Liste zurückgeben lassen. Ansonsten delegiert es die zeichnen()-Methode sowie die Methoden zur Verwaltung der History an die einzelnen Kisten weiter. Die Methode sindKistenImZiel() greift auf die Methode istImZiel() aller Kisten zurück um festzustellen, ob alle Kisten auf einem Zielfeld stehen.

Das Spielstand-Objekt speichert jeweils ein Spielfeld-, Sokoban- und Kistenverwalter-Objekt, um bei einer Rückkehr zu einem Level den letzten Spielstand wieder herzustellen. Es stellt eine reine Datenstruktur dar, die implementierten Methoden sind lediglich Zugriffsmethoden auf die Objekteigenschaften.

Das Cursor-Objekt implementiert bis auf den new-Handler keine weiteren Methoden. Es ist lediglich ein Sammelobjekt für die Cursor-Konstanten, ein sogenanntes Utility-Objekt. Durch dieses Objekt kann auf sämtliche benutzen Cursor über eine Globale zurückgegriffen werden, was den Programmcode übersichtlicher macht.


Objektverhältnisse des Sokoban-Spiels

6.2.3 Besonderheiten

So simpel die Idee hinter dem Sokoban-Spiel ist: in einem Spiel mit einem hohen Grad an Interaktivität bestimmen viele Parameter den Spielablauf. Entsprechend sinnvoll ist es, diese in logischen Objekten zu kapseln. Zwar hätte man dieses Spiel auch mit nicht-objektorientierten Mitteln und einer ganzen Reihe von Listen programmieren können, jedoch wäre der Code schnell unübersichtlich geworden. So hätte man z.B. die Positionen der Kisten in einer Liste speichern müssen, deren Spritenummer u.s.w. Außerdem hätte jede Änderung im Programm Auswirkungen auf andere Programmteile. Mit Objekten sind Änderungen leichter durchzuführen. Wenn z.B. der Sokoban durch eine kleine Animation einer laufenden Spielfigur dargestellt werden soll, müssen nur die gehen- und schieben-Methoden des Sokoban-Objektes geändert werden.

Dennoch macht auch dieses Programm Gebrauch von herkömmlichen Programmiertechniken. Im Spielfeldobjekt wird beispielsweise rekursiv der kürzeste Weg berechnet, den der Sokoban benötigt, um zu einem bestimmten Feld zu kommen. Die Methode _kuerzesterWeg(), die dafür benötigt wird, wird von keinem anderen Objekt als dem Spielfeldobjekt selbst gebraucht. Da in Lingo Methoden nicht privat deklariert werden können, wurde diese Methode mit einem Unterstrich gekennzeichnet. Als praktisch erweist sich bei dieser Methode das Lingo-Objekt Point, das zwei Integer- oder Floatwerte speichern kann. Die effizienteste Möglichkeit zum Auslesen der einzelnen Werte sind die Objekteigenschaften locH und locV. Diese Herangehensweise ist etwas schneller als das Erzeugen einer Liste mit zwei Einträgen und das Auslesen mit den "[ ]"-Operatoren.

Durch die Benutzung des Spielstandobjekts stellt sich das Speichern des Spielfeld-, Sokoban- und Kistenverwalterobjektes sehr einfach dar. Diese werden einfach dem new-Handler des Spielstandobjektes übergeben und die entsprechenden globalen Variablen mit neu instantiierten Objekten überschrieben. Dadurch ist es nicht nötig, eine Kopie der Objekte zu erstellen. Da die Kistenobjekte nur vom Kistenverwalter abhängig sind, werden diese automatisch mitgespeichert. Um den Spielstand wieder herzustellen, genügt es, die gespeicherten Werte den Globalen wieder zuzuordnen.

Quellcode des Sokoban-Spiels

6.1 3×3-Puzzle 6.3 Tic Tac Toe