您的位置:首页 > 其它

移动迷宫——拼图游戏

2016-06-14 16:31 399 查看
拼图游戏就是将用户选择的图片根据选择的难度进行分割,并抽出一张图片用空白块代替,同时随机打乱顺序,作为拼图的初始游戏界面。当用户点击空白块周围上下左右相邻的图片时,可以移动该图片,当整个图片的位置都与原始图片一致时,拼图完成。

拼图算法

这个拼图游戏用到了人工智能领域的一个非常著名的算法——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;
}


程序运行后效果图如下所示:



代码地址
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  puzzle