移动迷宫——拼图游戏
Posted 黄飞_hf
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了移动迷宫——拼图游戏相关的知识,希望对你有一定的参考价值。
拼图游戏就是将用户选择的图片根据选择的难度进行分割,并抽出一张图片用空白块代替,同时随机打乱顺序,作为拼图的初始游戏界面。当用户点击空白块周围上下左右相邻的图片时,可以移动该图片,当整个图片的位置都与原始图片一致时,拼图完成。
拼图算法
这个拼图游戏用到了人工智能领域的一个非常著名的算法——N Puzzle问题。随机交换图片的位置之后,生成的拼图游戏很多是无解的,且这个比例高达50%左右,所以必须先判断生成的拼图是否有解。
我们将图片暂时用数字来代替,这样整个拼图游戏就变成了一个数字矩阵,假设随机得到了这个矩阵,其中X用来代表空格的图片,如下所示:
对图片拼图的还原,与还原上图中的数字矩阵,其实是一个道理。现在,将这个矩阵写成一维数组的形式,并将其记为序列A, 如下所示。
A = 12, 1, 10, 2, 7, 11, 4, 14, 5, X, 9, 15, 8, 13, 6, 3
再定义一个“倒置变量值”的算法——
T
。
11, 0, 8, 0, 4, 6, 1, 6, 1, 3, 4, 2, 2, 1
最后,求得所有“倒置变量值”的和
- 如果序列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;
程序运行后效果图如下所示:
以上是关于移动迷宫——拼图游戏的主要内容,如果未能解决你的问题,请参考以下文章