@Darcy
2017-08-11T03:13:44.000000Z
字数 12077
阅读 4417
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方法,相当于构造方法*/@Overridepublic 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*/@Overridepublic 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,其他为0for (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;elseweight[i] = 0;}//如果一条线上的值为2则表示有两个'O'在这条直线上,如果有两个'X',则是-2int 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;elsereturn 2;}// 看看第2行if (weight[3] + weight[4] + weight[5] == twoWeights) {if (weight[3] == 0)return 3;else if (weight[4] == 0)return 4;elsereturn 5;}// 看看第3行if (weight[6] + weight[7] + weight[8] == twoWeights) {if (weight[6] == 0)return 6;else if (weight[7] == 0)return 7;elsereturn 8;}// 看看第1列if (weight[0] + weight[3] + weight[6] == twoWeights) {if (weight[0] == 0)return 0;else if (weight[3] == 0)return 3;elsereturn 6;}// 看看第2列if (weight[1] + weight[4] + weight[7] == twoWeights) {if (weight[1] == 0)return 1;else if (weight[4] == 0)return 4;elsereturn 7;}// 看看第3列if (weight[2] + weight[5] + weight[8] == twoWeights) {if (weight[2] == 0)return 2;else if (weight[5] == 0)return 5;elsereturn 8;}//看看左上角到右下角的对角线if (weight[0] + weight[4] + weight[8] == twoWeights) {if (weight[0] == 0)return 0;else if (weight[4] == 0)return 4;elsereturn 8;}//看看右上角到左下角的对角线if (weight[2] + weight[4] + weight[6] == twoWeights) {if (weight[2] == 0)return 2;else if (weight[4] == 0)return 4;elsereturn 6;}// 没有就返回-1return -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 中國大陸 授權條款授權.