【问题标题】:Shortest path to solve a maze解决迷宫的最短路径
【发布时间】:2015-07-23 01:05:11
【问题描述】:

这个类使用图形来打印迷宫。该程序将运行 3 次。在第一个之后,我保存了一个 steps 数组,它计算角色跨过一个点的次数。然后我将此数组写入文件。在下一次运行之前,我打开文件。我需要使用文件的内容来“学习”下一次试验不去哪里(即避免死胡同,找到最快的完成路径)。

我有一个方法 hasBadBranch() ,我想为所有具有分支(交叉点的北、南、东和西)且“步数”> 1 的交叉点返回 true。我正在获取数组索引当我在我的 solve() 方法中添加 hasBadBranch 条件时,边界和角色不再正确地穿过迷宫。如果有人发现我的逻辑有任何缺陷,我将非常感谢您的反馈。谢谢。

public class Maze extends JFrame {

    private static final int MAX_WIDTH = 255;
    private static final int MAX_HEIGHT = 255;

    private char[][] maze = new char[MAX_HEIGHT][MAX_WIDTH];
    private int[][] steps = new int[MAX_HEIGHT][MAX_WIDTH];

    private Random random = new Random();
    private JPanel mazePanel = new JPanel();
    private int width = 0;
    private int height = 0;
    private boolean step = false;

    private boolean timerFired = false;
    private Timer timer;
    private final int TIMER_DELAY = 20;

    private final int SPRITE_WIDTH = 25;
    private final int SPRITE_HEIGHT = 25;

    private BufferedImage mazeImage;
    private ImageIcon ground = new ImageIcon("sprites/ground.png");
    private ImageIcon wall1 = new ImageIcon("sprites/cactus.png");
    private ImageIcon wall2 = new ImageIcon("sprites/rock.png");
    private ImageIcon finish = new ImageIcon("sprites/well.png");
    private ImageIcon south1 = new ImageIcon("sprites/cowboy-forward-1.png");
    private ImageIcon south2 = new ImageIcon("sprites/cowboy-forward-2.png");
    private ImageIcon north1 = new ImageIcon("sprites/cowboy-back-1.png");
    private ImageIcon north2 = new ImageIcon("sprites/cowboy-back-2.png");
    private ImageIcon west1 = new ImageIcon("sprites/cowboy-left-1.png");
    private ImageIcon west2 = new ImageIcon("sprites/cowboy-left-2.png");
    private ImageIcon east1 = new ImageIcon("sprites/cowboy-right-1.png");
    private ImageIcon east2 = new ImageIcon("sprites/cowboy-right-2.png");

    private long startTime;
    private long currentTime;

    private static final int MAX_TIME = 500000;

    /**
     * Constructor for class Maze. Opens a text file containing the maze, then
     * attempts to solve it.
     *
     * @param fname String value containing the filename of the maze to open.
     */
    //Notes to user: 
    //delete steps.txt BEFORE testing begins and AFTER third run of program
    //use appropriate file paths
    public Maze(String fname) throws FileNotFoundException, IOException {
        openMaze(fname);
        mazeImage = printMaze();

        readInFile();

        timer = new Timer(TIMER_DELAY, new TimerHandler());     // setup a Timer to slow the animation down.
        timer.start();

        addWindowListener(new WindowHandler());     // listen for window event windowClosing

        setTitle("Cowboy Maze");
        setSize(width * SPRITE_WIDTH + 10, height * SPRITE_HEIGHT + 30);
        setVisible(true);

        add(mazePanel);
        setContentPane(mazePanel);

        solveMaze();
    }

    public void readInFile() throws FileNotFoundException, IOException {
        //Note to user: adjust file path accordingly
        File stepsFile = new File("C:\\Users\\Ashley Bertrand\\Desktop\\Data Structures\\Lab6\\mazeStartSourceMazesGraphics\\steps.txt");

        if (stepsFile.isFile()) {
            Scanner scanner = new Scanner(stepsFile);
            int lineCount = 0;
            while (scanner.hasNextLine()) {
                String[] currentLine = scanner.nextLine().trim().split("\\s+");
                for (int i = 0; i < currentLine.length; i++) {
                    steps[lineCount][i] = Integer.parseInt(currentLine[i]);
                }
                lineCount++;
            }

            System.out.println("Contents of steps.txt:");
            for (int m = 0; m < width; m++) {
                for (int n = 0; n < height; n++) {
                    System.out.print(steps[m][n]);
                }
                System.out.println();
            }
            System.out.println();
        } else {
            System.out.println("Running first trial so steps.txt does not exist");
        }
    }

