Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 28 additions & 19 deletions src/main/java/com/thealgorithms/backtracking/MazeRecursion.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,19 @@
* ends are marked with different integers.
*/
public final class MazeRecursion {
private static final int OPEN = 0;
private static final int PATH = 2;
private static final int DEAD_END = 3;

private static final int DEFAULT_START_ROW = 1;
private static final int DEFAULT_START_COLUMN = 1;
private static final int DEFAULT_GOAL_ROW = 6;
private static final int DEFAULT_GOAL_COLUMN = 5;

// Each entry is {rowDelta, colDelta}
// down -> right -> up -> left
private static final int[][] FIRST_STRATEGY_DIRECTIONS = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
// up -> right -> down -> left
private static final int[][] SECOND_STRATEGY_DIRECTIONS = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};

private MazeRecursion() {
Expand All @@ -21,14 +33,7 @@ private MazeRecursion() {
* @throws IllegalArgumentException if the maze is null, empty, or not rectangular
*/
public static int[][] solveMazeUsingFirstStrategy(int[][] map) {
validateMaze(map);
if (!isInBounds(map, 1, 1) || !isInBounds(map, 6, 5)) {
return null;
}
if (map[6][5] == 2) {
return map;
}
return solve(map, 1, 1, 6, 5, FIRST_STRATEGY_DIRECTIONS) ? map : null;
return solveWithDefaultEndpoints(map, FIRST_STRATEGY_DIRECTIONS);
}

/**
Expand All @@ -40,14 +45,7 @@ public static int[][] solveMazeUsingFirstStrategy(int[][] map) {
* @throws IllegalArgumentException if the maze is null, empty, or not rectangular
*/
public static int[][] solveMazeUsingSecondStrategy(int[][] map) {
validateMaze(map);
if (!isInBounds(map, 1, 1) || !isInBounds(map, 6, 5)) {
return null;
}
if (map[6][5] == 2) {
return map;
}
return solve(map, 1, 1, 6, 5, SECOND_STRATEGY_DIRECTIONS) ? map : null;
return solveWithDefaultEndpoints(map, SECOND_STRATEGY_DIRECTIONS);
}

/**
Expand Down Expand Up @@ -84,12 +82,23 @@ public static int[][] solveMazeUsingSecondStrategy(int[][] map, int startRow, in
return solve(map, startRow, startColumn, goalRow, goalColumn, SECOND_STRATEGY_DIRECTIONS) ? map : null;
}

private static int[][] solveWithDefaultEndpoints(int[][] map, int[][] directions) {
validateMaze(map);
if (!isInBounds(map, DEFAULT_START_ROW, DEFAULT_START_COLUMN) || !isInBounds(map, DEFAULT_GOAL_ROW, DEFAULT_GOAL_COLUMN)) {
return null;
}
if (map[DEFAULT_GOAL_ROW][DEFAULT_GOAL_COLUMN] == PATH) {
return map;
}
return solve(map, DEFAULT_START_ROW, DEFAULT_START_COLUMN, DEFAULT_GOAL_ROW, DEFAULT_GOAL_COLUMN, directions) ? map : null;
}

private static boolean solve(int[][] map, int row, int column, int goalRow, int goalColumn, int[][] directions) {
if (!isInBounds(map, row, column) || map[row][column] != 0) {
if (!isInBounds(map, row, column) || map[row][column] != OPEN) {
return false;
}

map[row][column] = 2;
map[row][column] = PATH;
if (row == goalRow && column == goalColumn) {
return true;
}
Expand All @@ -100,7 +109,7 @@ private static boolean solve(int[][] map, int row, int column, int goalRow, int
}
}

map[row][column] = 3;
map[row][column] = DEAD_END;
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,50 @@ public void testSolveMazeUsingFirstAndSecondStrategy() {
assertArrayEquals(expectedMap1, solvedMap1);
assertArrayEquals(expectedMap2, solvedMap2);
}

@Test
void testSmallMazeWithCustomStartAndTarget() {
int[][] map = {{0, 0}, {0, 0}};
int[][] result = MazeRecursion.solveMazeUsingFirstStrategy(map, 0, 0, 1, 1);
assertArrayEquals(new int[][] {{2, 0}, {2, 2}}, result); // down -> right
}

@Test
void testSecondStrategyKeepsItsTraversalOrder() {
int[][] map = {{0, 0}, {0, 0}};
int[][] result = MazeRecursion.solveMazeUsingSecondStrategy(map, 0, 0, 1, 1);
assertArrayEquals(new int[][] {{2, 2}, {0, 2}}, result); // right -> down
}

@Test
void testDefaultTargetOutsideSmallMazeReturnsNull() {
assertNull(MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {{0, 0}, {0, 0}}));
assertNull(MazeRecursion.solveMazeUsingSecondStrategy(new int[][] {{0, 0}, {0, 0}}));
}

@Test
void testNullAndEmptyMaze() {
assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(null));
assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingSecondStrategy(new int[][] {}));
assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {{}}));
}

@Test
void testJaggedMazeThrows() {
assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {{0, 0}, {0}}, 0, 0, 0, 1));
assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingSecondStrategy(new int[][] {{0}, {0, 0}}, 0, 0, 1, 1));
}

@Test
void testStartOrTargetOutOfBoundsThrows() {
assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {{0, 0}, {0, 0}}, -1, 0, 1, 1));
assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingSecondStrategy(new int[][] {{0, 0}, {0, 0}}, 0, 0, 2, 2));
}

@Test
void testUnsolvableMazeReturnsNull() {
int[][] map = {{0, 1}, {1, 0}};
assertNull(MazeRecursion.solveMazeUsingFirstStrategy(map, 0, 0, 1, 1));
assertNull(MazeRecursion.solveMazeUsingSecondStrategy(map, 0, 0, 1, 1));
}
}
Loading