六、编码

整个项目共有五个类,有四个类的代码前面已经介绍过了,而且是在其他项目中使用过的相对成熟的代码.现在只需全力去实现Displayable1类.Displayable1类的代码如下:

package huarongroad;

import javax.microedition.lcdui.*;

public class Displayable1 extends Canvas implements CommandListener {
    private int[] loc = new int[2]; //光标的当前位置,0是水平位置,1是竖直位置
    private int[] SelectArea = new int[4]; //被选定的区域,即要移动的区域
    private int[] MoveArea = new int[4]; //要移动到的区域
    private Map MyMap = new Map(); //地图类
    private boolean selected; //是否已经选中要移动区域的标志
    private int level; //但前的关面
    public Displayable1() { //构造函数
        try {
            jbInit(); //JBuilder定义的初始化函数
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void Init_game() {
        //初始化游戏,读取地图,设置选择区域,清空要移动到的区域
        this.loc = MyMap.read_map(this.level); //读取地图文件,并返回光标的初始位置
        //0为水平位置,1为竖直位置
        this.SelectArea[0] = this.loc[0]; //初始化选中的区域
        this.SelectArea[1] = this.loc[1];
        this.SelectArea[2] = 1;
        this.SelectArea[3] = 1;
        this.MoveArea[0] = -1; //初始化要移动到的区域
        this.MoveArea[1] = -1;
        this.MoveArea[2] = 0;
        this.MoveArea[3] = 0;
    }

    private void jbInit() throws Exception { //JBuilder定义的初始化函数
        //初始化实例变量
        this.selected = false; //设置没有被选中的要移动区域
        this.level = 1;
        Images.init(); //初始化图片常量
        Init_game(); //初始化游戏,读取地图,设置选择区域,清空要移动到的区域
        setCommandListener(this); //添加命令监听,这是Displayable的实例方法
        addCommand(new Command("Exit", Command.EXIT, 1)); //添加“退出”按钮
    }

    public void commandAction(Command command, Displayable displayable) {
        //命令处理函数
        if (command.getCommandType() == Command.EXIT) { //处理“退出”
            MIDlet1.quitApp();
        }
    }

    protected void paint(Graphics g) {
        //画图函数,用于绘制用户画面,即显示图片,勾画选中区域和要移动到的区域
        try {
            g.drawImage(Images.image_Frame, 0, 0,
                        Graphics.TOP | Graphics.LEFT); //画背景
            MyMap.draw_map(g); //按照地图内容画图
            if (this.selected) {
                g.setColor(0, 255, 0); //如果被选中,改用绿色画出被选中的区域
            }
            g.drawRect(this.SelectArea[0] * Images.UNIT + Images.LEFT,
                       this.SelectArea[1] * Images.UNIT + Images.TOP,
                       this.SelectArea[2] * Images.UNIT,
                       this.SelectArea[3] * Images.UNIT); //画出选择区域,
            //如果被选中,就用绿色
            //否则,使用黑色
            g.setColor(255, 255, 255); //恢复画笔颜色
            if (this.selected) { //已经选中了要移动的区域
                g.setColor(255, 0, 255); //改用红色
                g.drawRect(this.MoveArea[0] * Images.UNIT + Images.LEFT,
                           this.MoveArea[1] * Images.UNIT + Images.TOP,
                           this.MoveArea[2] * Images.UNIT,
                           this.MoveArea[3] * Images.UNIT); //画出要移动到的区域
                g.setColor(255, 255, 255); //恢复画笔颜色
            }
        } catch (Exception ex) {
        }
        System.out.println(Runtime.getRuntime().freeMemory());
        System.out.println(Runtime.getRuntime().totalMemory());
    }

    private void setRange() {
        //设置移动后能够选中的区域
        //调整当前光标位置到地图的主位置,即记录人物信息的位置
        if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFT) {
            this.loc[0] -= 1; //向左调
        } else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DUP) {
            this.loc[1] -= 1; //向上调
        } else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFTUP) {
            this.loc[0] -= 1; //向左调
            this.loc[1] -= 1; //向上调
        }
        this.SelectArea[0] = this.loc[0]; //设置光标的水平位置
        this.SelectArea[1] = this.loc[1]; //设置光标的竖直位置
        //设置光标的宽度
        if (this.loc[0] + 1 < Images.WIDTH) {
            this.SelectArea[2] = this.MyMap.Grid[this.loc[1]][this.loc[0] + 1] !=
                                 (byte) '1' ?
                                 1 : 2;
        } else {
            this.SelectArea[2] = 1;
        }
        //设置光标的高度
        if (this.loc[1] + 1 < Images.HEIGHT) {
            this.SelectArea[3] = this.MyMap.Grid[this.loc[1] + 1][this.loc[0]] !=
                                 (byte) '2' ?
                                 1 : 2;
        } else {
            this.SelectArea[3] = 1;
        }
    }

    private boolean setMoveRange() {
        //设置要移动到的区域,能够移动返回true,否则返回false
        for (int i = 0; i < this.SelectArea[2]; i++) {
            for (int j = 0; j < this.SelectArea[3]; j++) {
                if (this.loc[1] + j >= Images.HEIGHT ||
                    this.loc[0] + i >= Images.WIDTH ||
                    (!isInRange(this.loc[0] + i, this.loc[1] + j) &&
                     this.MyMap.Grid[this.loc[1] + j][this.loc[0] + i] !=
                     Images.BLANK)) {
                    return false;
                }
            }
        }
        this.MoveArea[0] = this.loc[0];
        this.MoveArea[1] = this.loc[1];
        this.MoveArea[2] = this.SelectArea[2];
        this.MoveArea[3] = this.SelectArea[3];
        return true;
    }

    private boolean isInRange(int x, int y) {
        //判断给定的(x,y)点是否在选定区域之内,x是水平坐标,y是竖直坐标
        if (x >= this.SelectArea[0] &&
            x < this.SelectArea[0] + this.SelectArea[2] &&
            y >= this.SelectArea[1] &&
            y < this.SelectArea[1] + this.SelectArea[3]) {
            return true;
        } else {
            return false;
        }
    }

    private boolean isInRange2(int x, int y) {
        //判断给定的(x,y)点是否在要移动到的区域之内,x是水平坐标,y是竖直坐标
        if (x >= this.MoveArea[0] &&
            x < this.MoveArea[0] + this.MoveArea[2] &&
            y >= this.MoveArea[1] &&
            y < this.MoveArea[1] + this.MoveArea[3]) {
            return true;
        } else {
            return false;
        }
    }

    protected void keyPressed(int keyCode) {
        //处理按下键盘的事件,这是Canvas的实例方法
        switch (getGameAction(keyCode)) { //将按键的值转化成方向常量
        case Canvas.UP: //向上
            if (!this.selected) { //还没有选定要移动的区域
                if (this.loc[1] - 1 >= 0) { //向上还有移动空间
                    this.loc[1]--; //向上移动一下
                    setRange(); //设置光标移动的区域,该函数能将光标移动到地图主位置
                    repaint(); //重新绘图
                }
            } else { //已经选定了要移动的区域
                if (this.loc[1] - 1 >= 0) { //向上还有移动空间
                    this.loc[1]--; //向上移动一下
                    if (setMoveRange()) { //能够移动,该函数能够设置要移动到的区域
                        repaint(); //重新绘图
                    } else { //不能移动
                        this.loc[1]++; //退回来
                    }
                }
            }
            break;
        case Canvas.DOWN: //向下
            if (!this.selected) { //还没有选定要移动的区域
                if (this.loc[1] + 1 < Images.HEIGHT) { //向下还有移动空间
                    if (this.MyMap.Grid[this.loc[1] + 1][this.loc[0]] ==
                        Images.DUP) { //该图片有两个格高
                        this.loc[1]++; //向下移动一下
                        if (this.loc[1] + 1 < Images.HEIGHT) { //向下还有
                            //移动空间
                            this.loc[1]++; //向下移动一下
                            setRange(); //设置光标移动的区域,
                            //该函数能将光标移动到地图主位置
                            repaint(); //重新绘图
                        } else { //向下没有移动空间
                            this.loc[1]--; //退回来
                        }
                    } else { //该图片只有一个格高
                        this.loc[1]++; //向下移动一下
                        setRange(); //设置光标移动的区域,
                        //该函数能将光标移动到地图主位置
                        repaint(); //重新绘图
                    }
                } else {
                }
            } else { //已经选定了要移动的区域
                if (this.loc[1] + 1 < Images.HEIGHT) { //向下还有移动空间
                    this.loc[1]++; //向下移动一下
                    if (setMoveRange()) { //能够移动,该函数能够设置要移动到的区域
                        repaint(); //重新绘图
                    } else { //不能移动
                        this.loc[1]--; //退回来
                    }
                }
            }
            break;
        case Canvas.LEFT: //向左
            if (!this.selected) { //还没有选定要移动的区域
                if (this.loc[0] - 1 >= 0) { //向左还有移动空间
                    this.loc[0]--; //向左移动一下
                    setRange(); //设置光标移动的区域,该函数能将光标移动到地图主位置
                    repaint(); //重新绘图
                }
            } else { //已经选定了要移动的区域
                if (this.loc[0] - 1 >= 0) { //向左还有移动空间
                    this.loc[0]--; //向左移动一下
                    if (setMoveRange()) { //能够移动,该函数能够设置要移动到的区域
                        repaint(); //重新绘图
                    } else { //不能移动
                        this.loc[0]++; //退回来
                    }
                }
            }
            break;
        case Canvas.RIGHT: //向右
            if (!this.selected) { //还没有选定要移动的区域
                if (this.loc[0] + 1 < Images.WIDTH) { //向右还有移动空间
                    if (this.MyMap.Grid[this.loc[1]][this.loc[0] + 1] ==
                        Images.DLEFT) { //该图片有两个格宽
                        this.loc[0]++; //向右移动一下
                        if (this.loc[0] + 1 < Images.WIDTH) { //向右还有
                            //移动空间
                            this.loc[0]++; //向右移动一下
                            setRange(); //设置光标移动的区域,
                            //该函数能将光标移动到地图主位置
                            repaint(); //重新绘图
                        } else { //向右没有移动空间
                            this.loc[0]--; //退回来
                        }
                    } else { //该图片只有一个格宽
                        this.loc[0]++; //向右移动一下
                        setRange(); //设置光标移动的区域,
                        //该函数能将光标移动到地图主位置
                        repaint(); //重新绘图
                    }
                } else {
                }
            } else { //已经选定了要移动的区域
                if (this.loc[0] + 1 < Images.WIDTH) { //向右还有移动空间
                    this.loc[0]++; //向右移动一下
                    if (setMoveRange()) { //能够移动,该函数能够设置要移动到的区域
                        repaint(); //重新绘图
                    } else { //不能移动
                        this.loc[0]--; //退回来
                    }
                }
            }
            break;
        case Canvas.FIRE:
            if (this.selected) { //已经选定了要移动的区域
                Move(); //将要移动的区域移动到刚选中的区域
                repaint(); //重新绘图
                this.selected = false; //清除已选定要移动区域的标志
                if (win()) {
                    System.out.println("win");
                }
            } else { //还没有选定要移动的区域
                if (this.MyMap.Grid[this.loc[1]][this.loc[0]] ==
                    Images.BLANK) { //要移到的位置是一个空白
                } else { //要移到的位置不是空白
                    this.selected = true; //设置已选定要移动区域的标志
                }
                repaint(); //重新绘图
            }
            break;
        }
    }

    private boolean win() {
        //判断是否已经救出了曹操
        if (this.MyMap.Grid[Images.HEIGHT - 2][Images.WIDTH - 3] ==
            Images.CAOCAO) {
            return true;
        } else {
            return false;
        }
    }

    private void PrintGrid(String a) {
        //打印当前地图的内容,用于调试
        System.out.println(a);
        for (int i = 0; i < Images.HEIGHT; i++) {
            for (int j = 0; j < Images.WIDTH; j++) {
                System.out.print((char)this.MyMap.Grid[i][j]);
            }
            System.out.println("");
        }
    }

    private void Move() {
        //将要移动的区域移动到刚选中的区域
        if (this.MoveArea[0] == -1 || this.MoveArea[1] == -1 ||
            this.SelectArea[0] == -1 || this.SelectArea[1] == -1) { //没有选中区域
        } else { //已经选中了要移动的区域和要移动到的区域
            byte[][] temp = new byte[this.SelectArea[3]][this.SelectArea[2]];
            //复制要移动的区域,因为这块区域可能会被覆盖掉
            for (int i = 0; i < this.SelectArea[2]; i++) {
                for (int j = 0; j < this.SelectArea[3]; j++) {
                    temp[j][i] =
                            this.MyMap.Grid[this.SelectArea[1] + j]
                            [this.SelectArea[0] + i];
                }
            }
            // 调试信息
            //将要移动的区域移动到刚选中的区域(即要移动到的区域)
            for (int i = 0; i < this.SelectArea[2]; i++) {
                for (int j = 0; j < this.SelectArea[3]; j++) {
                    this.MyMap.Grid[this.MoveArea[1] + j]
                            [this.MoveArea[0] + i] = temp[j][i];
                }
            }
            // 调试信息
            //将要移动的区域中无用内容置成空白
            for (int i = 0; i < this.SelectArea[3]; i++) {
                for (int j = 0; j < this.SelectArea[2]; j++) {
                    if (!isInRange2(this.SelectArea[0] + j,
                                    this.SelectArea[1] + i)) {
                        //该点是不在要移动到
                        //的区域之内,需置空
                        this.MyMap.Grid[this.SelectArea[1] + i]
                                [this.SelectArea[0] + j] = Images.BLANK;
                    } else {
                    }
                }
            }
            // 调试信息
            this.SelectArea[0] = this.MoveArea[0]; //重置选中位置的水平坐标
            this.SelectArea[1] = this.MoveArea[1]; //重置选中位置的竖直坐标
            this.MoveArea[0] = -1; //清空要移动到的位置
            this.MoveArea[1] = -1; //清空要移动到的位置
            this.MoveArea[2] = 0; //清空要移动到的位置
            this.MoveArea[3] = 0; //清空要移动到的位置
        }
    }
}
代码的相关分析,在详细设计阶段已经讲过,代码中有比较相近的注释,请读者自行研读分析.将全部的代码写好,用wtk2.0自 带的Ktoolbar工具建立一个工程,接下来把去不源文件放到正确位置下,然后点击build,再点run,就完成了程序的编写.当然如果有错误还要修 改和调试。

