import java.util.Iterator; import java.util.LinkedHashSet; public class MazeSolver { private Maze maze; private Coordinate start; private LinkedHashSet route; private boolean solvable = false; private Coordinate currPos; private boolean done = false; private static final int[] DIRECTIONS = {-2, -1, 2, 1}; public MazeSolver(Maze maze, Coordinate start) { if (!maze.isPassable(start) || maze.isOffLimits(start)) { throw new IllegalArgumentException("Invalid starting point: " + start); } this.maze = maze; this.start = start; this.route = new LinkedHashSet(); } public void solve() { route.add(start); currPos = start; int lastDirIdx = 1; while (!done) { boolean moved = false; // always try to keep a wall on the left hand side // lastDirIdx - 1 is the index for the direction to turn left from current facing for (int i = -1; i < DIRECTIONS.length - 1; i++) { // + 4 to stay positive. int dirIdx = (lastDirIdx + i + 4) % DIRECTIONS.length; //System.out.println(lastDirIdx + "/" + DIRECTIONS[lastDirIdx] + ", " + dirIdx + "/" + DIRECTIONS[dirIdx]); if (tryNext(currPos.move(DIRECTIONS[dirIdx]))) { lastDirIdx = dirIdx; moved = true; break; } } // this deals with the special case of an isolated starting point. if (!moved) { done = true; } } } private boolean tryNext(Coordinate next) { System.out.print("trying: " + next + ": "); if (maze.isOffLimits(next) || !maze.isPassable(next)) { System.out.println("impassable"); return false; } if (maze.isExit(next.x, next.y)) { if (!next.equals(start)) { // solved route.add(next); solvable = true; } done = true; } else { // remove possible loops boolean deleteMode = false; for (Iterator iCoord = route.iterator(); iCoord.hasNext(); ) { Coordinate c = iCoord.next(); if (deleteMode) { iCoord.remove(); } else if (c.equals(next)) { iCoord.remove(); deleteMode = true; System.out.print("backtracing to " + next + "... "); } } route.add(next); currPos = next; } System.out.println("moved"); return true; } public static class Coordinate { public int x; public int y; public Coordinate(int x, int y) { this.x = x; this.y = y; } @Override public String toString() { return "(" + x + ", " + y + ")"; } @Override public boolean equals(Object obj) { return ((Coordinate)obj).x == x && ((Coordinate)obj).y == y; } @Override public int hashCode() { final int PRIME = 31; int result = 1; result = PRIME * result + x; result = PRIME * result + y; return result; } public Coordinate move(int direction) { switch (direction) { case -2: // left return new Coordinate(x - 1, y); case -1: // up return new Coordinate(x, y - 1); case 1: // down return new Coordinate(x, y + 1); case 2: // right return new Coordinate(x + 1, y); default: throw new IllegalArgumentException("Invalid direction: " + direction); } } } public static class Maze { private int[] grid; private int height; private int length; public Maze(int[] grid, int length) { this.grid = grid; this.length = length; height = grid.length / length; } public boolean isPassable(Coordinate c) { return grid[c.y * length + c.x] == 1; } public boolean isExit(int x, int y) { return x == 0 || y == 0 || x == (length - 1) || y == height - 1; } public boolean isOffLimits(Coordinate c) { return c.x < 0 || c.y < 0 || c.x >= length || c.y >= height; } public int getLength() { return length; } public int getHeight() { return height; } public int getCell(int x, int y) { return grid[y * length + x]; } } // all test code from here down public void printRoute() { System.out.println("Solvable: " + solvable); for (int y = 0; y < maze.getHeight(); y++) { for (int x = 0; x < maze.getLength(); x++) { Coordinate c = new Coordinate(x, y); if (route.contains(c)) { if (c.equals(start)) { System.out.print('s'); } else { System.out.print('x'); } } else { System.out.print(maze.getCell(x, y)); } } System.out.println(); } } public static void main(String[] args) { // "2" marks the starting point. testMaze(new int[] { 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0 }, 4); testMaze(new int[] { 0, 0, 0, 0, 2, 1, 1, 0, 0, 0, 0, 0 }, 4); testMaze(new int[] { 0, 0, 0, 2, 1, 0, 0, 1, 0 }, 3); testMaze(new int[] { 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, }, 5); testMaze(new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 2, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0 }, 6); testMaze(new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 }, 7); } public static void testMaze(int[] maze, int length) { Coordinate start = null; for (int i = 0; i < maze.length; i++) { if (maze[i] == 2) { maze[i] = 1; start = new Coordinate(i % (i / length), i / length); } } System.out.println("Starting at: " + start); MazeSolver ms = new MazeSolver(new Maze(maze, length), start); ms.solve(); ms.printRoute(); } }