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 Board
Objekt identifiziert das Schachbrett. Es dient als Container für Checker
Objekte, die verschiedene Quadrate einnehmen. Es kann sich selbst zeichnen und verlangen, dass jedes enthaltene Checker
Objekt sich selbst zeichnet.
Ein Checker
Objekt 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 Checker
Größe beeinflusst wird .
CheckerType
ist eine ENUM , das eine Kontrolleur Farbe und Art über seine vier Konstanten identifiziert: BLACK_KING
, BLACK_REGULAR
, RED_KING
, und RED_REGULAR
.
Ein Player
Objekt ist eine Steuerung zum Bewegen eines Prüfers mit optionalen Sprüngen. Da ich mich entschieden habe, dieses Spiel in Swing zu implementieren, Player
ist dies nicht erforderlich. Stattdessen habe ich mich Board
in 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 Board
Methode (z. B. move()
) implementieren .
Welche öffentlichen APIs leisten Board
und Checker
tragen dazu bei? Nach einigem Überlegen habe ich mir die folgende öffentliche Board
API ausgedacht :
Board()
: Konstruiere einBoard
Objekt. Der Konstruktor führt verschiedene Initialisierungsaufgaben aus, z. B. die Listener-Registrierung.void add(Checker checker, int row, int column)
In:checker
bisBoard
auf die Position identifiziert durchrow
undcolumn
. Zeile und Spalte sind 1-basierte Werte und keine 0-basierten Werte (siehe Abbildung 1). Dieadd()
Auslöser,java.lang.IllegalArgumentException
wenn das Zeilen- oder Spaltenargument kleiner als 1 oder größer als 8 ist. Außerdem wird das Kontrollkästchen deaktiviert,AlreadyOccupiedException
wenn Sie versuchenChecker
, einem belegten Quadrat ein a hinzuzufügen .Dimension getPreferredSize()
: Geben Sie dieBoard
bevorzugte 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 Checker
API entwickelt:
Checker(CheckerType checkerType)
Konstruierte ein:Checker
Objekt der angegebenencheckerType
(BLACK_KING
,BLACK_REGULAR
,RED_KING
, oderRED_REGULAR
).void draw(Graphics g, int cx, int cy)
: Zeichnen Sie einenChecker
mit dem angegebenen Grafikkontext,g
wobei sich die Mitte des Prüfers bei (cx
,cy
) befindet. Diese Methode soll nur von aufgerufen werdenBoard
.boolean contains(int x, int y, int cx, int cy)
: Eine von dortstatic
aufgerufeneBoard
Hilfsmethode bestimmt, ob Mauskoordinaten (x
,y
) im Prüfer liegen, deren Mittelkoordinaten durch (cx
,cy
) angegeben sind und deren Dimension an anderer Stelle in derChecker
Klasse angegeben ist.int getDimension()
: Eine daraufstatic
aufgerufeneBoard
Hilfsmethode 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 AlreadyOccupiedException
den Quellcode.
Listing 1: AlreadyOccupiedException.java
public class AlreadyOccupiedException extends RuntimeException { public AlreadyOccupiedException(String msg) { super(msg); } }
AlreadyOccupiedException
erweitert java.lang.RuntimeException
, wodurch AlreadyOccupiedException
eine ungeprüfte Ausnahme entsteht (sie muss nicht in einer throws
Klausel 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
.
AlreadyOccupiedException
deklariert einen Konstruktor, der ein Zeichenfolgenargument verwendet, das den Grund für die Ausnahme beschreibt. Dieses Argument wird an die RuntimeException
Oberklasse 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; } }
Board
verlängert javax.swing.JComponent
, was Board
eine Swing-Komponente macht. Daher können Sie eine Board
Komponente direkt zum Inhaltsbereich einer Swing-Anwendung hinzufügen .
Board
Deklarationen SQUAREDIM
und BOARDDIM
Konstanten, die die Pixelabmessungen eines Quadrats und des Schachbretts identifizieren. Beim Initialisieren SQUAREDIM
rufe ich auf, Checker.getDimension()
anstatt auf eine äquivalente öffentliche Checker
Konstante zuzugreifen . Joshua Block antwortet, warum ich dies in Punkt 30 (Verwenden von Aufzählungen anstelle von int
Konstanten) der zweiten Ausgabe seines Buches Effective Java tue : "Programme, die das int
Aufzählungsmuster verwenden, sind spröde. Da int
Aufzählungen Konstanten zur Kompilierungszeit sind, werden sie kompiliert Wenn die int
mit 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 PosCheck
Klasse, die einen positionierten Prüfer durch Speichern einer Checker
Referenz und ihrer Mittelkoordinaten beschreibt, die sich auf die obere linke Ecke der Board
Komponente beziehen . Wenn Sie dem Checker
Objekt ein Objekt hinzufügen Board
, wird es PosCheck
zusammen 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
.