Checkers, siapapun?

Beberapa bulan yang lalu, saya diminta untuk membuat perpustakaan Java kecil yang dapat diakses oleh aplikasi untuk membuat antarmuka pengguna grafis (GUI) untuk permainan Checkers. Selain membuat papan catur dan catur, GUI harus memungkinkan pemeriksa diseret dari satu kotak ke kotak lainnya. Selain itu, pemeriksa harus berpusat di tengah kotak dan tidak boleh ditetapkan ke kotak yang ditempati oleh pemeriksa lain. Dalam posting ini, saya mempresentasikan perpustakaan saya.

Merancang perpustakaan GUI checker

Tipe publik apa yang harus didukung perpustakaan? Dalam catur, masing-masing dari dua pemain secara bergantian menggerakkan salah satu catur reguler (bukan raja) di atas papan hanya ke arah depan dan mungkin melompati checker pemain lain. Ketika checker mencapai sisi lain, itu dipromosikan menjadi raja, yang juga bisa bergerak ke arah belakang. Dari uraian ini, kita dapat menyimpulkan jenis-jenis berikut:

  • Board
  • Checker
  • CheckerType
  • Player

Sebuah Boardobjek mengidentifikasi papan catur. Ini berfungsi sebagai wadah untuk Checkerobjek yang menempati berbagai kotak. Itu bisa menggambar sendiri dan meminta setiap Checkerobjek yang berisi menggambar itu sendiri.

Sebuah Checkerobjek mengidentifikasi pemeriksa. Ini memiliki warna dan indikasi apakah itu pemeriksa biasa atau pemeriksa raja. Ia dapat menggambar dirinya sendiri dan membuat ukurannya tersedia untuk Board, yang ukurannya dipengaruhi oleh Checkerukurannya.

CheckerTypeadalah enum yang mengidentifikasi warna checker dan jenis melalui empat konstanta: BLACK_KING, BLACK_REGULAR, RED_KING, dan RED_REGULAR.

Sebuah Playerobjek adalah kontroler untuk memindahkan checker dengan melompat opsional. Karena saya telah memilih untuk mengimplementasikan game ini di Swing, Playertidak perlu. Sebagai gantinya, saya telah berubah Boardmenjadi komponen Swing yang konstruktornya mendaftarkan pendengar gerakan mouse dan mouse yang menangani gerakan checker atas nama pemain manusia. Di masa mendatang, saya dapat menerapkan pemutar komputer melalui utas lain, penyinkron, dan Boardmetode lain (seperti move()).

API publik apa yang dilakukan Boarddan Checkerberkontribusi? Setelah beberapa pemikiran, saya menemukan BoardAPI publik berikut :

  • Board(): Bangun sebuah Boardobjek. Konstruktor melakukan berbagai tugas inisialisasi seperti pendaftaran pendengar.
  • void add(Checker checker, int row, int column): Tambahkan checkerke Boardposisi yang diidentifikasi oleh rowdan column. Baris dan kolom adalah nilai berbasis 1 dan bukan berbasis 0 (lihat Gambar 1). The add()melempar java.lang.IllegalArgumentExceptionketika baris atau kolom argumen kurang dari 1 atau lebih besar dari 8. Selain itu, melempar dicentang AlreadyOccupiedExceptionketika Anda mencoba untuk menambahkan Checkerke diduduki persegi.
  • Dimension getPreferredSize(): Kembalikan Boardukuran pilihan komponen untuk tujuan tata letak.

Gambar 1. Sudut kiri atas papan centang terletak di (1, 1)

Saya juga mengembangkan CheckerAPI publik berikut :

  • Checker(CheckerType checkerType): Buatlah sebuah Checkerobjek tertentu checkerType( BLACK_KING, BLACK_REGULAR, RED_KING, atau RED_REGULAR).
  • void draw(Graphics g, int cx, int cy): Gambarlah Checkermenggunakan konteks grafik yang ditentukan gdengan pusat pemeriksa terletak di ( cx, cy). Metode ini dimaksudkan untuk dipanggil dari Boardsaja.
  • boolean contains(int x, int y, int cx, int cy): staticMetode pembantu yang dipanggil dari Boardyang menentukan apakah koordinat mouse ( x, y) berada di dalam pemeriksa yang koordinat pusatnya ditentukan oleh ( cx, cy) dan yang dimensinya ditentukan di tempat lain di Checkerkelas.
  • int getDimension(): staticMetode pembantu yang dipanggil dari Boardyang menentukan ukuran checker sehingga papan dapat mengukur persegi dan ukuran keseluruhannya dengan tepat.

Ini cukup banyak mencakup semua pustaka GUI checker dalam hal jenisnya dan API publiknya. Sekarang kita akan fokus pada bagaimana saya mengimplementasikan perpustakaan ini.

Menerapkan perpustakaan GUI pemeriksa

Perpustakaan catur GUI terdiri dari empat jenis publik yang terletak di file sumber yang sama-bernama: AlreadyOccupiedException, Board, Checker, dan CheckerType. Kode 1 menyajikan AlreadyOccupiedExceptionkode sumber.

Daftar 1. AlreadyOccupiedException.java

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

AlreadyOccupiedExceptionextends java.lang.RuntimeException, yang membuat AlreadyOccupiedExceptionpengecualian tidak dicentang (tidak harus ditangkap atau dideklarasikan dalam throwsklausa). Jika saya ingin AlreadyOccupiedExceptiondiperiksa, saya akan memperpanjang java.lang.Exception. Saya memilih untuk membuat jenis ini tidak dicentang karena cara kerjanya mirip dengan yang tidak dicentang IllegalArgumentException.

AlreadyOccupiedExceptionmendeklarasikan konstruktor yang mengambil argumen string yang menjelaskan alasan pengecualian. Argumen ini diteruskan ke RuntimeExceptionsuperclass.

Daftar 2 hadiah Board.

Daftar 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; } }

Boardmeluas javax.swing.JComponent, yang membuat Boardkomponen Ayun. Dengan demikian, Anda dapat langsung menambahkan Boardkomponen ke panel konten aplikasi Swing.

Boardmendeklarasikan SQUAREDIMdan BOARDDIMkonstanta yang mengidentifikasi dimensi piksel dari sebuah persegi dan papan centang. Saat menginisialisasi SQUAREDIM, saya memanggil Checker.getDimension()alih-alih mengakses Checkerkonstanta publik yang setara . Joshua Block menjawab mengapa saya melakukan ini di Item # 30 (Gunakan enum daripada intkonstanta) dari edisi kedua bukunya, Java Efektif : "Program yang menggunakan intpola enum adalah rapuh. Karena intenum adalah konstanta waktu kompilasi, mereka dikompilasi ke klien yang menggunakannya. Jika yang intterkait dengan konstanta enum diubah, kliennya harus dikompilasi ulang. Jika tidak, mereka akan tetap berjalan, tetapi perilakunya tidak ditentukan. "

Karena banyak komentar, saya tidak banyak bicara tentang Board. Namun, perhatikan PosCheckkelas bersarang , yang mendeskripsikan pemeriksa posisi dengan menyimpan Checkerreferensi dan koordinat pusatnya, yang relatif terhadap sudut kiri atas Boardkomponen. Saat Anda menambahkan Checkerobjek ke Board, itu disimpan dalam PosCheckobjek baru bersama dengan posisi tengah pemeriksa, yang dihitung dari baris dan kolom yang ditentukan.

Daftar 3 hadiah Checker.