一个J2ME拼图游戏的开发

来源:J2ME开发网 作者:asklxf 2007-11-23 出处:pcdog.com

j2me  java  jsp  多线程  协议  

  MIDP规范的出现使得我们在手机上开发Java游戏成为可能。今天我们要实现的是一个简单的拼图游戏。这个拼图游戏是一个3x3的拼图,由9个分割的小图片构成。这样,在手机上,可以用按键1-9对应每个图片。需要移动某个图片时,只需按下对应的数字键即可,非常方便。
(当然,对于键盘不规则的手机,就只能委屈了)当按下0键时,显示整个原始图片。

虽然MIDP提供了许多高级和低级的UI API接口,但是整个应用程序的结构设计仍然至关重要,一个灵活的框架能大大降低游戏开发的复杂度。

MVC模式几乎是UI应用开发的标准模式了,通过Model-View-Controller的分工合作,使得整个应用程序的不同功能部分被分离开来,从而降低开发难度。

MVC有MVC1和MVC2两种模式,其不同之处在于Model能否主动通知View。在窗口程序中,Model可以主动通知View是否需要Update,因此应使用MVC1;在Web程序中,由于http协议的限制,服务器端的Model无法主动调用View(如JSP页面),因此只能使用MVC2,由Controller取得Model并渲染View。

在窗口程序中,View通常仅有一个,但Model可能有很多;而在Web程序中,Model通常被放在Session中,每个JSP页面都是一个View,因此View有很多。

微软的MFC框架也是一个基于MVC模式的框架,其View-Document框架是专门针对桌面应用程序设计的,因此,我们在MIDP程序中也可借鉴其思想。

在MIDP程序中,MIDlet起着Controller的作用,每个Screen或者Canvas就是一个View,而Model可以用一个单独的类来表示,用于存储程序运行中的数据。对于这个拼图游戏来说,设计以下几个类:

PuzzleMIDlet:控制整个游戏的生命周期。
MainCanvas:绘制游戏的主窗口。
Document:存储游戏运行中的数据。

当用户通过MainCanvas输入命令后(例如,按下某个键),将可能引起Document数据的更新,如果需要更新屏幕,则Document应通知View更新显示,这是一个Observer模式的应用。

由于这个拼图游戏不需要频繁地更新画面,因此连多线程也不必了。

下面是运行在手机上的效果图:
一个J2ME拼图游戏的开发(图一)

由于公司发的手机还停留在CF62/MIDP1.0的水平,因此只好用MIDP1.0写这个拼图游戏了。不过好在我的重点不在如何绘制Canvas,因此MIDP2.0中的Game Package绝大部分都用不上。

下面我们开始设计每个类。

设计Document类

Document类需要保存游戏运行中所有的状态数据,对于这个拼图游戏来说,我们设计以下成员变量:

    Updatable updatable;
    int state;
    Image[] images = new Image[9];
    int[][] current = new int[3][3];
    int hiddenX, hiddenY;
    int steps; // 移动的步数


MainCanvas需要实现Updatable接口,因此,Document保存了一个View的引用,在恰当的时候,Document可以调用updatable.update()方法通知View需要重绘。这样,MainCanvas和Document就实现了Observer模式。

游戏中,state用于存储游戏状态,一共有3种状态:
PUZZLE_STATE:表示正在拼图;
IMAGE_STATE:表示正在查看原始图片;
FINISH_STATE:表示拼图完成。

images数组按次序存储原始图片,我们把这个90x90大小的原始图片<image>切割成9个30x30的小图片,并依次编号0-8:

一个J2ME拼图游戏的开发(图二)

current[3][3]是一个二维数组,存储Image在images[]数组中的索引号,这样就可以从current[][]中获得对应的Image对象。

hiddenX和hiddenY用来标识空白方格的位置。仅当位于(hiddenX, hiddenY)上下左右的方格可以移动。

初始化current

为了打乱一个拼好的方格,我们需要一个算法来随机打乱9个方格。在我们想出这个算法前,最简单的方法便是用一个可拼好的数据来写死current[][],使得我们能集中精力先把游戏的框架搭起来:

current = new int[][] {
    {2, 7, 5},
    {1, 0, 6},
    {4, 3, 8}
}


然后设定hiddenX=2, hiddenY=2,使得右下角current[2][2]的方格被隐藏。

要取得某个方格对应的Image对象,我们用

    public Image getCurrentImage(int x, int y) {
        if( (x==hiddenX) && (y==hiddenY) )
            return null;
        return images[current[x][y]];
    }


对于位于(hiddenX, hiddenY)位置的方格,返回null表示不显示该方格。

如何判断拼图是否完成?