    /**
     * Called from the operating system. If no command line arguments are
     * supplied, the method displays an error message and exits. Otherwise, a
     * new instance of Maze() is created with the supplied filename from the
     * command line.
     *
     * @param args[] Command line arguments, the first of which should be the
     * filename to open.
     */
    public static void main(String[] args) throws FileNotFoundException, IOException {
        int runny = 1;
        if (args.length > 0) {
            new Maze(args[0]);
        } else {
            System.out.println();
            System.out.println("Usage: java Maze <filename>.");
            System.out.println("Maximum Maze size:" + MAX_WIDTH + " x " + MAX_HEIGHT + ".");
            System.out.println();
            System.exit(1);
        }
    }

    /**
     * Finds the starting location and passes it to the recursive algorithm
     * solve(x, y, facing). The starting location should be the only '.' on the
     * outer wall of the maze.
     */
    public void solveMaze() throws FileNotFoundException {
        boolean startFound = false;
        if (!startFound) {
            for (int i = 0; i < width; i++) {       // look for the starting location on the top and bottom walls of the Maze.
                if (maze[0][i] == '.') {
                    maze[0][i] = 'S';
                    steps[0][i]++;
                    preSolve(i, 0, "south");
                    startFound = true;
                } else if (maze[height - 1][i] == '.') {
                    maze[height - 1][i] = 'S';
                    steps[height - 1][i]++;
                    preSolve(i, height - 1, "north");
                    startFound = true;
                }
            }
        }
        if (!startFound) {
            for (int i = 0; i < height; i++) {      // look for the starting location on the left and right walls of the Maze.
                if (maze[i][0] == '.') {
                    maze[i][0] = 'S';
                    steps[i][0]++;
                    preSolve(0, i, "east");
                    startFound = true;
                } else if (maze[i][width - 1] == '.') {
                    maze[i][width - 1] = 'S';
                    steps[i][width - 1]++;
                    preSolve(width - 1, i, "west");
                    startFound = true;
                }
            }
        }
        if (!startFound) {
            System.out.println("Start not found!");
        }
    }

    public void preSolve(int x, int y, String facing) throws FileNotFoundException {
        Scanner input = new Scanner(System.in);
        System.out.println("Press 1 to start");
        input.nextLine();
        startTime = System.currentTimeMillis();
        solve(x, y, facing);
    }

    /**
     * Recursive algorithm to solve a Maze. Places an X at locations already
     * visited. This algorithm is very inefficient, it follows the right hand
     * wall and will never find the end if the path leads it in a circle.
     *
     * @param x int value of the current X location in the Maze.
     * @param y int value of the current Y location in the Maze.
     * @param facing String value holding one of four cardinal directions
     * determined by the current direction facing.
     */
    private void solve(int x, int y, String facing) throws FileNotFoundException {
        Graphics2D g2 = (Graphics2D) mazePanel.getGraphics(); //don't mess with the next 

        while (!timerFired) {   // wait for the timer.
            try {
                Thread.sleep(10);
            } catch (Exception e) {
            }
        }
        timerFired = false;
        currentTime = System.currentTimeMillis();
        if ((currentTime - startTime) > MAX_TIME) {
            closingMethod();
        }

        if (maze[y][x] != 'F') {  //this is if it doesn't find the finish on a turn.........
            g2.drawImage(mazeImage, null, 0, 0);
            g2.drawImage(printGuy(facing), x * SPRITE_WIDTH, y * SPRITE_HEIGHT, null, null);
            mazePanel.setSize(width * SPRITE_WIDTH + 10, height * SPRITE_HEIGHT + 30);
            maze[y][x] = 'X';   // mark this spot as visited. This is how you can keep track of where you've been. 

            if (facing.equals("east")) {
                if (maze[y + 1][x] != '#' && maze[y + 1][x] != '%' && maze[y + 1][x] != 'S' && !hasBadBranch(y+1, x)) {
                    steps[y + 1][x]++;
                    solve(x, y + 1, "south");
                } else if (maze[y][x + 1] != '#' && maze[y][x + 1] != '%' && maze[y][x + 1] != 'S' && !hasBadBranch(y, x+1)) {
                    steps[y][x + 1]++;
                    solve(x + 1, y, "east");
                } else {
                    solve(x, y, "north");
                }
            } else if (facing.equals("west")) {
                if (maze[y - 1][x] != '#' && maze[y - 1][x] != '%' && maze[y - 1][x] != 'S' && !hasBadBranch(y-1, x)) {
                    steps[y - 1][x]++;
                    solve(x, y - 1, "north");
                } else if (maze[y][x - 1] != '#' && maze[y][x - 1] != '%' && maze[y][x - 1] != 'S'&& !hasBadBranch(y, x-1)) {
                    steps[y][x - 1]++;
                    solve(x - 1, y, "west");
                } else {
                    solve(x, y, "south");
                }
            } else if (facing.equals("south")) {
                if (maze[y][x - 1] != '#' && maze[y][x - 1] != '%' && maze[y][x - 1] != 'S' && !hasBadBranch(y, x-1)) {
                    steps[y][x - 1]++;
                    solve(x - 1, y, "west");
                } else if (maze[y + 1][x] != '#' && maze[y + 1][x] != '%' && maze[y + 1][x] != 'S' && !hasBadBranch(y+1, x)) {
                    steps[y + 1][x]++;
                    solve(x, y + 1, "south");
                } else {
                    solve(x, y, "east");
                }
            } else if (facing.equals("north")) {
                if (maze[y][x + 1] != '#' && maze[y][x + 1] != '%' && maze[y][x + 1] != 'S' && !hasBadBranch(y, x+1)) {
                    steps[y][x + 1]++;
                    solve(x + 1, y, "east");
                } else if (maze[y - 1][x] != '#' && maze[y - 1][x] != '%' && maze[y - 1][x] != 'S' && !hasBadBranch(y-1, x)) {
                    steps[y - 1][x]++;
                    solve(x, y - 1, "north");
                } else {
                    solve(x, y, "west");
                }
            }

        } else {
            writeToFile();
            System.out.println("Found the finish!");

            currentTime = System.currentTimeMillis();
            long endTime = currentTime - startTime;
            long finalTime = endTime / 1000;
            System.out.println("Final Time = " + finalTime);

        }
    }