七、测试

作为一个真正的产 品要经过单体测试、结合测试和系统测试。由于项目本身简单,而且大部分代码已经是相对成熟的,我们跳过单体测试;又由于笔者的实际环境所限,无法搞到 Java手机,无法架设OTA服务器,因此我们也只能放弃系统测试。那么就让我们开始结合测试吧。测试之前要先出一个测试式样书,也就是测试的计划。我们 将它简化一下,只测试如下几种情况:第一、对各种形状的区域的选择和移动;第二、临近边界区域的选择和移动;第三、同一区域的反复选择和反复移动;第四、 非法选择和非法移动。有了测试的目标,接下来的工作就是用wtk2.0自带的Run MIDP Application工具进行测试。打开这个工具,加载huarongRoad的jad文件,程序就会自动运行,选择launch上MIDlet1这个 程序,华容道游戏就会跃然屏幕之上,接下来的工作就是左三点.右三点,拇指扭扭,来做测试。测试过程中发现任何的问题,立刻发一个bug票给自己,然后就 又是痛苦的调试和修正bug,如此如此。

八、发布

谈到发布,其实是个关键,再好的产品不能很好的发布出去也只是个产品而已,变不成商品也就得不到回报.由于笔者的条件所限,这里只能是纸上谈兵,不过还是希望能够使读者对这一过程有所了解(网上的资料也很多)。

