今天我们要继续开发Android游戏拼图,今天同样是做一些准备工作,昨天我们把界面的准备工作做好了,今天呢,我们想想,要完成一个拼图,我们还需要做哪些准备。
首先,我们需要一个工具类,去获取屏幕的相关信息,让我们的程序去自动适应不同分辨率大小的屏幕:
package com.xys.xpuzzle.util; import android.content.Context; import android.util.DisplayMetrics; import android.view.Display; import android.view.WindowManager; /** * 屏幕工具类:实现获取屏幕相关参数 * * @author xys * */ public class ScreenUtil { /** * 获取屏幕相关参数 * * @param context * @return DisplayMetrics 屏幕宽高 */ public static DisplayMetrics getScreenSize(Context context) { DisplayMetrics metrics = new DisplayMetrics(); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); display.getMetrics(metrics); return metrics; } }
下面我们要开始设计一个实体bean,去封装我们的拼图,这里的方法有很多,我只是举个砖头。
我们知道,拼图时会把图片分割成NXN个方块,移动方块以完成拼图,所以我将每个分割后的方块做成一个对象,我们所有的实体bean都是基于每个分割后的方块,所以自然的我们可以抽象出一个实体bean:
package com.xys.xpuzzle.bean; import android.graphics.Bitmap; /** * 拼图Item逻辑实体类:封装逻辑相关属性 * * @author xys * */ public class ItemBean { // Item的Id private int itemId; // bitmap的Id private int bitmapId; // bitmap private Bitmap bitmap; public int getItemId() { return itemId; } public void setItemId(int itemId) { this.itemId = itemId; } public ItemBean() { } public int getBitmapId() { return bitmapId; } public void setBitmapId(int bitmapId) { this.bitmapId = bitmapId; } public Bitmap getBitmap() { return bitmap; } public void setBitmap(Bitmap bitmap) { this.bitmap = bitmap; } public ItemBean(int itemId, int bitmapId, Bitmap bitmap) { this.itemId = itemId; this.bitmapId = bitmapId; this.bitmap = bitmap; } }
bean里面封装了
1、方块对应在NXN格中的序号
2、分割后的图片的ID和对应的图片
接下来就是对图片的分割:
package com.xys.xpuzzle.util; import java.util.ArrayList; import java.util.List; import com.xys.xpuzzle.R; import com.xys.xpuzzle.activity.PuzzleMain; import com.xys.xpuzzle.bean.ItemBean; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; /** * 图像工具类:实现图像的分割与自适应 * * @author xys * */ public class ImagesUtil { public ItemBean itemBean; /** * 切图、初始状态(正常顺序) * * @param type * @param picSelected * @param context */ public void createInitBitmaps(int type, Bitmap picSelected, Context context) { Bitmap bitmap = null; List<Bitmap> bitmapItems = new ArrayList<Bitmap>(); // 每个Item的宽高 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.itemBeans.add(itemBean); } } // 保存最后一个图片在拼图完成时填充 PuzzleMain.lastBitmap = bitmapItems.get(type * type - 1); // 设置最后一个为空Item bitmapItems.remove(type * type - 1); GameUtil.itemBeans.remove(type * type - 1); Bitmap blankBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.blank); blankBitmap = Bitmap.createBitmap(blankBitmap, 0, 0, itemWidth, itemHeight); bitmapItems.add(blankBitmap); GameUtil.itemBeans.add(new ItemBean(type * type, 0, blankBitmap)); GameUtil.blankItemBean = GameUtil.itemBeans.get(type * type - 1); } /** * 处理图片 放大、缩小到合适位置 * * @param newWidth * @param newHeight * @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; } }
这个工具类主要完成这两件事:
1、切图、初始状态(正常顺序)
2、处理图片 放大、缩小到合适位置
根据上一篇讲的算法,我们需要将图片进行分割,然后生成一个切好图后的Items集合,当然,还要对要拼图的图片进行下大小的处理,因为我们除了有默认的图片,还可以自定义图片
图片工具类完成后,接下来我们就要在一个工具类中实现这些算法,同时还要做一些对游戏的封装:
package com.xys.xpuzzle.util; import java.util.ArrayList; import java.util.List; import com.xys.xpuzzle.activity.PuzzleMain; import com.xys.xpuzzle.bean.ItemBean; /** * 拼图工具类:实现拼图的交换与生成算法 * * @author xys * */ public class GameUtil { // 游戏信息单元格Bean public static List<ItemBean> itemBeans = new ArrayList<ItemBean>(); // 空格单元格 public static ItemBean blankItemBean = new ItemBean(); /** * 判断点击的Item是否可移动 * * @param position * @return 能否移动 */ public static boolean isMoveable(int position) { int type = PuzzleMain.type; // 获取空格Item int blankId = GameUtil.blankItemBean.getItemId() - 1; // 不同行 相差为type if (Math.abs(blankId - position) == type) { return true; } // 相同行 相差为1 if ((blankId / type == position / type) && Math.abs(blankId - position) == 1) { return true; } return false; } /** * 交换空格与点击Item的位置 * * @param from * @param blank */ public static void swapItems(ItemBean from, ItemBean blank) { ItemBean tempItemBean = new ItemBean(); // 交换BitmapId tempItemBean.setBitmapId(from.getBitmapId()); from.setBitmapId(blank.getBitmapId()); blank.setBitmapId(tempItemBean.getBitmapId()); // 交换Bitmap tempItemBean.setBitmap(from.getBitmap()); from.setBitmap(blank.getBitmap()); blank.setBitmap(tempItemBean.getBitmap()); // 设置新的Blank GameUtil.blankItemBean = from; } /** * 生成随机的Item */ public static void getPuzzleGenerator() { int index = 0; for (int i = 0; i < itemBeans.size(); i++) { index = (int) (Math.random() * PuzzleMain.type * PuzzleMain.type); swapItems(itemBeans.get(index), GameUtil.blankItemBean); } List<Integer> data = new ArrayList<Integer>(); for (int i = 0; i < itemBeans.size(); i++) { data.add(itemBeans.get(i).getBitmapId()); } // 判断生成是否有解 if (canSolve(data)) { return; } else { getPuzzleGenerator(); } } /** * 是否拼图成功 * * @return 是否拼图成功 */ public static boolean isSuccess() { for (ItemBean tempBean : GameUtil.itemBeans) { if (tempBean.getBitmapId() != 0 && (tempBean.getItemId()) == tempBean.getBitmapId()) { continue; } else if (tempBean.getBitmapId() == 0 && tempBean.getItemId() == PuzzleMain.type * PuzzleMain.type) { continue; } else { return false; } } return true; } /** * 该数据是否有解 * * @param data * @return 该数据是否有解 */ public static boolean canSolve(List<Integer> data) { // 获取空格Id int blankId = GameUtil.blankItemBean.getItemId(); // 可行性原则 if (data.size() % 2 == 1) { return getInversions(data) % 2 == 0; } else { // 从底往上数,空格位于奇数行 if (((int) (blankId - 1) / PuzzleMain.type) % 2 == 1) { return getInversions(data) % 2 == 0; } else { // 从底往上数,空位位于偶数行 return getInversions(data) % 2 == 1; } } } /** * 计算倒置和算法 * * @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; } }
可以看到,游戏工具类中,我们主要有以下几个功能:
1、判断点击的Item是否可移动:主要难点是判断需要分同行与不同行,否则会出现上一行的最后一个和本行的第一个可以移动的BUG
2、交换空格与点击Item的位置:实际上是交换GridView中的某2个Item的背景
3、生成随机的Item:根据上一篇讲的算法,打随机打乱分割后的图片
4、判断是否拼图成功:根据上一篇讲的算法的结论判断
5、判断该数据是否有解:根据上一篇讲的算法的结论判断
6、计算倒置和算法:算法的核心注意要踢出空格
到目前为止,我们的准备工作就基本结束了,很多人可能会说开始的时候准备这么多干嘛,其实这是我真实的思考顺序,刚开始项目的时候,一定要先把项目整体规划一下,而不是上手就做,当你有了一个清晰的思路后,coding只是一个体力活而已。所以,工欲善其事,必先利其器,画竹需要成竹在胸。
ps : 需要源码的朋友请留言。