移动迷宫——拼图游戏
2016-06-14 16:31
399 查看
拼图游戏就是将用户选择的图片根据选择的难度进行分割,并抽出一张图片用空白块代替,同时随机打乱顺序,作为拼图的初始游戏界面。当用户点击空白块周围上下左右相邻的图片时,可以移动该图片,当整个图片的位置都与原始图片一致时,拼图完成。
我们将图片暂时用数字来代替,这样整个拼图游戏就变成了一个数字矩阵,假设随机得到了这个矩阵,其中X用来代表空格的图片,如下所示:
⎡⎣⎢⎢⎢12758111X1310496214153⎤⎦⎥⎥⎥拼图矩阵
对图片拼图的还原,与还原上图中的数字矩阵,其实是一个道理。现在,将这个矩阵写成一维数组的形式,并将其记为序列A, 如下所示。
A = {12, 1, 10, 2, 7, 11, 4, 14, 5, X, 9, 15, 8, 13, 6, 3}
再定义一个“倒置变量值”的算法——T。Ti表示序列A中位于第i位之后,比Ai小的元素的个数(不算X)。例如对上面的序列A中的每个元素进行“倒置变量值”的计算,其结果如下所示。
11, 0, 8, 0, 4, 6, 1, 6, 1, 3, 4, 2, 2, 1
最后,求得所有“倒置变量值”的和SumT=49。在N Puzzle算法中,使用如下两个原则来判断一个N Puzzle问题是否有解。
如果序列A的宽度为奇数,那么每个可解的问题所定义的“倒置变量值”的和——SumT必须为偶数。
如果序列A的宽度为偶数,那么当空格X位于从下往上数的奇数行中时,定义的“倒置变量值”的和——SumT必须为偶数;当空格X位于从下往上数的偶数行中时,定义的“倒置变量值”的和——SumT必须为奇数。
如果判读可以移动,则使用swapItems方法进行交换。每次交换后,还需要对当前游戏进行判断。判断是否已经还原成功,即当前图片Item的ID与初始状态下图片的ID是否相同。如果还原成功,那么就将最后缺失的那一块图片补齐。
程序运行后效果图如下所示:
![](http://img.blog.csdn.net/20160614162817914)
代码地址
拼图算法
这个拼图游戏用到了人工智能领域的一个非常著名的算法——N Puzzle问题。随机交换图片的位置之后,生成的拼图游戏很多是无解的,且这个比例高达50%左右,所以必须先判断生成的拼图是否有解。我们将图片暂时用数字来代替,这样整个拼图游戏就变成了一个数字矩阵,假设随机得到了这个矩阵,其中X用来代表空格的图片,如下所示:
⎡⎣⎢⎢⎢12758111X1310496214153⎤⎦⎥⎥⎥拼图矩阵
对图片拼图的还原,与还原上图中的数字矩阵,其实是一个道理。现在,将这个矩阵写成一维数组的形式,并将其记为序列A, 如下所示。
A = {12, 1, 10, 2, 7, 11, 4, 14, 5, X, 9, 15, 8, 13, 6, 3}
再定义一个“倒置变量值”的算法——T。Ti表示序列A中位于第i位之后,比Ai小的元素的个数(不算X)。例如对上面的序列A中的每个元素进行“倒置变量值”的计算,其结果如下所示。
11, 0, 8, 0, 4, 6, 1, 6, 1, 3, 4, 2, 2, 1
最后,求得所有“倒置变量值”的和SumT=49。在N Puzzle算法中,使用如下两个原则来判断一个N Puzzle问题是否有解。
如果序列A的宽度为奇数,那么每个可解的问题所定义的“倒置变量值”的和——SumT必须为偶数。
如果序列A的宽度为偶数,那么当空格X位于从下往上数的奇数行中时,定义的“倒置变量值”的和——SumT必须为偶数;当空格X位于从下往上数的偶数行中时,定义的“倒置变量值”的和——SumT必须为奇数。
图片工具
通过这两步处理,就把图片分割成了NxN个小的Item,并将最后一个图片剔除,用于显示要移动的空格图片(即数字X)。最后,通过GridView的数据适配器,将这些图片添加到拼图游戏的GridView中显示出来。/** * Created by Administrator on 2016/6/14. * 图像工具类:实现图像的分割与自适应 */ public class ImagesUtil { /** * 切图、初始状态(正常顺序) * * @param type 游戏种类 * @param picSelected 选择的图片 * @param context context */ public void createInitBitmaps(int type, Bitmap picSelected, Context context) { ItemBean itemBean = null; 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.mItemBeans.add(itemBean); } } // 保存最后一个图片在拼图完成时填充 PuzzleActivity.mLastBitmap = bitmapItems.get(type * type -1); // 设置最后一个为空Item bitmapItems.remove(type * type -1); GameUtil.mItemBeans.remove(type * type -1); Bitmap blankBitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.blank); blankBitmap = Bitmap.createBitmap(blankBitmap, 0 , 0, itemWidth, itemHeight); bitmapItems.add(blankBitmap); GameUtil.mItemBeans.add(new ItemBean(type * type, 0, blankBitmap)); GameUtil.mBlankItemBean = GameUtil.mItemBeans.get(type * type - 1); } /** * 处理图片 放大、缩小到合适位置 * * @param newWidth 缩放后Width * @param newHeight 缩放后Height * @param bitmap bitmap * @return bitmap */ 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; } }
生成游戏
前面获取了按顺序分割的图片,下面通过一个循环来随机交换Item中的图片,从而生成杂乱的拼图游戏。由于随机生成的拼图游戏有将近50%都是无解的,所以还需要判断当前生成的游戏是否有解。// 游戏信息单元格Bean public static List<ItemBean> mItemBeans = new ArrayList<ItemBean>(); // 空格单元格 public static ItemBean mBlankItemBean = new ItemBean(); /** * 生成随机的Item */ public static void getPuzzleGenerator() { int index = 0; // 随机打乱顺序 for (int i = 0; i < mItemBeans.size(); i++) { index = (int) (Math.random() * PuzzleActivity.TYPE * PuzzleActivity.TYPE); swapItems(mItemBeans.get(index), mBlankItemBean); } List<Integer> data = new ArrayList<Integer>(); for (int i = 0; i < mItemBeans.size(); i++) { data.add(mItemBeans.get(i).getBitmapId()); } // 判断生成是否有解 if (!canSolve(data)) getPuzzleGenerator(); } /** * 交换空格与点击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.mBlankItemBean = from; } /** * 该数据是否有解 * * @param data 拼图数组数据 * @return 该数据是否有解 */ public static boolean canSolve(List<Integer> data) { //获取空格Id int blankId = mBlankItemBean.getItemId(); // 可行性原则 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; } } } /** * 计算倒置和算法 * * @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; }
移动图片
生成游戏后,就可以给GridView的每个Item来响应点击事件了。当点击的图片是空格图片上下左右的图片时,就可以交换两个Item,实现移动的效果。如果点击的是其他图片,自然是不能移动。/** * 判断点击的Item是否可移动 * * @param position position * @return 能否移动 */ public static boolean isMoveable(int position) { int type = PuzzleActivity.TYPE; // 获取空格Item int blankId = mBlankItemBean.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; }
如果判读可以移动,则使用swapItems方法进行交换。每次交换后,还需要对当前游戏进行判断。判断是否已经还原成功,即当前图片Item的ID与初始状态下图片的ID是否相同。如果还原成功,那么就将最后缺失的那一块图片补齐。
/** * 是否拼图成功 * * @return 是否拼图成功 */ public static boolean isSuccess() { for (ItemBean tempBean : mItemBeans) { if(tempBean.getBitmapId() != 0 && tempBean.getItemId() == tempBean.getBitmapId()){ continue; }else if(tempBean.getBitmapId() == 0 && tempBean.getItemId() == PuzzleActivity.TYPE * PuzzleActivity.TYPE){ continue; } else { return false; } } return true; }
程序运行后效果图如下所示:
代码地址
相关文章推荐
- A puzzle for me (cookie)
- 行列变换
- Puzzle - Light Bulb Switching
- 拼图最后两张图片异位问题的解决
- 拼图小游戏"ST--拼图“开发篇之主要功能实现(一)
- UVALive 3907 (LA 3907) Puzzle AC自动机 + 搜索DP 记录路径
- 【白书之路】 227 - Puzzle 模拟
- Cast Puzzle(魔金)智力玩具
- Sliding Puzzle Board
- For 3dsMax 拼图建模工具系列
- 重写JavaScript的Math.round方法
- Cracking the Coding Interview: Math and Logic Puzzles
- 图像形态学操作—腐蚀膨胀深入
- svg base64
- 【转】使用Apache Kylin搭建企业级开源大数据分析平台
- eclipse快捷键以及使用技巧大全
- iOS 万能跳转界面方法 (runtime实用篇一)
- Binary Search Tree Iterator
- 拦截器和filter
- python使用