Checker dragging

Drag a checker from square to square

Have you ever wanted to create a checkers game—perhaps one that involves two human players or a human player versus a computer player? Creating a checkers game presents many challenges. One of these challenges involves dragging a checker from one square to another square on a checkerboard. This Java Fun And Games installment presents a CheckerDrag applet that demonstrates checker dragging.

The CheckerDrag applet displays a checkerboard and a single checker. It lets you drag the checker to any area within the checkerboard's boundaries. CheckerDrag doesn't care where the checker ends up—on a square of the opposite color, between two squares, several squares away from the current square, and so on. The goal is to show you how to drag a checker. Listing 1 presents the applet's source code.

Listing 1. CheckerDrag.java

 

// CheckerDrag.java

import java.awt.*; import java.awt.event.*;

public class CheckerDrag extends java.applet.Applet { // Dimension of checkerboard square.

final static int SQUAREDIM = 40;

// Dimension of checkerboard -- includes black outline.

final static int BOARDDIM = 8 * SQUAREDIM + 2;

// Dimension of checker -- 3/4 the dimension of a square.

final static int CHECKERDIM = 3 * SQUAREDIM / 4;

// Square colors are dark green or white.

final static Color darkGreen = new Color (0, 128, 0);

// Dragging flag -- set to true when user presses mouse button over checker // and cleared to false when user releases mouse button.

boolean inDrag = false;

// Left coordinate of checkerboard's upper-left corner.

int boardx;

// Top coordinate of checkerboard's upper-left corner.

int boardy;

// Left coordinate of checker rectangle origin (upper-left corner).

int ox;

// Top coordinate of checker rectangle origin (upper-left corner).

int oy;

// Left displacement between mouse coordinates at time of press and checker // rectangle origin.

int relx;

// Top displacement between mouse coordinates at time of press and checker // rectangle origin.

int rely;

// Width of applet drawing area.

int width;

// Height of applet drawing area.

int height;

// Image buffer.

Image imBuffer;

// Graphics context associated with image buffer.

Graphics imG;

public void init () { // Obtain the size of the applet's drawing area.

width = getSize ().width; height = getSize ().height;

// Create image buffer.

imBuffer = createImage (width, height);

// Retrieve graphics context associated with image buffer.

imG = imBuffer.getGraphics ();

// Initialize checkerboard's origin, so that board is centered.

boardx = (width - BOARDDIM) / 2 + 1; boardy = (height - BOARDDIM) / 2 + 1;

// Initialize checker's rectangle's starting origin so that checker is // centered in the square located in the top row and second column from // the left.

ox = boardx + SQUAREDIM + (SQUAREDIM - CHECKERDIM) / 2 + 1; oy = boardy + (SQUAREDIM - CHECKERDIM) / 2 + 1;

// Attach a mouse listener to the applet. That listener listens for // mouse-button press and mouse-button release events.

addMouseListener (new MouseAdapter () { public void mousePressed (MouseEvent e) { // Obtain mouse coordinates at time of press.

int x = e.getX (); int y = e.getY ();

// If mouse is over draggable checker at time // of press (i.e., contains (x, y) returns // true), save distance between current mouse // coordinates and draggable checker origin // (which will always be positive) and set drag // flag to true (to indicate drag in progress).

if (contains (x, y)) { relx = x - ox; rely = y - oy; inDrag = true; } }

boolean contains (int x, int y) { // Calculate center of draggable checker.

int cox = ox + CHECKERDIM / 2; int coy = oy + CHECKERDIM / 2;

// Return true if (x, y) locates with bounds // of draggable checker. CHECKERDIM / 2 is the // radius.

return (cox - x) * (cox - x) + (coy - y) * (coy - y) < CHECKERDIM / 2 * CHECKERDIM / 2; }

public void mouseReleased (MouseEvent e) { // When mouse is released, clear inDrag (to // indicate no drag in progress) if inDrag is // already set.

if (inDrag) inDrag = false; } });

// Attach a mouse motion listener to the applet. That listener listens // for mouse drag events.

addMouseMotionListener (new MouseMotionAdapter () { public void mouseDragged (MouseEvent e) { if (inDrag) { // Calculate draggable checker's new // origin (the upper-left corner of // the checker rectangle).

int tmpox = e.getX () - relx; int tmpoy = e.getY () - rely;

// If the checker is not being moved // (at least partly) off board, // assign the previously calculated // origin (tmpox, tmpoy) as the // permanent origin (ox, oy), and // redraw the display area (with the // draggable checker at the new // coordinates).

if (tmpox > boardx && tmpoy > boardy && tmpox + CHECKERDIM < boardx + BOARDDIM && tmpoy + CHECKERDIM < boardy + BOARDDIM) { ox = tmpox; oy = tmpoy; repaint (); } } } }); }

public void paint (Graphics g) { // Paint the checkerboard over which the checker will be dragged.

paintCheckerBoard (imG, boardx, boardy);

// Paint the checker that will be dragged.

paintChecker (imG, ox, oy);

// Draw contents of image buffer.

g.drawImage (imBuffer, 0, 0, this); }

void paintChecker (Graphics g, int x, int y) { // Set checker shadow color.

g.setColor (Color.black);

// Paint checker shadow.

g.fillOval (x, y, CHECKERDIM, CHECKERDIM);

// Set checker color.

g.setColor (Color.red);

// Paint checker.

g.fillOval (x, y, CHECKERDIM - CHECKERDIM / 13, CHECKERDIM - CHECKERDIM / 13); }

void paintCheckerBoard (Graphics g, int x, int y) { // Paint checkerboard outline.

g.setColor (Color.black); g.drawRect (x, y, 8 * SQUAREDIM + 1, 8 * SQUAREDIM + 1);

// Paint checkerboard.

for (int row = 0; row < 8; row++) { g.setColor (((row & 1) != 0) ? darkGreen : Color.white);

for (int col = 0; col < 8; col++) { g.fillRect (x + 1 + col * SQUAREDIM, y + 1 + row * SQUAREDIM, SQUAREDIM, SQUAREDIM);

g.setColor ((g.getColor () == darkGreen) ? Color.white : darkGreen); } } }

// The AWT invokes the update() method in response to the repaint() method // calls that are made as a checker is dragged. The default implementation // of this method, which is inherited from the Container class, clears the // applet's drawing area to the background color prior to calling paint(). // This clearing followed by drawing causes flicker. CheckerDrag overrides // update() to prevent the background from being cleared, which eliminates // the flicker.

public void update (Graphics g) { paint (g); } }

