在棋盘的两个方格之间拖动棋子
摘要
编写一个跳棋游戏会遇到许多挑战,在棋盘上拖动棋子就是其中之一,本期的Java Fun and Games讨论了这个方面,并着重介绍了如何消除拖动棋子时产生的屏幕闪烁。
你有没有想过要编写一个跳棋游戏――可以是双人对战,也可以是人机对战?编写一个跳棋游戏会遇到许多挑战,在棋盘上把棋子从一个方格拖动到另外一个方格就是其中之一。本期的Java Fun and Games通过一个名为CheckerDrag的applet演示了棋子的拖动。
CheckerDrag applet在屏幕上显示了一个棋盘和一个棋子,你可以在棋盘的边界内任意拖动棋子。CheckerDrag不考虑棋子会落在哪里――也许是在不同颜色的格子上,在两个格子之间,或者是离开几个格子远。该程序的目的是演示如何拖动棋子。表1展示了applet的源代码:
表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.
// 棋子的尺寸 ? 方格尺寸的3/4
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.
// 拖动标记 --当用户在棋子上按下鼠标按键时设为true,
// 释放鼠标按键时设为false
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.
// applet绘图区域的宽度
int width;
// Height of applet drawing area.
// applet绘图区域的高度
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.
// 获取applet绘图区域的尺寸
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.
// 向applet添加一个用来监听鼠标按键的按下和释放事件的鼠标监听器
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).
// 在按键时如果鼠标位于可拖动的棋子上方
// (也就是contains (x, y)返回true),则保存当前
// 鼠标坐标与棋子的原点之间的距离(始终为正值)并且
// 将拖动标志设为true(用来表明正处在拖动过程中)
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.
// 如果(x, y)仍处于棋子范围内则返回true
// CHECKERDIM / 2为半径
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.
// 当鼠标按键被释放时,如果inDrag已经为true,
// 则将其置为false(用来表明不在拖动过程中)
if (inDrag)
inDrag = false;
}
});
// Attach a mouse motion listener to the applet. That listener listens
// for mouse drag events.
//向applet添加一个用来监听鼠标拖动事件的鼠标运动监听器
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).
// 如果棋子(至少是棋子的一部分)没有被
// 移出棋盘,则将之前计算的原点
// (tmpox, tmpoy)赋值给永久性的原点(ox, oy),
// 并且刷新显示区域(此时的棋子已经位于新坐标上)
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.
// AWT调用了update()方法来响应拖动棋子时所调用的repaint()方法。该方法从
// Container类继承的默认实现会在调用paint()之前,将applet的绘图区域清除
// 为背景色,这种绘制之后的清除就导致了闪烁。CheckerDrag重写了update()来
// 防止背景被清除,从而消除了闪烁。
public void update (Graphics g)
{
paint (g);
}
}
表1的状态变量和事件处理器(响应鼠标按键按下、按键释放和拖动事件)共同地实现了棋子的拖动。我没有深入的研究这些状态变量和事件处理器会如何相互影响,是因为对源代码及其众多注释的细致学习已经可以揭示出棋子的拖动是如何实现的。然而,仍然有一个细节是我希望探讨的:闪烁及其消除。
我把闪烁看作在绘制图像表面(例如applet的绘图区域)的时候,背景像素的瞬间扩散。这种效果常常是非常令人心烦意乱的。未能重写update()并且执行多重绘制操作来刷新一个applet的绘图区域将从两个方面导致闪烁:
•当applet调用repaint()来请求重绘它的绘图区域时,AWT(抽象窗口工具包)将会调用update()。除非对其进行重写,否则applet将从Container超类继承该方法。继承得到的update()方法在调用paint()之前,会将applet的绘图区域清除为背景色,而这种清除导致在真正的重绘操作之前,会暂时产生一个空白的绘图区域,从而引起了闪烁。可以采用重写update()来调用paint()而不清除绘图区域的方法来消除这种来源的闪烁。
•当applet在其绘图区域执行多重绘制操作时,就会有发生闪烁的趋势。我认为其原因涉及到屏幕刷新时缺乏同步。这种来源的闪烁可以采用如下方法来消除:创建一个图像缓冲,在缓冲中绘制所有的图形,并且通过在applet的绘图区域中调用drawImage()来一次性绘制出缓冲的内容。
在编译了CheckerDrag.java之后,你可以运行这个applet。然而在运行之前,你必须使用HTML来为appletviewer描述该applet。表2提供了所需的HTML。
表2. CheckerDrag.html
调用appletviewer CheckerDrag.html(大小写并不重要)来运行applet。下面的图象展示了棋盘和位于初始位置的棋子。
棋子的初始位置是第一行左数第二列
为了拖动棋子,首先将鼠标置于棋子之上,然后按下适当的鼠标按键,之后再移动棋子到棋盘上的任意位置,最后放开鼠标按键。
我为你准备了一个练习:修改CheckerDrag.java来防止棋子被移动到白色格子上或者两个格子之间。按如下步骤来完成这个任务:用鼠标释放事件处理器来检测拖动的结束,如果拖动结束,则检测棋子的位置。如果棋子完全位于绿色格子上或者部分位于一个单独的绿色格子上,则将棋子置于该绿色格子的中心。如果棋子分别位于两个绿色格子上面或者完全位于一个白色格子上,就将棋子还原到它被拖动之前的位置(并且居中)。
在棋盘的两个方格之间拖动棋子是编写跳棋游戏的开发者所要面对的众多挑战之一,本文的CheckerDrag applet展示了如何实现拖动。它的状态变量和事件处理器(响应鼠标按键按下、按键释放和拖动事件)共同支持了棋子的拖动。同许多别的applet一样,CheckerDrag容易出现闪烁,为了解决这个问题,可以重写update(),创建一个图像缓冲,在applet的绘图区域中调用drawImage()来一次性绘制出缓冲的内容。
版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:Jeff Friesen;jlearner(作者的blog:http://blog.matrix.org.cn/page/jlearner)
原文:http://www.javaworld.com/javaworld/jw-08-2005/jw-0822-funandgames.html
译文:http://www.matrix.org.cn/resource/article/44/44296_Checker+dragging.html
关键字:Java;Game