Dame, jemand?

Vor einigen Monaten wurde ich gebeten, eine kleine Java-Bibliothek zu erstellen, auf die eine Anwendung zugreifen kann, um eine grafische Benutzeroberfläche (GUI) für das Spiel Checkers zu rendern. Neben dem Rendern eines Schachbretts und von Schachbrettmustern muss die Benutzeroberfläche auch das Ziehen eines Schachbretts von einem Quadrat zum anderen ermöglichen. Außerdem muss ein Checker auf einem Quadrat zentriert sein und darf keinem Quadrat zugeordnet werden, das von einem anderen Checker belegt wird. In diesem Beitrag präsentiere ich meine Bibliothek.

Entwerfen einer Checkers-GUI-Bibliothek

Welche öffentlichen Typen sollte die Bibliothek unterstützen? Bei Checkern bewegt jeder von zwei Spielern abwechselnd einen seiner regulären (Nicht-Königs-) Checker nur in Vorwärtsrichtung über ein Brett und springt möglicherweise die Checker des anderen Spielers. Wenn der Stein die andere Seite erreicht, wird er zu einem König befördert, der sich auch rückwärts bewegen kann. Aus dieser Beschreibung können wir die folgenden Typen ableiten:

  • Board
  • Checker
  • CheckerType
  • Player

Ein BoardObjekt identifiziert das Schachbrett. Es dient als Container für CheckerObjekte, die verschiedene Quadrate einnehmen. Es kann sich selbst zeichnen und verlangen, dass jedes enthaltene CheckerObjekt sich selbst zeichnet.

Ein CheckerObjekt identifiziert einen Prüfer. Es hat eine Farbe und einen Hinweis darauf, ob es sich um einen regulären oder einen King Checker handelt. Es kann sich selbst zeichnen und stellt seine Größe zur Verfügung Board, deren Größe von der CheckerGröße beeinflusst wird .

CheckerTypeist eine ENUM , das eine Kontrolleur Farbe und Art über seine vier Konstanten identifiziert: BLACK_KING, BLACK_REGULAR, RED_KING, und RED_REGULAR.

Ein PlayerObjekt ist eine Steuerung zum Bewegen eines Prüfers mit optionalen Sprüngen. Da ich mich entschieden habe, dieses Spiel in Swing zu implementieren, Playerist dies nicht erforderlich. Stattdessen habe ich mich Boardin eine Swing-Komponente verwandelt, deren Konstruktor Maus- und Mausbewegungs-Listener registriert, die die Checker-Bewegung im Auftrag des menschlichen Spielers ausführen. In Zukunft könnte ich einen Computer-Player über einen anderen Thread, einen Synchronizer und eine andere BoardMethode (z. B. move()) implementieren .

Welche öffentlichen APIs leisten Boardund Checkertragen dazu bei? Nach einigem Überlegen habe ich mir die folgende öffentliche BoardAPI ausgedacht :

  • Board(): Konstruiere ein BoardObjekt. Der Konstruktor führt verschiedene Initialisierungsaufgaben aus, z. B. die Listener-Registrierung.
  • void add(Checker checker, int row, int column)In: checkerbis Boardauf die Position identifiziert durch rowund column. Zeile und Spalte sind 1-basierte Werte und keine 0-basierten Werte (siehe Abbildung 1). Die add()Auslöser, java.lang.IllegalArgumentExceptionwenn das Zeilen- oder Spaltenargument kleiner als 1 oder größer als 8 ist. Außerdem wird das Kontrollkästchen deaktiviert, AlreadyOccupiedExceptionwenn Sie versuchen Checker, einem belegten Quadrat ein a hinzuzufügen .
  • Dimension getPreferredSize(): Geben Sie die Boardbevorzugte Größe der Komponente für Layoutzwecke zurück.

Abbildung 1. Die obere linke Ecke des Schachbretts befindet sich bei (1, 1).

