Programmering

Skak, nogen?

For flere måneder siden blev jeg bedt om at oprette et lille Java-bibliotek, som en applikation kan få adgang til, til at gengive en grafisk brugergrænseflade (GUI) til spillet Checkers. Ud over at gengive et skakbræt og brikker skal GUI tillade, at en brikker trækkes fra en firkant til en anden. En brikker skal også være centreret på en firkant og må ikke tildeles en firkant, der er optaget af en anden brikker. I dette indlæg præsenterer jeg mit bibliotek.

Design af et GUI-bibliotek med brikker

Hvilke offentlige typer skal biblioteket understøtte? I brikker bevæger hver af to spillere skiftevis en af ​​sine regelmæssige (ikke-konge) brikker over et bræt kun i en fremadrettet retning og muligvis springer den anden spillers brik. Når brikken når den anden side, forfremmes den til en konge, som også kan bevæge sig baglæns. Fra denne beskrivelse kan vi udlede følgende typer:

  • Bestyrelse
  • Checker
  • CheckerType
  • Spiller

EN Bestyrelse objekt identificerer skakternet. Det fungerer som en container til Checker genstande, der indtager forskellige firkanter. Det kan tegne sig selv og anmode om, at hver indeholdt Checker objekt tegner sig selv.

EN Checker objekt identificerer en brik. Det har en farve og en indikation af, om det er en almindelig brik eller en konge brik. Det kan tegne sig selv og gøre sin størrelse tilgængelig for Bestyrelse, hvis størrelse er påvirket af Checker størrelse.

CheckerType er et enum, der identificerer en kontrolfarve og -type via dens fire konstanter: BLACK_KING, SVART_REGULÆR, RED_KINGog RØD_REGULÆR.

EN Spiller objekt er en controller til at flytte en brik med valgfri spring. Fordi jeg har valgt at implementere dette spil i Swing, Spiller er ikke nødvendigt. I stedet har jeg vendt mig Bestyrelse ind i en Swing-komponent, hvis konstruktør registrerer mus- og musebevægelseslyttere, der håndterer kontrolbevægelse på vegne af den menneskelige spiller. I fremtiden kunne jeg implementere en computerafspiller via en anden tråd, en synkronisering og en anden Bestyrelse metode (f.eks bevæge sig()).

Hvad offentlige API'er gør Bestyrelse og Checker bidrage? Efter lidt eftertanke kom jeg op med følgende offentlighed Bestyrelse API:

  • Bestyrelse(): Konstruer en Bestyrelse objekt. Konstruktøren udfører forskellige initialiseringsopgaver såsom lytterregistrering.
  • ugyldig tilføjelse (Checker checker, int row, int column): Tilføje checker til Bestyrelse på den position, der er identificeret af række og kolonne. Rækken og kolonnen er 1-baserede værdier i modsætning til at være 0-baserede (se figur 1). Det tilføje() kaster java.lang.IllegalArgumentException når dens række- eller søjleargument er mindre end 1 eller større end 8. Det kaster også det umarkerede AlreadyOccupiedException når du prøver at tilføje en Checker til en besat plads.
  • Dimension getPreferredSize (): Returner Bestyrelse komponentens foretrukne størrelse til layoutformål.

Figur 1. Checkboardets øverste venstre hjørne er placeret ved (1, 1)

Jeg udviklede også følgende publikum Checker API:

  • Checker (CheckerType checkerType): Konstruer en Checker genstand for det angivne checkerType (BLACK_KING, SVART_REGULÆR, RED_KING, eller RØD_REGULÆR).
  • ugyldig tegning (grafik g, int cx, int cy): Tegn en Checker ved hjælp af den specificerede grafiske kontekst g med midten af ​​brikken placeret på (cx, cy). Denne metode er beregnet til at blive kaldt fra Bestyrelse kun.
  • boolsk indeholder (int x, int y, int cx, int cy): A statisk hjælpemetode kaldet fra Bestyrelse der bestemmer, om musekoordinater (x, y) ligger inde i briketten, hvis centerkoordinater er specificeret af (cx, cy) og hvis dimension er angivet andetsteds i Checker klasse.
  • int getDimension (): A statisk hjælper metode kaldet fra Bestyrelse der bestemmer størrelsen på en brik, så tavlen kan dimensionere dens firkanter og den samlede størrelse korrekt.