    public boolean hasBadBranch(int y, int x) {
        //9999 will be used to tell character not to take that branch
        if (steps[y][x] > 1) {
            if (steps[y + 1][x] > 1) {
                steps[y + 1][x] = 9999;
                return true;    //south
            }
            if (steps[y - 1][x] > 1) {
                steps[y - 1][x] = 9999;
                return true;    //north
            }
            if (steps[y][x + 1] > 1) {
                steps[y][x + 1] = 9999;
                return true;    //east
            }
            if (steps[y][x - 1] > 1) {
                steps[y][x - 1] = 9999;
                return true;    //west
            }
        }
        return false;
    }

    /**
     * Opens a text file containing a maze and stores the data in the 2D char
     * array maze[][].
     *
     * @param fname String value containing the file name of the maze to open.
     */
    public void openMaze(String fname) {
        String in = "";
        int i = 0;
        try {
            Scanner sc = new Scanner(new File(fname));
            while (sc.hasNext()) {
                in = sc.nextLine();
                in = trimWhitespace(in);
                if (in.length() <= MAX_WIDTH && i < MAX_HEIGHT) {
                    for (int j = 0; j < in.length(); j++) {
                        if (in.charAt(j) == '#') {      // if this spot is a wall, randomize the wall peice to display
                            if (random.nextInt(2) == 0) {
                                maze[i][j] = '#';
                            } else {
                                maze[i][j] = '%';
                            }
                        } else {
                            maze[i][j] = in.charAt(j);
                        }
                    }
                } else {
                    System.out.println("Maximum maze size exceeded: (" + MAX_WIDTH + " x " + MAX_HEIGHT + ")!");
                    System.exit(1);
                }
                i++;
            }
            width = in.length();
            height = i;
            System.out.println("(" + width + " x " + height + ") maze opened.");
            System.out.println();
            sc.close();
        } catch (FileNotFoundException e) {
            System.err.println("File not found: " + e);
        }
    }

