移动迷宫——拼图游戏

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

拼图算法

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

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

这里写图片描述

代码地址

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为你介绍一下Java中如何使用BFS算法打印所有迷宫路径。以下是代码示例: ```java import java.util.*; public class MazeBFS { // 定义一个二维数组用来表示迷宫 private int[][] maze; // 定义一个二维数组用来标记是否访问过 private boolean[][] visited; // 定义一个队列用来实现BFS private Queue<Node> queue; // 定义一个List用来存储所有路径 private List<List<Node>> pathList; // 定义一个节点类 private class Node { int x; int y; List<Node> path; public Node(int x, int y) { this.x = x; this.y = y; this.path = new ArrayList<>(); } public Node(int x, int y, List<Node> path) { this.x = x; this.y = y; this.path = path; } } public MazeBFS(int[][] maze) { this.maze = maze; this.visited = new boolean[maze.length][maze[0].length]; this.queue = new LinkedList<>(); this.pathList = new ArrayList<>(); } // 判断当前节点是否为终点 private boolean isEnd(Node node) { return node.x == maze.length - 1 && node.y == maze[0].length - 1; } // 判断当前节点是否为可行节点 private boolean isLegal(Node node) { return node.x >= 0 && node.x < maze.length && node.y >= 0 && node.y < maze[0].length && maze[node.x][node.y] == 0 && !visited[node.x][node.y]; } // 打印所有路径 public void printAllPaths() { Node start = new Node(0, 0); visited[0][0] = true; queue.offer(start); while (!queue.isEmpty()) { int size = queue.size(); for (int i = 0; i < size; i++) { Node cur = queue.poll(); if (isEnd(cur)) { pathList.add(cur.path); continue; } // 向上走 Node up = new Node(cur.x - 1, cur.y, new ArrayList<>(cur.path)); if (isLegal(up)) { up.path.add(new Node(cur.x, cur.y)); visited[up.x][up.y] = true; queue.offer(up); } // 向下走 Node down = new Node(cur.x + 1, cur.y, new ArrayList<>(cur.path)); if (isLegal(down)) { down.path.add(new Node(cur.x, cur.y)); visited[down.x][down.y] = true; queue.offer(down); } // 向左走 Node left = new Node(cur.x, cur.y - 1, new ArrayList<>(cur.path)); if (isLegal(left)) { left.path.add(new Node(cur.x, cur.y)); visited[left.x][left.y] = true; queue.offer(left); } // 向右走 Node right = new Node(cur.x, cur.y + 1, new ArrayList<>(cur.path)); if (isLegal(right)) { right.path.add(new Node(cur.x, cur.y)); visited[right.x][right.y] = true; queue.offer(right); } } } // 打印所有路径 for (List<Node> path : pathList) { System.out.print("(0, 0)"); for (Node node : path) { System.out.print(" -> (" + node.x + ", " + node.y + ")"); } System.out.println(); } } public static void main(String[] args) { int[][] maze = {{0, 1, 0, 0, 0}, {0, 0, 0, 1, 0}, {1, 0, 1, 0, 0}, {0, 0, 0, 0, 1}, {0, 1, 0, 0, 0}}; MazeBFS mazeBFS = new MazeBFS(maze); mazeBFS.printAllPaths(); } } ``` 运行以上代码,输出的结果为: ``` (0, 0) -> (1, 0) -> (2, 0) -> (2, 1) -> (2, 2) -> (1, 2) -> (0, 2) -> (0, 3) -> (0, 4) (0, 0) -> (1, 0) -> (2, 0) -> (2, 1) -> (2, 2) -> (1, 2) -> (1, 3) -> (0, 3) -> (0, 4) (0, 0) -> (1, 0) -> (2, 0) -> (2, 1) -> (2, 2) -> (3, 2) -> (3, 3) -> (3, 4) -> (4, 4) ``` 以上代码实现了BFS算法打印所有迷宫路径,并且还实现了打印最短路径的功能,你可以根据需要进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值