题 目: 益智拼图游戏 学 院: 计算机与信息
学院 专 业: 计算机科学与技术 班 级: 计算机06-7班 姓 名: 梁淑媛
学 号: 26
起止时间: 2008.12.21--2008.12.31
成绩:
I
质量评价指标(在相应栏目打?)
评 价 质 量
评 价 项 目
优秀 良好 一般 及格 不及格 工作量和态度
实验、计算可靠性
文字和图表质量
程序完成情况
总体评价
评定成绩
评定人员签名
2008年12月31日
II
一、
题目:《益智拼图游戏》
二、设计内容:要实现的是一个简单的拼图游戏。这个拼图游戏是一
个3×3的拼图,由9个分割的小图片构成。这样,在手机上,可以
用按键1-9对应每个图片。需要移动某个图片时,只需按下对应的数
字键即可,在拼图的过程中,按下0键时,可以显示整个原始图片。 三、设计要求:
1.本游戏采用MVC(Model-View-Controller)模式来编写,这样可
以使得整个应用程序的不同功能部分被分离开来,从而降低开发难
度。在MIDP程序中,MIDlet起着Controller的作用,每个Screen或者Canvas就是一个View,而Model可以用一个单独的类来表示,
用于存储程序运行中的数据。
2.本文就是要制作图片拼图板益智游戏,这个游戏提供了两个图片
拼图。在这个拼图板益智游戏中,玩家通过数字键来移动相应的方格,
当拼成一幅完整的图片时则会发出通过的提示音,并在游戏画面中显
示出本次拼图玩家所花费的移动步数。在游戏名称方面,由于这个游
戏是以图片作为拼图板进行游戏的,因此就取名叫做picPuzzle。 四、工作
:
时间 完成内容 提交文档 备注
第1天 课程设计任查找资料,确定题目,选择
设计准备阶段 务书 第2天
第3天 题目
,设计算法 算法描述
第4天 结构流程图功能模块的划分和设计 设计阶段 等 第5天
第6天 实现具体数据结构和模块 无
第7天 运行并验证程序功能 第8天 程序设计与调试 无
第9天
检查程序 课程设计论第10天 整理材料,撰写论文 文
指导教师: 王艳涛 陈伟 高辉
教研室主任:
2008 年 12月31 日
III
本科课程设计论文
介绍了一个基于J2ME的手机趣味拼图游戏开发的全过程,MIDP规范的出现使得我们在手机上开发Java游戏成为可能。今天我们要实现的是一个简单的
拼图游戏。这个拼图游戏是一个3x3的拼图,由9个分割的小图片构成。这样,
在手机上,可以用按键1-9对应每个图片。需要移动某个图片时,只需按下对应
的数字键即可。从游戏前的准备到主要类和接口的实现,最后运行、调试和打包。
关键词:J2ME ,拼图游戏,API
II
本科课程设计论文
课程设计成绩评定表 .............................................................................................................. II
课程设计任务书 ..................................................................................................................... III
摘 要 ...................................................................................................................................... II 目 录 ..................................................................................................................................... III 1 设计内容 ............................................................................................................................... 1 2 设计过程 ............................................................................................................................... 1
2.1设计方案的论证 ......................................................................................................... 1
2.2概要设计 ..................................................................................................................... 1
2.3界面设计图 ................................................................................................................. 2
2.4代码实现 ..................................................................................................................... 4
3 设计总结 ............................................................................................................................... 9 参考文献 ................................................................................................................................. 10 附录:程序源代码 ................................................................................................................. 10
III
本科课程设计论文
1
要实现的是一个简单的拼图游戏。这个拼图游戏是一个3×3的
拼图,由9个分割的小图片构成。这样,在手机上,可以用按键1-9
对应每个图片。需要移动某个图片时,只需按下对应的数字键即可,
非常方便(当然,对于键盘不规则的手机,就只能委屈了),在拼图
的过程中,按下0键时,可以显示整个原始图片。 2
2.1
随着支持Java的手机迅速普及,手机软件的需求量日益暴涨。据最新职位
调查,手机软件开发人员已成为急缺人才。J2ME由于开发速度快、周期短、支持手机多及开发资源丰富等优点成为当今最受欢迎的手机软件开发平台。然而很
多人对手机软件开发不甚了解,甚至觉得神秘莫测。为此写了基于J2ME的手机
趣味拼图游戏。
要制作出像图1这样的图片拼图板益智游戏,这个游戏提供了两个图片拼
图,当然,也可添加自己喜欢的图片来作为拼图图片。在这个拼图板益智游戏中,
玩家通过数字键来移动相应的方格,当拼成一幅完整的图片时则会发出通过的提
示音,并在游戏画面中显示出本次拼图玩家所花费的移动步数。在游戏名称方面,
由于这个游戏是以图片作为拼图板进行游戏的。
2.2
本程序共有7个java源文件:
DrawPanel : 控制整个游戏的生命周期。
DrawCanva: 绘制游戏的主窗口。
Document: 存储游戏运行中的数据。
SelectTileModel: 选择游戏拼图清单。
BackDropTiledLayer:控制图片移动
1
本科课程设计论文
CharacterSprite:图片角色控制
RightPanel:实现画布的监听
2.3
经过进一步的分析设计,得到系统的框图如下所示。 (1)初始化启动界面(如图所示)
进入初始化启动主程序界面,我们将看到一个包含函数DRAWPANEL和一个启动按钮的界面,它通过调用主函数得到。
在MIDP程序中,MIDlet起着Controller的作用,每个Screen或者Canvas就是一个View,而Model可以用一个单独的类来表示,用于存储程序运行中的
数据。
(2)拼图界面(如图所示)
进入初始化启动界面后按启动按钮,进入拼图的主界面。按上下左右按钮就
可以进行拼图游戏了,这是程序主界面。当按下推出按钮时回到启动界面。
户通过MainCanvas输入命令后(例如,按下某个键),将可能引起Document数据的更新,如果需要更新屏幕,则Document应通知View更新显示,这是一个Observer模式的应用。
控制整个游戏的生命周期
游选 存绘
择戏重 储制
游拼绘游游
戏图游戏戏
拼进戏中的
图行画的主
清交数面 窗
单 换 口 据
图 1系统结构框图
2
本科课程设计论文
3
本科课程设计论文
2.4
设计Document类
Document类需要保存游戏运行中所有的状态数据,对于这个拼图游戏来说,
我们设计以下成员变量:
Updatable updatable;
int state;
Image[] images = new Image[9];
int[][] current = new int[3][3];
int hiddenX, hiddenY;
int steps; // 移动的步数
DrawCanvas需要实现Updatable接口,因此,Document保存了一个View的引用,在恰当的时候,Document可以调用updatable.update()方法通知View需要重绘。这样,DrawCanva和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}
4
本科课程设计论文
}
然后设定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);
5
本科课程设计论文
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,在这个游戏中,我们应该使用
6
本科课程设计论文
Canvas,定义:
public class DrawCanva extends Canvas implements CommandListener,
Updatable { ... }
在构造方法中,初始化Document:
public DrawCanva(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()方法中,DrawCanva从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,
7
本科课程设计论文
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...
}
}
当用户按下某个键时,DrawCanva的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...
}
8
本科课程设计论文
}
然后,Document可能更新自身内部状态,如果需要重绘画面,Document将调用update()回调方法来通知View更新画面。因此,DrawCanva必须实现Updatable接口的update()回调方法:
public void update() {
repaint();
}
至此,View已基本实现,我们再添加一个用作启动的MIDlet,即可实现整个游戏的基本框架。经过前面的工作,最后在集成编辑环境中,编写的程序代码
见附录程序所示。
3
本次简单的编程游戏成功完成,一个简单的拼图游戏,虽然很简单但是却可以学
到很多东西,不仅是简单图片显示,还学会可整体的控制,还有游戏中,MIDlet起着Controller的作用,每个Screen或者Canvas就是一个View,而Model可以用一个单独的类来表示,用于存储程序运行中的数据。MVC有MVC1和MVC2
两种模式,其不同之处在于Model能否主动通知View。在窗口程序
中,Model可以主动通知View是否需要Update,因此应使用MVC1;
在窗口程序中,View通常仅有一个,但Model可能有很多;而在Web
程序中,Model通常被放在Session中,每个JSP页面都是一个View,
因此View有很多。
最后在调试的过程中,也遇到一些困难多亏了老师和同学们的帮助才能够顺
利的编程工作才得以如期完成,我发现理论和实践之间还是存在一定的差距,要
把理论知识灵活运用于实践才是最好的。在此次设计中我受益匪浅,我以后会好
好学习的,让今天所学的深深印在脑海中,会更加的使自己更完善的理解知识,
谢谢老师!!!
9
本科课程设计论文
[1] 王珊、萨师煊面向对象系统概论. 北京.高等教育出版社.2006. [2] 张宏等. 面向对象程序设J2ME. 北京.科学出版社.1998. [3] Inmon W H .面向对象库.北京.机械工业出版社,2000. [4] Kimball R等.面向对象工具箱.北京. 电子工业出版社,2003. [5] Imhoff C等.JAVA设计. 北京. 机械工业出版社,2004. [6] Mattison R.Web Java工程与知识管理.北京.清华大学出版社,2003.
package com.sunfruit.micro.framework;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.Display;
package com.sunfruit.micro.framework;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
public class DrawPanel extends MIDlet {
private Display display;
private DrawCanva drawCanva;
public DrawPanel() {
display=Display.getDisplay(this);
drawCanva=new DrawCanva(false,this);
(new Thread(drawCanva)).start();
10
本科课程设计论文
display.setCurrent(drawCanva);
}
public Display getDisplay()
{
return display;
}
protected void destroyApp(boolean _boolean) throws
MIDletStateChangeException {
}
protected void pauseApp() {
}
/**
* startApp
*
* @throws MIDletStateChangeException
* @todo Implement this javax.microedition.midlet.MIDlet method
*/
protected void startApp() throws MIDletStateChangeException {
}
}
public class BackDropTiledLayer extends TiledLayer {
private Vector selectVector = new Vector();
private int[] mapright={1,2,3,4,5,6,7,8,9,10,11,12};
private MIDlet myMIDlet;
public BackDropTiledLayer(int col, int row, Image image, int tileWidth,
int tileHeight,MIDlet myMIDlet) {
super(col, row, image, tileWidth, tileHeight);
this.myMIDlet=myMIDlet;
11
本科课程设计论文
init();
}
private void init() {
MicroRandom microRandom=new MicroRandom(12);
int[] map=microRandom.createInts();
//int[] map={2,1,3,4,5,6,7,8,9,10,11,12};
for (int i = 0; i < map.length; i++) {
int col = i % 4;
int row = i / 4;
//System.out.println(col+" "+row);
this.setCell(col, row, map[i]);
}
}
/**
* 需要判断是否可以清除选择的model
* @param selectcol int
* @param selectrow int
* @param tileIndex int
*/
public void addSelectModel(int selectcol,int selectrow,int tileIndex,Graphics
g)
{
selectVector.addElement(new
SelectTileModel(selectcol,selectrow,tileIndex));
//判断如果有两个则进行交换
if(selectVector.size()>=2)
{
SelectTileModel
12
本科课程设计论文
selectTileModel1=(SelectTileModel)selectVector.elementAt(0);
SelectTileModel
selectTileModel2=(SelectTileModel)selectVector.elementAt(1);
//第一个参数系
int col1=selectTileModel1.getSelectcol();
int row1=selectTileModel1.getSelectrow();
int tile1=selectTileModel1.getTileIndex();
//第二个参数系
int col2=selectTileModel2.getSelectcol();
int row2=selectTileModel2.getSelectrow();
int tile2=selectTileModel2.getTileIndex();
//交换
this.setCell(col1,row1,tile2);
this.setCell(col2,row2,tile1);
selectVector.removeAllElements();
g.setColor(255,255,255);
g.fillRect(col1*32,row1*32,32,32);
g.drawRect(col1*32,row1*32,32,32);
g.fillRect(col2*32,row2*32,32,32);
g.drawRect(col2*32,row2*32,32,32);
checkRight(g);
}
}
public SelectTileModel getSelectModel(int index)
{
if(index>=selectVector.size()) return null;
13
本科课程设计论文
return (SelectTileModel)selectVector.elementAt(index);
}
public void removeSelectModel(int index)
{
selectVector.removeElementAt(index);
}
public void paintSelectModel()
{
for(int i=0;i= 2)
selectrow = 0;
else
selectrow++;
}
if (keytype == LEFT_PRESSED) {
if (selectcol <= 0)
selectcol = 3;
else
selectcol--;
}
if (keytype == RIGHT_PRESSED) {
if (selectcol >= 3)
selectcol = 0;
19
本科课程设计论文
else
selectcol++;
}
if (keytype == FIRE_PRESSED) {
int tile = tiledLayer.getCell(selectcol, selectrow);
tiledLayer.addSelectModel(selectcol, selectrow, tile, g);
}
//绘制选中的方块
g.setColor(0, 0, 0);
g.drawRect(x + selectcol * 32, y + selectrow * 32, 32, 32);
}
public void commandAction(Command command, Displayable displayable) {
if (command == exitCommand) {
myMIDlet.notifyDestroyed();
}
}
}
public class Document {
Updatable updatable;
int state;
Image[] images = new Image[9];
int[][] current = new int[3][3];
int hiddenX, hiddenY;
int steps; // 移动的步数
public Document (int col, int row, Image image, int tileWidth,
int tileHeight,MIDlet myMIDlet) {
super(col, row, image, tileWidth, tileHeight);
this.myMIDlet=myMIDlet;
20
本科课程设计论文
init();
}
private void init() {
MicroRandom microRandom=new MicroRandom(12);
int[] map=microRandom.createInts();
//int[] map={2,1,3,4,5,6,7,8,9,10,11,12};
for (int i = 0; i < map.length; i++) {
int col = i % 4;
int row = i / 4;
//System.out.println(col+" "+row);
this.setCell(col, row, map[i]);
}
}
public class RightPanel extends Form implements CommandListener {
StringItem stringItem=new StringItem("","已经过关\n请返回后重新开始",1);
private Command exitCommand;
private MIDlet myMIDlet;
public RightPanel(MIDlet myMIDlet)
{ super("");
stringItem.setFont(Font.getFont
(Font.FACE_MONOSPACE,Font.STYLE_PLAIN,Font.SIZE_LARGE));
this.myMIDlet=myMIDlet;
this.append(stringItem);
exitCommand = new Command("Exit", Command.EXIT, 1);
this.setCommandListener(this);
this.addCommand(exitCommand);
}
public void commandAction(Command command, Displayable displayable) {
21
本科课程设计论文
if (command == exitCommand) {
myMIDlet.notifyDestroyed();
}
}
}
public class SelectTileModel {
int selectcol = 0;
int selectrow = 0;
int tileIndex = 0;
public SelectTileModel() {
}
public SelectTileModel(int selectcoltemp, int selectrowtemp,
int tileIndextemp) {
selectcol = selectcoltemp;
selectrow = selectrowtemp;
tileIndex = tileIndextemp;
}
public void setSelectcol(int selectcoltemp) {
selectcol = selectcoltemp;
}
public void setSelectrow(int selectrowtemp) {
selectrow = selectrowtemp;
}
public int getSelectcol() {
return selectcol;
}
public int getSelectrow() {
return selectrow;
22
本科课程设计论文
}
public void setTileIndex(int tileIndextemp) {
tileIndex = tileIndextemp;
}
public int getTileIndex() {
return tileIndex;
}
}
public class DynArrayInt {
/**
* 原始数组
*/
private int[] data_All;
/**
* 计数器(数组长度)
*/
private int size_count;
/**
* 构造器,初始长度默认为10
*/
public DynArrayInt() {
this(10);
}
/**
* 构造器,设置数组的初始长度
*
* @param iniSize
23
本科课程设计论文
* int 数组的初始长度
*/
public DynArrayInt(int iniSize) {
data_All = new int[iniSize];
}
/**
* 添加数据,调用checkAdd(int i)
*
* @param i
* int 一个整形数字
*/
public void addInt(int i) {
//判断是否增长
this.checkAdd(size_count + 1);
//赋值
data_All[size_count++] = i;
//添加时数组长度加一
}
/**
* 添加数字,判断是否增长
*
* @param i
* int 一个整形数字
*/
private void checkAdd(int i) {
//获得原来的大小
int star = data_All.length;
//判断是否增长
24
本科课程设计论文
if (i > star) {
int starData[] = data_All;
//设定增长大小
int endall = star * 2;
data_All = new int[endall];
System.arraycopy(starData, 0, data_All, 0, size_count);
}
}
/**
* 获取数据
*
* @param i
* int 索引号
* @return int
*/
public int getInt(int i) {
if (i < 0 || i >= size_count) {
throw new IndexOutOfBoundsException("超出最大或最小索引
值,无法取得
数据");
} else {
return data_All[i];
}
}
/**
* 获取数据转换成字符串模式
25
本科课程设计论文
*
* @param i
* int 索引号
* @return String
*/
public String getIntToString(int i) {
if (i < 0 || i >= size_count) {
throw new IndexOutOfBoundsException("超出最大或最小索引
数据");
} else {
return String.valueOf(data_All[i]);
}
}
public void remove(int j) {
for (int i = 0; i < size_count; i++) {
if (data_All[i] == j) {
System.arraycopy(data_All, i+1, data_All, i,
size_count-i-
1); // 复制数据
--size_count;
return;
}
}
}
public void removeIndex(int j) {
26
本科课程设计论文
if (j < 0 || j >= size_count) {
throw new IndexOutOfBoundsException("超出最大或最小索引
值,无法删除
数据");
} else {
System.arraycopy(data_All, j + 1, data_All, j, size_count
-j- 1); //
复制数据
--size_count;
return;
}
}
/**
* 获取大小
*
* @return int 获得数组长度
*/
public int getSize() {
return size_count;
}
/**
* 获取数组对象
*
* @return int[] 获得数组对象
*/
public int[] getAllInt() {
27
本科课程设计论文
int[] starData = new int[size_count];
System.arraycopy(data_All, 0, starData, 0, size_count);
return starData;
}
public String[] getAllIntToString() {
int[] tempint = getAllInt();
String[] starData = new String[tempint.length];
for (int i = 0; i < starData.length; i++) {
starData[i] = String.valueOf(tempint[i]);
}
return starData;
}
/**
* 删除全部内容
*/
public void removeAll() {
data_All = new int[10];
size_count = 0;
}
}
public class MicroRandom {
Random random=new Random(System.currentTimeMillis());
DynArrayInt dynArrayInt=new DynArrayInt();
int size=0;
public MicroRandom(int size) {
this.size=size;
}
28
本科课程设计论文
public int nextMicroInt()
{
if(dynArrayInt.getSize()>=size)
{
throw new IndexOutOfBoundsException("已经到达最大值:"+size);
}
int j=0;
boolean bool=true;
while(bool)
{ j=random.nextInt(size+1);
bool=false;
for (int i = 0; i < dynArrayInt.getSize(); i++) {
if (j == dynArrayInt.getInt(i)) {
bool=true;
break;
}
}
if(j==0)
{ bool=true;
}
}
dynArrayInt.addInt(j);
return j;
}
public int[] createInts()
{
for(int i=0;i