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
177 changes: 96 additions & 81 deletions src/main/java/com/thealgorithms/backtracking/MazeRecursion.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,121 +5,136 @@
* The maze is represented as a 2D array where walls, paths, and visited/dead
* ends are marked with different integers.
*
* The goal is to find a path from a starting position to the target position
* (map[6][5]) while navigating through the maze.
* Cell values: 0 = open, 1 = wall, 2 = path, 3 = dead end.
*/
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_COL = 1;
private static final int DEFAULT_TARGET_ROW = 6;
private static final int DEFAULT_TARGET_COL = 5;

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

private MazeRecursion() {
}

/**
* This method solves the maze using the "down -> right -> up -> left"
* movement strategy.
* Solves the maze from (1,1) to (6,5) using "down -> right -> up -> left".
*
* @param map The 2D array representing the maze (walls, paths, etc.)
* @return The solved maze with paths marked, or null if no solution exists.
* @param map the maze
* @return the solved maze, or null if no solution exists
* @throws IllegalArgumentException if the maze is invalid or the default
* start/target lie outside the maze
*/
public static int[][] solveMazeUsingFirstStrategy(int[][] map) {
if (setWay(map, 1, 1)) {
return map;
}
return null;
return solveMazeUsingFirstStrategy(map, DEFAULT_START_ROW, DEFAULT_START_COL, DEFAULT_TARGET_ROW, DEFAULT_TARGET_COL);
}

/**
* This method solves the maze using the "up -> right -> down -> left"
* movement strategy.
* Solves the maze from (1,1) to (6,5) using "up -> right -> down -> left".
*
* @param map The 2D array representing the maze (walls, paths, etc.)
* @return The solved maze with paths marked, or null if no solution exists.
* @param map the maze
* @return the solved maze, or null if no solution exists
* @throws IllegalArgumentException if the maze is invalid or the default
* start/target lie outside the maze
*/
public static int[][] solveMazeUsingSecondStrategy(int[][] map) {
if (setWay2(map, 1, 1)) {
return map;
}
return null;
return solveMazeUsingSecondStrategy(map, DEFAULT_START_ROW, DEFAULT_START_COL, DEFAULT_TARGET_ROW, DEFAULT_TARGET_COL);
}

