@Darcy
2017-08-11T11:13:44.000000Z
字数 12077
阅读 4022
JavaForKids
处于安全的考虑,最新版本的JRE已经禁止本地Applet在浏览器上的运行了,所以在以下提到的Applet程序中,你只需通过Eclipse来运行测试结果即可。[1]
当你去浏览一些你喜欢的网站的时候,你可能有碰到过用Java语言写的一些小程序或者游戏,这种技术就是applet
。这种特殊的程序可以在网页浏览器上运行。网页浏览器能够解析叫做HTML
的简单标签语言,你可以通过这些标签让文本更美观地在浏览器中呈现。除了文本外,你还可以在HTML文件中添加<applet>
标签,它会告诉浏览器呈现Java applet
的位置和方式。
Java applet
会作为网页的一部分从Internet
下载到你的电脑中,浏览器会自动启动Java虚拟机(JVM)来运行这些applet
。
在这章中你将会学习到如何在你的电脑中创建applet
,附录C 会告诉你如何发布你的网页到Internet
上,这样其他人就可以使用你写的applet
程序了。
用户在Internet
上浏览网页的时候并不需要知道网页中是否包含有Java applet
小程序,但他们想确保自己的电脑不会受到一些恶意的applet
的破坏。这也是为什么applet
会设计成有下面的一些限制:
applet
相关的权限,否则它不能访问你的电脑硬盘中的文件applet
只能连接提供这个applet
下载的服务器applet
不能执行在你电脑中的任何其他程序为了运行applet
,你需要用一种特殊的方式来编写Java 类,要在HTML
文件中添加<applet>
指向这个类,而且浏览器也必须要支持Java。你可以在Eclipse
中或者在一个叫做appletviewer
的特殊程序中测试applet
。但是,在学习applet
之前,让我们先花15分钟来熟悉一下HTML
标签吧。
假设你已经写好并编译完成了TicTacToe
这个applet
小游戏。现在你需要创建一个HTML
文件来展示它的有关信息。那么首先要创建一个文本文件,我们把它命名为TicTacToe.html
(顺便说一下,Eclipse
也是可以创建文本文件的)。HTML
文件的文件名后缀是.html
或者.htm
, 它们通常包括有header
和body
部分。大部分的HTML
标签都有相应的带斜杠(/
)的结束标签,如<Head>
和</Head>
是一对。TicTacToe.html
的内容可能是这样子:
<HTML>
<Head>
<Title>My First Web Page</Title>
</Head>
<BODY>
My Tic-Tac-Toe game is coming soon…
</BODY>
</HTML>
你可以把标签放到同一行,如<Title>
和</Title>
,也可以分开不同的行。现在,用浏览器打开这个文件,你就可以看到标题My First Web Page
在浏览器的窗口上方,而网页里面你可以看到有My Tic-Tac-Toe game is coming soon…
现在,让我们把Tic-Tac-Toe这个applet
添加到这个文件中:
<HTML>
<BODY>
Here is my Tic-Tac-Toe game:
<APPLET code=”TicTacToe.class” width=300 height=250>
</APPLET>
</BODY>
</HTML>
然后刷新或者重新打开 TicTacToe.html
,你会看到下面的内容:
正如我们所料的,浏览器没有找到TicTacToe.class
,而只是显示了一个灰色的边框。别急,我们待会就会告诉你怎么创建它。
HTML
标签是用尖括号来包裹的,有些标签还会有额外的属性。例如我们用到的<APPLET>
就有下面的一些属性:
applet
对于Java类的名字applet
占用屏幕的矩形区域的宽度,单位为像素。想象一下电脑 屏幕是很多个小小的点组成的,这里的每个点就是一个像素。applet
占用屏幕的矩形区域的高度如果一个Java applet
由多个类组成,可以使用jdk自带的jar程序把它们全部压缩到一个.jar
文件。如果你这样做了,这个打包文件必须具备名称的属性。你可以在附录A中阅读jars
相关的知识点。
如果Swing
库表现得更好,那为什么还要使用AWT
图形库来编写applet
呢? 那我们可以用Swing
来编写applet
么?答案是肯定的,但是有些事情你是需要了解的。
网页浏览器会自带了它们自己的Java虚拟机(JVM),这个JVM肯定是支持AWT的,但是它有可能不会支持你的Applet
的Swing
类。当然用户可能会下载最新版本的JVM来支持它,但是你真的想让你的用户去这么做么?当你的网页发布到Internet
上之后,你压根就不知道谁会来访问它。想象一下有个在沙漠中的家伙在用着10年前的电脑 - 他宁愿离开你的网页也不愿意这么麻烦地安装软件。如果你想让这个applet
帮助你在网上卖游戏,那你肯定不愿意失去这个用户 - 他有可能是我们的潜在用户喔(沙漠中的人们也是有信用卡的)。
如果你不能确定你的用户是用什么浏览器的,那么请用AWT
来编写applet
吧。
实际上,还有个选择就是强制你的用户去下载新的Java插件,然后配置他们的浏览器去使用这个插件而不是使用浏览器自带的JVM。你可以阅读这个网页来获取更多的相关信息: http://java.sun.com/j2se/1.5.0/docs/guide/plugin/.
用 Java AWT
编写applet
必须要继承java.applet.Applet
这个类,例如:
class TicTacToe extends java.applet.Applet {
}
不像Java一般的应用程序,applet
不需要main()
方法,因为网页浏览器在遇到<applet>
标签时,会自动下载和执行这个applet
。浏览器也会传递一些信号给applet
当一些重要的事件发生时,比如applet
开始启动,重新绘制等。为了确保applet
能够对这些事件作出反应,你应该覆盖其中的一些特殊回调方法: init()
, start()
, paint()
, stop()
,和destroy()
。浏览器的JVM会在下面的情况下调用这些方法:
init()
会在浏览器刚加载完applet
的时候调用,而且只会被调用一次,所以它就相当于构造方法对于Java类类似的角色。start()
会在init()
之后紧接着被调用。它也会在用户访问完另外一个网页之后回来时被调用。paint()
会在applet
的窗口需要显示或者刷新的时候被调用。比如applet
和其他的窗口重叠,浏览器需要去重新绘制它。stop()
会在用户离开包含了applet
的网页时被调用。destroy()
会在浏览器关闭时被调用。只有当applet
使用其他的一些资源时你才应该在这个方法里面写代码,比如你的applet
保持了和提供applet
的电脑的连接时。即便你不需要所有这些回调方法,每个applet
也至少应该重写init()
或者paint()
方法。下面的applet
代码展示了Hello World
这句话。这个applet
只重写了paint()
方法,JVM会通过它返回一个Graphics
的对象实例。这个对象提供了非常完整的绘制方法。在下面的例子中我们使用了drawString()
来绘制一个字符串Hello World
。
public class HelloApplet extends java.applet.Applet {
public void paint(java.awt.Graphics graphics) {
graphics.drawString("Hello World!", 70, 40);
}
}
在Eclipse
中创建这个类并运行它: 选择 Run as
-> Java applet
就可以了。
为了在网页浏览器中测试它,我们创建一个叫做Hello.html
的文件,把它放到和HelloApplet.class
相同的文件夹上:
<HTML>
<BODY>
Here is my first applet:<P>
<APPLET code=”HelloApplet.class” width=200 height=100></APPLET>
</BODY>
</HTML>
现在,在你的浏览器上打开Hello.html
,就可以看到像下面的结果:
你觉得通过这个简单的例子我们准备好了写一个游戏程序了吗?当然可以啦~系好你的安全带吧...
每个游戏都会使用一些特定的算法 - 根据一系列的规则或者策略来对 玩家的动作做出相关的反馈。同一个游戏的算法可以很简单,也可以非常复杂。你应该听说过国际象棋冠军Gary Kasparov对战计算机的故事吧,他实际上就是和计算机的算法对战。计算机专家团队试图去发明一种智能的算法去打败他。同样地,Tic-Tac-Toe
游戏可以使用很多不同的策略,在这里我们会使用相对简单的一种:
3×3
的九宫格。X
,计算机则使用O
。New Game
的按钮重新开始。O
时,它必须要试图找到有没有已经出现两个连续的了,如果有,那么它要相应放到第三个连续的位置上。O
,那么计算机必须要找到有没有两个连续的X
出现,如果有,就要放到第三个连续的位置去阻止对手赢。我会在这里给你简单地描述一下这个程序,因为在applet
的代码中已经有很多注释去帮助你理解了。
这个applet
会使用BorderLayout
布局,然后窗口的North
部分会放置New Game
的按钮,Center
部分会显示九个按钮代表九宫格,South
部分会展示一些信息,程序中会使用一维数组来存储这些按钮,它们的位置对应的下标如图所示:
所有的窗口组件都会在applet
的init()
方法里面被创建。所有的事件都会通过ActionListener
监听器的actionPerformed()
方法来处理。方法lookForWinner()
会在每移动一步的时候来检查游戏是否已经结束。
我们策略的规则8、9、10会在computerMove()
方法里面编码实现,这个方法需要产生的随机数, 随机数的产生我们将会使用Java提供的Math
类中的random()
方法。
你可能会发现有些比较特殊的语法,比如连续调用多个方法来作为一个表达式:
if(squares[0].getLabel().equals(squares[1].getLabel())){…}
这一长长的表达式是为了让代码更简洁,它的表达结果实际上和下面的一样:
String label0 = squares[0].getLabel();
String label1 = squares[1].getLabel();
if(label0.equals(label1)){…}
在复杂的表达式中,Java会先处理括号里面的代码,然后再处理其他的。在上面简短版本的代码中,首先会计算最里面的括号的结果[也就是squares[1].getLabel()
的结果],然后用这个结果和外面的squares[0].getLabel()
的结果通过equals()
方法进行比较。
尽管下面的代码有几页长,但是它应该不难理解。对照着注释来开始阅读吧。
public class TicTacToe extends JApplet implements ActionListener {
JButton squares[];
JButton newGameButton;
JLabel score;
int emptySquaresLeft = 9;
/**
* applet 的init方法,相当于构造方法
*/
@Override
public void init() {
// 获取这个applet的窗口面板
Container appletContent = this.getContentPane();
//设置这个面板的布局管理器,背景颜色
appletContent.setLayout(new BorderLayout());
appletContent.setBackground(Color.CYAN);
// 创建新游戏的按钮,并给它注册点击事件的监听器
newGameButton = new JButton("New Game");
newGameButton.addActionListener(this);
JPanel topPanel = new JPanel();
topPanel.add(newGameButton);
appletContent.add(topPanel, BorderLayout.NORTH);
//中间的面板是个3*3的网格布局类型,当你往上面添加按钮时,就会从左往右,从上往下依次添加到面板中
JPanel centerPanel = new JPanel();
centerPanel.setLayout(new GridLayout(3, 3));
appletContent.add(centerPanel, BorderLayout.CENTER);
score = new JLabel("Your turn!");
appletContent.add(score, BorderLayout.SOUTH);
//创建JButton类型的数组,它负责存放各个按钮的引用
squares = new JButton[9];
// 创建游戏面板上那9个按钮,设置他们的背景为橙色,为它们注册事件,依次添加到面板上。
for (int i = 0; i < 9; i++) {
squares[i] = new JButton();
squares[i].addActionListener(this);
squares[i].setBackground(Color.ORANGE);
centerPanel.add(squares[i]);
}
}
/**
* 这个方法会处理所有的点击事件
*
* @param ActionEvent
* object
*/
@Override
public void actionPerformed(ActionEvent e) {
JButton theButton = (JButton) e.getSource();
//点击的是新游戏按钮
if (theButton == newGameButton) {
for (int i = 0; i < 9; i++) {
squares[i].setEnabled(true);
squares[i].setText("");
squares[i].setBackground(Color.ORANGE);
}
emptySquaresLeft = 9;
score.setText("Your turn!");
newGameButton.setEnabled(false);
return; //退出这个方法
}
String winner = "";
//点击的是中间的方格按钮
for (int i = 0; i < 9; i++) {
if (theButton == squares[i]) {
squares[i].setText("X");
winner = lookForWinner();
if (!"".equals(winner)) {
endTheGame();
} else {
computerMove();
winner = lookForWinner();
if (!"".equals(winner)) {
endTheGame();
}
}
break;
}
} //循环结束
if (winner.equals("X")) {
score.setText("You won!");
} else if (winner.equals("O")) {
score.setText("You lost!");
} else if (winner.equals("T")) {
score.setText("It's a tie!"); //平局
}
}
/**
* 这个方法走一步之后都会被调用,它用于检查每行,每列,每个对角线是否出现相同的'O'或者'X'符号。如果有则表示一方赢了,然后高亮显示赢的结果。
*
* @return "X", "O", "T" for tie or "" for no winner ‘X’代表玩家赢了,'O'代表计算机赢了,'T' 代表平局,""代表还没结束
*/
String lookForWinner() {
String theWinner = "";
emptySquaresLeft--;
//没有空白格子了,平局
if (emptySquaresLeft == 0) {
return "T";
}
//检查第1行是否是相同的符号
if (!squares[0].getText().equals("")
&& squares[0].getText().equals(squares[1].getText())
&& squares[0].getText().equals(squares[2].getText())) {
theWinner = squares[0].getText();
highlightWinner(0, 1, 2);
//检查第2行
} else if (!squares[3].getText().equals("")
&& squares[3].getText().equals(squares[4].getText())
&& squares[3].getText().equals(squares[5].getText())) {
theWinner = squares[3].getText();
highlightWinner(3, 4, 5);
//检查第3行
} else if (!squares[6].getText().equals("")
&& squares[6].getText().equals(squares[7].getText())
&& squares[6].getText().equals(squares[8].getText())) {
theWinner = squares[6].getText();
highlightWinner(6, 7, 8);
//检查第1列
} else if (!squares[0].getText().equals("")
&& squares[0].getText().equals(squares[3].getText())
&& squares[0].getText().equals(squares[6].getText())) {
theWinner = squares[0].getText();
highlightWinner(0, 3, 6);
//检查第2列
} else if (!squares[1].getText().equals("")
&& squares[1].getText().equals(squares[4].getText())
&& squares[1].getText().equals(squares[7].getText())) {
theWinner = squares[1].getText();
highlightWinner(1, 4, 7);
//检查第3列
} else if (!squares[2].getText().equals("")
&& squares[2].getText().equals(squares[5].getText())
&& squares[2].getText().equals(squares[8].getText())) {
theWinner = squares[2].getText();
highlightWinner(2, 5, 8);
//检查对角线
} else if (!squares[0].getText().equals("")
&& squares[0].getText().equals(squares[4].getText())
&& squares[0].getText().equals(squares[8].getText())) {
theWinner = squares[0].getText();
highlightWinner(0, 4, 8);
} else if (!squares[2].getText().equals("")
&& squares[2].getText().equals(squares[4].getText())
&& squares[2].getText().equals(squares[6].getText())) {
theWinner = squares[2].getText();
highlightWinner(2, 4, 6);
}
return theWinner;
}
/**
* 这个方法负责用设定好的规则去找出最合适计算机的一步,如果没有找到,就随机选一个
*/
void computerMove() {
int selectedSquare;
// 第一步,试图找到同一条线上是否存在一个空方格连着两个'O',如果有就意味着你输了
selectedSquare = findEmptySquare("O");
// 第二步,如果不存在连个相连的'O',,则找是否有空格之间存在两个连着的'X',有的话就计算机就要去阻止它了。
if (selectedSquare == -1)
selectedSquare = findEmptySquare("X");
//第三步,如果都没有,则看看中间是不是空的,是的话就填这个吧。
if ((selectedSquare == -1) && (squares[4].getText().equals("")))
selectedSquare = 4;
//好吧,中间已经被占领了,那就随便选一个吧。
if (selectedSquare == -1)
selectedSquare = getRandomSquare();
squares[selectedSquare].setText("O");
}
/**
* 这个方法会检查每行,每列,每一条对角线是否存在一个空格在两个和参数player相同符号间,如果有则返回这个空格的位置。
* @param player X是玩家,O是计算机
* @return 返回这个空格的位置,如果不存在,则返回-1
*/
int findEmptySquare(String player) {
int weight[] = new int[9];
//'O'的格子填-1, 'X'的格子填1,其他为0
for (int i = 0; i < 9; i++) {
if (squares[i].getText().equals("O"))
weight[i] = -1;
else if (squares[i].getText().equals("X"))
weight[i] = 1;
else
weight[i] = 0;
}
//如果一条线上的值为2则表示有两个'O'在这条直线上,如果有两个'X',则是-2
int twoWeights = player.equals("O") ? -2 : 2;
// 看看第1行是否存在空格连着两个相同符号的
if (weight[0] + weight[1] + weight[2] == twoWeights) {
if (weight[0] == 0)
return 0;
else if (weight[1] == 0)
return 1;
else
return 2;
}
// 看看第2行
if (weight[3] + weight[4] + weight[5] == twoWeights) {
if (weight[3] == 0)
return 3;
else if (weight[4] == 0)
return 4;
else
return 5;
}
// 看看第3行
if (weight[6] + weight[7] + weight[8] == twoWeights) {
if (weight[6] == 0)
return 6;
else if (weight[7] == 0)
return 7;
else
return 8;
}
// 看看第1列
if (weight[0] + weight[3] + weight[6] == twoWeights) {
if (weight[0] == 0)
return 0;
else if (weight[3] == 0)
return 3;
else
return 6;
}
// 看看第2列
if (weight[1] + weight[4] + weight[7] == twoWeights) {
if (weight[1] == 0)
return 1;
else if (weight[4] == 0)
return 4;
else
return 7;
}
// 看看第3列
if (weight[2] + weight[5] + weight[8] == twoWeights) {
if (weight[2] == 0)
return 2;
else if (weight[5] == 0)
return 5;
else
return 8;
}
//看看左上角到右下角的对角线
if (weight[0] + weight[4] + weight[8] == twoWeights) {
if (weight[0] == 0)
return 0;
else if (weight[4] == 0)
return 4;
else
return 8;
}
//看看右上角到左下角的对角线
if (weight[2] + weight[4] + weight[6] == twoWeights) {
if (weight[2] == 0)
return 2;
else if (weight[4] == 0)
return 4;
else
return 6;
}
// 没有就返回-1
return -1;
}
/**
* 随机产生0到9之间的数字, 而且这个数字所对应的方格还必须是空的
*/
int getRandomSquare() {
boolean gotEmptySquare = false;
int selectedSquare = -1;
do {
selectedSquare = (int) (Math.random() * 9);
if (squares[selectedSquare].getText().equals("")) {
gotEmptySquare = true; // 循环结束
}
} while (!gotEmptySquare);
return selectedSquare;
}
/**
* 这个方法负责高亮显示赢的那条路径
*/
void highlightWinner(int win1, int win2, int win3) {
squares[win1].setBackground(Color.CYAN);
squares[win2].setBackground(Color.CYAN);
squares[win3].setBackground(Color.CYAN);
}
/**
* 禁用方格的所有按钮(点击无效),让新游戏的按钮生效(可以点击)
*/
void endTheGame() {
for (int i = 0; i < 9; i++) {
squares[i].setEnabled(false);
}
newGameButton.setEnabled(true);
}
}
恭喜你!你已经完成了你的第一个Java游戏!
你可以直接在Eclipse
上运行这个applet
,或者通过浏览器打开我们在章节一开始就提到的TicTacToe.html
文件,只需要把HTML
文件和TicTacToe.class
拷贝到同一个文件夹下面就可以了。我们的TicTacToe
类还有一个小bug(或许你已经注意到了),但是我肯定在完成接下来的任务之后你能够解决它。
我们的TicTacToe
类只是使用了简单的策略,因为我们的目标只是为了学习如何编程,但是如果你想要完善这个游戏,可以去学习极小极大策略去让计算机选择最佳的步骤。极小极大策略不在这本书的讨论范围内,你可以在网上找到它的相关资料。
Java Applets:
http://java.sun.com/docs/books/tutorial/applet/
Java 类 Math
:
http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Math.html
在类TicTacToe
的顶部面板上添加两个标签去显示输和赢的数量。声明两个类变量去存储输和赢的次数,每输或者赢一次就相应地加1。每赢一次或者输一次都要立刻刷新标签的显示。
我们的程序现在是允许在已经有X
或者O
的格子上点击。这是一个bug! 如果你做了一次有效的移动程序还是会继续运行的。修改代码,让它忽略掉这些点击。
为TicTacToe
添加一个main()
方法,让它作为一个普通的Java 应用程序运行,而不是applet
。
重写TicTacToe
,用一个二维的3 × 3
数组:
JButton squares[][]
来替换原来的一维数组:
JButton squares[]
去存储中间的九个格子按钮。
关于多维数组的内容可以上网了解更多。
本著作係採用創用 CC 姓名標示-非商業性-禁止改作 2.5 中國大陸 授權條款授權.