方块游戏”简介
“方块”游戏使用一个3x3的网格,其中每一个单元格要么显示一种颜色,要么什么都没有(表示为黑色)。游戏开始时一些单元格随机填充颜色,其他的都用默认黑色。只要你在30秒内清除所有单元格的颜色(全部变为黑色,没有其他颜色存在),你就获胜了。
你要么移动鼠标点击一个单元格,要么直接按小键盘的相应数字键,都可以清除那个单元格里的颜色。类似的,如果你所点击的单元格本身是黑色,那么那个单元格就会被填充一种其他颜色。也就是说会有这样的循环:黑色变彩色,彩色变黑色。如果仅仅这样游戏就太容易了,因此我设计的方块游戏是,你对单元格的点击/按键会影响他自己和他的周围单元格,如图1所示。

图1. (a) 游戏板布局;(b) 当单元格1改变而受到影响的单元格;(c) 当单元格2改变而受到影响的单元格;(d) 当单元格5改变而受到影响的单元格
图1根据数字小键盘的布局显示了相应的游戏板。例如,数字键7对应左上角的单元格。图1中还展示了当一个单元格改变而受到影响的相应单元格(b、c、d中)。如果改变的是角上的,周围三个单元格也会受到影响(b);如果你改变的是边上的,同一边的其他两个单元格也会受到影响(c);如果改变的是中心的,它东南西北的单元格也都会受影响(d)。
用java重写
我最早是用c写的“方块”游戏。因为c和java的语法很相似,所以用java重写并不困难。在我展示我的第一个“方块”applet的代码之前,你大概想知道界面是怎样的。图2显示了你运行那个applet时的界面。

图2. 包含一个游戏板、两个按钮的“方块”游戏界面
游戏板控件是一个类似于“石头剪子”游戏的网格的区域,并且在它下边有一个白色的消息区域。这个控件还有一个边框,这个边框在空间失去焦点的时候是黑色的,在获得焦点时变成蓝色。“change square color”按钮初始时无效,只有游戏开始以后才可用(如果游戏没有进行,也就没理由改变颜色了)。点击“start”按钮可以开始游戏,如图3所示。

图3. “方块”游戏开始以后,在游戏板的消息区域会显示当前剩余的秒数
图3显示了游戏进行时的界面。消息区显示了把所有单元格变为黑色还剩余的秒数。如果这个数字到达0,你就输了。如果你能在此之前把所有单元格变为黑色,那你就赢了。在游戏进行时,你可以点击“change square color”按钮以随机改变各单元的颜色。不过如果你输了或者赢了,那“change square color”按钮会变成无效,而“start”按钮会恢复有效,这样你就可以开始另一个游戏了。
下边是源代码:
squares.java
由于已经包含了丰富的注释,我们不再重述。这里我要强调两点。
-我并没有用运行japplet的public void init()方法的线程创建gui,而是把创建过程延迟到swing的事件处理线程;这正是sun的java教程里推荐的办法。我通过把所有applet的活动限制在事件处理线程里以避免同步问题。
-在j2se 1.4(此专栏所使用的版本)之前的版本里,聚焦系统(控制你用tab键在组件之间切换)有很多缺陷,并且具有平台差异。j2se 1.4通过提供java.awt.keyboardfocusmanager类、焦点循环根节点、以及焦点遍历策略来修正了聚焦系统。由于j2se 1.4的japplet类依赖于abstract window toolkit(awt)的焦点遍历策略(appletviewer及java plug-in都使用java.awt.frame类作为japplet的顶级父类,因此说他们依赖于awt的焦点遍历策略),因此如果没有外在帮助,你无法在一个japplet里同tab键从一个组件切换到另一个。这样的外在帮助包括将j2se 1.4的japplet设为焦点循环根节点,以及设定一个焦点遍历策略。此外,我在gameboard的构造函数里调用setfocusable(true),以保证游戏板组件可以获得焦点。(尽管我们做了这么多,在我们开始游戏的时候,游戏板及两个按钮都没有得到焦点。)这个却现在j2se 1.4及以后的版本中已经得到纠正。
音效
到目前为止,“方块”游戏并没有想象中的那么有趣。不过我们可以通过增加音效来让游戏更有趣。我们至少可以有三种音效:当玩家切换单元格(及其周围)颜色时的音效,当玩家获胜时的音效,以及当玩家失败时的音效。
我为这些情形准备了一套适当的音效,分别是toggle.au,win.au,lose.au。(我决定使用sun的声音文件,而不是microsoft的wave文件,以增强可移植性。)在下边从第二版的squares.java里摘录的代码片断里,音效文件被加载到声音剪辑里,并且在applet初始化时通过构造函数传递给gameboard。
可能你没法克服这个bug,不过一般在j2se 5.0以及5.x或j2se 1.4不会需要播放很短的声音片断(你可能顶多只听到一声,仅此而已)。例如,如果你在j2se5.0下运行第二个(或者第三、第四个)“方块”applet,toggle.au里的声音似乎只播放了一次(我没法播放更多次)。幸运的是,再j2se 1.4里这个问题并不存在。
视觉特效
另一个增加“方块”有戏可玩性的办法是利用视觉特效。尝试了不同特效以后,我选择了简单的办法:在玩家获胜或失败的时候显示一个从右到左、通过applet中心的滚动消息。例如,当玩家获胜时,如图4所示的“祝贺你!”信息会再applet里水平滚动。

