


// Squares.java
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class Squares extends JApplet
{
PRivate void createGUI ()
{
// 設定界面
getContentPane ().setLayout (new FlowLayout ());
// 創(chuàng)建游戲板控件:每個單元格有40像素寬,默認綠色,并且在獲得焦點時邊框是藍色,
// 而失去焦點時變?yōu)楹谏0芽丶拥絚ontent pane里。
final GameBoard gb;
gb = new GameBoard (40, Color.green, Color.blue, Color.black);
getContentPane ().add (gb);
// 界面其他部分包括兩個按鈕,他們會被放到一個panel里以作為整體處理。例如,
// 假如Applet的寬度變大了,兩個按鈕(而不是一個按鈕)都會向游戲板的右側對齊。
JPanel p = new JPanel ();
// 創(chuàng)建“Change Square Color”按鈕并設置為無效。只有游戲進行中可以改變顏色。
final JButton BTnChangeSquareColor = new JButton ("Change Square Color");
btnChangeSquareColor.setEnabled (false);
// 建立“Change Square Color”按鈕的action事件監(jiān)聽器,點擊此按鈕,會隨機改變
// 單元格的顏色
ActionListener al;
al = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
Random rnd = new Random ();
while (true)
{
int r = rnd.nextInt (256);
int g = rnd.nextInt (256);
int b = rnd.nextInt (256);
// 不使用所有組成原色(紅、綠、藍)都小于192的顏色,因為那不
// 輕易和背景的黑色區(qū)分出來。
if (r < 192 && g < 192 && b < 192)
continue;
gb.changeSquareColor (new Color (r, g, b));
break;
}
}
};
btnChangeSquareColor.addActionListener (al);
p.add (btnChangeSquareColor);
// 創(chuàng)建“Start”按鈕
final JButton btnStart = new JButton ("Start");
// 建立“Start”按鈕的action事件監(jiān)聽器。點擊這個按鈕時,它本身會變?yōu)闊o效(沒
// 理由開始一個正在進行的游戲),并使“Change Square Color”按鈕有效(游戲進
// 行時可以改變單元格顏色)。“done”事件監(jiān)控器則用于在游戲結束時使“Start”按
// 鈕有效,以及使“Change Square Color”按鈕無效。
al = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
btnStart.setEnabled (false);
btnChangeSquareColor.setEnabled (true);
gb.start (new GameBoard.DoneListener ()
{
public void done ()
{
btnStart.setEnabled (true);
btnChangeSquareColor.setEnabled (false);
}
});
}
};
btnStart.addActionListener (al);
// 通過一個panel把兩個按鈕添加到content pane里邊。
p.add (btnStart);
getContentPane ().add (p);
// 在Java 1.4.0里,假如不設置JApplet為焦點循環(huán)根節(jié)點、并且新建一個焦點遍歷
// 規(guī)則的話,你就沒有辦法把焦點從一個控件切換到另一個。你可以在以下鏈接看到相關信
// 息:http://bugs.sun.com/bugdatabase/view_bug.do;:YfiG?bug_id=4705205
if (System.getProperty ("java.version").equals ("1.4.0"))
{
setFocusCycleRoot (true);
setFocusTraversalPolicy (new LayoutFocusTraversalPolicy ());
}
}
public void init ()
{
// Sun的Java教程說Swing控件應該在事件處理線程里創(chuàng)建、查詢、以及操作。由于大
// 多數瀏覽器都不去調用Applet的主如init()的那些主要方法,我們在那個線程里調
// 用SwingUtilities.invokeAndWait()以保證在事件處理線程里GUI被正確創(chuàng)建。
// 我們用invokeAndWait()而不是invokeLater(),因為后者會導致在GUI創(chuàng)建之前
// init()方法會返回;這會造成一些很難跟蹤的applet問題。
try
{
SwingUtilities.invokeAndWait (new Runnable ()
{
public void run ()
{
createGUI ();
}
});
}
catch (Exception e)
{
System.err.println ("Unable to create GUI");
}
}
}
class GameBoard extends JPanel
{
// 游戲狀態(tài)
private final static int INITIAL = 0;
private final static int INPLAY = 1;
private final static int LOSE = 2;
private final static int WIN = 3;
// 邊框尺寸
private final static int BORDER_SIZE = 5;
// 當前游戲狀態(tài)
private int state = INITIAL;
// 在單元格邊框之間的像素寬度
private int cellSize;
// 游戲板的寬度(包含邊框)
private int width;
// 游戲板及消息區(qū)的總計高度(包含邊框)
private int height;
// 每一個單元格的顏色
private Color squareColor;
// 在游戲板擁有焦點時的邊框顏色
private Color focusBorderColor;
// 在游戲板是去焦點時的邊框顏色
private Color nonfocusBorderColor;
// 游戲板當前的邊框顏色
private Color borderColor;
// 單元格狀態(tài):true代表特定單元格包含一個有顏色的方塊(非黑色)
private boolean [] cells = new boolean [9];
// 對游戲結束監(jiān)聽器的引用
private GameBoard.DoneListener dl;
// 對倒計時的計時器的引用;這個計數器判定玩家時候獲勝/失敗,并且通知當游戲結束時通
// 知DoneListener
private Timer timer;
// 計時器的計時數字
private int counter;
// 游戲板構造函數
GameBoard (int cellSize, Color squareColor, Color focusBorderColor,
Color nonfocusBorderColor)
{
this.cellSize = cellSize;
width = 3*cellSize+2+2*BORDER_SIZE;
height = width + 50;
setPreferredSize (new Dimension (width, height));
this.squareColor = squareColor;
this.focusBorderColor = focusBorderColor;
this.nonfocusBorderColor = nonfocusBorderColor;
this.borderColor = nonfocusBorderColor;
addFocusListener (new FocusListener ()
{
public void focusGained (FocusEvent e)
{
borderColor = GameBoard.this.focusBorderColor;
repaint ();
}
public void focusLost (FocusEvent e)
{
borderColor = GameBoard.this.nonfocusBorderColor;
repaint ();
}
});
addKeyListener (new KeyAdapter ()
{
public void keyTyped (KeyEvent e)
{
if (state != INPLAY)
return;
char key = e.getKeyChar ();
// 假如玩家通過數字小鍵盤輸入,則將輸入映射到相應的單
// 元格,并對此單元格及其四周的單元格做出相應變動。
if (Character.isDigit (key))
switch (key)
{
case ’1’: GameBoard.this.toggle (6);
break;
case ’2’: GameBoard.this.toggle (7);
break;
case ’3’: GameBoard.this.toggle (8);
break;
case ’4’: GameBoard.this.toggle (3);
break;
case ’5’: GameBoard.this.toggle (4);
break;
case ’6’: GameBoard.this.toggle (5);
break;
case ’7’: GameBoard.this.toggle (0);
break;
case ’8’: GameBoard.this.toggle (1);
break;
case ’9’: GameBoard.this.toggle (2);
}
}
});
addMouseListener (new MouseAdapter ()
{
public void mouseClicked (MouseEvent e)
{
if (state != INPLAY)
return;
// 當鼠標點擊游戲板時,確保游戲板獲得焦點,以便玩家
// 使用鍵盤作為替代輸入方法。
GameBoard.this.requestFocusInWindow ();
// 哪一個單元格被點擊?
int cell = GameBoard.this.
mouseToCell (e.getX (), e.getY ());
// 假如一個單元格被點擊(cell != -1),則翻轉那個
// 單元格及其鄰居的顏色。
if (cell != -1)
GameBoard.this.toggle (cell);
}
});
setFocusable (true);
}
// 修改當前單元格的顏色。注重:這個方法被事件處理線程調用
void changeSquareColor (Color squareColor)
{
if (!SwingUtilities.isEventDispatchThread ())
return;
this.squareColor = squareColor;
repaint ();
}
// 繪制組件:先畫邊框,對后畫消息
public void paintComponent (Graphics g)
{
// 推薦首先調用父類的paintComponent()
super.paintComponent (g);
// 用當前邊框顏色繪制四邊
g.setColor (borderColor);
for (int i = 0; i < BORDER_SIZE; i++)
g.drawRect (i, i, width-2*i-1, height-2*i-1);
// 將組件的游戲板畫為黑色(除了邊框及消息區(qū))
g.setColor (Color.black);
g.fillRect (BORDER_SIZE, BORDER_SIZE, width-2*BORDER_SIZE,
width-2*BORDER_SIZE);
// 畫游戲板的水平線
g.setColor (Color.white);
g.drawLine (BORDER_SIZE, BORDER_SIZE+cellSize,
BORDER_SIZE+width-2*BORDER_SIZE-1, BORDER_SIZE+cellSize);
g.drawLine (BORDER_SIZE, BORDER_SIZE+2*cellSize+1,
BORDER_SIZE+width-2*BORDER_SIZE-1, BORDER_SIZE+2*cellSize+1);
// 畫游戲板的垂直線
g.drawLine (BORDER_SIZE+cellSize, BORDER_SIZE, BORDER_SIZE+cellSize,
BORDER_SIZE+width-2*BORDER_SIZE-1);
g.drawLine (BORDER_SIZE+2*cellSize+1, BORDER_SIZE,
BORDER_SIZE+2*cellSize+1, BORDER_SIZE+width-2*BORDER_SIZE-1);
// 畫方格
g.setColor (squareColor);
for (int i = 0; i < cells.length; i++)
{
if (cells [i])
{
int x = BORDER_SIZE+(i%3)*(cellSize+1)+3;
int y = BORDER_SIZE+(i/3)*(cellSize+1)+3;
int w = cellSize-6;
int h = w;
g.fillRect (x, y, w, h);
}
}
// 將消息區(qū)畫為白色(在游戲板下方,邊框之內)
g.setColor (Color.white);
g.fillRect (BORDER_SIZE, width-BORDER_SIZE, width-2*BORDER_SIZE,
height-width);
// 假如游戲板不是初始化狀態(tài),則打印出相應消息
if (state != INITIAL)
{
g.setColor (Color.black);
String text;
switch (state)
{
case LOSE:
text = "YOU LOSE!";
break;
case WIN:
text = "YOU WIN!";
break;
default:
text = "" + counter;
}
g.drawString (text, (width-g.getFontMetrics ().stringWidth (text))/2,
width-BORDER_SIZE+30);
}
}
// 假如游戲不再進行中,則開始一個新游戲。注冊游戲結束監(jiān)聽器,并且初始化一個方塊顏色
// 的圖案,同時啟動一個間隔為1秒的計時器。注重:這個方法將被事件處理線程調用。
void start (GameBoard.DoneListener dl)
{
if (!SwingUtilities.isEventDispatchThread ())
return;
if (state == INPLAY)
return;
this.dl = dl;
Random rnd = new Random ();
while (true)
{
for (int i = 0; i < cells.length; i++)
cells [i] = rnd.nextBoolean ();
int counter = 0;
for (int i = 0; i < cells.length; i++)
if (cells [i])
counter++;
if (counter != 0 && counter != cells.length)
break;
}
ActionListener al;
al = new ActionListener ()
{
public void actionPerformed (ActionEvent e)
{
// 假如玩家贏了,則通知游戲結束監(jiān)聽器
if (state == WIN)
{
timer.stop ();
GameBoard.this.dl.done ();
return;
}
// 假如計時器到達0,則玩家輸了;通知游戲結束監(jiān)聽器
if (--counter == 0)
{
state = LOSE;
timer.stop ();
GameBoard.this.dl.done ();
}
repaint ();
}
};
timer = new Timer (1000, al);
state = INPLAY;
counter = 30;
timer.start ();
}
// 將鼠標位置映射到單元格編號[0,8],假如鼠標坐標在任何單元格之外,則返回-1。
private int mouseToCell (int x, int y)
{
// 檢查第一列
if (x >= BORDER_SIZE && x < BORDER_SIZE+cellSize)
{
if (y >= BORDER_SIZE && y < BORDER_SIZE+cellSize)
return 0;
if (y >= BORDER_SIZE+cellSize+1 && y < BORDER_SIZE+2*cellSize+1)
return 3;
if (y >= BORDER_SIZE+2*cellSize+2 && y < BORDER_SIZE+3*cellSize+2)
return 6;
}
// Examine second column.
// 檢查第二列
if (x >= BORDER_SIZE+cellSize+1 && x < BORDER_SIZE+2*cellSize+1)
{
if (y >= BORDER_SIZE && y < BORDER_SIZE+cellSize)
return 1;
if (y >= BORDER_SIZE+cellSize+1 && y < BORDER_SIZE+2*cellSize+1)
return 4;
if (y >= BORDER_SIZE+2*cellSize+2 && y < BORDER_SIZE+3*cellSize+2)
return 7;
}
// 檢查第三列
if (x >= BORDER_SIZE+2*cellSize+2 && x < BORDER_SIZE+3*cellSize+2)
{
if (y >= BORDER_SIZE && y < BORDER_SIZE+cellSize)
return 2;
if (y >= BORDER_SIZE+cellSize+1 && y < BORDER_SIZE+2*cellSize+1)
return 5;
if (y >= BORDER_SIZE+2*cellSize+2 && y < BORDER_SIZE+3*cellSize+2)
return 8;
}
return -1;
}
// 翻轉一個單元格及其四周的顏色。文中圖1A展示了如下遵循數字鍵盤布局的單元格映射表:
// 7 8 9
// 4 5 6
// 1 2 3
//
// 由于單元格數組從0開始,更輕易使用的映射方式如下圖所示:
// 0 1 2
// 3 4 5
// 6 7 8
//
// 當調用toggle(),調用的代碼必須把數字鍵(1-9)轉換為如上所示的索引(0-8)。
private void toggle (int cell)
{
// 切換單元格顏色
switch (cell)
{
case 0: cells [0] = !cells [0];
cells [1] = !cells [1];
cells [3] = !cells [3];
cells [4] = !cells [4];
break;
case 1: cells [0] = !cells [0];
cells [1] = !cells [1];
cells [2] = !cells [2];
break;
case 2: cells [1] = !cells [1];
cells [2] = !cells [2];
cells [4] = !cells [4];
cells [5] = !cells [5];
break;
case 3: cells [0] = !cells [0];
cells [3] = !cells [3];
cells [6] = !cells [6];
break;
case 4: cells [0] = !cells [0];
cells [2] = !cells [2];
cells [4] = !cells [4];
cells [6] = !cells [6];
cells [8] = !cells [8];
break;
case 5: cells [2] = !cells [2];
cells [5] = !cells [5];
cells [8] = !cells [8];
break;
case 6: cells [3] = !cells [3];
cells [4] = !cells [4];
cells [6] = !cells [6];
cells [7] = !cells [7];
break;
case 7: cells [6] = !cells [6];
cells [7] = !cells [7];
cells [8] = !cells [8];
break;
case 8: cells [4] = !cells [4];
cells [5] = !cells [5];
cells [7] = !cells [7];
cells [8] = !cells [8];
}
// 檢測玩家是否獲勝。這段代碼放在這兒不和遞減計時器及判定玩家是否失敗的代碼一塊兒放到
//start()方法的事件監(jiān)聽器,否則假如玩家碰巧把所有方塊都交換成黑色,而又馬上換成了其它顏色,
//結果本來該獲勝的玩家卻被判輸了。這種辦法不可取。
int i;
for (i = 0; i < cells.length; i++)
if (cells [i])
break;
if (i == cells.length)
state = WIN;
// 繪制游戲板,以及單元的顏色
repaint ();
}
// 游戲結束監(jiān)聽器的接口定義。Start()方法接受一個實現(xiàn)此接口的對象作為參數。
interface DoneListener
{
void done ();
}
}
// 加載玩家切換單元格顏色、獲勝、以及失敗時播放的聲音剪輯。
AudioClip acToggle;
acToggle = getAudioClip (getClass ().getResource ("toggle.au"));
AudioClip acWin = getAudioClip (getClass ().getResource ("win.au"));
AudioClip acLose = getAudioClip (getClass ().getResource ("lose.au"));
// 創(chuàng)建游戲板組件:每個單元格有40像素寬,方塊顏色是綠色,并且游戲板在得到焦點時邊框是藍色,失
// 去焦點時邊框是黑色。游戲板組件被添加到content pane里。
final GameBoard gb;
gb = new GameBoard (40, Color.green, Color.blue, Color.black, acToggle,acWin, acLose);
新聞熱點
疑難解答