1,概述

在写博客之前,需要声明的一下是:本项目参照于徐宜生编著的《安卓群英传》。
拼图游戏相对来说,功能实现起来比较简单。对于学习刚入门的开发者来说,做这么一个小项目,还是可以学到很多知识的。在此,我就分享一下我在做这个项目过程中学到的知识。

2,实现的效果

  1. 在第一个界面中,用户可以选择游戏难度;点击界面中的图片,进入拼图界面。
  2. 第一个界面中,用户可以通过拍照或者从相册中选择图片,进而进行拼图。
  3. 在第二个界面(拼图界面)。一旦开始拼图,需要记录用户从拼图开始到拼图成功所用的步数以及花费的时间。
  4. 拼图完成时,将空白的图片补上。
  5. 在拼图界面,用户可以查看原图;可以重新对生成新的拼图界面。可以返回上一个界面。

3,功能分析

  1. 图片的分隔。比如游戏的难度是3X3的,就需要将图片分成等大的9块。
  2. 在拼图界面,需要将分隔好的图片随机打乱。
  3. 判断图片随机打乱的界面是否遵循拼图规则。有50%随机生成的拼图界面是无解的。这里涉及一个算法——N-puzzle
  4. 用户点击后,怎么实现两张图片的交换以及判断用户的点击是否有效。(必须点击和空白图片相邻的图片才能和空白图片交换位置,否则该点击事件不予以响应)
  5. 判断拼图是否成功。
  6. 成功后,需要将空白图片补上。在切割图片时,需要将其中的一张图片用空白图片代替。

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;
            }
        }

    }

这篇博客就先写这么多。