图4 当玩家获胜或失败时,一条消息会再applet中部水平滚动
下边从第三版的squares.java的gameboard的start()方法里取出的代码片断显示了如何再applet使用这个视觉特效。
当玩家获胜时,调用animate ("congratulations!", color.red);
会显示一条滚动的红色“祝贺你!”信息。类似的,调用animate ("better luck next time!", color.red);
会显示一条“下次好运”的红色滚动信息。接下来这些方法会调用gameboard的
private void animate(string message, color msgcolor)方法来设置合适的动画:
“方块”游戏使用一个3x3的网格,其中每一个单元格要么显示一种颜色,要么什么都没有(表示为黑色)。游戏开始时一些单元格随机填充颜色,其他的都用默认黑色。只要你在30秒内清除所有单元格的颜色(全部变为黑色,没有其他颜色存在),你就获胜了。
你要么移动鼠标点击一个单元格,要么直接按小键盘的相应数字键,都可以清除那个单元格里的颜色。类似的,如果你所点击的单元格本身是黑色,那么那个单元格就会被填充一种其他颜色。也就是说会有这样的循环:黑色变彩色,彩色变黑色。如果仅仅这样游戏就太容易了,因此我设计的方块游戏是,你对单元格的点击/按键会影响他自己和他的周围单元格,如图1所示。

图1. (a) 游戏板布局;(b) 当单元格1改变而受到影响的单元格;(c) 当单元格2改变而受到影响的单元格;(d) 当单元格5改变而受到影响的单元格
图1根据数字小键盘的布局显示了相应的游戏板。例如,数字键7对应左上角的单元格。图1中还展示了当一个单元格改变而受到影响的相应单元格(b、c、d中)。如果改变的是角上的,周围三个单元格也会受到影响(b);如果你改变的是边上的,同一边的其他两个单元格也会受到影响(c);如果改变的是中心的,它东南西北的单元格也都会受影响(d)。
用java重写
我最早是用c写的“方块”游戏。因为c和java的语法很相似,所以用java重写并不困难。在我展示我的第一个“方块”applet的代码之前,你大概想知道界面是怎样的。图2显示了你运行那个applet时的界面。

图2. 包含一个游戏板、两个按钮的“方块”游戏界面
游戏板控件是一个类似于“石头剪子”游戏的网格的区域,并且在它下边有一个白色的消息区域。这个控件还有一个边框,这个边框在空间失去焦点的时候是黑色的,在获得焦点时变成蓝色。“change square color”按钮初始时无效,只有游戏开始以后才可用(如果游戏没有进行,也就没理由改变颜色了)。点击“start”按钮可以开始游戏,如图3所示。

