圖1: KT的界面由一個棋盤, 一個選擇開始方格的組合框和一個開始游歷的按鈕組成
在啟動巡游之前, 先從組合框中選擇騎士開始的角落。 程序響應會讓騎士顯示在正確的角落上(默認情況下騎士在最左上角)。 然后單擊"Take the Tour"(開始巡游)按鈕來開始整個巡游過程。 按鈕和組合框在巡游過程中都將被禁止。巡游過程是怎么樣的呢? 圖2展現了一系列的線段(軌跡), 每一個線段都是隨著騎士在棋盤的行動從上一個方格的中心到當前方格的中心。
圖2: 巡游從左上角開始
現在你已經看到了這個小程序的界面和巡游過程, 讓我們開始學習它的源代碼吧。
清單1. KT.java
// KT.java
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.net.URL;
import java.util.ArrayList;
public class KT extends Applet
{
// 線程延遲以毫秒為單位
public final static int DELAY = 500;
// 開始騎士巡游線程
PRivate Thread thd;
// 初始化小程序
public void init ()
{
// 創建一個標簽對象來標明小程序的標題
Label lblTitle = new Label ("Knight's Tour", Label.CENTER);
lblTitle.setFont (new Font ("Arial", Font.BOLD, 18));
// 把標簽對象加到小程序的面板容器
add (lblTitle);
// 創建一個ChessBoard對象,它具有顯示一個棋盤、移動騎士到
// 任何方格并留下騎士巡游軌跡的能力.
final ChessBoard cb = new ChessBoard (this);
//把ChessBoard對象加入到小程序的面板容器
add (cb);
// 創建一個Panel對象來保存Label,Choice和按鈕對象.
Panel pnl = new Panel ();
//創建一個標簽來標明Choice對象并把它添加到Panel中
pnl.add (new Label ("Choose starting position:"));
//創建一個Choice對象,用來選擇騎士的開始位置(棋盤的四個角落)
//并把它添加到Panel中.
final Choice c = new Choice ();
c.add ("Upperleft corner");
c.add ("Upperright corner");
//創建Choice的item listener,這個監聽器按選擇結果來重設騎士的開始位置.
c.addItemListener (new ItemListener ()
{
public void itemStateChanged (ItemEvent e)
{
Choice c = (Choice) e.getSource ();
if (c.getSelectedIndex () == 0)
cb.moveKnight (1);
else
cb.moveKnight (8);
cb.reset ();
}
});
pnl.add (c);
//把Panel加入到小程序的面板容器
add (pnl);
//創建一個按鈕對象用來開始騎士巡游.
final Button BTn = new Button ("Take the Tour");
//創建按鈕的Action listener(動作監聽器),用來確定騎士巡游的位置.
//按照規則將騎士從一個位置移動到另一個位置.
ActionListener al;
al = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
Runnable r;
r = new Runnable ()
{
int [] boardPositions1 =
{
1, 18, 33, 50, 60, 54, 64, 47,
32, 15, 5, 11, 17, 34, 49, 59,
53, 63, 48, 31, 16, 6, 12, 2,
19, 25, 42, 57, 51, 61, 55, 40,
23, 8, 14, 4, 10, 27, 44, 38,
21, 36, 46, 29, 35, 41, 58, 52,
62, 56, 39, 24, 7, 13, 3, 9,
26, 43, 37, 22, 28, 45, 30, 20
};
int [] boardPositions2 =
{
8, 23, 40, 55, 61, 51, 57, 42,
25, 10, 4, 14, 24, 39, 56, 62,
52, 58, 41, 26, 9, 3, 13, 7,
22, 32, 47, 64, 54, 60, 50, 33,
18, 1, 11, 5, 15, 30, 45, 35,
20, 37, 43, 28, 38, 48, 63, 53,
59, 49, 34, 17, 2, 12, 6, 16,
31, 46, 36, 19, 29, 44, 27, 21
};
public void run ()
{
cb.reset ();
// thd用來檢查用戶離開小程序網頁
// 以便停止小程序的運行.
for (int i = 0; i < boardPositions1.length &&
thd != null; i++)
{
if (c.getSelectedIndex () == 0)
cb.moveKnight (boardPositions1 [i]);
else
cb.moveKnight (boardPositions2 [i]);
try
{
Thread.sleep (DELAY);
}
catch (InterruptedException e2)
{
}
}
c.setEnabled (true);
btn.setEnabled (true);
}
};
c.setEnabled (false);
btn.setEnabled (false);
thd = new Thread (r);
thd.start ();
}
};
btn.addActionListener (al);
//添加按鈕到小程序面板容器
add (btn);
}
//停止小程序
public void stop ()
{
//用戶離開網頁時必須停止”騎士巡游”線程
thd = null;
}
}
final class ChessBoard extends Canvas
{
//非白色方格的顏色
private final static Color SQUARECOLOR = new Color (195, 214, 242);
//棋盤方格的尺寸
private final static int SQUAREDIM = 40;
//棋盤方格的尺寸(包括黑邊框)
private final static int BOARDDIM = 8 * SQUAREDIM + 2;
//棋盤左上角的左坐標(X坐標)
private int boardx;
//棋盤左上角的頂坐標(Y坐標)
private int boardy;
//棋盤長度
private int width;
// 棋盤寬度
private int height;
// 圖像緩沖
private Image imBuffer;
// Graphics context associated with image buffer.
private Graphics imG;
// 騎士圖像
private Image imKnight;
// 騎士圖像的長度
private int knightWidth;
// 騎士圖像的寬度
private int knightHeight;
//騎士軌跡的坐標
private ArrayList trail;
// Left coordinate of knight rectangle origin (upper-left corner).
private int ox;
// Top coordinate of knight rectangle origin (upper-left corner).
private int oy;
// 創建ChessBoard的Applet--調用它的getImage()和getDocumentBase()方法,
// 并且我們將使用它作為drawImage()方法的image observer
Applet a;
// 構造棋盤
ChessBoard (Applet a)
{
// 確定部件的大小
width = BOARDDIM+1;
height = BOARDDIM+1;
// 初始化棋盤, 使它處于中心
boardx = (width - BOARDDIM) / 2 + 1;
boardy = (height - BOARDDIM) / 2 + 1;
//使用MediaTracker來確保騎士圖像在我們獲取它的長和寬之前被完全導入
MediaTracker mt = new MediaTracker (this);
// 導入騎士圖像
imKnight = a.getImage (a.getDocumentBase (), "knight.gif");
mt.addImage (imKnight, 0);
try
{
mt.waitForID (0);
}
catch (InterruptedException e) {}
//獲得騎士的長度和寬度, 幫助騎士定位于方格中心
knightWidth = imKnight.getWidth (a);
knightHeight = imKnight.getHeight (a);
//初始化騎士圖像, 使騎士定位于左上角方格的中心
ox = boardx + (SQUAREDIM - knightWidth) / 2 + 1;
oy = boardy + (SQUAREDIM - knightHeight) / 2 + 1;
//創建一個數據結構, 用來保存騎士巡游時的軌跡
trail = new ArrayList ();
//保存applet引用以便后面調用drawImage()時使用.
this.a = a;
}
// This method is called when the ChessBoard component's peer is created.
// The code in this method cannot be called in the ChessBoard constrUCtor
// because the createImage() method returns null at that point. It doesn't
// return a meaningful value until the ChessBoard component has been added
// to its container. The addNotify() method is not called until the first
// time ChessBoard is added to a container.
public void addNotify ()
{
// Given this object's Canvas "layer" a chance to create a Canvas peer.
super.addNotify ();
//創建圖像緩沖
imBuffer = createImage (width, height);
//得到圖像緩沖的內容。
imG = imBuffer.getGraphics ();
}
//當小程序的布局治理器布置它的組件時,會調用這個方法。
//假如可能,組件會顯示為首選大小。
public Dimension getPreferredSize ()
{
return new Dimension (width, height);
}
//移動騎士到指定的棋盤位置。假如位置小于1或大于64則拋出一個異常
public void moveKnight (int boardPosition)
{
if (boardPosition < 1 boardPosition > 64)
throw new IllegalArgumentException ("invalid board position: " +
boardPosition);
int rebasedBoardPosition = boardPosition-1;
int col = rebasedBoardPosition % 8;
int row = rebasedBoardPosition / 8;
ox = boardx + col * SQUAREDIM + (SQUAREDIM - knightWidth) / 2 + 1;
oy = boardy + row * SQUAREDIM + (SQUAREDIM - knightHeight) / 2 + 1;
trail.add (new Point (ox + knightWidth / 2, oy + knightHeight / 2));
repaint ();
}
//畫出所有部件――先棋盤然后是騎士
public void paint (Graphics g)
{
//畫出棋盤
paintChessBoard (imG, boardx, boardy);
//畫出騎士
paintKnight (imG, ox, oy);
//畫出騎士的軌跡
paintTrail (imG);
//畫出圖像緩沖的內容
g.drawImage (imBuffer, 0, 0, this);
}
//畫出棋盤――(x, y)是左上角坐標
void paintChessBoard (Graphics g, int x, int y)
{
// 畫出棋盤的邊框
g.setColor (Color.black);
g.drawRect (x, y, 8 * SQUAREDIM + 1, 8 * SQUAREDIM + 1);
//畫出棋盤
for (int row = 0; row < 8; row++)
{
g.setColor (((row & 1) != 0) ? SQUARECOLOR : 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 () == SQUARECOLOR) ? Color.white :
SQUARECOLOR);
}
}
}
//畫出騎士――(x, y)是圖片左上角坐標
void paintKnight (Graphics g, int x, int y)
{
g.drawImage (imKnight, x, y, a);
}
//畫出騎士的軌跡
void paintTrail (Graphics g)
{
g.setColor (Color.black);
int len = trail.size ();
if (len == 0)
return;
Point pt = (Point) trail.get (0);
int ox = pt.x;
int oy = pt.y;
for (int i = 1; i < len; i++)
{
pt = (Point) trail.get (i);
g.drawLine (ox, oy, pt.x, pt.y);
ox = pt.x;
oy = pt.y;
}
}
//清空ArrayList來重設棋盤
public void reset ()
{
trail.clear ();
}
// The AWT invokes the update() method in response to the repaint() method
// call that is made as a knight is moved. 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. KT overrides
// update() to prevent the background from being cleared, which eliminates
// the flicker.
public void update (Graphics g)
{
paint (g);
}
}
|
新聞熱點
疑難解答