Ich habe auch die folgende öffentliche CheckerAPI entwickelt:

  • Checker(CheckerType checkerType)Konstruierte ein: CheckerObjekt der angegebenen checkerType( BLACK_KING, BLACK_REGULAR, RED_KING, oder RED_REGULAR).
  • void draw(Graphics g, int cx, int cy): Zeichnen Sie einen Checkermit dem angegebenen Grafikkontext, gwobei sich die Mitte des Prüfers bei ( cx, cy) befindet. Diese Methode soll nur von aufgerufen werden Board.
  • boolean contains(int x, int y, int cx, int cy): Eine von dort staticaufgerufene BoardHilfsmethode bestimmt, ob Mauskoordinaten ( x, y) im Prüfer liegen, deren Mittelkoordinaten durch ( cx, cy) angegeben sind und deren Dimension an anderer Stelle in der CheckerKlasse angegeben ist.
  • int getDimension(): Eine darauf staticaufgerufene BoardHilfsmethode bestimmt die Größe eines Checkers, damit das Board seine Quadrate und die Gesamtgröße entsprechend dimensionieren kann.

Dies deckt so ziemlich alle GUI-Bibliotheken der Prüfer in Bezug auf ihre Typen und ihre öffentlichen APIs ab. Wir werden uns jetzt darauf konzentrieren, wie ich diese Bibliothek implementiert habe.

Implementierung der GUI-Bibliothek der Prüfer

Die Kontrolleure GUI - Bibliothek besteht aus vier öffentlichen Typen befindet sich in derselben genannten Quelldateien: AlreadyOccupiedException, Board, Checker, und CheckerType. Listing 1 zeigt AlreadyOccupiedExceptionden Quellcode.

Listing 1: AlreadyOccupiedException.java

public class AlreadyOccupiedException extends RuntimeException { public AlreadyOccupiedException(String msg) { super(msg); } }

AlreadyOccupiedExceptionerweitert java.lang.RuntimeException, wodurch AlreadyOccupiedExceptioneine ungeprüfte Ausnahme entsteht (sie muss nicht in einer throwsKlausel abgefangen oder deklariert werden ). Wenn ich AlreadyOccupiedExceptionüberprüfen wollte, hätte ich verlängert java.lang.Exception. Ich habe diesen Typ deaktiviert, da er ähnlich wie der deaktivierte funktioniert IllegalArgumentException.

AlreadyOccupiedExceptiondeklariert einen Konstruktor, der ein Zeichenfolgenargument verwendet, das den Grund für die Ausnahme beschreibt. Dieses Argument wird an die RuntimeExceptionOberklasse weitergeleitet.

Listing 2 präsentiert Board.

Listing 2: Board.java