图3. “方块”游戏开始以后,在游戏板的消息区域会显示当前剩余的秒数
图3显示了游戏进行时的界面。消息区显示了把所有单元格变为黑色还剩余的秒数。如果这个数字到达0,你就输了。如果你能在此之前把所有单元格变为黑色,那你就赢了。在游戏进行时,你可以点击“change square color”按钮以随机改变各单元的颜色。不过如果你输了或者赢了,那“change square color”按钮会变成无效,而“start”按钮会恢复有效,这样你就可以开始另一个游戏了。
下边是源代码:
squares.java
// squares.java |
由于已经包含了丰富的注释,我们不再重述。这里我要强调两点。
-我并没有用运行japplet的public void init()方法的线程创建gui,而是把创建过程延迟到swing的事件处理线程;这正是sun的java教程里推荐的办法。我通过把所有applet的活动限制在事件处理线程里以避免同步问题。
-在j2se 1.4(此专栏所使用的版本)之前的版本里,聚焦系统(控制你用tab键在组件之间切换)有很多缺陷,并且具有平台差异。j2se 1.4通过提供java.awt.keyboardfocusmanager类、焦点循环根节点、以及焦点遍历策略来修正了聚焦系统。由于j2se 1.4的japplet类依赖于abstract window toolkit(awt)的焦点遍历策略(appletviewer及java plug-in都使用java.awt.frame类作为japplet的顶级父类,因此说他们依赖于awt的焦点遍历策略),因此如果没有外在帮助,你无法在一个japplet里同tab键从一个组件切换到另一个。这样的外在帮助包括将j2se 1.4的japplet设为焦点循环根节点,以及设定一个焦点遍历策略。此外,我在gameboard的构造函数里调用setfocusable(true),以保证游戏板组件可以获得焦点。(尽管我们做了这么多,在我们开始游戏的时候,游戏板及两个按钮都没有得到焦点。)这个却现在j2se 1.4及以后的版本中已经得到纠正。
音效
到目前为止,“方块”游戏并没有想象中的那么有趣。不过我们可以通过增加音效来让游戏更有趣。我们至少可以有三种音效:当玩家切换单元格(及其周围)颜色时的音效,当玩家获胜时的音效,以及当玩家失败时的音效。
我为这些情形准备了一套适当的音效,分别是toggle.au,win.au,lose.au。(我决定使用sun的声音文件,而不是microsoft的wave文件,以增强可移植性。)在下边从第二版的squares.java里摘录的代码片断里,音效文件被加载到声音剪辑里,并且在applet初始化时通过构造函数传递给gameboard。
| // 加载玩家切换单元格颜色、获胜、以及失败时播放的声音剪辑。 audioclip actoggle; actoggle = getaudioclip (getclass ().getresource ("toggle.au")); audioclip acwin = getaudioclip (getclass ().getresource ("win.au")); audioclip aclose = getaudioclip (getclass ().getresource ("lose.au")); // 创建游戏板组件:每个单元格有40像素宽,方块颜色是绿色,并且游戏板在得到焦点时边框是蓝色,失 // 去焦点时边框是黑色。游戏板组件被添加到content pane里。 final gameboard gb; gb = new gameboard (40, color.green, color.blue, color.black, actoggle,acwin, aclose); |
代码片断里使用了getclass().getresource(),以便那些声音文件可以和applet的class文件一并打包到一个jar里边。
当玩家获胜或者失败时,会播放相应的声音片断;这是在下边从gameboard的void start(gameboard.donelistener dl)里取出的代码片断里实现的:
| // 如果玩家获胜,则通知游戏结束监听器。 if (state == win) { acwin.play (); timer.stop (); gameboard.this.dl.done (); return; } // 当计时器到达0,则玩家失败,并通知游戏结束监视器。 if (--counter == 0) { state = lose; aclose.play (); timer.stop (); gameboard.this.dl.done (); } |
最后,颜色切换的声音片断会在单元格颜色被切换时播放,这是在以下从gameboard的
private void toggle(int cell)方法里取出的代码片断中实现的:
| // 绘制游戏板,以及有颜色的单元格。 repaint (); // 播放颜色切换的声音。如果你用早期的java 1.5.0或后期的java 1.4.x,那有一个bug会阻止很短 // 的声音文件播放出来,因此你可能听不到声音(或者只听到的一声)。你可以在以下链接了解到更多信息: // http://bugs.sun.com/bugdatabase/view_bug.do;:yfig?bug_id=6251460 actoggle.play (); |
可能你没法克服这个bug,不过一般在j2se 5.0以及5.x或j2se 1.4不会需要播放很短的声音片断(你可能顶多只听到一声,仅此而已)。例如,如果你在j2se5.0下运行第二个(或者第三、第四个)“方块”applet,toggle.au里的声音似乎只播放了一次(我没法播放更多次)。幸运的是,再j2se 1.4里这个问题并不存在。
视觉特效
另一个增加“方块”有戏可玩性的办法是利用视觉特效。尝试了不同特效以后,我选择了简单的办法:在玩家获胜或失败的时候显示一个从右到左、通过applet中心的滚动消息。例如,当玩家获胜时,如图4所示的“祝贺你!”信息会再applet里水平滚动。

