Programmering

Brikker, noen?

For flere måneder siden ble jeg bedt om å lage et lite Java-bibliotek som en applikasjon kan få tilgang til for å gjengi et grafisk brukergrensesnitt (GUI) for spillet Checkers. I tillegg til å gjengi et rutetavle og brikker, må GUI tillate at en kontrollør blir dratt fra en firkant til en annen. En sjekker må også være sentrert på en firkant og må ikke tildeles et torg som er okkupert av en annen kontroller. I dette innlegget presenterer jeg biblioteket mitt.

Designe et GUI-bibliotek for brikker

Hvilke offentlige typer bør biblioteket støtte? I brikker flytter hver av to spillere vekselvis en av sine vanlige (ikke-konge) brikker over et brett kun i fremoverretning og muligens hopper den andre spillerens brikker. Når kontrolløren når den andre siden, blir den promotert til en konge, som også kan bevege seg bakover. Fra denne beskrivelsen kan vi utlede følgende typer:

  • Borde
  • Checker
  • CheckerType
  • Spiller

EN Borde objektet identifiserer rutetavlen. Den fungerer som en container for Checker gjenstander som okkuperer forskjellige firkanter. Den kan tegne seg selv og be om at hver inneholder Checker objektet tegner seg selv.

EN Checker objektet identifiserer en kontroller. Den har en farge og en indikasjon på om det er en vanlig kontroller eller en king checker. Den kan tegne seg selv og gjør størrelsen tilgjengelig for Borde, hvis størrelse er påvirket av Checker størrelse.

CheckerType er en enum som identifiserer en kontrollerfarge og -type via sine fire konstanter: BLACK_KING, SVART_REGULÆR, RED_KING, og RØD_REGULÆR.

EN Spiller objektet er en kontroller for å flytte en sjekker med valgfrie hopp. Fordi jeg har valgt å implementere dette spillet i Swing, Spiller er ikke nødvendig. I stedet har jeg snudd Borde inn i en Swing-komponent hvis konstruktør registrerer mus- og musebevegelseslyttere som håndterer kontrollerbevegelse på vegne av den menneskelige spilleren. I fremtiden kunne jeg implementere en dataspiller via en annen tråd, en synkroniser og en annen Borde metode (for eksempel bevege seg()).

Hva offentlige API-er gjør Borde og Checker bidra? Etter litt ettertanke kom jeg opp med følgende publikum Borde API:

  • Borde(): Konstruer en Borde gjenstand. Konstruktøren utfører forskjellige initialiseringsoppgaver som lytterregistrering.
  • void add (Checker checker, int row, int column): Legge til sjekker til Borde på stillingen identifisert av rad og kolonne. Raden og kolonnen er 1-baserte verdier i motsetning til å være 0-baserte (se figur 1). De legge til() kaster java.lang.IllegalArgumentException når argumentet for rad eller kolonne er mindre enn 1 eller større enn 8. Den kaster også det ukontrollerte AlreadyOccupiedException når du prøver å legge til en Checker til et okkupert torg.
  • Dimensjon getPreferredSize (): Returner Borde komponentens foretrukne størrelse for layoutformål.

Figur 1. Kontrollbordets øvre venstre hjørne er plassert ved (1, 1)

Jeg utviklet også følgende publikum Checker API:

  • Checker (CheckerType checkerType): Konstruer en Checker objektet til det spesifiserte checkerType (BLACK_KING, SVART_REGULÆR, RED_KING, eller RØD_REGULÆR).
  • ugyldig tegning (grafikk g, int cx, int cy): Tegn en Checker ved å bruke den spesifiserte grafikkonteksten g med midten av kontrolløren på (cx, cy). Denne metoden er ment å kalles fra Borde kun.
  • boolsk inneholder (int x, int y, int cx, int cy): A statisk hjelpermetode kalt fra Borde som avgjør om musekoordinater (x, y) ligger inne i kontrolløren hvis senterkoordinater er spesifisert av (cx, cy) og hvis dimensjon er spesifisert andre steder i Checker klasse.
  • int getDimension (): A statisk hjelpermetode kalt fra Borde som bestemmer størrelsen på en sjekker slik at brettet kan dimensjonere kvadratene og den totale størrelsen på riktig måte.

