您的位置:首页 > 编程语言

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

2012-11-27 14:42 78 查看
本章,我们来讨论如何为游戏加上动作。

先从整体上考虑,很多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的代码如下:

package org.stephen.bubblebreaker.listener;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import org.stephen.bubblebreaker.control.EventDispatcher;
import org.stephen.bubblebreaker.model.Event;
import org.stephen.bubblebreaker.model.Game;

public class StartActionListener implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
Game game = Game.getInstance();
game.shuffle();
game.score.clearCurrentScore();
game.state = Game.State.PLAYING;

EventDispatcher.send(Event.UPDATE_BALLS);
EventDispatcher.send(Event.UPDATE_SCORES);
EventDispatcher.send(Event.UPDATE_BUTTONS);
}
}


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

其代码如下:

public void shuffle() {
for (int y = 0; y < 12; y++) {
for (int x = 0; x < 12; x++) {
grid.balls[y][x] = new Ball();
grid.balls[y][x].color = Ball.Color.values()[(int) (Math
.random() * Ball.Color.values().length)];
grid.balls[y][x].x = x;
grid.balls[y][x].y = y;
grid.balls[y][x].state = Ball.State.NORMAL;
}
}
}


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

该枚举定义为:

public enum Color {
RED, GREEN, BLUE, YELLOW, PURPLE;
public ImageIcon getImageIcon() {
return new ImageIcon("image/" + name().toLowerCase() + ".png");
}
}


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

然后是Score的clearCurrentScore();

public void clearCurrentScore() {
selected = 0;
current = 0;
}


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

public enum State {
STAND_BY, PLAYING, GAME_OVER;
}


之后是Event和EventDispatcher

package org.stephen.bubblebreaker.model;

public class Event {
public static final int UPDATE_BALLS = 0x1000;
public static final int UPDATE_SCORES = 0x1001;
public static final int UPDATE_BUTTONS = 0x1002;
}


package org.stephen.bubblebreaker.control;

import org.stephen.bubblebreaker.model.Event;
import org.stephen.bubblebreaker.model.Game;
import org.stephen.bubblebreaker.view.GamePad;

public class EventDispatcher {

public static void send(int event) {
GamePad gamePad = Game.getInstance().gamePad;
switch (event) {
case Event.UPDATE_BALLS:
gamePad.main.render();
break;
case Event.UPDATE_SCORES:
gamePad.helper.render();
break;
case Event.UPDATE_BUTTONS:
gamePad.control.render();
break;
}
}
}


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

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

private Game() {
grid = new Grid();
score = new Score();
state = State.STAND_BY;
}


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

public GamePad() {
Game.getInstance().gamePad = this;
}


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

package org.stephen.bubblebreaker.model;

public class Grid {

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


然后是各个render的实现

MainFrame.render()

public void render() {
Ball[][] balls = Game.getInstance().grid.balls;
for (int y = 0; y < 12; y++) {
for (int x = 0; x < 12; x++) {
this.balls[y][x].setIcon(balls[y][x].color.getImageIcon());
this.balls[y][x].invalidate();
}
}
}


HelperFrame.render()

public void render() {
Score score = Game.getInstance().score;
current.setText(String.valueOf(score.current));
selected.setText(String.valueOf(score.selected));
highest.setText(String.valueOf(score.highest));
average.setText(String.valueOf(score.average));
playCount.setText(String.valueOf(score.playCount));
}


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

ControlFrame.render()

public void render() {
Game.State state = Game.getInstance().state;
if (state == Game.State.PLAYING) {
start.setText("Restart");
} else {
start.setText("Start");
}
}


然后调试,修正错误。

点击Start后的界面如下:



下一章,我们将会讲解BallActionListener的实现。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: