1. 前言

这个游戏的经典程度就不用说了吧,相信大家都玩过,而且在学习之初说不定也都还写过呢,
这个实际上写了已经很久了,所以可能注释, 代码风格等信息可能不是那么的好看

设计的思路 : 因为当时在写这个程序的时候, 并没有了解A* 算法, dijkstra算法[看过, 但是没有用过], 所以当时使用的是dfs, 深 + 盲 搜, 如果条件稍差, 则可能遍历整个图, 所以寻路的效果就不是那么好了, 请见谅
路径的绘制 也比较重要, 涉及多线程
这个游戏 主要是挪用了推箱子的界面, 素材等等

元素介绍 :
角色 : 即我们控制的人物
地板 : 地板砖, 人物可在上面行走
: 障碍物, 会挡住我们角色的去向
终点 : 我们角色的终点站

游戏画面 :

04 迷宫寻路_加载

2. 基本的数据结构介绍

这里 基本上是挪用了推箱子的所有数据结构, 这里着重介绍几种修改的数据结构

2.1 Map : 整个地图的model, 控制着图形的显示

/**
 * file name : Map.java
 * created at : 9:20:56 PM Apr 13, 2015
 * created by 970655147
 */

package com.hx.maze;

// Map整个程序的主Model
public class Map {

    // 地图的模型, 加载完成的的地图元素
    private int[][] map;
    private Element[][] eles;
    // 角色, 游戏面板, 所有的箱子数, 已经推到正确位置的箱子数, 当前关卡数
    private Hero hero;
    private MyPanel gamePanel;
    private int allBoxNums;
    private int finishedBoxNums;
    private int stageNum;
    private LinkedList<Node> turns;
    private LinkedList<Node> path;
    private int updateDirections;
    private int[] preferencesDirections;

    // 初始化
    // 如果没有显示 指定stageNum 默认设置为0
    public Map (int stageNum) throws IOException {
        this(new FileInputStream(new File(System.getProperty("user.dir"), getMapPathByStage(stageNum))) );
        this.stageNum = stageNum;
    }
    public Map (InputStream inputStream) throws IOException {
        this(FileParser.parse(inputStream) );
        stageNum = 0;
        turns = new LinkedList<Node>();
        path = new LinkedList<Node>();
    }
    public Map() {

    }
    public Map (int[][] map) {
        this.map = map;
        refreshEles();
        stageNum = 0;
        preferencesDirections = Constants.ALL_DIRECTIONS;
        updateDirections = 16 - 1;
    }

    // 获取地图文件相对路径
    public static String getMapPathByStage(int stageNum2) {
        return "src/com/hx/maze/map01.txt";
    }

    // getter & setter
    // 省略 ...