当current[][]数组的内容按照{0, 1, 2}, {3, 4, 5}, {6, 7, 8}排列时,表示该拼图已经拼好,因此,判断代码非常简单:

    public boolean isFinish() {
        for(int i=0; i<3; i++) {
            for(int j=0; j<3; j++) {
                if(current[i][j]!=(i*3+j))
                    return false;
            }
        }
        return true;
    }


当用户移动某个方格时,Document接收方格位置(x, y)并负责判断能否移动,如果能,更新current[][]的数据和hiddenX, hiddenY,并返回true表示数据已更新,否则返回false表示不可移动。

    public boolean move(int x, int y) {
        // 如果用户试图移动隐藏方格,直接返回false:
        if(hiddenX==x && hiddenY==y)
            return false;

        // 如果方格位于(hiddexX, hiddenY)的相邻位置,
        // 交换该方格(x, y)和(hiddenX, hiddenY)的相关数据:
        boolean moved = false;
        if( ((x-1)==hiddenX) && (y==hiddenY) ) {
            sweep(x, y);
            moved = true;
        }
        if( ((x+1)==hiddenX) && (y==hiddenY) ) {
            sweep(x, y);
            moved = true;
        }
        if( (x==hiddenX) && ((y-1)==hiddenY) ) {
            sweep(x, y);
            moved = true;
        }
        if( (x==hiddenX) && ((y+1)==hiddenY) ) {
            sweep(x, y);
            moved = true;
        }
        if(moved) {
            steps++;
            if(isFinish()) {
                // TODO...
            }
              updatable.update();
        }
    }

    private void sweep(int x, int y) {
        int temp = current[x][y];
        current[x][y] = current[hiddenX][hiddenY];
        current[hiddenX][hiddenY] = temp;
        hiddenX = x;
        hiddenY = y;
    }


至此,Document类基本完成。Document不涉及任何显示功能,仅仅存储和更新数据,并在恰当的时候通知View更新显示。

实现View

在MIDP中,View就是Screen或者Canvas,在这个游戏中,我们应该使用Canvas,定义:

    public class MainCanvas extends Canvas implements CommandListener, Updatable { ... }

在构造方法中,初始化Document:

    public MainCanvas(String imageName) {
        // 读图像:
        Image[] images = new Image[9];
        for(int i=0; i<9; i++) {
            try {
                images[i] = Image.createImage("/image/" + i + ".png");
            }
            catch(IOException ioe) {}
        }
        document = new Document(this, images, 2, 2);
    }


在paint()方法中,MainCanvas从Document中获得数据,然后更新画面:

    protected void paint(Graphics g) {
        g.fillRect(0,0,getWidth(),getHeight());
        // 获得当前状态:
        int state = document.getState();
        if(state==Document.PUZZLE_STATE) {
            for(int x=0; x<3; x++) {
                for(int y=0; y<3; y++) {
                    Image image = document.getImage(x, y);
                    if(image!=null) {
                        g.drawImage(image, y*IMAGE_WIDTH, x*IMAGE_WIDTH, Graphics.LEFT|Graphics.TOP);
                    }
                    else {
                        g.setColor(0x000000);
                        g.fillRect(y*IMAGE_WIDTH, x*IMAGE_WIDTH, IMAGE_WIDTH, IMAGE_WIDTH);
                    }
                }
            }
            // draw line:
            g.setColor(0xffffff);
            for(int i=0; i<=3; i++) {
                g.drawLine(0, i*IMAGE_WIDTH, 3*IMAGE_WIDTH, i*IMAGE_WIDTH);
                g.drawLine(i*IMAGE_WIDTH, 0, i*IMAGE_WIDTH, 3*IMAGE_WIDTH);
            }
        }
        else {
            // TODO...
        }
    }


当用户按下某个键时,MainCanvas的keyPressed()方法被执行,然后将用户输入数据传递给Document:

    protected void keyPressed(int keyCode) {
        switch(keyCode) {
        case KEY_NUM1:
            document.move(0,0);
            break;
        case KEY_NUM2:
            document.move(0,1);
            break;
        case KEY_NUM3:
            document.move(0,2);
            break;
        // case KEY_NUM4, 5, 6...
        }
    }


然后,Document可能更新自身内部状态,如果需要重绘画面,Document将调用update()回调方法来通知View更新画面。因此,MainCanvas必须实现Updatable接口的update()回调方法:

    public void update() {
        repaint();
    }


至此,View已基本实现,我们再添加一个用作启动的MIDlet,即可实现整个游戏的基本框架。

一个J2ME拼图游戏的开发(图三) 点击下载jar文件 (54kb)
一个J2ME拼图游戏的开发(图三) 点击下载源代码 (53kb)


更多内容请看PCdog.com--J2ME安装配置  j2me开发  J2ME开发基础专题
上一篇:介绍J2ME可选包FileConnection
下一篇:J2ME潜艇大战游戏设计与实现