Die Implementierung des Views von Gomoku

 Eine einfache Implementierung des Gomoku-Views liegt in View.java vor. Sie enthält zwei Testtreiber: Der erste ist autonom, d.h. stellt nur ein Brett mit besetzten Figuren dar. Die Größe des Bretts und des Feld sowie die Figur stehen als Konstanten fest. Der zweite Test braucht Model, das durch Zufall gesteuert wird: Innerhalb der Grenzen des dargestellten Bretts werden die zwei Figuren (O und X) abwechselnd gesetzt. Model erkennt, wenn ein Sieger vorliegt. Wenn main() ohne Kommandozeilenparameter anläuft, wird der autonome Test ausgeführt, ansonsten er zufallsgesteuerte.

package view;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import model.BesetztException;
import model.IModel;
import model.Spieler;
import model.Model; // nur zu Testzwecken
/** View.java
 * Einfaches graphisches View für Gomoku mit MVC
 * Stellt das Spielbrett und eine Informationstafel auf einem swing.JFrame dar.
 * Auf der Informationstafel wird während des Spiels "Stein setzen: " (mit "O" oder "X" je nach dem nächsten Spielers) dargestellt.
 * Am Ende des Spiels steht dort "o gewinnt" bzw. "x gewinnt", je nach Sieger. Die Koordinaten der Endpunkte der Siegerlinie werden auch angegeben. 
 * Die Brettgröße (Anzahl der Felder) und die Feldgröße (in Pixeln) wird als Konstruktorparameter angegeben.
 * Beenden erfolgt durch Schließen des Fensters.
 * @author Prof. Dr. Andreas Solymosi, Beuth-Hochschule für Technik, (c) 2012, solymosi@tfh-berlin.de
 * @version 5/1/12 
 */