图4 当玩家获胜或失败时,一条消息会再applet中部水平滚动
下边从第三版的squares.java的gameboard的start()方法里取出的代码片断显示了如何再applet使用这个视觉特效。
| // 如果玩家获胜,通知游戏结束监听器,并且动态显示祝贺信息。 if (state == win) { acwin.play (); timer.stop (); gameboard.this.dl.done (); animate ("congratulations!", color.red); return; } // 如果计时器到达0,则玩家失败,通知游戏结束监听器,并动态显示“下次好运”的消息。 if (--counter == 0) { state = lose; aclose.play (); timer.stop (); gameboard.this.dl.done (); animate ("better luck next time!", color.red); } |
当玩家获胜时,调用animate ("congratulations!", color.red);
会显示一条滚动的红色“祝贺你!”信息。类似的,调用animate ("better luck next time!", color.red);
会显示一条“下次好运”的红色滚动信息。接下来这些方法会调用gameboard的
private void animate(string message, color msgcolor)方法来设置合适的动画:
| // 通过从左到右滚动一条消息而在玻璃板上显示动画。 private void animate (string message, color msgcolor) { actionlistener al; al = new actionlistener () { public void actionperformed (actionevent e) { if (gp.isdone ()) { timeranim.stop (); applet.getglasspane ().setvisible (false); } } }; timeranim = new timer (100, al); gp = new glasspane (message, msgcolor); applet.setglasspane (gp); applet.getglasspane ().setvisible (true); // 阻止鼠标事件被玻璃板之下的组件截获。 applet.getglasspane ().addmouselistener (new mouseadapter () {}); applet.getglasspane () .addmousemotionlistener (new mousemotionadapter () {}); timeranim.start (); } |
animate()方法通过以下步骤实现动画:创建一个计时器,一个定制的动画组件,在japplet的新的玻璃板组件
(它覆盖了整个applet绘图区)里安装这个动画组件,显示这个新的玻璃板,并且起动计时器。这个定制的动画
组件是一个的gameboard里glasspane的内部类的实例。
| // glasspane组件类 private class glasspane extends jpanel { private string text; private color msgcolor; private boolean first = true; private boolean done; private int width, height; private int scrolltextheight, scrolltextwidth; private int xoffset, yoffset; private font font; glasspane (string text, color msgcolor) { this.text = text; this.msgcolor = msgcolor; setopaque (false); font = new font ("serif", font.bold, 24); } boolean isdone () { repaint (); return done; } public void paintcomponent (graphics g) { super.paintcomponent (g); g.setfont (font); // 在第一次调用paintcomponent()方法时取得玻璃板的宽和高是最容易的途径。 if (first) { width = getwidth (); height = getheight (); fontmetrics fm = g.getfontmetrics (); scrolltextwidth = fm.stringwidth (text);  
扫描关注微信公众号 |
闽公网安备 35060202000074号