Dette dækker stort set alle brikkerens GUI-bibliotek med hensyn til dets typer og deres offentlige API'er. Vi vil nu fokusere på, hvordan jeg implementerede dette bibliotek.

Implementering af brikkerens GUI-bibliotek

Brikkerens GUI-bibliotek består af fire offentlige typer, der er placeret i kildefiler med samme navn: AlreadyOccupiedException, Bestyrelse, Checkerog CheckerType. Notering 1 gaver AlreadyOccupiedExceptionkildekode.

Liste 1. AlreadyOccupiedException.java

offentlig klasse AlreadyOccupiedException udvider RuntimeException {public AlreadyOccupiedException (String msg) {super (msg); }}

AlreadyOccupiedException strækker sig java.lang.RuntimeException, hvilket gør AlreadyOccupiedException en ukontrolleret undtagelse (den behøver ikke at blive fanget eller erklæret i en kaster klausul). Hvis jeg ville lave AlreadyOccupiedException kontrolleret, ville jeg have forlænget java.lang.Undtagelse. Jeg valgte at gøre denne type ukontrolleret, fordi den fungerer på samme måde som den ukontrollerede IllegalArgumentException.

AlreadyOccupiedException erklærer en konstruktør, der tager et strengargument, der beskriver årsagen til undtagelsen. Dette argument sendes til RuntimeException superklasse.

Notering 2 gaver Bestyrelse.

Liste 2. Board.java

import java.awt.Color; import java.awt.Dimension; import java.awt.Grafik; importere java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.MouseEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseMotionAdapter; importere java.util.ArrayList; importere java.util.List; import javax.swing.JComponent; public class Board udvider JComponent {// dimension af skakbræt-firkant (25% større end skak) privat endelig statisk int SQUAREDIM = (int) (Checker.getDimension () * 1.25); // dimension af skakbræt (bredde på 8 firkanter) private final int BOARDDIM = 8 * SQUAREDIM; // foretrukken størrelse af Board komponent privat Dimension dimPrefSize; // trækflag - indstillet til sand, når brugeren trykker på museknappen over kontrolløren // og ryddet til falsk, når brugeren frigiver museknappen privat boolsk inDrag = false // forskydning mellem trækstartkoordinater og kontrolcenterkoordinater privat int deltax, deltay; // henvisning til positioneret kontrol ved start af træk privat PosCheck posCheck; // center placering af brik ved start af træk private int oldcx, oldcy; // liste over Checker-objekter og deres oprindelige positioner private List posChecks; public Board () {posChecks = new ArrayList (); dimPrefSize = ny dimension (BOARDDIM, BOARDDIM); addMouseListener (ny MouseAdapter () {@Override public void mousePressed (MouseEvent me) {// Få musekoordinater på tidspunktet for tryk. int x = me.getX (); int y = me.getY (); // Find positioneret kontrol under musetryk. for (PosCheck posCheck: posChecks) hvis (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) {// Når musen frigives, skal du rydde iDrag (til // angive ingen træk i gang) hvis inDrag er // allerede indstillet. hvis (inDrag) inDrag = false; ellers returneres; // Snap-kontrol til midten af ​​firkanten. int x = me.getX (); int y = me.getY (); posCheck .cx = (x - deltax) / SQUAREDIM * SQUAREDIM + SQUAREDIM / 2; posCheck.cy = (y - deltage) / SQUAREDIM * SQUAREDIM + SQUAREDIM / 2; // Flyt ikke checker på en optaget firkant. til (PosCheck posCheck : posChecks) hvis (posCheck! = Board.this.posCheck && posC heck.cx == Board.this.posCheck.cx && posCheck.cy == Board.this.posCheck.cy) {Board.this.posCheck.cx = oldcx; Board.this.posCheck.cy = oldcy; } posCheck = null; genmaling (); }}); // Vedhæft en musebevægelseslytter til appleten. Denne lytter lytter // til musetrækbegivenheder. addMouseMotionListener (ny MouseMotionAdapter () {@ Overstyr offentlig ugyldig mouseDragged (MouseEvent mig) {hvis (inDrag) {// Opdater placering af kontrolcenter. posCheck.cx = me.getX () - deltax; posCheck.cy = me.getY ( ) - deltage; genmaling ();}}}); } public void add (Checker checker, int row, int col) {if (row 8) throw new IllegalArgumentException ("row out of range:" + row); hvis (kol 8) smider nyt IllegalArgumentException ("kol uden for rækkevidde:" + kol); PosCheck posCheck = ny PosCheck (); posCheck.checker = kontrol; posCheck.cx = (col - 1) * SQUAREDIM + SQUAREDIM / 2; posCheck.cy = (række - 1) * SQUAREDIM + SQUAREDIM / 2; for (PosCheck _posCheck: posChecks) hvis (posCheck.cx == _posCheck.cx && posCheck.cy == _posCheck.cy) smider nyt alleredeOccupiedException ("firkant ved (" + række + "," + col + ") er optaget" ); posChecks.add (posCheck); } @ Override public Dimension getPreferredSize () {return dimPrefSize; } @ Override beskyttet ugyldig paintComponent (Grafik g) {paintCheckerBoard (g); for (PosCheck posCheck: posChecks) hvis (posCheck! = Board.this.posCheck) posCheck.checker.draw (g, posCheck.cx, posCheck.cy); // Tegn trukket checker sidst, så den vises over enhver underliggende // checker. hvis (posCheck! = null) posCheck.checker.draw (g, posCheck.cx, posCheck.cy); } privat hulrum paintCheckerBoard (Grafik g) {((Grafik2D) g) .setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Mal skakternet. for (int række = 0; række <8; række ++) {g.setColor (((række & 1)! = 0)? Color.BLACK: Color.WHITE); for (int col = 0; col <8; col ++) {g.fillRect (col * SQUAREDIM, række * SQUAREDIM, SQUAREDIM, SQUAREDIM); g.setColor ((g.getColor () == Color.BLACK)? Color.WHITE: Color.BLACK); }}} // positioneret checker hjælperklasse privat klasse PosCheck {public Checker checker; offentlig int cx; offentlig int cy; }}

Bestyrelse strækker sig javax.swing.JKomponent, hvilket gør Bestyrelse en gyngekomponent. Som sådan kan du direkte tilføje en Bestyrelse komponent til en Swing-applikations indholdsrude.

Bestyrelse erklærer SQUAREDIM og BOARDDIM konstanter, der identificerer pixeldimensionerne på en firkant og checkboardet. Ved initialisering SQUAREDIM, Påberåber jeg mig Checker.getDimension () i stedet for at få adgang til en tilsvarende offentlighed Checker konstant. Joshua Block svarer hvorfor jeg gør dette i punkt 30 (brug enums i stedet for int konstanter) af anden udgave af hans bog, Effektiv Java: "Programmer, der bruger int enum mønster er sprøde. Fordi int enums er kompileringstidskonstanter, de samles til de klienter, der bruger dem. Hvis den int forbundet med en enumkonstant ændres, skal dens klienter kompileres igen. Hvis de ikke er det, løber de stadig, men deres adfærd vil være udefineret. "

På grund af de omfattende kommentarer har jeg ikke meget mere at sige om Bestyrelse. Bemærk dog den indlejrede PosCheck klasse, der beskriver en positioneret kontrol ved at gemme en Checker reference og dens centerkoordinater, som er relative til det øverste venstre hjørne af Bestyrelse komponent. Når du tilføjer en Checker modsætter sig Bestyrelse, det er gemt i en ny PosCheck objekt sammen med kontrolens midterposition, der beregnes ud fra den angivne række og kolonne.

Notering 3 gaver Checker.

$config[zx-auto] not found$config[zx-overlay] not found