Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9463613
  • 博文数量: 1750
  • 博客积分: 12961
  • 博客等级: 上将
  • 技术积分: 20091
  • 用 户 组: 普通用户
  • 注册时间: 2009-01-09 11:25
个人简介

偷得浮生半桶水(半日闲), 好记性不如抄下来(烂笔头). 信息爆炸的时代, 学习是一项持续的工作.

文章分类

全部博文(1750)

文章存档

2024年(26)

2023年(26)

2022年(112)

2021年(217)

2020年(157)

2019年(192)

2018年(81)

2017年(78)

2016年(70)

2015年(52)

2014年(40)

2013年(51)

2012年(85)

2011年(45)

2010年(231)

2009年(287)

分类: LINUX

2009-04-30 11:25:01

不想再写Hello123了,今天开始做一个数独小游戏,因为这个游戏比较简单应该容易上手,就作为我学习之后的第一个程序比较合适。

初 步的设计是只有一个界面(如下图),然后用绿色字体表示题目中有的固定的数字,黄色字体显示玩家输入的数字,而红色字体则是程序判断输入错误后的显示。另 外模式分为三种:普通写入、标记(玩家用蓝色小方块标记当前单元格可以输入的数字)、排除模式(玩家指定数字,游戏自动判断一下这个数字肯定不能输入的单 元格,将它反相显示出来)。

准备工作就是做一张背景图(棋盘)和三套数字小图片(红、绿、黄)即可。

首先建立工程sudo,程序主体类 MainActivity以后,再修改一下那个main.xml文件,去掉TextView标签即可。因为我们会自己定义一个View,所以不再需要它了。程序不大,所以不打算做过多的类,下面把几个类的分工描述一下:

1MainActivity,主体类,负责处理键盘事件和维护一个题库。

2MainView,显示类,负责维护并显示当前棋局,它的核心在于它的onDraw函数。

3GridCellQuestion两个实体类,分别描述了棋盘单元格的信息和题目信息。

4Helper类,助手类,封装一些函数,如读写记录、自动解题函数等。

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中的CanvasPaint

一是显示图片的方法,因为图片来源于资源,所以显示它的代码如:

Bitmap bmp = BitmapFactory.decodeResource(this.getResources(),R.drawable.grid);
canvas.drawBitmap(bmp, 0, 0, 
null);

这是显示背景,如果是数字呢,如何将数字1R.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);

如果不setStyleStyle.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中提供的两个函数 openFileOutputopenFileInput,虽可用来读写文件,但是总是不太方便。

另外,用SQLite其实也不方便,因为手机中弄这些东西,事实上用处不大。

继续,最后再讨论一下定时器的实现。

本来很简单的一件事,直接用java.util.timer应该就够用了,但是发现在它的task中无法去invalidate我们的MainView,很郁闷。这一点的处理说明 还是相对线程安全的。

折腾良久,明白了非得再做一个Handler,才能在线程中操作界面元素。所以,代码比复杂了一点。

先还是用TimerTimerTask来做,如下:

public TimerHandler timerHandler;

public Timer timer;
public MyTimerTask    task;

... ...

timer=
new Timer(true);
task=
new MyTimerTask(this);

... ...

那个MyTimerTaskMainActivity的一个内嵌类,实现如下:

    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系统)。
阅读(3064) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~