    /**
     * Removes white space from the supplied string and returns the trimmed
     * String.
     *
     * @param s String value to strip white space from.
     * @return String stripped of white space.
     */
    public String trimWhitespace(String s) {
        String newString = "";
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) != ' ') {
                newString += s.charAt(i);
            }
        }
        return newString;
    }

    /**
     * Returns the sprite facing the direction supplied.
     *
     * @param facing String value containing 1 of 4 cardinal directions to make
     * the sprite face.
     * @return Image of the sprite facing the proper direction.
     */
    private Image printGuy(String facing) {
        if (facing.equals("south")) {  // draw sprite facing south
            if (step) {
                step = false;
                return south1.getImage();
            } else {
                step = true;
                return south2.getImage();
            }
        } else if (facing.equals("north")) {  // draw sprite facing north
            if (step) {
                step = false;
                return north1.getImage();
            } else {
                step = true;
                return north2.getImage();
            }
        } else if (facing.equals("east")) {  // draw sprite facing east
            if (step) {
                step = false;
                return east1.getImage();
            } else {
                step = true;
                return east2.getImage();
            }
        } else if (facing.equals("west")) {  // draw sprite facing west
            if (step) {
                step = false;
                return west1.getImage();
            } else {
                step = true;
                return west2.getImage();
            }
        }
        return null;
    }

    /**
     * Prints the Maze using sprites.
     *
     * @return BufferedImage rendition of the maze.
     */
    public BufferedImage printMaze() {
        BufferedImage mi = new BufferedImage(width * SPRITE_WIDTH, height * SPRITE_HEIGHT, BufferedImage.TYPE_INT_ARGB);
        Graphics g2 = mi.createGraphics();

        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                if (maze[i][j] == '#') {    // draw wall
                    g2.drawImage(wall1.getImage(), j * SPRITE_WIDTH, i * SPRITE_HEIGHT, null, null);
                } else if (maze[i][j] == '%') {   // draw wall
                    g2.drawImage(wall2.getImage(), j * SPRITE_WIDTH, i * SPRITE_HEIGHT, null, null);
                } else if (maze[i][j] == '.' || maze[i][j] == 'X') {  // draw ground
                    g2.drawImage(ground.getImage(), j * SPRITE_WIDTH, i * SPRITE_HEIGHT, null, null);
                } else if (maze[i][j] == 'F') {   // draw finish
                    g2.drawImage(finish.getImage(), j * SPRITE_WIDTH, i * SPRITE_HEIGHT, null, null);
                }
            }
        }
        return mi;
    }

    public void writeToFile() throws FileNotFoundException {
        PrintWriter printWriter = new PrintWriter("steps.txt");
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                printWriter.print(steps[i][j] + " ");
            }
            printWriter.println();
        }
        printWriter.close();
    }

    public void closingMethod() {
        long endTime = currentTime - startTime;
        long finalTime = endTime / 100;
        System.exit(0);
    }

    private class TimerHandler implements ActionListener {

        public void actionPerformed(ActionEvent e) {
            timerFired = true;
        }
    }

    private class WindowHandler extends WindowAdapter {

        public void windowClosing(WindowEvent e) {
            removeAll();
            closingMethod();
            System.exit(0);
        }
    }

}

【问题讨论】:

  • 你为什么决定用这种方式解迷宫?这不会给你最佳答案。您应该改用广度优先搜索。
  • 我的教授给了我们这个解迷宫的大致大纲 3 次,每次都节省你学到的东西,所以第三次尝试你可以通过直接路径来解决它。
  • 我了解广度优先搜索的工作原理,但您如何将其应用到这样的迷宫中? @好准
  • @CSstudent 嘿,你的问题是什么?你知道什么需要保存到文件中吗?我看到了一个openMaze 方法,但您似乎还没有保存任何东西?
  • @CSstudent 顺便说一句,没有输入文件和图像资源,我们实际上无法运行您的程序并查看您在做什么。你能把你的问题缩小到一个具体的问题吗?

标签: java maze


【解决方案1】:

基本上,您所做的是使用具有固定探索规则(右手)的 DFS 来解决迷宫问题。您应该保存的信息是您踩到正方形的次数。当您遇到死胡同时,您将返回十字路口尝试另一个分支。

所以在第二次求解时,当你找到一个分数 > 1 的交集时,你应该修改探索规则以避免分数 > 1 的分支(1 意味着你永远不必回来 == 先前的解决方案路径,2 (或更多)意味着您必须在死胡同(或几个)之后回来)。希望这是有道理的。

【讨论】:

  • BFS 不会更快地找到最短的解决方案吗?
  • 感谢@softwarenewbie7331。我正在尝试创建一种方法来检查交集并查看其任何分支是否> 1,如果是,则返回true。我所拥有的似乎不起作用(数组越界,角色不再正确地穿过迷宫)。看到我的逻辑有任何缺陷吗? public boolean badIntersection(int y, int x) { if (steps[y][x] > 1) { if (steps[y + 1][x] > 1) { steps[y + 1][x] = 9999 ; //标记不应该采用的路径 return true; //南 } ... } }
  • 在我的求解方法中,我添加了: if ( faces.equals("east")) { if (maze[y + 1][x] != '#' && maze[y + 1][x] != '%') && !isBadIntersection(y+1, x)) { 步骤[y + 1][x]++;解决(x,y + 1,“南”); } else if (maze[y][x + 1] != '#' && maze[y][x + 1] != '%' && !isBadIntersection(y, x+1)) { steps[y][ x + 1]++;解决(x + 1,y,“东”); } else { 解决(x,y,“北”); } ... }
  • 您应该将其放在问题中,以便其他人可以阅读/帮助。出界显然是因为您需要检查右边界和下边界。不确定第二个评论代码在做什么。 @M.Shaw 我假设他一直在使用他的教授想要的东西(比如 dfs)
猜你喜欢
  • 2018-03-24
  • 1970-01-01
  • 1970-01-01
  • 2012-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多