JAVA游戏编程之二----j2me MIDlet 手机游戏入门开发--扫雷(3)-带线程--仿windows扫雷
作者:雷神
QQ:38929568
QQ群:28048051JAVA游戏编程(满) 28047782(将满)
扫雷(3)是在扫雷(1.2)的基础上增加 完善了部分代码基本逻辑不变!
增加绘图,线程,时间等,使得游戏更好玩了,代码400行,比较适合初学者,可读性强,有详尽的代码注释。
数字键1标红旗,不确定,取消标记。方向键,数字键2468,控制光标上下左右移动!
工程文件已经打包上传到csdn,地址如下
download.youkuaiyun.com/user/kome2000/
程序运行如图
代码如下
Minesweeper.java这个类每有变化
////////////////////////////////////////////////////////////////////////////////
//
// cGame.java
//
// Project: Minesweeper
// Author(s): Gao Lei
// Create: 2007-10-11
////////////////////////////////////////////////////////////////////////////////

import java.util.Random; //得到 随机函数
import javax.microedition.lcdui.*; //写界面所需要的包

////////////////////////////////////////////////////////////////////////////////
//实现 Runnable 这个接口 必须创建Thread的对象,重写run()这个方法
class cGame extends Canvas implements Runnable

...{
//游戏状态
private static final int STATEPLAY = 0; //游戏中
private static final int STATELOST = 1; //游戏失败
private static final int STATEWIN = 2; //游戏胜利
//格子状态
private static final int MINE_OFF_SHOW = 0; //不显示格子中雷的数
private static final int MINE_ON_SHOW = 1; //显示格子中雷的数
private static final int MINE_SHOW = 9; //雷
private static final int MINE_BOOM = 10; //踩到的雷
private static final int MINE_GUESS_ERR = 11; //显示猜错了雷
private static final int MINE_FLAG = 12; //设置红旗
private static final int MINE_ASK = 13; //设置问号
//定义键值
private static final int KEY_UP = 1; //上
private static final int KEY_DOWN = 2; //下
private static final int KEY_LEFT = 3; //左
private static final int KEY_RIGHT = 4; //右
private static final int KEY_FIRE = 5; //中间确认键

public static Random rand; //随机数对象

private int map_x = 9; //雷区的 行数 // 15
private int map_y = 9; //雷区的 列数 // 12
private int map_w = 16; //一个雷区的格子的宽度
private int map_h = 16; //一个雷区的格子的高度
private int key_x = map_x / 2; //游戏初始时 光标所在雷区的格子位置
private int key_y = map_y / 2; //游戏初始时 光标所在雷区的格子位置
private int mine_num = 10; //雷区的雷数 不应该大于雷区的格子总数
private int flagNum = mine_num; //剩余红旗数
private int rightNum = 0; //猜对的雷数
private int gameState = STATEPLAY; //游戏状态
private int s_width = 0; //屏幕尺寸 宽
private int s_height = 0; //屏幕尺寸 高
private int addMine = 0; //重新开始后雷数增加的个数
private int randPicNum = 0; //本关胜利的图片
private int palyTime = 0; //游戏时间
private long palyStartTime= 0; //游戏开始时间
private long updates = 0; //更新次数
private int[][] map; //雷区的地图数组 >=10 为雷, <10 为周围的雷数, 0位附近没有雷
private int[][] map_show; //雷区的地图数组是否显示该位置的雷数//1显示//0不显示//9问号//10红旗
private boolean isShowInfo = false; //是否显示游戏信息
private String strFlagNum = "红旗数";
private String strPalyTime = "时间";

private String[] gameInfo = ...{"游戏中","失败 按0开始","胜利 按0开始"};
private Image[] imgMine = new Image[14];//格子周围的雷数0-8,9雷,10踩到的雷,11显示猜错了雷,12设置红旗,13设置问号
private Image[] imgTime = new Image[10];//数字图片
private Image[] imgGameState= new Image[3]; //游戏状态图片//0游戏中//1失败//2胜利
private Image[] imgGameWin = new Image[7]; //游戏胜利图片
private Image imgBg; //背景图片
//定义一种大字体
private Font font = Font.getFont( Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_LARGE );
private Thread thread; //创建线程对象

cGame()

...{
setFullScreenMode(true); //设置游戏为全屏幕模式,该函数只能在支持midp2.0的手机上使用
s_width = getWidth(); //得到屏幕尺寸 宽
s_height= getHeight(); //得到屏幕尺寸 高
try

...{
//创建单张图片
imgBg = Image.createImage("/pics/bg.png");
//创建图片数组
for( int i=0; i<imgGameState.length; i++ )

...{
imgGameState[i]=Image.createImage("/pics/game_over_"+i+".png");
}
for( int i=0; i<imgGameWin.length; i++ )

...{
imgGameWin[i]=Image.createImage("/pics/game_win_"+i+".png");
}
//另外一种创建图片数组的方法
Image temp = Image.createImage("/pics/time.png"); //将大图创建到临时Image对象中
Graphics gn; //创建临时绘图设备
for( int i=0; i<imgTime.length; i++ ) //大图将分割成多个小图

...{
imgTime[i] = Image.createImage(13, 23); //创建小图的大小
gn = imgTime[i].getGraphics(); //创建小图的大小的临时绘图设备
//在该设备上绘制大图temp,但 绘图设备比较小,只有小图那么大,大图多余部分不会被绘制出来
gn.drawImage(temp, -i*13, 0, gn.LEFT|gn.TOP); //绘制大图时候的起点位置
}
temp = Image.createImage("/pics/tile.png"); //将大图创建到临时Image对象中
for( int i=0; i<imgMine.length; i++ )

...{
imgMine[i] = Image.createImage(16, 16);
gn = imgMine[i].getGraphics();
gn.drawImage(temp, -i*16, 0, gn.LEFT|gn.TOP);
}
gn = null;
temp = null;
System.gc(); //通知垃圾回收机制,在需要时候会进行垃圾回收

}catch(Exception e)...{ e.printStackTrace(); }

rePlay( 0 ); //游戏初始化//重新游戏
randPicNum = rand.nextInt( imgGameWin.length );//本关胜利画面
thread = new Thread(this); //参数为 实现Runnable的类对象
thread.start(); //启动线程
}

public void run()

...{
while( true )

...{
try

...{
updates++; //计算更新次数
repaint(); //刷屏
thread.sleep(100); //线程休眠 100毫秒
}catch(Exception e)

...{
e.printStackTrace();
}
}
}

/** *//**
* 系统自动调用该绘图函数,并传入绘图设备g,通过该设备,我们可以绘制如直线,矩形快,字符串,图片等,
*/
public void paint(Graphics g)

...{
g.setClip(0, 0, s_width, s_height); //设置参数描述的区域为操作区
g.setColor(0x000000); //设置颜色为 黑色, 三个16进制数表示,RGB,如0x00ff00 为绿色
g.fillRect(0, 0, s_width, s_height); //绘制一个实心矩形区域

g.translate(3, -40); //设置屏幕偏移
g.drawImage(imgBg, 0, 0, g.LEFT|g.TOP); //绘制图片到屏幕上,苗点 水平左对齐,垂直顶对齐
g.drawImage(imgGameState[gameState], 74, 55, 0);
paintNum( g, 20, 56, flagNum ); //显示剩余旗数
if( gameState == STATEPLAY )
palyTime = (int)((System.currentTimeMillis()-palyStartTime)/1000);
paintNum( g,113, 56, palyTime ); //显示游戏时间
g.translate(15, 95); //设置屏幕偏移
// 绘制雷区
for( int i=0; i<map_x; i++ )

...{
for( int j=0; j<map_y; j++ )

...{
if( map_show[i][j] == MINE_ON_SHOW ) //遍历地图数组 看该位置的雷数 是否应该显示

...{
if(map[i][j]<9) //显示周围的雷数

...{
g.drawImage(imgMine[map[i][j]], j*map_h, i*map_w, 0);
}
else //踩到雷了

...{
g.drawImage(imgMine[MINE_BOOM], j*map_h, i*map_w, 0);
}
}
else if( map_show[i][j] == MINE_FLAG ) //显示红旗

...{
g.drawImage(imgMine[MINE_FLAG], j*map_h, i*map_w, 0);
}
else if( map_show[i][j] == MINE_ASK ) //显示问号

...{
g.drawImage(imgMine[MINE_ASK], j*map_h, i*map_w, 0);
}
else if( gameState != STATEPLAY ) //如果游戏结束

...{
if( map[i][j] >= MINE_SHOW ) //显示雷

...{
if( map_show[i][j] == MINE_ON_SHOW )//踩到雷了
g.drawImage(imgMine[MINE_BOOM], j*map_h, i*map_w, 0);
else
g.drawImage(imgMine[MINE_SHOW], j*map_h, i*map_w, 0);
}
else if( map_show[i][j] == MINE_GUESS_ERR )//显示猜错了

...{
g.drawImage(imgMine[MINE_GUESS_ERR], j*map_h, i*map_w, 0);
}
}
}
}
g.setColor(0xFF0000); //设置颜色 红
g.drawRect(key_x*map_w+1, key_y*map_h+1, map_w-2, map_h-2); //绘制一个空心矩形框//为光标
g.drawRect(key_x*map_w+2, key_y*map_h+2, map_w-4, map_h-4); //绘制一个空心矩形框//为光标
g.translate(-15, -95); //设置屏幕偏移//恢复屏幕便宜
g.translate(-3, 40); //设置屏幕偏移//恢复屏幕便宜
if( isShowInfo || gameState == STATEWIN ) //如果游戏胜利

...{
if( updates %12 < 6 ) //每6桢
g.drawImage ( imgGameWin[randPicNum], 0, 45, g.LEFT|g.TOP ); //绘制游戏结束图片
g.setFont ( font );
g.drawString( strFlagNum +":"+flagNum, 50, 100-20, g.LEFT|g.TOP ); //显示剩余旗数
g.drawString( strPalyTime+":"+palyTime, 50, 100, g.LEFT|g.TOP ); //显示游戏时间
g.drawString( gameInfo[ gameState ], 50, 100+20, g.LEFT|g.TOP ); //显示游戏状态
}
}


/** *//**
* 系统自动调用该函数,当有键盘事件发生为按下某键,参数key为按下键的键值
*/
public void keyPressed(int key)

...{
key = Math.abs(key);
System.out.println("key="+key);
//上下左右 为移动光标事件,只需要调整光标位置即可,但需要做边界判断
switch( key )

...{
case KEY_NUM2:
case KEY_UP:
if( gameState != STATEPLAY ) //如果游戏没结束//结束了就不做确认键操作了
break;
else

...{
key_y--;
if( key_y<0 )
key_y = map_x-1;
}
break;

case KEY_NUM8:
case KEY_DOWN:
if( gameState != STATEPLAY ) //如果游戏没结束//结束了就不做确认键操作了
break;
else

...{
key_y++;
key_y %=map_x;
}
break;

case KEY_NUM4:
case KEY_LEFT:
if( gameState != STATEPLAY ) //如果游戏没结束//结束了就不做确认键操作了
break;
else

...{
key_x--;
if( key_x<0 )
key_x = map_y-1;
}
break;

case KEY_NUM6:
case KEY_RIGHT:
if( gameState != STATEPLAY ) //如果游戏没结束//结束了就不做确认键操作了
break;
else

...{
key_x++;
key_x %=map_y;
}
break;

case KEY_FIRE:
case KEY_NUM5:
if( gameState == STATEPLAY ) //如果游戏没结束//结束了就不做确认键操作了

...{
if( map_show[key_y][key_x] == MINE_FLAG )
break;
showMap( key_y, key_x ); //显示该位置的雷数
if( map[key_y][key_x] >=10 )//如果雷数>=10 该位置是雷,

...{
isWinGame();
addMine = 0;
isShowInfo = true;
gameState = STATELOST; //游戏失败
}
}
break;

case KEY_NUM1: //设置红旗//问号//取消
if( gameState != STATEPLAY ) //如果游戏没结束//结束了就不做确认键操作了
break;
switch( map_show[key_y][key_x] )

...{
case MINE_OFF_SHOW:
map_show[key_y][key_x] = MINE_FLAG;
flagNum--;
if( flagNum == 0 )

...{
if( isWinGame() )

...{
addMine = 5;
isShowInfo = true;
gameState = STATEWIN;
randPicNum = rand.nextInt( imgGameWin.length );//本关胜利画面
}
else

...{
addMine = 0;
isShowInfo = true;
gameState = STATELOST;
}
}
break;
case MINE_FLAG:
flagNum++;
map_show[key_y][key_x] = MINE_ASK;
break;
case MINE_ASK:
map_show[key_y][key_x] = MINE_OFF_SHOW;
break;
}
break;
case KEY_NUM3: //是否显示游戏信息
isShowInfo = !isShowInfo;
break;
case KEY_NUM0: //当按下 数字键 0
rePlay( addMine ); //重新开始游戏
break;
}

/** *//**
* 重新执行 paint() 但该函数是立刻返回,也就是说他不会等待paint()执行完毕就返回了,
* 如果 需要 paint()执行完毕才返回,可以使用serviceRepaints(),也可以两个都是用,但
* repaint()应该在serviceRepaints()之前.
*/
this.repaint(); //本例为键盘事件驱动,刷新函数也就是每次按下键盘才作
}
boolean isWinGame()

...{
boolean isWin = true;
for( int i=0; i<map_x; i++ )

...{
for( int j=0; j<map_y; j++ )

...{
if( map_show[i][j] == MINE_FLAG ) //显示红旗

...{
if( map[i][j] < 10 ) //地雷猜错了

...{
map_show[i][j] = MINE_GUESS_ERR;
isWin = false;
}
else

...{
rightNum ++;
}
}
}
}
return isWin; //群不红旗都插对了 才能通关
}

//将三位数字以图片相识绘制到屏幕制定坐标
void paintNum( Graphics g, int x, int y, int num )

...{
if( num<=9 ) //个位数

...{
g.drawImage(imgTime[0] , x , y, 0);
g.drawImage(imgTime[0] , x+13, y, 0);
g.drawImage(imgTime[num], x+26, y, 0);
}
else if( num>=10 && num<=99 ) //两位数

...{
g.drawImage(imgTime[0] , x , y, 0);
g.drawImage(imgTime[num/10] , x+13, y, 0);
g.drawImage(imgTime[num%10] , x+26, y, 0);
}
else //最多只保留三位数

...{
num %= 1000;
g.drawImage(imgTime[num/100], x , y, 0);
num %= 100;
g.drawImage(imgTime[num/10] , x+13, y, 0);
g.drawImage(imgTime[num%10] , x+26, y, 0);
}
}
//该函数是一个递归函数,把当前位置设置成显示,并判断当前位置雷数是否为0个
//如果是0个雷,那么它周围的8个格子都要再作一次showMap
void showMap(int x, int y)

...{
if( map_show[x][y] == MINE_FLAG )
return;
if( map_show[x][y] == MINE_ON_SHOW )
return;
else
map_show[x][y] = MINE_ON_SHOW;

if( map[x][y] == 0 )

...{
if( x-1 >= 0 )

...{
showMap( x-1, y );
if( y-1 >= 0) showMap( x-1, y-1 );
if( y+1 < map_y) showMap( x-1, y+1 );
}
if( y-1 >= 0) showMap( x , y-1 );
if( y+1 < map_y) showMap( x , y+1 );
if( x+1 < map_x )

...{
showMap( x+1, y );
if( y-1 >= 0) showMap( x+1, y-1 );
if( y+1 < map_y) showMap( x+1, y+1 );
}
}
}
//重新 开始 游戏
public void rePlay( int add )

...{
key_x = map_x / 2; //游戏初始时 光标所在雷区的格子位置
key_y = map_y / 2; //游戏初始时 光标所在雷区的格子位置
mine_num += add; //雷区的雷数 不应该大于雷区的格子总数
if(mine_num >= 64)
mine_num = 64; //纠正雷数不能过多
flagNum = mine_num; //剩余红旗数
rightNum = 0; //猜对的雷数
gameState = STATEPLAY;
map = new int[map_x][map_y];
map_show = new int[map_x][map_y];
rand = new Random( System.currentTimeMillis() ); //用事件作随机数种子的随机数
isShowInfo = false;
palyTime = 0; //游戏所用时间
palyStartTime = System.currentTimeMillis(); //游戏开始时间
//布雷
for(int i=0; i<mine_num; i++) //随机mine_num个雷的位置

...{
int x = rand.nextInt( map_x ); //得到 随机数 雷格子的x方向位置
int y = rand.nextInt( map_y ); //得到 随机数 雷格子的y方向位置
if( map[x][y] >= 10) //如果该位置已经是雷了,就要重新布雷

...{
i--;
continue;
}
map[x][y] = 10; //否则 将该位置 设定为雷
//并在该雷的周围 的雷数都作+1操作
//以下判断为 边角判断,防止访问数组越界
if( x-1 >= 0 )

...{
map[x-1][y ] += 1;
if( y-1 >= 0) map[x-1][y-1] += 1;
if( y+1 < map_y) map[x-1][y+1] += 1;
}
if( y-1 >= 0) map[x ][y-1] += 1;
if( y+1 < map_y) map[x ][y+1] += 1;
if( x+1 < map_x )

...{
map[x+1][y ] += 1;
if( y-1 >= 0) map[x+1][y-1] += 1;
if( y+1 < map_y) map[x+1][y+1] += 1;
}
}
}
}

屏幕大小 170*210 大于这个屏幕的手机都可以正常显示
如果机器建值不对,请修改后编译。