不想再写Hello123了,今天开始做一个数独小游戏,因为这个游戏比较简单应该容易上手,就作为我学习之后的第一个程序比较合适。
初
步的设计是只有一个界面(如下图),然后用绿色字体表示题目中有的固定的数字,黄色字体显示玩家输入的数字,而红色字体则是程序判断输入错误后的显示。另
外模式分为三种:普通写入、标记(玩家用蓝色小方块标记当前单元格可以输入的数字)、排除模式(玩家指定数字,游戏自动判断一下这个数字肯定不能输入的单
元格,将它反相显示出来)。
准备工作就是做一张背景图(棋盘)和三套数字小图片(红、绿、黄)即可。
首先建立工程sudo,程序主体类 MainActivity以后,再修改一下那个main.xml文件,去掉TextView标签即可。因为我们会自己定义一个View,所以不再需要它了。程序不大,所以不打算做过多的类,下面把几个类的分工描述一下:
1、MainActivity,主体类,负责处理键盘事件和维护一个题库。
2、MainView,显示类,负责维护并显示当前棋局,它的核心在于它的onDraw函数。
3、GridCell和Question两个实体类,分别描述了棋盘单元格的信息和题目信息。
4、Helper类,助手类,封装一些函数,如读写记录、自动解题函数等。
在MainActivity中的onCreate中,加几句话就可以让游戏全屏显示了。如下:
setTheme(android.R.style.Theme_Dark);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.NO_STATUS_BAR_FLAG,WindowManager.LayoutParams.NO_STATUS_BAR_FLAG);
主要来看看MainView类的代码吧,它的onDraw负责显示当前棋局,涉及到的API主要是android.graphics中的Canvas和Paint。
一是显示图片的方法,因为图片来源于资源,所以显示它的代码如:
Bitmap bmp = BitmapFactory.decodeResource(this.getResources(),R.drawable.grid);
canvas.drawBitmap(bmp, 0, 0, null);
这是显示背景,如果是数字呢,如何将数字1与R.drawable.a1资源关联呢?
private int[] thumbNormal=new int[]...{0,
R.drawable.a1,R.drawable.a2,R.drawable.a3,R.drawable.a4,R.drawable.a5,
R.drawable.a6,R.drawable.a7,R.drawable.a8,R.drawable.a9
};
然后就简单地加载即可了。
Bitmap b = BitmapFactory.decodeResource(this.getResources(),this.thumbNormal[this.grid[i].value]);
canvas.drawBitmap(b, xx, yy, null);
二是显示文本的方法,刚才显示图像的drawBitmap中最后一个参数直接给了null,因为我们实在没有什么效果需要给图像的,但是文本则不同,我们用Paint来控制文本的样式。
Paint paintText=new Paint();
paintText.setFlags(Paint.ANTI_ALIAS_FLAG);
paintText.setColor(Color.WHITE);
... ...
canvas.drawText(Long.toString(this.ti.code), xx, yy, paintText);
三是画一下框的方法,同样是用Paint来做的。
Paint paintRect = new Paint();
paintRect.setColor(Color.RED);
paintRect.setStrokeWidth(2);
paintRect.setStyle(Style.STROKE);
Rect r=new Rect();
r.left=this.curCol*CELL_WIDTH+GRID_X;
r.top=this.curRow*CELL_WIDTH+GRID_Y;
r.bottom=r.top+CELL_WIDTH;
r.right=r.left+CELL_WIDTH;
canvas.drawRect(r, paintRect);
如果不setStyle为Style.STROKE,则缺省为填充模式。
四是反相显示的方法,更简单了,就是一句话了:
Paint paintHint=new Paint();
paintHint.setXfermode(new PixelXorXfermode(Color.WHITE));
继续,今天讨论的是记录文件的读写。因为原来在Brew平台上实现的数独将题库是一个二进制文件,所以在就直接拿那个文件来用了。
计划实现两个函数,先是LoadTiList(),加载题库,先装题库文件放在资源里,然后从资源里加载它作为一个DataInputStream即可。代码也没几行,如下:
public static boolean LoadTiList(MainActivity me) ...{
DataInputStream in = null;
try
...{
in = new DataInputStream(me.getResources().openRawResource(R.raw.ti));
byte[] bufC4=new byte[4];
byte[] bufC81=new byte[81];
//总个数
in.read(bufC4,0,4);
int len=((int)bufC4[3]<<24)+((int)bufC4[2]<<16)+((int)bufC4[1]<<8)+(int)bufC4[0];
for(int i=0;i ...{
Question ti = new Question();
//代码
in.read(bufC4,0,4);
ti.code=(long) (((long)bufC4[3]<<24)+((long)bufC4[2]<<16)+((long)bufC4[1]<<8)+(long)bufC4[0]);
//时间
in.read(bufC4,0,4);
SharedPreferences sp = me.getPreferences(Context.MODE_WORLD_READABLE);
ti.time=sp.getLong(Long.toString(ti.code), 0);
//数据
in.read(bufC81,0,81);
for(int j=0;j<81;j++)ti.data[j]=bufC81[j];
me.tiList.add(ti);
}
in.close();
}
catch(Exception ex)...{
return false;
}
finally...{
try...{in.close();}catch(Exception e)...{}
}
return true;
}
这里最麻烦的是因为java里没有unsigned类型,所以会溢出,比较郁闷,这个没有解决,只能是生成题库文件里注意一下了,不能与平台共用那个题库文件了。
二是保存记录,在平台我直接用一个文件搞定,读写它,但是android不能这样了,因为ti.dat是从资源中加载的,所以只能是静态的,不可修改,那记录只能放入preferences中了,代码如下:
public static boolean SaveTiList(MainActivity me)
...{
try
...{
SharedPreferences sp=me.getPreferences(Context.MODE_WORLD_WRITEABLE);
Question ti = me.gridView.ti;
sp.edit().putLong(Long.toString(ti.code),ti.time);
sp.edit().commit();
}
catch(Exception ex)...{
return false;
}
return true;
}
SharePreferences可以按key-value来保存记录,所以key用题目的code,则value就是解它所用的时间了。
Android不能直接访问app目录下的文件,所以不能够象那样将数据文件放在程序目录下供它读写,而在Activity中提供的两个函数 openFileOutput和openFileInput,虽可用来读写文件,但是总是不太方便。
另外,用SQLite其实也不方便,因为手机中弄这些东西,事实上用处不大。
继续,最后再讨论一下定时器的实现。
本来很简单的一件事,直接用java.util.timer应该就够用了,但是发现在它的task中无法去invalidate我们的MainView,很郁闷。这一点的处理说明 还是相对线程安全的。
折腾良久,明白了非得再做一个Handler,才能在线程中操作界面元素。所以,代码比复杂了一点。
先还是用Timer和TimerTask来做,如下:
public TimerHandler timerHandler;
public Timer timer;
public MyTimerTask task;
... ...
timer=new Timer(true);
task=new MyTimerTask(this);
... ...
那个MyTimerTask是MainActivity的一个内嵌类,实现如下:
private class MyTimerTask extends TimerTask
...{
private MainActivity me;
private int a=0;
public MyTimerTask(MainActivity p)...{
me=p;
}
public void run()...{
me.gridView.time++;
Log.d("MyTask",Integer.toString(me.gridView.time));
timerHandler.sendEmptyMessage(0);
}
}
这里做两件事,一是将gridView中的time加一,二是发送一个消息通知timerHandler。原来我在这里直接让MainView去刷新屏幕,发现不行,所以就改成这样处理了。
然后就是如何实现TimerHandler类的,也不复杂,就是让它去刷新一下屏幕即可。
public class TimerHandler extends Handler ...{
private MainView me;
public TimerHandler(MainView m)...{
me=m;
}
@Override
public void handleMessage(Message msg) ...{
Log.d("Ti",msg.toString());
me.invalidate();
}
}
如此一来,就顺了。
在MainView中的onDraw,根据当前的time值显示成00:00:00的格式即可。
另外,发现Android的模拟器运算速度不如BREW的模拟器,相当的慢。
再补充一点吧,如果需要给游戏加上背景音乐,其实也是非常容易的事情。因为提供了一个 MediaPlayer类可以方便的播放音乐文件。
android.media.MediaPlayer类没有构造函数,一般是用它的静态方法create生成实例,简单地告诉它音乐文件的资源ID即可(支持mp3/wav/midi等)。
首先,我们得建立一个Service,就叫MediaService吧,它的代码如下:
Android/UploadFiles_8448/200804/20080419152842539.gif" align=top>Android/UploadFiles_8448/200804/20080419152843671.gif" align=top>public class MediaService extends Service implements MediaPlayer.OnErrorListener,MediaPlayer.OnCompletionListener,MediaPlayer.OnPreparedListener ...{
... ...
private MediaPlayer player;
@Override
protected void onDestroy() ...{
// TODO Auto-generated method stub
super.onDestroy();
if(player!=null)...{
player.stop();
player.release();
}
}
@Override
protected void onStart(int startId, Bundle arguments) ...{
// TODO Auto-generated method stub
Log.d("Media","onStart");
player=MediaPlayer.create(this.getApplication(),R.raw.tonhua);
if(player!=null)...{
player.setAudioStreamType(AudioSystem.STREAM_MUSIC);
player.setOnCompletionListener(this);
player.setOnPreparedListener(this);
player.setOnErrorListener(this);
player.prepareAsync();
}
}
@Override
public void onCompletion(MediaPlayer arg0) ...{
// TODO Auto-generated method stub
Log.d("Media","finished.");
}
@Override
public void onPrepared(MediaPlayer arg0) ...{
// TODO Auto-generated method stub
Log.d("Media","prepared.");
player.start();
}
@Override
public void onError(MediaPlayer arg0,int what, int extra) ...{
Log.d("Media","onError");
player.stop();
}
}
这个服务主要就是用一个MediaPlayer去播放资源中的tonghua(一首MP3音乐)。次序一般是先create出这个实例,然后prepare一下(如果是文件直接prepare,如果是流则最好异步parepareAsync),接着就可以start了,同步可以直接start,异步则必须放到onPrepared中再start。
在MainActivity中启动这个服务即可。
mediaServiceIntent= new Intent();
mediaServiceIntent.setClass(this, MediaService.class);
this.startService(mediaServiceIntent, new Bundle());
当前,在Activity停止时也别忘了将这个Service停掉,而在Service停止时关掉MediaPlayer。
在模拟器上试了,效果不是太好,声音有点断断续续,不知道是不是我的解码器的问题(Vista系统)。