Inhalt:
I.Worum geht's hier eigentlich?
Das Applet befindet sich auf der Diskette mit der Online-Dokumentation. Einfach die HTML-Seite "reversi.html" aufrufen, dies ist die Startseite der Dokumentation. Mit einem Klick auf den Link "Spiel" gelangt man zum Applet.
Wird das Applet an einem anderen Ort installiert, muß die auf der Diskette vorgefundene Verzeichnishierarchie beibehalten werden, um volle Funktionalität zu gewährleisten.
Das Verzeichnis Bilder beinhaltet sämtliche Grafiken, die in der HTML-Dokumentation benötigt werden. Der Ordner Reversi beinhaltet die *.java - Dateien, der Ordner Felder liegt eine Ebene tiefer. Er beinhaltet die Grafiken, die das Applet verwendet. |
Das Wichtigste bei Reversi sind die sogenannten sicheren Steine. Dies sind Steine, welche der Gegenspieler nicht mehr wegnehmen kann. Die ersten "sichern Steine" sind die Ecken. Sie können nicht mehr vom Gegenspieler umgedreht werden. Ausgehend von den Ecken sind alle angrenzenden Steine, die nicht mehr vom Mitspieler übersprungen werden können, sichere Steine.
Die grundlegende Strategie bei Reversi liegt nicht darin, so viele Steine wie möglich bei jedem Zug zu machen, sondern am Schluß die meisten zu haben. Das Spiel wird im allgemeinen erst in den letzten 3-5 Zügen entschieden.
Die Oberfläche zeigt natürlich das Spielfeld an, außerdem im unteren Bereich den aktuellen Spielstand, das heißt, wieviel Steine jeder Spieler besitzt, außerdem wer gerade dran ist. An der rechten Seite bietet das Applet zwei Knöpfe: einen zum Neustart des Spiels und einen zum Passen. Passen ist natürlich nur im Einklang mit den oben erwähnten Voraussetzungen möglich.
Sobald der Spieler seinen Zug gemacht hat, übernimmt der Computer und zieht selbst. Dabei orientiert er sich an der "Sichere-Steine-Regel", indem er den Ecksteinen höhere Prioritäten als den anderen Steinen zuordnet. (Dazu später mehr)
Das Programm besteht aus 5 Klassen:
Beispielsweise:
Zug AktionSetzen = new ZugVorschau();
AktionSetzen = new ZugDurchfuehrung();
Wird später die Methode AktionSetzen.macheZug aufgerufen, wird immer die Aktion ausgeführt, die der momentan in den HotSpot geladenen Klasse entspricht.
Das Spielfeld besteht aus einem 2 dimensionalen IntegerArray. In jedem Feld stehen Werte von 0 bis 3. Entsprechend dieser Zahlen wird in der Methode paint() das entsprechende Bild ausgewählt und dargestellt. 0 steht für ein leeres Feld, 1 für Schwarz, 2 für Weiß und 3 für Verboten. Der ganze Zauber besteht nun darin, die Elemente des Arrays zu jeder Zeit richtig mit Zahlen zu befüllen.
Die Klasse Reversi besteht aus folgenden Methoden
Diese Methode stellt die Startbedingungen her, lädt also die Grafiken in den MediaTracker, außerdem leert sie das SpielFeld-Array und das Prioritätenfeld-Array, welches der Computer für seine Züge benötigt. Des Weiteren werden die vier Steine in der Mitte gesetzt.
Der MediaTracker registriert alle Bilder, die im Spielverlauf benötigt werden, also das Spielfeld, die Spielsteine und diverse Knöpfe. Dann werden mit waitForAll() sämtliche registrierten Bilder geladen. So wird sichergestellt, daß bereits zu Spielbeginn alle Bilder geladen sind. Störendes Flackern, verursacht durch das Nachladen von Bildern, ist somit Geschichte.
Diese Methode wird ausgeführt, wenn der Spieler auf den Neustart-Knopf der Spieloberfläche klickt. Dabei werden wieder alle Spielfelder geleert und dann die Ausgangsbelegung geladen.
Die paint-Methode wird automatisch von der Java Virtual Machine aufgerufen und sie setzt je nach Markierung im SpielFeld-Array die passenden Graphiken in die Oberfläche. Außerdem zeigt sie die Statistiken an, also die Anzahl der bereits eroberten Steine pro Spieler.
Zusätzlich wird die paint(Graphics g) Methode nach jeder Aktion, die Auswirkungen auf das sichtbare Spielfeld hat, aufgerufen, diese wären: mousePressed(), mouseClicked(), mouseReleased() und mouseMoved().
In dieser Methode wird eine gerade Linie (diagonal, vertikal oder horizontal)des Gegners bis zum Ende abgewandert. Sollte sich dort ein Stein des gerade aktuellen Spielers befinden, so wird beim Zurückwandern mittels macheZug die gerade im HotSpot geladene Aktion ausgeführt. Ist jedoch das Spielfeld zu Ende oder die Rekursion trifft auf ein leeres Feld, wandert sie unverrichteter Dinge zurück.
Im Detail: Die rekursive Methode addiert auf den Punkt P(x|y) immer die Variablen xPlus bzw. yPlus auf. xPlus und yPlus ändern sich während dem Ausführen der Methode nicht, also wandert der Punkt P(x|y) immer geradeaus, pro Rekursionsaufruf um ein Feld. Die Richtung, in die er wandert, wird von xPlus und yPlus vorgegeben.
Die Rekursion ruft sich so lange selbst auf, bis sie entweder auf einen gegnerischen Stein stößt, auf ein leeres Feld oder an den Rand des Spielfeldes. Mit jedem Aufruf wandert auch der Punkt P(x|y) um ein Feld weiter. Beim Auftreffen an den Rand des Spielbrettes geschieht in der Abbruchbedingung nichts, trifft man auf einen gegnerischen Stein, so wird auf dem Weg zurück aus der Rekursion heraus immer die AktionSetzen.macheZug(...)-Methode des Interfaces ausgeführt. Das Resultat dieses Aufrufes hängt von der im Interface geladenen Klasse ab.
Diese Methode überprüft, ob die gerade im Interface (HotSpot) geladene Aktion möglich ist, dann wird sie ausgeführt, in dem die rekAktionAusfuehren(...) gestartet wird.
In einer Schleife wird die Methode rekAktionAusfuehren(...) 9mal aufgerufen, einmal mit den Koordinaten des Feldes, über welchem die Methode AktionAusfuehren(...) ausgelöst wurde, und je einmal pro umliegendes Feld.
Die an die Rekursion übergebenen Parameter entsprechen den Koordinaten der umliegenden Felder relativ zum auslösenden Feld. Entsprechend hat das Feld links neben dem auslösenden Feld die Koordinaten xPlus == -1, yPlus == 0. Da xPlus und yPlus in der Rekursion immer auf die verwendeten Koordinaten aufaddiert werden, ist somit die Richtung, in der die nachfolgenden Felder abgewandert werden, festgelegt.
Diese Methode wird von paint(Graphics g) aufgerufen und überprüft über diverse Bedingungen, ob das Spiel schon zu Ende ist. Die Methode liefert auf jeden Fall einen String zurück. Ist das SpielFeld-Array voll, wird die Farbe des Spielers, der die meisten Steine besitzt und somit Sieger ist, zurückgegeben. Bei gleicher Anzahl Steine lautet der Inhalt des Strings "Unentschieden".
Die Variable int zaehlerPass steht zu Beginn des Spiels auf 2 und wird immer dann um 1 erniedrigt, wenn ein Spieler keinen Zug mehr machen kann und passen muß. Kann der Gegenspieler dann auch nicht ziehen, wird die Variable nochmals erniedrigt und steht somit auf 0. Kann der Gegner jedoch einen Zug machen, wird zaehlerPass wieder auf 2 gesetzt. In der Methode spielEndet(...) wird dies berücksichtigt, ist zaehlerPass == 0, wird das Spiel mit "Unentschieden" beendet.
Ist das Spiel noch nicht zuende, gibt die Methode lediglich die Farbe des aktuellen Spielers aus.
Diese Methode sorgt für den Wechsel der Spieler. Die Variable bekommt den Integer-Wert des Gegners und umgekehrt.
Wie oben schon erwähnt, kann auch die Situation auftreten, daß kein Zug möglich ist. In diesem Fall muß man passen, also den Gegner spielen lassen. Diese Methode überprüft bei einem Klick auf den Passen-Knopf, ob Züge möglich sind. Ist dem nicht so, so wird der Spieler gewechselt.
Tatsächlich wird im HotSpot eine Instanz der Klasse ZugZaehlen eingesetzt, und die Methode AktionAusfuehren(...) für jedes der 64 Spielfelder aufgerufen. Die rekursive Methode kann, sobald ein Zug möglich ist, die Variable zugGueltig = true setzen. Es muß also zugGueltig = false gesetzt bleiben, damit ein Passen möglich ist.
Wenn der menschliche Spieler (immer WEISS) paßt, muß vor dem Computerzug die Farbe gewechselt werden, damit der Computer nicht mit den Steinen des Spielers weiterspielt.
Diese Methode entfernt vorhandene Kreuze (kennzeichnen einen ungültigen Zug) sobald die Maustaste losgelassen wird. Es wird in einer Schleife das gesamte SpielFeld-Array überprüft. Befindet sich ein Feld auf dem Wert VERBOTEN (==3), wird es kurzerhand auf LEER (==0) gesetzt.
Diese Methode führt den Zug des Computers durch. Dazu wird zunächst jedes Feld mit einer Priorität versehen und entsprechend dieser wird dann gezogen. Sollte kein Zug möglich sein, paßt der Computer natürlich.
Der Computer verwendet zum Ermitteln des strategisch günstigsten Feldes die im folgenden beschriebenen Methoden priorFeldFuellen() und priorFeldUpdate(), wobei erstere letztere automatisch aufruft.
Das auf diese Weise gefundene günstigste Feld wird in einem Array namens maxPrioritaet festgehalten. Der Computer füllt den HotSpot mit einer Instanz der Klasse ZugDurchfuehrung und besetzt das Feld, das im Array angegeben wird. Ist der Wert im Array allerdings 0, bedeutet dies, daß kein besetzbares Feld gefunden wurde, der Computer muß passen() ausführen.
Hier werden die für den Zug des Computers benötigten Prioritäten berechnet. Ein Integerarray mit den exakt selben Ausmaßen des SpielFeld-Arrays wird Feld für Feld abgewandert. Dabei bekommt jedes Feld dieses PriorFeld genannten Arrays einen Integer-Wert zugewiesen, der die Anzahl der zu erobernden Steine von diesem Feld aus repräsentiert.
Das Array maxPrioritaet kann 3 Werte aufnehmen, den Wert des PriorFeld-Arrays, eine Spalte und eine Zeile.
Wieder wird im HotSpot eine Instanz der Klasse ZugZaehlen installiert, und diese wird mit einer Schleife auf jedem Feld des SpielFeld-Arrays ausgeführt. ZugZaehlen erhöht mit jedem Aufruf eine globale Variable um 1, nach komplettem Durchlauf der Rekursion rekAktionAusfuehren(...) in eine Richtung wird das Ergebnis auf die bereits im PriorFeld-Array vorhandene Zahl aufaddiert.
Nachdem die gesamte Zahl der theoretisch zu erobernden Steine auf einem Feld ermittelt wurde, wird noch die Methode priorFeldUpdate() ausgeführt, die noch bestimmte strategische Änderungen vornimmt.
Schließlich wird überprüft, ob der ermittelte Integerwert der bisher größte Wert ist. Ist er das, wird er mitsamt der Spalte und der Zeile, in der er gefunden wurde, in das maxPrioritaet-Array gefüllt. Es ist nachvollziehbar, daß am Ende der Schleife, die alle 64 Felder überprüft, im maxPrioritaet-Array das Feld mit dem größten Wert verzeichnet ist. Und in dieses Feld setzt der Computer seinen Stein.
Diese Methode setzt die Prioritäten der Eckfelder auf den höchsten Wert von 64, da diese ja sehr wichtig für einen Sieg sind, außerdem setzt sie den Wert der Felder auf 1, die direkt an die Eckfelder angrenzen, da diese ja dem menschlichen Spieler Tür und Tor zu den Ecken öffnen würden.
In einfachen If-Abfragen wird also abgefragt, ob ein Zug in den Eck-Feldern ((spalte ==1 || spalte == 8) && (zeile == 1 || zeile == 8)) möglich ist, wenn ja, werden diese mit dem Wert 64 befüllt. Analog wird mit den Feldern, die direkt an die Ecken anschließen, verfahren.
Zu guter Letzt müssen noch die Mausbewegungen und -klicks abgefragt werden. DA die EventHandler-Interfaces verlangen, daß alle darin angegebenen Methoden implementiert werden, wir in unserem Applet jedoch nicht alle davon benötigen, werden nachfolgend nur die erklärt, die tatsächlich eine Funktion ausüben:
Hier wird die Mausposition festgestellt, und dann, sollte das Spielfeld leer sein, der Zug durchgeführt (Im Detail: Der HotSpot wird mit einer Instanz der Klasse ZugDurchfuehrung versehen und die Methode AktionAusfuehren(...) aufgerufen). Ist dies gut gegangen, so wird an den Gegner übergeben.
Die Überprüfung, ob sich die Maus zum Zeitpunkt des Drückens im Tatsächlichen Spielfeld (Spalte und Zeile größer 0 und kleiner 9) befindet, dient lediglich dazu, ArrayIndexOutOfBoundsExceptions vorzubeugen.
Hier wird die Methode entfVERBOTEN() aufgerufen, es werden alle Kreuzchen auf dem Spielfeld gelöscht.
Hier werden die Mauspositionen wieder abgefragt und bei passenden Koordinaten das Spiel neu gestartet oder gepaßt.
Hier wird auch mal wieder die Mausposition abgefragt und dann der mögliche Zug vorgeführt. (Im Detail: Der bereits zur Genüge bekannte HotSpot wird mit der Instanz der Klasse ZugVorschau versehen und die Methode AktionAusfuehren(...) aufgerufen.)
Die Überprüfung, ob sich die Maus zum aktuellen Zeitpunkt im Tatsächlichen Spielfeld (Spalte und Zeile größer 0 und kleiner 9) befindet, dient lediglich dazu, ArrayIndexOutOfBoundsExceptions vorzubeugen.