一步一步写自表达代码——消小球(2)

本章,我们来讨论如何为游戏加上动作。

先从整体上考虑,很多Java例子程序喜欢直接在代码中加入:

button.addActionListener(new ActionListener() { public void performAction(Event e) {}});

这样的代码。这是不好的,因为这不符合单一职责原则,ActionListener应该独立出去,单独成类。这样,当发生问题的时候也容易寻找。有两种形式,一种是本文采用的单独成类的形式,另外一种是在对应的对象中声明内部类。例如:

class MyButton.ActionListener {}。

整体上来讲,我们会加入:

StartActionListener, ClearActionListener, ExitActionListener和BallActionListener几个类,然后绑定到对象上,下一步对每个动作进行细化。在这期间,我们发现,显示小球的JLabel无法加入ActionListener,于是修改成为JButton——这种情况在编码中很常见,就是无法在详细设计时将设计完美化,只有在编码时才能够发现问题。也是为什么我们主张不需要进行详细设计的原因,具体参照软件开发过程中的浪费——详细设计

 

关于如何绑定和加入的过程我们省略了。

第一个是Start。当点击Start的时候开始游戏。并且Start按钮变为Restart,表示重新开始当前局。

start之后的动作依次是:

生成随机的小球       Game.getInstance().shuffle();

布局到屏幕上          Game.getInstance().gamePad.main.render();

清空当前分数          Game.getInstance().score.current = 0;

                           Game.getInstance().score.selected = 0;

刷新分数显示          Game.getInstance().gamePad.helper.render();

更改Start按钮的文字为Restart  Game.getInstance().gamePad.control.start.setText("Restart");


据上述代码会产生如下的编译问题:

     1.Game中没有包含gamePad对象。

     2.很多变量都不是public的无法访问。

     3.render方法在MainFrame和HelperFrame中没有声明。

     4.shuffle()方法没有声明。

的确如此。自表达代码主张先用可以自表达的方式写出需要的操作,然后再去实现这些操作,而不是先实现一些操作,然后调用。

另外,这些代码可以抽取Game.getInstance()成为一个局部变量。

但是这里,我们发现,Listener试图同时更新Model层和View层,并且加大了代码的访问权限。这并不是一种好的方法。虽然按照这种方法继续下去也可以实现整个代码,但是这中书写方式会把原来拆开的MVC结构又给混合到一起。所以,为了保持分离性,我们不采用上述方式,而是引入消息机制,当数据更新的时候发送消息更新UI。提起这种消息机制,也要提一下不要滥用消息机制,不管什么东西都采用消息传递,因为消息机制虽然可以解耦合代码,但是也降低了可读性和可理解性。
所以,在这里,我们先顶一下,消息机制仅仅用于UI更新。

分布讲解,

startActionListener的代码如下:

 1 package org.stephen.bubblebreaker.listener;

 2 

 3 import java.awt.event.ActionEvent;

 4 import java.awt.event.ActionListener;

 5 

 6 import org.stephen.bubblebreaker.control.EventDispatcher;

 7 import org.stephen.bubblebreaker.model.Event;

 8 import org.stephen.bubblebreaker.model.Game;

 9 

10 public class StartActionListener implements ActionListener {

11 

12     @Override

13     public void actionPerformed(ActionEvent e) {

14         Game game = Game.getInstance();

15         game.shuffle();

16         game.score.clearCurrentScore();

17         game.state = Game.State.PLAYING;

18 

19         EventDispatcher.send(Event.UPDATE_BALLS);

20         EventDispatcher.send(Event.UPDATE_SCORES);

21         EventDispatcher.send(Event.UPDATE_BUTTONS);

22     }

23 }

其中,game.shuffle()代表生成随机小球。

其代码如下:

 1 public void shuffle() {

 2         for (int y = 0; y < 12; y++) {

 3             for (int x = 0; x < 12; x++) {

 4                 grid.balls[y][x] = new Ball();

 5                 grid.balls[y][x].color = Ball.Color.values()[(int) (Math

 6                         .random() * Ball.Color.values().length)];

 7                 grid.balls[y][x].x = x;

 8                 grid.balls[y][x].y = y;

 9                 grid.balls[y][x].state = Ball.State.NORMAL;

10             }

11         }

12     }

