diff --git a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java index 00c3fedca71f..c818aa8c888c 100644 --- a/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java +++ b/src/main/java/com/thealgorithms/backtracking/MazeRecursion.java @@ -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() { @@ -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); } /** @@ -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); } /** @@ -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; } @@ -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; } diff --git a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java index 7d8a0c66c262..7af4c9ea7fca 100644 --- a/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java +++ b/src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java @@ -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)); + } }