J2ME 的程序发布一般都是通过OTA(Over The Air),你只需要一台有公网IP的主机和一个普通的web Server就可以了(尽管要求很低,但笔者还是没有),这里我们以apache为例介绍一下OTA服务的配置,首先是安装好了apache服务器,然后 在conf目录下找到mime.types文件,在该文件中加入如下两行

application/java-archive jar

text/vnd.sun.j2me.app-descriptor jad

然 后重起apache服务器就可以了。接下来的工作就是修改jad文件中MIDlet-Jar-URL:后面的参数,将它改为URL的绝对路径,即http://***/huarongroad.jar(其中***是你的域名或IP地址)。在下面就是 用java手机下载jad文件,它会自动部署相应的jar文件并加载它。剩下的工作就和在模拟器上操作是一样的了。

九、项目总结

至 此,我们已经完成了一个J2ME游戏的全部开发过程,程序中涉及到了调研、分析、设计、编码、测试和发布等方面的问题,其实在实际的工作中还有很多更为具 体的问题,毕竟技术只在软件开发过程中占据很有限的一部分,这里限于篇幅的限制无法一一具体展开。今后,笔者计划再写一篇使用J2ME开发手机屏保的文 章,借此机会向读者展示J2ME动画技术;然后再写一篇J2ME网络应用的文章,做一个类似开心辞典那样的知识问答游戏,以便向读者展示J2ME的网络技 术;待这两方面的技术交待清楚之后,我将引领读者制作一个稍大一些的游戏。 (T117)