但其中涉及到Ball.Color的变更。因为Ball的Color选值范围有限,所以,改为自定义的内部枚举Color

该枚举定义为:

1 public enum Color {

2         RED, GREEN, BLUE, YELLOW, PURPLE;

3         public ImageIcon getImageIcon() {

4             return new ImageIcon("image/" + name().toLowerCase() + ".png");

5         }

6     }

可见,限制了选值范围为红绿蓝黄紫5种颜色,并且每种颜色都配上了一张图片,当重新排列的时候,可以通过名称获取相关的图片并显示。

然后是Score的clearCurrentScore();

1 public void clearCurrentScore() {

2         selected = 0;

3         current = 0;

4     }

再然后是,Game.State,这是一个用来通知按钮变化的中间变量,这个变量也将在游戏结束时起作用。

1 public enum State {

2         STAND_BY, PLAYING, GAME_OVER;

3     }

之后是Event和EventDispatcher

1 package org.stephen.bubblebreaker.model;

2 

3 public class Event {

4     public static final int UPDATE_BALLS = 0x1000;

5     public static final int UPDATE_SCORES = 0x1001;

6     public static final int UPDATE_BUTTONS = 0x1002;

7 }

 

 1 package org.stephen.bubblebreaker.control;

 2 

 3 import org.stephen.bubblebreaker.model.Event;

 4 import org.stephen.bubblebreaker.model.Game;

 5 import org.stephen.bubblebreaker.view.GamePad;

 6 

 7 public class EventDispatcher {

 8 

 9     public static void send(int event) {

10         GamePad gamePad = Game.getInstance().gamePad;

11         switch (event) {

12         case Event.UPDATE_BALLS:

13             gamePad.main.render();

14             break;

15         case Event.UPDATE_SCORES:

16             gamePad.helper.render();

17             break;

18         case Event.UPDATE_BUTTONS:

19             gamePad.control.render();

20             break;

21         }

22     }

23 }

然后开始运行,调试,发现还是有不少Bug的,首先,开始时显示的按钮的边界都不需要,删掉相关代码。

然后,Game初始化时应该初始化其中的变量。

1     private Game() {

2         grid = new Grid();

3         score = new Score();

4         state = State.STAND_BY;

5     }

对于Game.gamePad应该在GamePad初始化时进行赋值。

1     public GamePad() {

2         Game.getInstance().gamePad = this;

3     }

Grid中的变量声明也应该初始化。

1 package org.stephen.bubblebreaker.model;

2 

3 public class Grid {

4 

5     public Ball[][] balls = new Ball[12][12];

6 }

然后是各个render的实现

MainFrame.render()

1 public void render() {

2         Ball[][] balls = Game.getInstance().grid.balls;

3         for (int y = 0; y < 12; y++) {

4             for (int x = 0; x < 12; x++) {

5                 this.balls[y][x].setIcon(balls[y][x].color.getImageIcon());

6                 this.balls[y][x].invalidate();

7             }

8         }

9     }

HelperFrame.render()

1     public void render() {

2         Score score = Game.getInstance().score;

3         current.setText(String.valueOf(score.current));

4         selected.setText(String.valueOf(score.selected));

5         highest.setText(String.valueOf(score.highest));

6         average.setText(String.valueOf(score.average));

7         playCount.setText(String.valueOf(score.playCount));

8     }

同时,我们发现旧代码中的selected没有正确赋值,一并修改。

ControlFrame.render()

1     public void render() {

2         Game.State state = Game.getInstance().state;

3         if (state == Game.State.PLAYING) {

4             start.setText("Restart");

5         } else {

6             start.setText("Start");

7         }

8     }

然后调试,修正错误。

点击Start后的界面如下:

一步一步写自表达代码——消小球(2)

下一章,我们将会讲解BallActionListener的实现。

 

 

 

 

你可能感兴趣的:(代码)