Dette dekker stort sett alle brikker-GUI-biblioteket når det gjelder typer og deres offentlige API-er. Vi vil nå fokusere på hvordan jeg implementerte dette biblioteket.

Implementering av brikker-GUI-biblioteket

Dammen GUI-biblioteket består av fire offentlige typer som ligger i samme kildefiler: AlreadyOccupiedException, Borde, Checker, og CheckerType. Oppføring 1 gaver AlreadyOccupiedExceptionkildekoden.

Oppføring 1. AlreadyOccupiedException.java

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

AlreadyOccupiedException strekker java.lang.RuntimeException, som gjør AlreadyOccupiedException et ukontrollert unntak (det trenger ikke å bli fanget eller erklært i et kaster klausul). Hvis jeg ville lage AlreadyOccupiedException sjekket, ville jeg ha utvidet java.lang. unntak. Jeg valgte å gjøre denne typen ukontrollert fordi den fungerer på samme måte som den ukontrollerte IllegalArgumentException.

AlreadyOccupiedException erklærer en konstruktør som tar et strengargument som beskriver årsaken til unntaket. Dette argumentet blir oversendt til RuntimeException superklasse.

Oppføring 2 gaver Borde.

Oppføring 2. Styret.java

importer java.awt.Color; importere java.awt.Dimension; importere java.awt.Grafikk; importere java.awt.Graphics2D; importere java.awt.RenderingHints; importere java.awt.event.MouseEvent; importer java.awt.event.MouseAdapter; importer java.awt.event.MouseMotionAdapter; importere java.util.ArrayList; importere java.util.List; importere javax.swing.JComponent; public class Board utvider JComponent {// dimensjon av rutebrettet (25% større enn ruteren) privat sluttstatisk int SQUAREDIM = (int) (Checker.getDimension () * 1.25); // dimensjon av sjakkbrett (bredde på 8 firkanter) privat slutt int BOARDDIM = 8 * SQUAREDIM; // foretrukket størrelse på Board komponent private Dimensjon dimPrefSize; // dra flagg - sett til sant når brukeren trykker museknappen over kontrolløren // og tømmes til falsk når brukeren slipper museknappen privat boolsk inDrag = false; // forskyvning mellom dra startkoordinater og sjekksenter koordinater private int deltax, deltay; // referanse til posisjonert kontrollør ved start av dra privat PosCheck posCheck; // senterplassering av kontrolløren ved start av dra private int oldcx, oldcy; // liste over Checker-objekter og deres opprinnelige posisjoner private List posChecks; public Board () {posChecks = new ArrayList (); dimPrefSize = ny dimensjon (BOARDDIM, BOARDDIM); addMouseListener (new MouseAdapter () {@Override public void mousePressed (MouseEvent me) {// Skaff musekoordinater ved trykk. int x = me.getX (); int y = me.getY (); // Finn posisjonert kontrollør under musetrykk. 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 slippes, tøm inDrag (til // indikerer ingen dra i gang) hvis inDrag er // allerede satt. hvis (inDrag) inDrag = false; ellers returnerer; // Snap-kontroller til midten av firkanten. int x = me.getX (); int y = me.getY (); posCheck .cx = (x - deltax) / SQUAREDIM * SQUAREDIM + SQUAREDIM / 2; posCheck.cy = (y - deltid) / SQUAREDIM * SQUAREDIM + SQUAREDIM / 2; // Ikke flytt kontrolløren på et okkupert torg. for (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; male på nytt (); }}); // Fest en musebevegelseslytter til appleten. Denne lytteren lytter // etter musedragshendelser. addMouseMotionListener (new MouseMotionAdapter () {@Override public void mouseDragged (MouseEvent me) {if (inDrag) {// Oppdater plasseringen til kontrollsenteret. posCheck.cx = me.getX () - deltax; posCheck.cy = me.getY ( ) - deltay; ommaling ();}}}); } public void add (Checker checker, int row, int col) {if (row 8) throw new IllegalArgumentException ("row out of range:" + row); hvis (kol 8) kaster ny IllegalArgumentException ("kol utenfor rekkevidde:" + kol); PosCheck posCheck = ny PosCheck (); posCheck.checker = sjekker; posCheck.cx = (col - 1) * SQUAREDIM + SQUAREDIM / 2; posCheck.cy = (rad - 1) * SQUAREDIM + SQUAREDIM / 2; for (PosCheck _posCheck: posChecks) hvis (posCheck.cx == _posCheck.cx && posCheck.cy == _posCheck.cy) kaster ny AlreadyOccupiedException ("firkant ved (" + rad + "," + kol + ") er opptatt" ); posChecks.add (posCheck); } @ Override public Dimension getPreferredSize () {return dimPrefSize; } @ Override-beskyttet tomrom paintComponent (Grafikk g) {paintCheckerBoard (g); for (PosCheck posCheck: posChecks) hvis (posCheck! = Board.this.posCheck) posCheck.checker.draw (g, posCheck.cx, posCheck.cy); // Tegn den slepte sjekken sist slik at den vises over en underliggende // sjekker. hvis (posCheck! = null) posCheck.checker.draw (g, posCheck.cx, posCheck.cy); } privat tomrom paintCheckerBoard (Grafikk g) {((Grafikk2D) g) .setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Mal sjakkbrett. for (int rad = 0; rad <8; rad ++) {g.setColor (((rad & 1)! = 0)? Color.BLACK: Color.WHITE); for (int col = 0; col <8; col ++) {g.fillRect (col * SQUAREDIM, rad * SQUAREDIM, SQUAREDIM, SQUAREDIM); g.setColor ((g.getColor () == Color.BLACK)? Color.WHITE: Color.BLACK); }}} // posisjonert sjekkerhjelperklasse privat klasse PosCheck {offentlig sjekkerkontroll; offentlig int cx; offentlig int cy; }}

Borde strekker javax.swing.JKomponent, som gjør Borde en svingkomponent. Som sådan kan du direkte legge til en Borde komponent til et Swing-applikasjons innholdsrute.

Borde erklærer SQUAREDIM og BOARDDIM konstanter som identifiserer pikselmålene til et kvadrat og kontrollbordet. Ved initialisering SQUAREDIM, Påberoper jeg meg Checker.getDimension () i stedet for å få tilgang til et tilsvarende publikum Checker konstant. Joshua Block svarer hvorfor jeg gjør dette i Item # 30 (Bruk enums i stedet for int konstanter) av den andre utgaven av boken hans, Effektiv Java: "Programmer som bruker int enum mønster er sprø. Fordi int enums er kompileringstidskonstanter, de samles til klientene som bruker dem. Hvis den int knyttet til en enumkonstant endres, må kundene kompileres på nytt. Hvis de ikke er det, vil de fremdeles løpe, men deres oppførsel vil være udefinert. "

På grunn av de omfattende kommentarene har jeg ikke mye mer å si om Borde. Vær imidlertid oppmerksom på den nestede PosCheck klasse, som beskriver en posisjonert kontrollør ved å lagre en Checker referanse og dens midtkoordinater, som er i forhold til øvre venstre hjørne av Borde komponent. Når du legger til en Checker motsette seg Borde, den er lagret i en ny PosCheck objekt sammen med midtpunktet til kontrolløren, som beregnes fra den angitte raden og kolonnen.

Oppføring 3 gaver Checker.

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