public class View extends JPanel implements IView {
  private static final long serialVersionUID = 1L;
  private static final String steinSetzen = "Stein setzen: ",  // Texte für die Informationstafel
    feldBesetzt = "Feld besetzt -" + steinSetzen,
    gewinnt = " gewinnt";
  private int höhe, breite; // dargestellte Brettgröße
  private IModel model; // Konstruktorparameter
    private Panel panel; // für die Darstellung des Bretts
    private JLabel label; // Informationstafel
    /*
     * @param model
     * @param höhe Anzahl der Felder
     * @param breite Anzahl der Felder
     * @param feldgröße in Pixeln; wird nur lokal gebraucht
     */
    public View(final IModel model, int höhe, int breite, int feldgröße) {
        this.model = model;
        this.höhe = höhe;
        this.breite = breite;
        super.setLayout(new BorderLayout());
        this.label = new JLabel(steinSetzen + model.nächster());
        super.add(label, BorderLayout.NORTH);        
        this.panel = new Panel();
        panel.setPreferredSize(new Dimension(feldgröße*höhe, feldgröße*breite));
        super.add(panel, BorderLayout.CENTER);
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.getContentPane().add(this);
        frame.pack();
        frame.setVisible(true);
    }
  @Override
  public Dimension brettgröße() {
    return new Dimension(höhe, breite);
  }
  @Override
  public void neuZeichnen() {
    if (model.sieger() != Spieler.LEER)
      label.setText(model.sieger() + gewinnt);
    else
      label.setText(steinSetzen + model.nächster());      
        this.repaint();
  }
  public void feldBesetzt(int x, int y) {
    label.setText(feldBesetzt + model.nächster() + " " + x + "/" + y);      
        this.repaint();      
  }
  private class Panel extends JPanel { // Brett
    @Override
    public void paintComponent(Graphics g) {
      super.paintComponent(g);
      int dx = this.getWidth() / höhe;
      int dy = this.getHeight() / breite;            
      // Gitter zeichnen:
      g.setColor(Color.BLACK);
      for (int zeile = 1; zeile < höhe; zeile++)
        for (int spalte = 1; spalte < breite; spalte++) { // nur zwischen den Feldern
          g.drawLine(zeile*dx, 0, zeile*dx, getHeight());
          g.drawLine(0, spalte*dy, getWidth(), spalte*dy);
        }
      // auf besetzte Felder Figuren zeichnen: 
      for (int zeile = 0; zeile < höhe; zeile++)
        for (int spalte = 0; spalte < breite; spalte++) {                  
          // Figuren an der Siegerlinie rot zeichnen, andere schwarz:
          g.setColor(model.siegerlinie(zeile, spalte) ? Color.red : Color.black);
          if (model.besetztVon(zeile, spalte) == Spieler.X) { // Kreuz zeichnen
            final int x0 = zeile*dx, y0 = spalte*dy;  
              g.drawLine(x0, y0, x0+dx, y0+dy);
              g.drawLine(x0, y0+dy, x0+dx, y0);                      
            }
          else if (model.besetztVon(zeile, spalte) == Spieler.O) // Kreis zeichnen
            g.drawOval(zeile*dx, spalte*dy, dx, dy);
        }
    }
  }
  /* Zwei Testreiber: 
   * 1. autonom mit Dummy-Model
   * 2. mit zufallsgesteuertem Model wenn Kommandozeilenparameter besetzt: 
     * @param kzp[0] Bretthöhe, Anzahl der Felder
     * @param kzp[1] Brettbreite, Anzahl der Felder
     * @param kzp[2] Feldgröße, Anzahl der Pixel
     * @param kzp[3] "X" wenn X anfängt, sonst fängt O an 
     * @param kzp[4] Wartezeit zwischen den Zügen in Millisekunden
   */
  public static void main(String[] kzp) throws InterruptedException {
    if (kzp.length == 0) { // autonomer Test mit Dummy-Model
      final int breite = 5, höhe = 6, feldgröße = 100;
      final Spieler besetzung = Spieler.O; 
      IView view = new View(new IModel() {
        public Spieler besetztVon(int zeile, int spalte) {
          return besetzung;
        }
        public Spieler nächster() {
          return Spieler.O;
        }
        public void start() {
        }
        public void steinSetzen(int zeile, int spalte) throws BesetztException {
        }
        public Spieler sieger() {
          return Spieler.O;
        }
        public boolean siegerlinie(int zeile, int spalte) {
          return false;
        }}, höhe, breite, feldgröße);
      view.neuZeichnen();
    } else {
      // Zufallsgesteuerter Test mit Model
      int höhe = kzp.length > 0 ? Integer.parseInt(kzp[0]) : 20, // Bretthöhe, Anzahl der Felder, Standardwert = 20
        breite = kzp.length > 1 ? Integer.parseInt(kzp[1]) : 20, // Brettbreite, Anzahl der Felder, Standardwert = 20
        feldgröße = kzp.length > 2 ? Integer.parseInt(kzp[1]) : 50; // Feldgröße, Anzahl der Pixel, Standardwert = 20
      boolean xFängtAn = kzp.length > 3 ? kzp[3].charAt(0) == 'X' : false; // Standardwert: O fängt an
      int pause = kzp.length > 4 ? Integer.parseInt(kzp[4]) : 100;  // Standardwert: 0,1 Sekunden
      IModel model = new Model(xFängtAn);
      View view = new View(model, höhe, breite, feldgröße); // Testling
  
      Random r = new Random(new java.util.Date().getTime());
      while (true) {
        int zeile = Math.abs(r.nextInt() % höhe), spalte = Math.abs(r.nextInt() % breite); // Zufallszahl zwischen 0 und 20 (bzw. höhe/breite)    
        try {
          model.steinSetzen(zeile, spalte);
        } catch (BesetztException e) {
          // 
        }
        view.neuZeichnen();
        Thread.sleep(pause);
        if (model.sieger() != Spieler.LEER) { // Spiel ist zu Ende
          view.label.setText(model.sieger() + gewinnt + ": " + zeile + "#" + spalte); // privater Zugriff
          break;
        }
      }
    }
  }
}

Eine fortschrittlichere View-Implementierung würde das Brett unbegrenzt und auf dem Bildschirm verschiebbar darstellen. Anstelle von einfachen Figuren könnten schönere Ikone verwendet werden.


Version: 5. Februar 2012

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

solymosibht-berlin.de