    // 设置关卡  先获取逻辑地图, 在实际加载地图所有的元素  更新数据等等
    public void setStage(int stageNum) {
        try {
            this.map = (FileParser.parse(new FileInputStream(new File(System.getProperty("user.dir"), getMapPathByStage(stageNum)))));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        refreshEles();
        this.stageNum = stageNum;
    }

    // 依据map 加载所有的元素  统计所有的箱子数  清零已经完成的箱子数  重新绘制panel
    public void refreshEles() {
        eles = new Element[map.length][map.length];
        for(int i=0; i<map.length; i++) {
            for(int j=0; j<map[0].length; j++) {
                eles[i][j] = getEleForVal(i, j, getVal(i, j));
            }
        }
        eles[Constants.END_POINT_ROW][Constants.END_POINT_COL] = getEleForVal(Constants.END_POINT_ROW, Constants.END_POINT_COL, Constants.BOX_RIGHT);

        allBoxNums = countAllBoxNums(map);
        finishedBoxNums = 0;
//      Log.log(allBoxNums, finishedBoxNums);
        if(gamePanel != null) {
            gamePanel.repaint();
        }
    }

    // 统计箱子数[箱子目的图标 + 已经放置正确的箱子图标]
    private int countAllBoxNums(int[][] map) {
        int allBoxNums = 0;
        for(int i=0; i<map.length; i++) {
            for(int j=0; j<map[0].length; j++) {
                if(map[i][j] == Constants.BOX_RIGHT || map[i][j] == Constants.BOX_RIGHT_POS) {
                    allBoxNums ++;
                }
            }
        }
        return allBoxNums;
    }

    // 根据坐标以及value 返回相应的元素
    private Element getEleForVal(int i, int j, int val) {
        // 因为i 表示纵向的偏移
        // j表示横向的偏移
        if(val == 3 || val == 9) {
            PushableElement pe = new PushableElement(i, j, Constants.PUSHABLE[val], Constants.MOVABLE[val], val);
            pe.setMap(this);
            return pe;
        } else  if(val == 2 || val == 4) {
            return new MovableElement(i, j, Constants.PUSHABLE[val], Constants.MOVABLE[val], val);
        } else if(val >= 5 && val < 9) {
            hero = new Hero(i, j, Constants.PUSHABLE[val], Constants.MOVABLE[val], val);
            hero.setMap(this);
            return hero; 
        }

        return new Element(i, j, Constants.PUSHABLE[val], Constants.MOVABLE[val], val);
    }

    // 检查是否游戏当前关卡结束
    public void checkIsSuccess() {
        if(isSucc()) {
            JOptionPane.showMessageDialog(gamePanel, "congratulations...");
            gamePanel.nextStage();
        }
    }

    // 判断 是否符合成功的条件
    public boolean isSucc() {
        if(hero.getX() == (Constants.END_POINT_ROW) && hero.getY() == (Constants.END_POINT_COL)) {
            return true;
        }

        return false;
    }

    // 判断 node结点是否符合成功的条件
    private boolean isSucc(Node node, Point dst) {
        if(node.row == (dst.x) && node.col == (dst.y) ) {
            return true;
        }

        return false;
    }

    public void parse() {
        path.clear();
        parse(hero.getX(), hero.getY(), Constants.END_POINT_ROW, Constants.END_POINT_COL, path);
        optimize(path);

        Log.log(path);
    }

    // 解析是否 可到达终点
    // 获取当前的角色位置  解析其课走的方向 
    // 然后 试探的向一个方向[左>下>右>上]走  然后在循环判定该位置, 并继续走
    // 感觉 这样好傻,,
    // 后来 更新了一下, 更新了选择方向的优先级  没updateDirections次 更新一次
    private void parse(int srcRow, int srcCol, int dstRow, int dstCol, LinkedList<Node> path) {
        Point dst = new Point(dstRow, dstCol);
        preferencesDirections = Tools.getPreferenceDirections(new Point(srcRow, srcCol), dst );
        turns.clear();

        int cnt = 0;
        Node node = new Node(srcRow, srcCol );
        setDirectionsForNode(node);
        turns.push(node);
        path.add(node);

        // 原来Arrays.copyOf居然是浅拷贝..??        --2015.05.20
//      int[][] newMap = Arrays.copyOf(map, map.length);
        int[][] copyOfMap = copyMap();
        Node tmp = null;
        setBlock(map, node);

        while((tmp = turns.getFirst() ) != null) {
            if((cnt & updateDirections) == updateDirections) {
                preferencesDirections = Tools.getPreferenceDirections(new Point(tmp.row, tmp.col), dst );
            }
            int direction = -1;

            removeAfter(path, tmp);
            if((direction = tmp.nextDirection() ) != Constants.NULL) {
                tmp = new Node(tmp);
                tmp.move(direction);
                setBlock(map, tmp);

                setDirectionsForNode(tmp);

                // 擦 为什么没有打印出(0, 0)...  
                // 看清楚  我上面的tmp是从turns中取得的值, 所以对其move会对turns中的数据修改... 所以,没有(0, 0)...
                turns.push(tmp);                
                path.add(tmp);
                if(isSucc(tmp, dst)) {
                    break;
                }
            } else {
                Node n = turns.pop();
                if(turns.size() == 0) {
                    break;
                }
            }

            cnt ++;
        }

        if(turns.size() == 0) {
//          JOptionPane.showMessageDialog(null, "can't got it...");
        }

        // 恢复map
        this.map = copyOfMap;
    }

    // 优化那些正方向上相关的点
    // 两个迭代器 一个迭代器从头开始遍历 一个从后开始遍历
    // 如果两个结点在正方向上  则尝试从第一个结点走到第二个结点 
        // 如果所用的步数少于之前path中所用过的步数  则更新这两个节点之间的路径   continue Optimize;
    // 因为 更改了path   所以需要从新获取迭代器 遍历
    private void optimize(LinkedList<Node> path) {
        LinkedList<Node> tmp = new LinkedList<Node>();
        Iterator<Node> it01 = null;
        int idx01 = 0, idx02 = 0;

        optimize:
        while(true ) {
            for(it01 = path.iterator(), idx01 = 0; it01.hasNext(); ) {
                Node node01 = it01.next();
                idx02 = path.size() - 1;
                ListIterator<Node> it02 = path.listIterator(path.size());
                while(it02.hasPrevious() ) {
                    Node node02 = it02.previous();

                    int direction = Tools.getDirection(node01.row, node01.col, node02.row, node02.col);
                    if(Tools.isRightDirection(direction)) {
                        tmp.clear();
                        parse(node01.row, node01.col, node02.row, node02.col, tmp);
                        if(tmp.size() < (idx02 - idx01) ) {
                            while(idx02 >= idx01) {
                                Log.log(path.remove(idx02--) );
                            }
                            path.addAll(idx01, tmp);

                            continue optimize;
                        }
                    }                   
                    idx02 --;
                }
                idx01 ++;
            }

            break;
        }
    }

    // 复制一个int[][] 备份
    private int[][] copyMap() {
        int[][] newMap = new int[map.length][map[0].length];
        for(int row=0; row<map.length; row++) {
            for(int col=0; col<map[0].length; col++) {
                newMap[row][col] = map[row][col];
            }
        }

        return newMap;
    }

    // 移除tmp元素之后的元素
    private void removeAfter(LinkedList<Node> path2, Node tmp) {
        Node node = new Node(4, 18);
        int idx = path2.size();

        // get方法的node方法优化  头尾操作比较快
        // 我去 之前的时候 我这里path2 居然写的path, 所以优化的时候  出现了一个奇怪的问题, 很多两个结点之间可以直接到达...       --2015.05.22
        while(idx >0 && !tmp.eq(path2.get(--idx)) ) ;
        ++ idx;
        if(idx > 0) {
            while(path2.size() > idx) {
                path2.removeLast();
            }
        }
    }

    // 根据path 获取路径的Point[]表示
    public Point[] getPath() {
        Point[] res = new Point[path.size()];
        Iterator<Node> it = path.iterator();
        int idx = 0;
        while(it.hasNext()) {
            Node node = it.next();
            res[idx++] = new Point(getXByCol(node.col), getYByRow(node.row) );
        }

        return res;
    }

    // 通过col的数字  获取x方向的坐标
    private int getXByCol(int col) {
        return Constants.HEIGHT/2 + col*Constants.HEIGHT;
    }

    // 通过row的数字  获取y方向的坐标
    private int getYByRow(int row) {
        return Constants.WIDTH/2 + row*Constants.WIDTH;
    }

    private void setBlock(int[][] newMap, Node node) {
        newMap[node.row][node.col] = Constants.BLOCK;
    }

    // 获取一个node可以移动的方向
    private void setDirectionsForNode(Node node) {
        int[] dirs = new int[]{Constants.NULL, Constants.NULL, Constants.NULL, Constants.NULL };
        int idx = 0;

        int[] directions = preferencesDirections;
        for(int i=0; i<directions.length; i++) {
            if(node.canMove(directions[i])) {
                dirs[idx++] = directions[i];
            }
        }

        node.setDirs(dirs);
        node.setIdx(idx - 1);
    }

    // 一个Node
    public class Node {
        // 横坐标, 纵坐标, 可移动的方向, 下一个方向的索引
        int row;
        int col;
        int[] dirs;
        int dirIdx;

        // 初始化
        public Node() {

        }
        public Node(int row, int col) {
            this.row = row;
            this.col = col;
        }
        public Node(Node dst) {
            this.row = dst.row;
            this.col = dst.col;
            this.dirs = dst.dirs;
            this.dirIdx = dst.dirIdx;
        }

        // 相等判定
        public boolean eq(Node other) {
            return row == other.row && col == other.col;
        }

        // getter & setter
        // 省略 ...

        // 该方向是否可移动
        public boolean canMove(int direction) {
            boolean canMove = false;
            switch(direction) {
                case Constants.UP:
                    canMove = isMovable(row-1, col);
                    break;
                case Constants.RIGHT:
                    canMove = isMovable(row, col+1);
                    break;
                case Constants.DOWN:
                    canMove = isMovable(row+1, col);
                    break;
                case Constants.LEFT:
                    canMove = isMovable(row, col-1);
                    break;
            }

            return canMove;
        }

        // 向direction方向移动  更新坐标
        public void move(int direction) {
            switch(direction) {
                case Constants.UP:
                    up();
                    break;
                case Constants.RIGHT:
                    right();
                    break;
                case Constants.DOWN:
                    down();
                    break;
                case Constants.LEFT:
                    left();
                    break;
            }
        }

        // 各个方向的移动
        private void up() {
            move(row - 1, col); 
        }
        private void right() {
            move(row, col + 1);
        }
        private void down() {
            move(row + 1, col);
        }
        private void left() {
            move(row, col - 1);
        }

        // 移动的工具方法
        private void move(int x, int y) {
            this.row = x;
            this.col = y;
        }

        // row, col是否可移动
        private boolean isMovable(int row, int col) {
            if(Tools.isOutBounds(row, col)) {
                return false;
            }

            return map[row][col] != Constants.BLOCK;
        }

        // 获取下一个方向
        public int nextDirection() {
            if(dirIdx >= 0) {
                return dirs[dirIdx--];              
            }

            return -1;
        }

        // For Debug
        public String toString() {
            return "row : " + row + ", col : " + col;
        }

    }

}
  1. 2 MyPanel $DrawLineRunnable : 绘制路径的线程
// 绘制路径的Runnable
    class DrawLineRunnable implements Runnable {
        // 途经的各个点, 以及需要的时间, 以及是否值正向绘制
        Point[] path;
        int ms;
        boolean isPositive;

