[转载]一个J2ME拼图游戏的开发

一个J2ME拼图游戏的开发

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模式的应用。

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

下面是运行在手机上的效果图:
按此在新窗口打开图片

由于公司发的手机还停留在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大小的原始图片切割成9个30x30的小图片,并依次编号0-8:

按此在新窗口打开图片

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,即可实现整个游戏的基本框架。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-131769/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/374079/viewspace-131769/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值