Die Implementierung des Models von Gomoku

Die Implementierung des Gomoku-Spiels auf einem grenzenlosen Brett ist in Java nicht trivial. Die naheliegende Lösung, das Brett in einer zweidimensionalen Reihung darzustellen ist nicht praktikabel, zumal Java-Reihungen eine unveränderliche Größe haben. Bei Überschreiten der aktuellen Grenzen müsste dann eine größere Reihung erzeugt und die alte kopiert werden.

In Gomoku.Model.java wird eine alternative Lösung vorgestellt: Die besetzten Felder (mit Steinen O oder X) werden achtfach miteinander verkettet und in einer ArrayList gespeichert. Hierzu dient die innere Klasse Model.Brett mit ihrer inneren Klasse Model.Brett.Feld. Der letzteren Attribute sind zeile und spalte (ggf. negative int-Werte, die Koordinaten des Felds auf dem Brett), eine boolean-Variable (enthält ob der Stein auf dem Feld O ist) und eine Feld-Reihung felder der Länge 8 namens nachbar. Sie enthält Referenzen auf die Nachbarfelder in die 8 verschiedene Richtungen: OBEN, RECHTS_OBEN, RECHTS, RECHTS_UNTEN, UNTEN, LINKS_UNTEN, LINKS, LINKS_OBEN. Diese Reihung wird mit einer enum-Typ Richtung mit diesen 8 Werten (ordinal()) indiziert.

Die Methode Brett.besetztVon() durchläuft die Reihung und sucht nach dem gespeicherten Feld mit den in den Parametern angegebenen Koordinaten. Aufwändiger ist die Methode Brett.steinSetzen(). Sie erzeugt ein neues Feld mit den in den Parametern angegebenen Koordinaten, fügt es in die ArrayList felder ein und läuft sie durch, um nach Nachbarn zu suchen. Dank der 8-fachverkettung müssen nur die gespeicherten Koordinaten auf Nachbarschaft in den 8 Richtungen untersucht werden. Anschließend muss noch ermittelt werden, ob das Spiel zu Ende ist. Dies ist der Fall, wenn das neue Feld auf eine Siegerlinie fällt: Diese wird von 5 nebeneinander liegenden gleichen Steinen gebildet. Ob dies der Fall ist, wird in der Methode fünfNebeneinander() untersucht, die viermal nacheinander mit unterschiedlichen Richtung-Parametern aufgerufen wird. Jeder Aufruf sucht nach nebeneinander liegenden Steinen in der gegebenen Richtung und in der Gegenrichtung (z.B. OBEN und UNTEN). Die Treffer werden gezählt. Gleichzeitig werden der Anfang- und der Endpunkt der Siegerlinie in Brett-Attributen (z1, s1 bzw. z2, s2) gespeichert. Diese sind relevant nur, wenn der Sieger ermittelt wurde, d.h. 5 Treffer erzielt wurden. Die Suche in die eine und dann in die andere Richtung Dank läuft entlang der Verkettung in zwei while-Schleifen.

Etwas unsauber aber praktikabel ist die Lösung der Kommunikation zwischen Model- und Brett-Methoden über die Brett-Attribute (globale Variablen) sieger, z1, z2, s1, s2 und linie. Eine saubere Alternative wäre über Ausgabeparameter (etwas schwerfällig in Java), die aber über mehrere Methoden (Brett.fünfNebeneinander() à Brett.feldBesetzen() à Model.steinSetzen()) weitergegeben werden sollten.

Mit Hilfe der inneren Klasse Brett ist die Implementierung der IModel-Methoden nicht schwer. Der Konstruktor der autonomen Klasse Model braucht nur die Information, welcher Spieler anfängt. Die Methode start() initialisiert das Brett neu. Die Methoden steinSetzen() und besetztVon() bedienen sich der gleichnamigen Methoden von Brett. Aufwändig ist nur die Methode siegerlinie(), die von einem Feld (mit in ihrem Parameter angegebenen Koordinaten) entscheiden muss, ob es auf der Siegerlinie liegt oder nicht.

