1,概述
在写博客之前,需要声明的一下是:本项目参照于徐宜生编著的《安卓群英传》。
拼图游戏相对来说,功能实现起来比较简单。对于学习刚入门的开发者来说,做这么一个小项目,还是可以学到很多知识的。在此,我就分享一下我在做这个项目过程中学到的知识。
2,实现的效果
- 在第一个界面中,用户可以选择游戏难度;点击界面中的图片,进入拼图界面。
- 第一个界面中,用户可以通过拍照或者从相册中选择图片,进而进行拼图。
- 在第二个界面(拼图界面)。一旦开始拼图,需要记录用户从拼图开始到拼图成功所用的步数以及花费的时间。
- 拼图完成时,将空白的图片补上。
- 在拼图界面,用户可以查看原图;可以重新对生成新的拼图界面。可以返回上一个界面。
3,功能分析
- 图片的分隔。比如游戏的难度是3X3的,就需要将图片分成等大的9块。
- 在拼图界面,需要将分隔好的图片随机打乱。
- 判断图片随机打乱的界面是否遵循拼图规则。有50%随机生成的拼图界面是无解的。这里涉及一个算法——N-puzzle
- 用户点击后,怎么实现两张图片的交换以及判断用户的点击是否有效。(必须点击和空白图片相邻的图片才能和空白图片交换位置,否则该点击事件不予以响应)
- 判断拼图是否成功。
- 成功后,需要将空白图片补上。在切割图片时,需要将其中的一张图片用空白图片代替。
4, 代码实现
1,拼图中小图片的实体类
这个实体类中有三个属性,Bitmap不必多说。mItemId和mBitmapId的引入是为了判断拼图是否完成。对于每一个ItemBean 来说,mItemId是恒定不变的。改变的只是mBitmapId和mBitmap这两个属性。如果mItemId等于mBitmapId,则说明该图片处于刚分隔结束时的位置上。如果所有的小图片都满足这样的条件,那么拼图成功。
需要补充的是,刚开始mItemId等于mBitmapId。
public class ItemBean {
private int mItemId;
private int mBitmapId;
private Bitmap mBitmap;
public ItemBean(int mItemId, int mBitmapId, Bitmap mBitmap) {
this.mItemId = mItemId;
this.mBitmapId = mBitmapId;
this.mBitmap = mBitmap;
}
public ItemBean() {
}
public int getmItemId() {
return mItemId;
}
public void setmItemId(int mItemId) {
this.mItemId = mItemId;
}
public int getmBitmapId() {
return mBitmapId;
}
public void setmBitmapId(int mBitmapId) {
this.mBitmapId = mBitmapId;
}
public Bitmap getmBitmap() {
return mBitmap;
}
public void setmBitmap(Bitmap mBitmap) {
this.mBitmap = mBitmap;
}
}
2,图片的缩放
/**
* 处理图片 放大缩小到合适的位置
* @param newWidth 缩放后的width
* @param newHeight 缩放后的height
* @param bitmap
* @return
*/
public Bitmap resizeBitmap(float newWidth,float newHeight,Bitmap bitmap){
Matrix matrix=new Matrix();
matrix.postScale(newWidth/bitmap.getWidth(),newHeight/bitmap.getHeight());
Bitmap newBitmap=Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
return newBitmap;
}
3,图片的分隔
这里有三个参数,第一个参数是难度选择。如果是2X2的,那么type就为2,如果为3X3的,type就为3。第二个参数为所选中的图片。第三个参数不解释。
根据传入图片的宽高和难度。计算出分隔好的每个小图片的宽和高:
int itemWidth=picSelected.getWidth()/type;
int itemHeight=picSelected.getHeight()/type;
切割图片所用到的方法是:
bitmap=Bitmap.createBitmap(picSelected,(j-1)*itemWidth,(i-1)*itemHeight,itemWidth,itemHeight);
第一个参数为原图。第二个和第三个参数是分隔的起始坐标。第四个和第五个参数是小图片的宽和高。
有了分隔好的小图片,就可以生成ItemBean对象。并将所有的ItemBean对象保到一个List中。每个ItemBean对象刚生成时,mItemId和mBitmapId是相同的。
将生成的最后一个图片保存起来。并用一个空白的图片代替最后一个图片。
注意,空白图片需要先经过缩放然后才能分割成和其他小图片一样的大小,否则报错。
public void createBitmaps(int type, Bitmap picSelected, Context context){
Bitmap bitmap=null;
List<Bitmap>bitmapItems=new ArrayList<>();
int itemWidth=picSelected.getWidth()/type;
int itemHeight=picSelected.getHeight()/type;
for(int i=1;i<=type;i++){
for(int j=1;j<=type;j++){
bitmap=Bitmap.createBitmap(picSelected,(j-1)*itemWidth,(i-1)*itemHeight,itemWidth,itemHeight);
bitmapItems.add(bitmap);
itemBean=new ItemBean((i-1)*type+j,(i-1)*type+j,bitmap);
GameUtil.mItemBeans.add(itemBean);
}
}
PuzzleActivity.mLastBitmap=bitmapItems.get(type*type-1);
bitmapItems.remove(type*type-1);
GameUtil.mItemBeans.remove(type*type-1);
Bitmap blackBitmap= BitmapFactory.decodeResource(context.getResources(), R.drawable.link);
blackBitmap=resizeBitmap(itemWidth,itemHeight,blackBitmap);
blackBitmap=Bitmap.createBitmap(blackBitmap,0,0,itemWidth,itemHeight);
bitmapItems.add(blackBitmap);
GameUtil.mItemBeans.add(new ItemBean(type*type,0,blackBitmap));
GameUtil.mBlackItemBean=GameUtil.mItemBeans.get(type*type-1);
}
4,拼图的算法
所谓拼图的算法,就是指随机生成的拼图游戏是否有解。其中无解的比例高达50%。所以我们需要判断生成的拼图是否有解。这里用到一个算法:N puzzle:
具体参见:拼图是否有解的算法
在此,贴出判断生成的拼图是否有解的代码:
/**
* 计算倒置和算法
* @param data 拼图数组数据
* @return 该序列的倒置和
*/
public static int getInversions(List<Integer>data){
int inversions=0;
int inversionCount=0;
for(int i=0;i<data.size();i++){
for(int j=i+1;j<data.size();j++){
int index=data.get(i);
if(data.get(j)!=0&&data.get(j)<index){
inversionCount++;
}
}
inversions+=inversionCount;
inversionCount=0;
}
return inversions;
}
/**
* 该数据是否有解
* @param data
* @return
*/
public static boolean canSolve(List<Integer> data){
int blankId=GameUtil.mBlackItemBean.getmItemId();
if(data.size()%2==1){
return getInversions(data)%2==0;
}else{
if(((blankId-1)/ PuzzleActivity.TYPE)%2==1){
return getInversions(data)%2==0;
}else {
return getInversions(data)%2==1;
}
}
}
这篇博客就先写这么多。