/**
* Attempts to find a path through the maze using a "down -> right -> up -> left"
* movement strategy. The path is marked with '2' for valid paths and '3' for dead ends.
* Solves the maze between custom positions using "down -> right -> up -> left".
*
* @param map The 2D array representing the maze (walls, paths, etc.)
* @param i The current x-coordinate of the ball (row index)
* @param j The current y-coordinate of the ball (column index)
* @return True if a path is found to (6,5), otherwise false
* @param map the maze
* @param startRow start row index
* @param startCol start column index
* @param targetRow target row index
* @param targetCol target column index
* @return the solved maze, or null if no solution exists
* @throws IllegalArgumentException if the maze is null, empty, jagged, or
* start/target are outside the maze
*/
private static boolean setWay(int[][] map, int i, int j) {
if (map[6][5] == 2) {
return true;
}
public static int[][] solveMazeUsingFirstStrategy(int[][] map, int startRow, int startCol, int targetRow, int targetCol) {
return solve(map, startRow, startCol, targetRow, targetCol, FIRST_STRATEGY);
}

// If the current position is unvisited (0), explore it
if (map[i][j] == 0) {
// Mark the current position as '2'
map[i][j] = 2;
/**
* Solves the maze between custom positions using "up -> right -> down -> left".
*
* @param map the maze
* @param startRow start row index
* @param startCol start column index
* @param targetRow target row index
* @param targetCol target column index
* @return the solved maze, or null if no solution exists
* @throws IllegalArgumentException if the maze is null, empty, jagged, or
* start/target are outside the maze
*/
public static int[][] solveMazeUsingSecondStrategy(int[][] map, int startRow, int startCol, int targetRow, int targetCol) {
return solve(map, startRow, startCol, targetRow, targetCol, SECOND_STRATEGY);
}

// Move down
if (setWay(map, i + 1, j)) {
return true;
}
// Move right
else if (setWay(map, i, j + 1)) {
return true;
}
// Move up
else if (setWay(map, i - 1, j)) {
return true;
}
// Move left
else if (setWay(map, i, j - 1)) {
return true;
}
private static int[][] solve(int[][] map, int startRow, int startCol, int targetRow, int targetCol, int[][] directions) {
validate(map, startRow, startCol, targetRow, targetCol);
return setWay(map, startRow, startCol, targetRow, targetCol, directions) ? map : null;
}

map[i][j] = 3; // Mark as dead end (3) if no direction worked
return false;
private static void validate(int[][] map, int startRow, int startCol, int targetRow, int targetCol) {
if (map == null || map.length == 0) {
throw new IllegalArgumentException("Maze must not be null or empty");
}
int cols = map[0] == null ? 0 : map[0].length;
if (cols == 0) {
throw new IllegalArgumentException("Maze rows must not be null or empty");
}
for (int[] row : map) {
if (row == null || row.length != cols) {
throw new IllegalArgumentException("Maze must be rectangular");
}
}
if (!isInside(map, startRow, startCol)) {
throw new IllegalArgumentException("Start position is outside the maze");
}
if (!isInside(map, targetRow, targetCol)) {
throw new IllegalArgumentException("Target position is outside the maze");
}
return false;
}

private static boolean isInside(int[][] map, int row, int col) {
return row >= 0 && row < map.length && col >= 0 && col < map[0].length;
}

/**
* Attempts to find a path through the maze using an alternative movement
* strategy "up -> right -> down -> left".
*
* @param map The 2D array representing the maze (walls, paths, etc.)
* @param i The current x-coordinate of the ball (row index)
* @param j The current y-coordinate of the ball (column index)
* @return True if a path is found to (6,5), otherwise false
* Shared recursive backtracking logic used by both strategies.
* Marks the route with 2 and dead ends with 3.
*/
private static boolean setWay2(int[][] map, int i, int j) {
if (map[6][5] == 2) {
return true;
private static boolean setWay(int[][] map, int row, int col, int targetRow, int targetCol, int[][] directions) {
if (!isInside(map, row, col) || map[row][col] != OPEN) {
return false;
}

if (map[i][j] == 0) {
map[i][j] = 2;
map[row][col] = PATH;

// Move up
if (setWay2(map, i - 1, j)) {
return true;
}
// Move right
else if (setWay2(map, i, j + 1)) {
return true;
}
// Move down
else if (setWay2(map, i + 1, j)) {
return true;
}
// Move left
else if (setWay2(map, i, j - 1)) {
if (row == targetRow && col == targetCol) {
return true;
}

for (int[] direction : directions) {
if (setWay(map, row + direction[0], col + direction[1], targetRow, targetCol, directions)) {
return true;
}

map[i][j] = 3; // Mark as dead end (3) if no direction worked
return false;
}

map[row][col] = DEAD_END;
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.thealgorithms.backtracking;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -62,6 +64,51 @@

// Assert the results
assertArrayEquals(expectedMap1, solvedMap1);
assertArrayEquals(expectedMap2, solvedMap2);
assertArrayEquals(expectedMap2, solvedMap2);

@Test
void testSmallMazeWithCustomStartAndTarget() {

Check failure on line 70 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / AnalyzeJava

';' expected

Check failure on line 70 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / AnalyzeJava

';' expected

Check failure on line 70 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / build

';' expected

Check failure on line 70 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / build

';' expected
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() {

Check failure on line 77 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / AnalyzeJava

';' expected

Check failure on line 77 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / AnalyzeJava

';' expected

Check failure on line 77 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / build

';' expected

Check failure on line 77 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / build

';' expected
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 testDefaultTargetOutsideSmallMazeThrowsIllegalArgument() {

Check failure on line 84 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / AnalyzeJava

';' expected

Check failure on line 84 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / AnalyzeJava

';' expected

Check failure on line 84 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / build

';' expected

Check failure on line 84 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / build

';' expected
assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {{0, 0}, {0, 0}}));
}

@Test
void testNullAndEmptyMaze() {

Check failure on line 89 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / AnalyzeJava

';' expected

Check failure on line 89 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / build

';' expected
assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(null));
assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingSecondStrategy(new int[][] {}));
assertThrows(IllegalArgumentException.class, () -> MazeRecursion.solveMazeUsingFirstStrategy(new int[][] {{}}));
}

@Test
void testJaggedMazeThrows() {

Check failure on line 96 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / AnalyzeJava

';' expected

Check failure on line 96 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / build

';' expected
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() {

Check failure on line 102 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / AnalyzeJava

';' expected

Check failure on line 102 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / build

';' expected
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() {

Check failure on line 108 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / AnalyzeJava

';' expected

Check failure on line 108 in src/test/java/com/thealgorithms/backtracking/MazeRecursionTest.java

View workflow job for this annotation

GitHub Actions / build

';' expected
int[][] map = {{0, 1}, {1, 0}};
assertNull(MazeRecursion.solveMazeUsingFirstStrategy(map, 0, 0, 1, 1));
}
}
}
}

Loading