        // 初始化
        public DrawLineRunnable() {

        }
        public DrawLineRunnable(Point[] path, boolean isPositive, int ms) {
            this.path = path;
            this.ms = ms;
            this.isPositive = isPositive;
        }

        // 绘制path的轨迹  清理path的轨迹
        public void run() { 
            Graphics g = MyPanel.this.getGraphics();
            if(isPositive) {
                // 画出轨迹
                for(int i=0; i<path.length-1; i++) {
                    move(path[i], path[i+1], g);    
                }
                // 清理轨迹
                for(int i=0; i<path.length-1; i++) {
                    clear(path[i], path[i+1], g);   
                }
            } else {
                for(int i=path.length-1; i>0; i--) {
                    move(path[i], path[i-1], g);    
                }
                for(int i=path.length-1; i>0; i--) {
                    clear(path[i], path[i-1], g);   
                }
            }
        }

        // 绘制src到dst的路径
        public void move(Point src, Point dst, Graphics g) {
            draw(src, dst, g, BORDER_COLOR);
        }

        // 清理src到dst的路径
        public void clear(Point src, Point dst, Graphics g) {
            int num = ms * 3;
            int upInc = (dst.y - src.y) / num; 
            int rightInc = (dst.x - src.x) / num;
            Point tmp = new Point(src);

            for(int i=0; i<num; i++) {
                g.setColor(BG);
                g.drawLine(tmp.x, tmp.y, tmp.x+rightInc, tmp.y+upInc);
                tmp.x += rightInc;  tmp.y += upInc;
            }
        }