Listing 1's state variables and event handlers (responding to mouse-press, mouse-release, and mouse-drag events) collectively describe checker dragging. I won't delve into how those state variables and event handlers interact because a careful study of the source code and its many comments reveals how checker dragging works. However, there is one item that I want to discuss: flicker and its elimination.

I look at flicker as the momentary bleed-through of background pixels while painting a drawing surface, such as an applet's drawing area. This effect is often quite distracting. Failing to override update() and performing multiple drawing operations to update an applet's drawing area are two ways to produce flicker in an applet:

  • When the applet invokes repaint(), to request its drawing area be repainted, AWT (Abstract Window Toolkit) invokes update(). Unless overridden, the applet inherits that method from the Container superclass. The inherited update() method clears the applet's drawing area to its background color before invoking paint(). This clearing causes a blank drawing area to briefly appear prior to the actual repaint operation, resulting in flicker. This source of flicker can be eliminated by overriding update() to invoke paint() without clearing the drawing area.
  • When the applet performs multiple drawing operations on its drawing area, flicker tends to occur. I believe the cause is related to the lack of synchronization during screen refresh. This source of flicker can be eliminated by creating an image buffer, rendering all graphics to that buffer, and drawing the buffer's contents, via a drawImage() call, on the applet's drawing area in one operation.

After compiling CheckerDrag.java, you'll want to run this applet. Before you can do that, however, you must describe the applet to appletviewer via HTML. Listing 2 provides the needed HTML.

Listing 2. CheckerDrag.html

 <applet code=CheckerDrag.class width=400 height=400>
</applet> 

To run the applet, invoke appletviewer CheckerDrag.html (case is not important). The figure below reveals the checkerboard with the checker in its starting position.

The checker's starting position is the top row and second column from the left

To drag the checker, first, position the mouse pointer over the checker; second, press the appropriate mouse button; third, move the checker anywhere on the checkerboard; and, finally, release the mouse button.

I have an exercise for you: Modify CheckerDrag.java to prevent the checker from being dragged onto white squares or between squares. Accomplish that task as follows: From the mouse-release event handler, detect the end of a drag. If a drag is ending, determine the checker's position. If the checker is entirely on a green square or partially on a single green square, center the checker on that square. If the checker is partially on two green squares or entirely on a white square, restore (and center) the checker back on the square from which it was dragged.

Review

Dragging a checker from one square to another on a checkerboard is one of the many challenges faced by developers that create checkers games. This article's CheckerDrag applet reveals how to accomplish the drag. Its state variables and event handlers (responding to mouse-press, mouse-release, and mouse-drag events) collectively support checker dragging. As with many other applets, CheckerDrag is prone to flicker. To address this issue, override update(), create an image buffer, render all graphics to that buffer, and draw the buffer's contents, via a drawImage() call, on the applet's drawing area in one operation.

Next time, I explore kaleidoscopes.

Jeff Friesen is a freelance software developer and educator specializing in C, C++, and Java technology.