import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.MouseEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseMotionAdapter; import java.util.ArrayList; import java.util.List; import javax.swing.JComponent; public class Board extends JComponent { // dimension of checkerboard square (25% bigger than checker) private final static int SQUAREDIM = (int) (Checker.getDimension() * 1.25); // dimension of checkerboard (width of 8 squares) private final int BOARDDIM = 8 * SQUAREDIM; // preferred size of Board component private Dimension dimPrefSize; // dragging flag -- set to true when user presses mouse button over checker // and cleared to false when user releases mouse button private boolean inDrag = false; // displacement between drag start coordinates and checker center coordinates private int deltax, deltay; // reference to positioned checker at start of drag private PosCheck posCheck; // center location of checker at start of drag private int oldcx, oldcy; // list of Checker objects and their initial positions private List posChecks; public Board() { posChecks = new ArrayList(); dimPrefSize = new Dimension(BOARDDIM, BOARDDIM); addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent me) { // Obtain mouse coordinates at time of press. int x = me.getX(); int y = me.getY(); // Locate positioned checker under mouse press. for (PosCheck posCheck: posChecks) if (Checker.contains(x, y, posCheck.cx, posCheck.cy)) { Board.this.posCheck = posCheck; oldcx = posCheck.cx; oldcy = posCheck.cy; deltax = x - posCheck.cx; deltay = y - posCheck.cy; inDrag = true; return; } } @Override public void mouseReleased(MouseEvent me) { // When mouse released, clear inDrag (to // indicate no drag in progress) if inDrag is // already set. if (inDrag) inDrag = false; else return; // Snap checker to center of square. int x = me.getX(); int y = me.getY(); posCheck.cx = (x - deltax) / SQUAREDIM * SQUAREDIM + SQUAREDIM / 2; posCheck.cy = (y - deltay) / SQUAREDIM * SQUAREDIM + SQUAREDIM / 2; // Do not move checker onto an occupied square. for (PosCheck posCheck: posChecks) if (posCheck != Board.this.posCheck && posCheck.cx == Board.this.posCheck.cx && posCheck.cy == Board.this.posCheck.cy) { Board.this.posCheck.cx = oldcx; Board.this.posCheck.cy = oldcy; } posCheck = null; repaint(); } }); // Attach a mouse motion listener to the applet. That listener listens // for mouse drag events. addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseDragged(MouseEvent me) { if (inDrag) { // Update location of checker center. posCheck.cx = me.getX() - deltax; posCheck.cy = me.getY() - deltay; repaint(); } } }); } public void add(Checker checker, int row, int col) { if (row  8) throw new IllegalArgumentException("row out of range: " + row); if (col  8) throw new IllegalArgumentException("col out of range: " + col); PosCheck posCheck = new PosCheck(); posCheck.checker = checker; posCheck.cx = (col - 1) * SQUAREDIM + SQUAREDIM / 2; posCheck.cy = (row - 1) * SQUAREDIM + SQUAREDIM / 2; for (PosCheck _posCheck: posChecks) if (posCheck.cx == _posCheck.cx && posCheck.cy == _posCheck.cy) throw new AlreadyOccupiedException("square at (" + row + "," + col + ") is occupied"); posChecks.add(posCheck); } @Override public Dimension getPreferredSize() { return dimPrefSize; } @Override protected void paintComponent(Graphics g) { paintCheckerBoard(g); for (PosCheck posCheck: posChecks) if (posCheck != Board.this.posCheck) posCheck.checker.draw(g, posCheck.cx, posCheck.cy); // Draw dragged checker last so that it appears over any underlying // checker. if (posCheck != null) posCheck.checker.draw(g, posCheck.cx, posCheck.cy); } private void paintCheckerBoard(Graphics g) { ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Paint checkerboard. for (int row = 0; row < 8; row++) { g.setColor(((row & 1) != 0) ? Color.BLACK : Color.WHITE); for (int col = 0; col < 8; col++) { g.fillRect(col * SQUAREDIM, row * SQUAREDIM, SQUAREDIM, SQUAREDIM); g.setColor((g.getColor() == Color.BLACK) ? Color.WHITE : Color.BLACK); } } } // positioned checker helper class private class PosCheck { public Checker checker; public int cx; public int cy; } }

Boardverlängert javax.swing.JComponent, was Boardeine Swing-Komponente macht. Daher können Sie eine BoardKomponente direkt zum Inhaltsbereich einer Swing-Anwendung hinzufügen .

BoardDeklarationen SQUAREDIMund BOARDDIMKonstanten, die die Pixelabmessungen eines Quadrats und des Schachbretts identifizieren. Beim Initialisieren SQUAREDIMrufe ich auf, Checker.getDimension()anstatt auf eine äquivalente öffentliche CheckerKonstante zuzugreifen . Joshua Block antwortet, warum ich dies in Punkt 30 (Verwenden von Aufzählungen anstelle von intKonstanten) der zweiten Ausgabe seines Buches Effective Java tue : "Programme, die das intAufzählungsmuster verwenden, sind spröde. Da intAufzählungen Konstanten zur Kompilierungszeit sind, werden sie kompiliert Wenn die intmit einer Enum-Konstante verknüpfte geändert wird, müssen die Clients neu kompiliert werden. Wenn dies nicht der Fall ist, werden sie weiterhin ausgeführt, aber ihr Verhalten ist undefiniert. "

Aufgrund der umfangreichen Kommentare habe ich nicht viel mehr zu sagen Board. Beachten Sie jedoch die verschachtelte PosCheckKlasse, die einen positionierten Prüfer durch Speichern einer CheckerReferenz und ihrer Mittelkoordinaten beschreibt, die sich auf die obere linke Ecke der BoardKomponente beziehen . Wenn Sie dem CheckerObjekt ein Objekt hinzufügen Board, wird es PosCheckzusammen mit der Mittelposition des Prüfers, die aus der angegebenen Zeile und Spalte berechnet wird, in einem neuen Objekt gespeichert .

Listing 3 präsentiert Checker.