        // assistMethod
        public void draw(Point src, Point dst, Graphics g, Color color){
            int num = ms * 3;
            int upInc = (dst.y - src.y) / num; 
            int rightInc = (dst.x - src.x) / num;
            int xOffset = 10;
            int yOffset = 10;
            Point tmp = new Point(src);
            int direction = Tools.getDirection(src.y, src.x, dst.y, dst.x);

            if(direction <= 3) {
                for(int i=0; i<num; i++) {
                    g.setColor(BG);
                    drawArrow(tmp, xOffset, yOffset, direction, g);

                    g.setColor(color);
                    g.drawLine(tmp.x, tmp.y, tmp.x+rightInc, tmp.y+upInc);
                    tmp.x += rightInc;  tmp.y += upInc;
                    drawArrow(tmp, xOffset, yOffset, direction, g);
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                g.setColor(BG);
                drawArrow(tmp, xOffset, yOffset, direction, g);
            }

        }

        // 绘制箭头 -> 中的 ">"
        // 先获取上面点, 下面点的坐标, 在绘制tmp到两个点的直线, 形成">"
        public void drawArrow(Point tmp, int xOffset, int yOffset, int direction, Graphics g) {
            Point dstUp = new Point(tmp), dstDown = new Point(tmp);
            switch(direction ) {
                case Tools.LEFT:
                    dstUp.x += xOffset; dstUp.y -= yOffset;
                    dstDown.x += xOffset; dstDown.y += yOffset;
                    break;
                case Tools.UP:
                    dstUp.x -= xOffset; dstUp.y += yOffset;
                    dstDown.x += xOffset; dstDown.y += yOffset;
                    break;
                case Tools.RIGHT:
                    dstUp.x -= xOffset; dstUp.y -= yOffset;
                    dstDown.x -= xOffset; dstDown.y += yOffset;
                    break;
                case Tools.DOWN:
                    dstUp.x -= xOffset; dstUp.y -= yOffset;
                    dstDown.x += xOffset; dstDown.y -= yOffset;
                    break;
                default:
                throw new RuntimeException("have no this direction....");
            }
            g.drawLine(tmp.x, tmp.y, dstUp.x, dstUp.y);
            g.drawLine(tmp.x, tmp.y, dstDown.x, dstDown.y);
        }
    }

3 下载链接 [包含图片, 源码] :


04 迷宫寻路_结点_02

运行时截图 :

04 迷宫寻路_迷宫寻路_03

注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!