一步一步写自表达代码——消小球(1)
在写了这么多理论之后,读者都对实践比较感兴趣。那么如何写自表达代码呢?
我们跳开理论部分(以后我会补充理论部分),直接进入实践部分,从本章开始,将以消小球这个游戏为例,以Java代码为样本,引导大家一步一步书写自表达式代码。
消小球是一款见于Windows Mobile上的游戏,在12x12的方格子里随即放置各种颜色的小球。选中至少两个相邻的同颜色小球,再次点击,就消除选中的小球,消除的小球的个数累加作为得到的分数,消掉小球以后上面的小球会降落,某列被消除以后,会将其左边的列拉过来,直到无法继续消除位置。
好了,游戏规则介绍完毕。那么我们开始在Eclipse里建立工程。Java工程,名称BubbleBreaker。
然后根据MVC模式建立包结构。
Model
在这个游戏里,需要方格(Grid),小球(Ball),还有分数(Score),另外需要一个整体的游戏对象(Game),用来存放这些数据。
Control
在这个游戏里,需要选择(Select),消除(Clear),降落(FallDown),判定游戏是否结束(GameOver)等行为。
View
在这个游戏里,需要显示游戏主要区域(MainFrame)用来显示小球部分,以及辅助区域(HelperFrame)用来显示分数,选中个数等。以及一个最外边的大容器(GamePad)。用来组装所有的内容。
下面我们为Model建立各种属性。在建立各种属性的时候,我们需要开启Eclipse的保存动作 - 自动格式化以及自动引入Imports的功能,
具体方法是,选中该工程,右键选中属性,打开下面的对话框,按照下图勾选选项,并保存。
小球(Ball)
颜色 - 我们采用Java提供的颜色Color作为其颜色属性类型。Color color;
位置 - 整数。 int x, int y;
状态 - State - 一个枚举类型,应该至少包括NORMAL,SELECTED, DESTROYED几种状态,也许还会增加。注意,由于该State是属于小球的,所以它是一个内部枚举。因为在外部引用时可以看到Ball.State.SELECTED样式的调用。这是自表达代码的一个重要原则——找到合适的归宿。
那么Ball类的代码如下:
1 package org.stephen.bubblebreaker.model; 2 3 import java.awt.Color; 4 5 public class Ball { 6 7 Color color; 8 9 int x; 10 11 int y; 12 13 State state; 14 15 enum State { 16 NORMAL, SELECTED, DESTROYED; 17 } 18 }
好的,这里我们没有对public,protected,private进行定义,暂时保留这些内容,留待以后决定。
方格(Grid)
方格中包含的就是12x12个小球。那么,如何存放呢?
我们可以采用List,也可以采用数组[],可以采用1维的,也可以是2维的。
目前我们不知道哪个更合理,先选用数组吧,比较直观。这是自表达代码的另一个原则——先选取简单的模型,以后可以变更。
Grid类的代码如下:
1 package org.stephen.bubblebreaker.model; 2 3 public class Grid { 4 5 Ball[][] balls; 6 }
分数(Score)
应该包含:当前选中小球的总分数(selected),已经获得的分数(current),最高分数(highest),平均分数(average),已玩局数(playCount)。
1 package org.stephen.bubblebreaker.model; 2 3 public class Score { 4 5 int selected; 6 7 int current; 8 9 int highest; 10 11 int average; 12 13 int playCount; 14 }
最后是游戏(Game)。
这个对象是全局唯一的,用来存放其他数据,所以,它应该是单例模式,从外部可以随时通过getInstance获得数据对象。另外,它应该包含其他数据,就目前模型来看,它应该包含:
Grid和Score两个对象。注意:Ball是包含在Grid中的。
好了,到此,Model的属性已经初步定义完成了。
然后,来看View。我们用Swing来实现显示。
GamePad是主入口,所以,它应该包含main方法。其方法的内容就是建立GamePad对象,并显示。
由此,GamePad应该是JFrame的一个子类。而它包含了由MainFrame和HelperFrame两个对象构成的现实和区域,这两个类应该是JPanel的子类。
我们采用40x40的单元格代表一个小球,那么整个Grid的尺寸是480x480,Helper放在上部,那么整个GamePad的尺寸是
480x600。然后,我们逐个加入控件。
最后,我们在关闭工作中,加入exit代码,确保点击右上角红叉关闭的时候能够正确的退出应用程序。
相应代码如下:
GamePad:
1 package org.stephen.bubblebreaker.view; 2 3 import javax.swing.JFrame; 4 5 public class GamePad extends JFrame { 6 7 MainFrame main = new MainFrame(); 8 HelperFrame helper = new HelperFrame(); 9 10 public GamePad() { 11 12 } 13 14 public void build() { 15 this.setTitle("Bubble Breaker"); 16 this.setResizable(false); 17 this.setLayout(null); 18 this.setSize(480, 620); 19 main.build(); 20 helper.build(); 21 this.add(helper); 22 this.add(main); 23 this.setDefaultCloseOperation(EXIT_ON_CLOSE); 24 } 25 26 public static void main(String[] args) { 27 GamePad pad = new GamePad(); 28 pad.build(); 29 pad.setVisible(true); 30 } 31 32 }
MainFrame
1 package org.stephen.bubblebreaker.view; 2 3 import java.awt.Color; 4 import java.awt.GridBagConstraints; 5 import java.awt.GridBagLayout; 6 7 import javax.swing.BorderFactory; 8 import javax.swing.JLabel; 9 import javax.swing.JPanel; 10 11 public class MainFrame extends JPanel { 12 13 JLabel[][] balls; 14 15 public MainFrame() { 16 17 } 18 19 public void build() { 20 GridBagLayout grid = new GridBagLayout(); 21 this.setLayout(grid); 22 this.setSize(480, 480); 23 balls = new JLabel[12][12]; 24 25 GridBagConstraints constraints = new GridBagConstraints(); 26 constraints.weightx = 100; 27 constraints.weighty = 100; 28 for (int y = 0; y < 12; y++) { 29 for (int x = 0; x < 12; x++) { 30 JLabel ball = new JLabel("AD"); 31 ball.setBorder(BorderFactory.createLineBorder(Color.blue)); 32 ball.setSize(40, 40); 33 constraints.gridwidth = 40; 34 constraints.gridheight = 40; 35 constraints.gridx = x * 40; 36 constraints.gridy = y * 40; 37 this.add(ball, constraints); 38 } 39 } 40 } 41 }
HelperFrame
1 package org.stephen.bubblebreaker.view; 2 3 import javax.swing.JLabel; 4 import javax.swing.JPanel; 5 6 public class HelperFrame extends JPanel { 7 8 JLabel hintCurrent; 9 JLabel current; 10 11 JLabel hintSelected; 12 JLabel selected; 13 14 JLabel hintHighest; 15 JLabel highest; 16 17 JLabel hintAverage; 18 JLabel average; 19 20 JLabel hintPlayCount; 21 JLabel playCount; 22 23 public HelperFrame() { 24 25 } 26 27 public void build() { 28 this.setSize(480, 120); 29 hintCurrent = new JLabel("Current"); 30 this.add(hintCurrent); 31 32 current = new JLabel("0"); 33 this.add(current); 34 35 hintSelected = new JLabel("Selected"); 36 this.add(hintSelected); 37 38 hintSelected = new JLabel("0"); 39 this.add(hintSelected); 40 41 hintHighest = new JLabel("Highest"); 42 this.add(hintHighest); 43 44 highest = new JLabel("0"); 45 this.add(highest); 46 47 hintAverage = new JLabel("Average"); 48 this.add(hintAverage); 49 50 average = new JLabel("0"); 51 this.add(average); 52 53 hintPlayCount = new JLabel("PlayCount"); 54 this.add(hintPlayCount); 55 56 playCount = new JLabel("0"); 57 this.add(playCount); 58 } 59 }
到此,我们运行一下,可以看到下面的界面。
可以看到,计算的数值和显示之间是有一定误差的。并且,
底部留下了一定的空白面积——我们还忘记了控制面板:开始、重新开始、清空、退出等按钮。正好在底下加上。
顺便让屏幕居中,并且,修正了一个布局上的错误——Grid也是从0,0开始的。
重新调整完的代码如下:
GamePad.java
1 package org.stephen.bubblebreaker.view; 2 3 import java.awt.GraphicsEnvironment; 4 import java.awt.Point; 5 6 import javax.swing.JFrame; 7 8 public class GamePad extends JFrame { 9 10 MainFrame main = new MainFrame(); 11 HelperFrame helper = new HelperFrame(); 12 ControlFrame control = new ControlFrame(); 13 14 public GamePad() { 15 16 } 17 18 public void build() { 19 this.setTitle("Bubble Breaker"); 20 int width = 480; 21 int height = 600; 22 Point p = GraphicsEnvironment.getLocalGraphicsEnvironment() 23 .getCenterPoint(); 24 this.setLocation(p.x - width / 2, p.y - height / 2); 25 this.setResizable(false); 26 this.setLayout(null); 27 this.setSize(width, height); 28 29 main.build(); 30 helper.build(); 31 control.build(); 32 33 this.add(helper); 34 helper.setLocation(0, 0); 35 this.add(main); 36 main.setLocation(0, 40); 37 this.add(control); 38 control.setLocation(0, 520); 39 40 this.setDefaultCloseOperation(EXIT_ON_CLOSE); 41 } 42 43 public static void main(String[] args) { 44 GamePad pad = new GamePad(); 45 pad.build(); 46 pad.setVisible(true); 47 } 48 49 }
MainFrame.java
1 package org.stephen.bubblebreaker.view; 2 3 import java.awt.Color; 4 import java.awt.GridBagConstraints; 5 import java.awt.GridBagLayout; 6 7 import javax.swing.BorderFactory; 8 import javax.swing.JLabel; 9 import javax.swing.JPanel; 10 11 public class MainFrame extends JPanel { 12 13 JLabel[][] balls; 14 15 public MainFrame() { 16 17 } 18 19 public void build() { 20 GridBagLayout grid = new GridBagLayout(); 21 this.setLayout(grid); 22 this.setSize(480, 480); 23 balls = new JLabel[12][12]; 24 25 GridBagConstraints constraints = new GridBagConstraints(); 26 constraints.weightx = 100; 27 constraints.weighty = 100; 28 for (int y = 0; y < 12; y++) { 29 for (int x = 0; x < 12; x++) { 30 JLabel ball = new JLabel("AD"); 31 ball.setBorder(BorderFactory.createLineBorder(Color.blue)); 32 ball.setSize(40, 40); 33 constraints.gridwidth = 40; 34 constraints.gridheight = 40; 35 constraints.gridx = x * 40; 36 constraints.gridy = y * 40; 37 this.add(ball, constraints); 38 } 39 } 40 } 41 }
HelperFrame.java
1 package org.stephen.bubblebreaker.view; 2 3 import javax.swing.JLabel; 4 import javax.swing.JPanel; 5 6 public class HelperFrame extends JPanel { 7 8 JLabel hintCurrent; 9 JLabel current; 10 11 JLabel hintSelected; 12 JLabel selected; 13 14 JLabel hintHighest; 15 JLabel highest; 16 17 JLabel hintAverage; 18 JLabel average; 19 20 JLabel hintPlayCount; 21 JLabel playCount; 22 23 public HelperFrame() { 24 25 } 26 27 public void build() { 28 this.setSize(480, 40); 29 hintCurrent = new JLabel("Current"); 30 this.add(hintCurrent); 31 32 current = new JLabel("0"); 33 this.add(current); 34 35 hintSelected = new JLabel("Selected"); 36 this.add(hintSelected); 37 38 hintSelected = new JLabel("0"); 39 this.add(hintSelected); 40 41 hintHighest = new JLabel("Highest"); 42 this.add(hintHighest); 43 44 highest = new JLabel("0"); 45 this.add(highest); 46 47 hintAverage = new JLabel("Average"); 48 this.add(hintAverage); 49 50 average = new JLabel("0"); 51 this.add(average); 52 53 hintPlayCount = new JLabel("PlayCount"); 54 this.add(hintPlayCount); 55 56 playCount = new JLabel("0"); 57 this.add(playCount); 58 } 59 }
ControlFrame.java
1 package org.stephen.bubblebreaker.view; 2 3 import javax.swing.JButton; 4 import javax.swing.JPanel; 5 6 public class ControlFrame extends JPanel { 7 8 JButton start = new JButton("Start"); 9 JButton clear = new JButton("Clear"); 10 JButton exit = new JButton("Exit"); 11 12 public ControlFrame() { 13 14 } 15 16 public void build() { 17 this.setSize(480, 120); 18 this.add(start); 19 this.add(clear); 20 this.add(exit); 21 } 22 }
显示界面:
好的,显示就讲到这里,下一讲,我们讲述如何进行操控。