Um ihre Arbeit zu erleichtern, ermittelt die Methode Brett.fünfNebeneinander() die Lage der Siegerlinie (ZEILE, SPALTE, DIAGONAL, LINKS_UNTEN und RECHTS_OBEN) und sorgt dafür, dass in den ersten drei Fällen ihre Anfangskoordinaten (z1, s1) kleiner sind als ihre Endkoordinaten (z2, s2), wobei DIAGONAL nur auf von „links oben“ nach „rechts unten“ zutrifft. Bei zwischen „links unten“ und „rechts oben“ ist es nicht möglich, die Anfangskoordinaten kleiner zu machen – so bedeutet LINKS_UNTEN bzw. RECHTS_OBEN die Position der Anfangskoordinaten.

Mit dieser Vorarbeit von Brett.fünfNebeneinander() muss nun siegerlinie() die fünf Fälle unterscheiden und die in ihren Parametern angegebenen Koordinanten zeile und spalte mit denen der Siegerlinie vergleichen:

1. ZEILE: zeile = z1 = z2 und s1 spalte s2 à Das Feld liegt in der Zeile der Siegerlinie zwischen den Spalten ihrer Endpunkte. Dabei ist z1<z2 und s1<s2.

2. SPALTE: spalte = s1 = s2 und z1 zeile z2 à Das Feld liegt in der Spalte der Siegerlinie zwischen den Zeilen ihrer Endpunkte. Dabei ist z1<z2 und s1<s2.

3. DIAGONALE: z1 zeilez2 und s1spaltes2 und zeile-spalte = z1-s1 = z2-s2 à  Das Feld liegt zwischen den Zeilen und den Spalten der Endpunkte der Siegerlinie und die Differenz zeile-spalte ist dieselbe wie die der Endpunkte. Dabei ist z1<z2 und s1<s2.

4. RECHTS_OBEN: z2 zeilez1 und s1spaltes2 und zeile+spalte = z1+s1 = z2+s2 à  Das Feld liegt zwischen den Zeilen und den Spalten der Endpunkte (wobei z1 > z2 und s1 < s2) der Siegerlinie und die Summe zeile+spalte ist dieselbe wie die der Endpunkte.

5. LINKS_UNTEN: z1 zeilez2 und s2spaltes1 und zeile+spalte = z1+s1 = z2+s2 à  Das Feld liegt zwischen den Zeilen und den Spalten der Endpunkte (wobei z1<z2 und s1>s2) der Siegerlinie und die Summe zeile+spalte ist dieselbe wie die der Endpunkte.

Nach Konvention werden private Attribute und Methoden in der inneren Klasse Brett nur hier benutzt (obwohl sie auch von der äußeren Klasse Model erreichbar sind). Die auch in Model benutzten Attribute und Methoden in der inneren Klasse wurden paketweit (ohne Zugriffsschutz) vereinbart.

Die Klasse Model enthält zwei Testtreiber in ihrer Methode main(): einen mit Testdaten und einen zufallsgesteuerten. Die Testdaten werden aus den Kommandozeilenparametern übernommen: die zeilenweise Besetzung des Spielfelds (mit O und X – pro Zeile ein Parameter. Ohne Kommandozeilenparameter läuft der Testtreiber mit einer Zufallsbesetzung. Dabei ausgehend vom Nullpunkt in der Mitte des Bretts wird ein leeres Feld gesucht und besetzt. Sobald ein Sieger (fünf gleiche Steine nebeneinander) vorliegt, wird standAusgeben() aufgerufen. Diese Methode gibt das Ergebnis und das Feld über die Konsole formatiert aus, wobei die Siegerlinie an den Großbuchstaben zu erkennen ist.  Zwei Beispielausgaben:

    87654321012345678
-8 ---------------x-
-7 -x--oo--ox----xx-
-6 -o--oo--xooo-----
-5 ----oxx-ooo------
-4 -x--oxxooxx------
-3 -----o---x-o-----
-2 --o-x---o-o-o---o
-1 --x-xo--oxoxx-ooo
 0 o--o-oxoooo-xx-oo
 1 --xox-X-oxx--xoox
 2 oxo---xX-xoxxx-x-
 3 ---o---xX-xo-----
 4 -o-xxx-o-X-ox----
 5 -xxo---xooXxxx--o
 6 -ox-x---x-oXox-o-
 7 --ox-o-------x---
 8 ----o----------x-
     10987654321012345678901
-10 --------x----------x---
 -9 -----o-----x-----------
 -8 --o-----------------x--
 -7 -----x-----x------x----
 -6 -x----------o-o-xx-o---
 -5 ---xx------x--------o--
 -4 x--x---------o---x---o-
 -3 -----x-xo---oo----x----
 -2 ----xoXx-o-oox-o-------
 -1 --oo--X--oooxx---------
  0 --oo--Xx-x-oxxx--o-----
  1 ----ooX-o-xo-xo-x------
  2 ---o--Xo-oo-xoxo-o-----
  3 -oo-x---oox-oxx--xx--o-
  4 --ooxx-xx--x---o-------
  5 o--x-o------o--x--ox---
  6 ------o-oo--------xoo--
  7 ------x-x--------x-----
  8 --------x--------------
  9 -----------------------
 10 ---------o-------------
 11 ----------------------o
 12 -------------o---------

Durch die Zyklizität der int-Werte in Java ergibt sich aus dem theoretisch unendlichem Brett ein Brett auf der Sphäre.


package model;
import java.util.ArrayList;
import java.util.Random;
/**
 * Implementierung der Model-Schnittstelle für Gomoku mit MVC
 * @author Prof. Dr. Andreas Solymosi, Beuth-Hochschule für Technik, (c) 2012, solymosi@tfh-berlin.de
 * @version 5/2/12 
 */
public class Model implements IModel {
  private Brett brett; // Felder besetzt mit Spieler.LEER oder Spieler.O oder Spieler.X
  private boolean oSetzt, // ist der nächste Spieler O?
    oFängtAn; // hat O das Spiel angefangen?
  public Model (final boolean oFängtAn) {
    this.oFängtAn = oFängtAn;
    // dieses = this; // nur für Testzwecke
    start(); // erstmalig
  }
  @Override
  public void start() { // oder restart
    brett = new Brett(); // Spielfeld leeren
    oFängtAn = ! oFängtAn; // abwächselnd
    oSetzt = oFängtAn; // nächster Spieler
  }
  @Override
  public Spieler nächster() {
    return oSetzt ? Spieler.O : Spieler.X;
  }
  @Override
  public void steinSetzen(final int zeile, final int spalte) throws BesetztException {
    if (besetztVon(zeile, spalte) != Spieler.LEER)
      throw new BesetztException();
    brett.steinSetzen(zeile, spalte, oSetzt); // Nebeneffekt: spielfeld.sieger wird gesetzt
    oSetzt = ! oSetzt;
  }
  @Override
  public Spieler besetztVon(final int zeile, final int spalte) {
    return brett.besetztVon(zeile, spalte);
  }
  @Override
  public boolean siegerlinie(int zeile, int spalte) {
    if (this.sieger() == Spieler.LEER)
      return false;
    // die Koordinaten der Siegerlinie (s2, z1) bis (s2, z2) wurden in brett.fünfNebeneinander() gesetzt, sobald der Sieger ermittelt wurde.
    // enum brett.siegerlinie wurde auch gesetzt:
    return (brett.linie == Linie.ZEILE && zeile == brett.z1 && spalte >= brett.s1 && spalte <= brett.s2) || // zeile == brett.z2
      (brett.linie == Linie.SPALTE && spalte == brett.s1 && zeile >= brett.z1 && zeile <= brett.z2) || // spalte == brett.s2
      (brett.linie == Linie.DIAGONALE && zeile >= brett.z1 && zeile <= brett.z2 && spalte >= brett.s1 && spalte <= brett.s2 && 
          zeile - spalte == brett.z1 - brett.s1) || // zeile - spalte == brett.z2 - brett.s2
      (brett.linie == Linie.RECHTS_OBEN && zeile >= brett.z1 && zeile <= brett.z2 && spalte <= brett.s1 && spalte >= brett.s2 && zeile + spalte == brett.z1 + brett.s1) || // zeile - spalte == brett.s2 - brett.z2
      (brett.linie == Linie.LINKS_UNTEN && zeile <= brett.z1 && zeile >= brett.z2 && spalte >= brett.s1 && spalte <= brett.s2 && zeile + spalte == brett.z1 + brett.s1);  // zeile + spalte == brett.z2 + brett.s2
  }
  @Override
  public Spieler nächster() {
    return oSetzt ? Spieler.O : Spieler.X;
  }
  // Position der Siegerlinie (fünf Steine Nebeneinander) mit Koordinaten (s2,z1) bis (s2,z2), gesetzt in der inneren Klasse Brett:
  private static enum Linie { SPALTE, // Siegersteine in der Spalte: z1 == z2 
    ZEILE, // Siegersteine in der Zeile: s2 == s2
    DIAGONALE, // Siegersteine in der Diagonale, sie fängt links oben an: s2 < s2 && z1 < z2
    RECHTS_OBEN,  // Antidiagonale fängt rechts oben an: s2 < s2 && z1 > z2  
    LINKS_UNTEN}; // Antidiagonale fängt links unten an: s2 > s2 && z1 < z2
  /*
   * Stellt ein unbegrenztes Spielbrett mit Feldern dar. Wegen der Zyklizität der int-Werten (bei Integer.MAX_VALUE) ergibt sich eine Sphäre als Spielbrett. 
   * Die leeren Felder (mit Spieler.LEER) werden nicht dargestellt.
   * Die besetzten Felder (mit Spieler.O oder Spieler.X) werden 8-fach (in 8 Richtungen) miteinander verkettet.
   * Sie werden in einer ArrayList in der Reihenfolge ihrer Entstehung (Besetzung) gespeichert.
   */
  private static class Brett {
    // Die Felder werden in 8 Richtungen miteinander verkettet:
    private static enum Richtung { OBEN, RECHTS_OBEN, RECHTS, RECHTS_UNTEN, UNTEN, LINKS_UNTEN, LINKS, LINKS_OBEN }
    // Das Brett besteht aus (mit Steinen besetzten) Feldern:
    private static class Feld {
      final int zeile, spalte; // Koordinaten innerhalb des Spielfelds, ggf. auch negativ
      final Feld[] nachbar; // indiziert mit Richtung.ordinal() zwischen 0 und 7
      final boolean besetztVonO;
      /* 
       * @param zeile Koordinaten des Felds innerhalb des Bretts (ggf. negativ)
       * @param spalte Koordinaten des Felds innerhalb des Bretts (ggf. negativ)
       * @param besetztVonO Feld ist besetzt mit Spieler.O (nicht mit Spieler.X)
       */
      Feld(final int zeile, final int spalte, final boolean besetztVonO) {
        this.zeile = zeile;
        this.spalte = spalte;
        this.besetztVonO = besetztVonO;
        nachbar = new Feld[Richtung.values().length]; // 8 Nachbarzellen in 8 Richtungen 
      }
    }
    private boolean brettIstLeer; // am Anfang
    private final ArrayList<Feld> felder; // nur die besetzten Felder werden gespeichert - alle anderen enthalten Spieler.LEER
    // Brett.neueZelleIstSieger() und Brett.fünfNebeneinander() legen ihre Ergebnisse in paketweit erreichbaren Attributen von Brett ab.
    // Model.steinSetzen() Model.sieger() und Model.siegerlinie() holen sie von dort ab:
    Spieler sieger;
    int z1, z2, s1, s2; // Koordinaten der Siegerlinie, relevant nur bei sieger != Spieler.LEER
    Linie linie; // Position der Siegerlinie; relevant nur bei sieger != Spieler.LEER 
    int grenzeLinks, grenzeOben, grenzeRechts, grenzeUnten; // nur für den Testtreiber
    Brett() {
      brettIstLeer = true;
      felder = new ArrayList<Feld>();
      sieger = Spieler.LEER; // noch kein Sieger
    }
    // liefert den Spieler (O, X oder LEER), der das gegebene Feld besetzt:
    Spieler besetztVon(final int zeile, final int spalte) {
      // ArrayList wird durchsucht nach dem gegebenen Feld:
      if (schonGefunden && zeile == zeileGefunden && spalte == spalteGefunden) // wiederholtes Suchen vermeiden
        return spielerGefunden;
      for (Feld zelle : felder) // Zelle suchen
        if (zelle.zeile == zeile && zelle.spalte == spalte) { // gefunden
          schonGefunden = true; // wiederholtes Suchen vermeiden
          zeileGefunden = zeile;
          spalteGefunden = spalte;
          spielerGefunden = zelle.besetztVonO ? Spieler.O : Spieler.X;
          return spielerGefunden;
        }
      return Spieler.LEER;
    }
    private boolean schonGefunden = false; // wiederholtes Suchen in besetztVon() vermeiden
    private int zeileGefunden, spalteGefunden;
    private Spieler spielerGefunden;
    /* Ein Stein wird auf ein Feld gesetzt.
     * @param z Zeile des Felds
     * @param s Spalte des Felds
     * @param spielerO true wenn das Feld mit Spieler.O besetzt ist, false wenn mit Spieler.X
     * @result true wenn das neu besetzte Feld Sieger ist (d.h. das Spiel ist zu Ende)
     * Funktion mit Nebeneffekten (Attribute werden verändert): 
     * 1. Ein neues Feld mit Koordinaten (z,s) wird in die ArrayList eingetragen und verkettet.
     * 2. sieger wird gesetzt, falls es welche gibt.
     * 3. Die Koordinaten der Siegerlinie (fünf Steine nebeneinander) mit Anfangpunkt (s1,z1) und und Endpunkt (s2,z2) werden gesetzt.
     * 4. Die Art der Siegerlinie (enum linie) wird berechnet und gesetzt.
     */
    boolean steinSetzen(final int z, final int s, final boolean spielerO) {
      grenzeOben = brettIstLeer ? z : Math.min(z, grenzeOben); // aktuelle Grenzen erweitern - nur für den Testtreiber
      grenzeLinks = brettIstLeer ? s : Math.min(s, grenzeLinks); // nur für den Testtreiber
      grenzeUnten = brettIstLeer ? z : Math.max(z, grenzeUnten); // nur für den Testtreiber
      grenzeRechts = brettIstLeer ? s : Math.max(s, grenzeRechts); // nur für den Testtreiber
      brettIstLeer = false;
      final Feld neu = new Feld(z, s, spielerO);
      for (final Feld zelle : felder) { // Nachbarschaften suchen und verketten
        if (s == zelle.spalte-1 && z == zelle.zeile-1) { // links oben     --
          zelle.nachbar[Richtung.LINKS_OBEN.ordinal()] = neu;
          neu.nachbar[Richtung.RECHTS_UNTEN.ordinal()] = zelle;        
        }
        if (s == zelle.spalte && z == zelle.zeile-1) { // oben        0-
          zelle.nachbar[Richtung.OBEN.ordinal()] = neu;
          neu.nachbar[Richtung.UNTEN.ordinal()] = zelle;              
        }
        if (s == zelle.spalte+1 && z == zelle.zeile-1) { // rechts oben    +-
          zelle.nachbar[Richtung.RECHTS_OBEN.ordinal()] = neu;
          neu.nachbar[Richtung.LINKS_UNTEN.ordinal()] = zelle;        
        }
        if (s == zelle.spalte+1 && z == zelle.zeile) { // rechts      +0
          zelle.nachbar[Richtung.RECHTS.ordinal()] = neu;
          neu.nachbar[Richtung.LINKS.ordinal()] = zelle;        
        }
        if (s == zelle.spalte+1 && z == zelle.zeile+1) { // rechts unten  ++
          zelle.nachbar[Richtung.RECHTS_UNTEN.ordinal()] = neu;
          neu.nachbar[Richtung.LINKS_OBEN.ordinal()] = zelle;        
        }
        if (s == zelle.spalte && z == zelle.zeile+1) { // unten        0+
          zelle.nachbar[Richtung.UNTEN.ordinal()] = neu;
          neu.nachbar[Richtung.OBEN.ordinal()] = zelle;        
        }
        if (s == zelle.spalte-1 && z == zelle.zeile+1) { // links unten    -+
          zelle.nachbar[Richtung.LINKS_UNTEN.ordinal()] = neu;
          neu.nachbar[Richtung.RECHTS_OBEN.ordinal()] = zelle;        
        }
        if (s == zelle.spalte-1 && z == zelle.zeile) { // links        -0
          zelle.nachbar[Richtung.LINKS.ordinal()] = neu;
          neu.nachbar[Richtung.RECHTS.ordinal()] = zelle;        
        }
      }
      this.felder.add(neu); // Nebeneffekt: neue Zelle wird in die ArrayList eingetragen
      // ist das Spiel zu Ende:
      if (fünfNebeneinander(neu, Richtung.OBEN.ordinal()) // oben und unten in der Spalte 
          || fünfNebeneinander(neu, Richtung.RECHTS_OBEN.ordinal()) // in der Diagonale 
          || fünfNebeneinander(neu, Richtung.RECHTS.ordinal()) // rechts und links in der Reihe
          || fünfNebeneinander(neu, Richtung.RECHTS_UNTEN.ordinal())) { // in der Antidiagonale
        this.sieger = spielerO ? Spieler.O : Spieler.X; // Nebeneffekt: sieger wurde ermittelt
        // Daten für siegerlinie():
        if (z1 > z2 || s1 > s2) { // Vertauschen, um Abfrage in siegerlinie() zu vereinfachen
          int t = z1; z1 = z2; z2 = t;
          t = s1; s1 = s2; s2 = t;
        } // hilft nicht bei Antidiagonale
        if (z1 == z2) 
          linie = Linie.ZEILE;
        else if (s1 == s2) 
          linie = Linie.SPALTE;
        else if (s1 < s2 && z1 < z2) // s1 > s2 && z1 > z2 -> wurde vertauscht
          linie = Linie.DIAGONALE;        
        else if (s1 < s2) // z1 > z2 
          linie = Linie.LINKS_UNTEN; // Antidiagonale, Anfang links unten
        else // s1 > s2 -> z1 < z2 (sonst wäre es vertauscht gewesen) 
          linie = Linie.RECHTS_OBEN; // Antidiagonale, Anfang rechts oben
        return true; // neues Feld ist Sieger
      }
      return false; // das Spiel läuft weiter
    }
    /*
     * Findet heraus, ob um die gegebene Zelle in der gegebenen Richtung 5 gleiche Steine nebeneinander liegen 
     * @param feld - um diese herum wird gesucht
     * @param richtung OBEN, RECHTS_OBEN, RECHTS oder RECHTS_UNTEN - auch in die Gegenrichtung wird gesucht
     * @return true wenn fünf Steine nebeneinander liegen
     * Nebeneffekt: Koordinaten der Siegerlinie (z1,s1) bis (z2,s2) werden gesetzt
     */
    private boolean fünfNebeneinander(final Feld feld, int richtung) {
      int treffer = -1; // feld selbst wird zweimal getroffen -> treffer = 1
      final boolean spielerO = feld.besetztVonO;
      Feld f = feld; // Laufvariable
      while (f != null && f.besetztVonO == spielerO) {
        treffer++;
        z1 = f.zeile; s1 = f.spalte; // Anfang der Siegerlinie
        f = f.nachbar[richtung]; // Nachbar in die gegebene Richtung
      }
      // neue Suche in die Gegenrichtung:
      richtung = richtung + 4;
      f = feld; // Laufvariable neu initialisieren
      while (f != null && f.besetztVonO == spielerO) {
        treffer++;
        z2 = f.zeile; s2 = f.spalte; // Ende der Siegerlinie
        f = f.nachbar[richtung]; // in die Gegenrichtung
      }
      return treffer > 4; // true wenn 5
    }
  }
  // Nur für den Testtreiber:
  private int höhe() { // const // aktuelle Höhe des Spielfelds
    return brett.grenzeUnten - brett.grenzeOben + 1;
  }
  private int breite() { // const // aktuelle Breite des Spielfelds
    return brett.grenzeRechts - brett.grenzeLinks + 1;
  }
  // aktuellen Zustand auf der Konsole ausgeben ('this' nutzt 'private' Zugriff auf 'spielfeld')
  private void standAusgeben() { // const // nur für den Testtreiber
    if (this.sieger() == Spieler.LEER)
      System.out.println("Kein Sieger");
    System.out.print("    ");
    for (int spalte = this.brett.grenzeLinks; spalte <= this.brett.grenzeRechts; spalte++)
      System.out.print(String.format("%1d", Math.abs(spalte%10)));
    System.out.println();
    for (int zeile = this.brett.grenzeOben; zeile <= this.brett.grenzeUnten; zeile++) {
      System.out.print("" + String.format("%3d", zeile) + " ");
      for (int spalte = this.brett.grenzeLinks; spalte <= this.brett.grenzeRechts; spalte++) {
        String zeichen = "" + this.besetztVon(zeile, spalte);
        if (this.siegerlinie(zeile, spalte))
          zeichen = zeichen.toUpperCase();
        System.out.print(zeichen);
      }
      System.out.println();
    }    
  }
  // Testreiber: Testdaten im Kommandozeilenparameter, ansonsten zufallsgesteuert
  // Test mit Testdaten nutzt 'private' Zugriff auf 'spielfeld', zufallsgesteuerter nur 'public':
  public static void main(String[] testdaten) throws BesetztException, InterruptedException {
    final Model model = new Model(false);
    if (testdaten.length > 0) {
      final int höhe = testdaten.length / 2, breite = testdaten[0].length() / 2; // (0,0) ist in der Mitte
      ZEILE: for (int zeile = 0; zeile < testdaten.length; zeile++)
        for (int spalte = 0; spalte < testdaten[zeile].length(); spalte++) {
          if (testdaten[zeile].charAt(spalte) == Spieler.O.toString().charAt(0) && model.brett.steinSetzen(zeile - höhe, spalte - breite, true))
            // Nebeneffekt: feldBesetzen hat die neue Zelle in die ArrayList eingetragen 
            break ZEILE;
          if (testdaten[zeile].charAt(spalte) == Spieler.X.toString().charAt(0) && model.brett.steinSetzen(zeile - höhe, spalte - breite, false))
            // Nebeneffekt: feldBesetzen hat die neue Zelle in die ArrayList eingetragen 
            break ZEILE;
        }
    }
    else {
      Random r = new Random(new java.util.Date().getTime()); // Zufallsgenerator
      while (model.sieger() == Spieler.LEER) {
        // ein leeres Feld wird gesucht, um es zu besetzen:
        final int höhe = model.höhe(), breite = model.breite();
        int zeile = r.nextInt(höhe+1) - höhe/2, spalte = r.nextInt(breite+1) - breite/2; // Beginn in der Mitte 
        while(true) {
          int zufallX = r.nextInt(3) - 1, zufallY = r.nextInt(3) - 1; // -1, 0 oder +1
          zeile += zufallX; // nächster Versuch      
          spalte += zufallY;
          if (model.besetztVon(zeile, spalte) == Spieler.LEER) { // gefunden
            model.steinSetzen(zeile, spalte); // besetzen
            break;
          }
        }      
      }
    }
    model.standAusgeben();
  }
}

Version: 5. Februar 2012

© Prof. Solymosi, 20102 Beuth-Hochschule für Technik Berlin, Fachbereich VI (Informatik und Medien)

solymosibht-berlin.de