|  | 
|  | 1 | +import java.util.Random; | 
|  | 2 | +import java.util.random.RandomGenerator; | 
|  | 3 | +import java.util.BitSet; | 
|  | 4 | +import java.util.EnumSet; | 
|  | 5 | +import java.util.Set; | 
|  | 6 | + | 
|  | 7 | +import static java.util.stream.IntStream.range; | 
|  | 8 | + | 
|  | 9 | +public class MazeGenerator { | 
|  | 10 | + | 
|  | 11 | +    public char[][] generatePerfectMaze(int rows, int columns) { | 
|  | 12 | +        validateDimensions(rows, columns); | 
|  | 13 | +        return new Grid(new Dimensions(rows, columns), RandomGenerator.getDefault()) | 
|  | 14 | +                .generateMaze() | 
|  | 15 | +                .placeDoors() | 
|  | 16 | +                .print(); | 
|  | 17 | +    } | 
|  | 18 | + | 
|  | 19 | +    public char[][] generatePerfectMaze(int rows, int columns, int seed) { | 
|  | 20 | +        validateDimensions(rows, columns); | 
|  | 21 | +        return new Grid(new Dimensions(rows, columns), new Random(seed)) | 
|  | 22 | +                .generateMaze() | 
|  | 23 | +                .placeDoors() | 
|  | 24 | +                .print(); | 
|  | 25 | +    } | 
|  | 26 | + | 
|  | 27 | +    private void validateDimensions(int rows, int columns) { | 
|  | 28 | +        if (rows < 5 || columns < 5 || rows > 100 || columns > 100) { | 
|  | 29 | +            throw new IllegalArgumentException("Dimensions must be in range."); | 
|  | 30 | +        } | 
|  | 31 | +    } | 
|  | 32 | +} | 
|  | 33 | + | 
|  | 34 | +enum Direction { | 
|  | 35 | +    NORTH(0, 1), | 
|  | 36 | +    EAST(1, 0), | 
|  | 37 | +    SOUTH(0, -1), | 
|  | 38 | +    WEST(-1, 0); | 
|  | 39 | +    private final int dx; | 
|  | 40 | +    private final int dy; | 
|  | 41 | + | 
|  | 42 | +    Direction(int dx, int dy) { | 
|  | 43 | +        this.dx = dx; | 
|  | 44 | +        this.dy = dy; | 
|  | 45 | +    } | 
|  | 46 | + | 
|  | 47 | +    public int dx() { | 
|  | 48 | +        return dx; | 
|  | 49 | +    } | 
|  | 50 | + | 
|  | 51 | +    public int dy() { | 
|  | 52 | +        return dy; | 
|  | 53 | +    } | 
|  | 54 | +} | 
|  | 55 | + | 
|  | 56 | +final class Grid { | 
|  | 57 | +    private final Dimensions dimensions; | 
|  | 58 | +    private final BitSet grid; | 
|  | 59 | +    private final RandomGenerator randomGenerator; | 
|  | 60 | + | 
|  | 61 | +    Grid(Dimensions dimensions, RandomGenerator randomGenerator) { | 
|  | 62 | +        this.dimensions = dimensions; | 
|  | 63 | +        this.grid = new BitSet(dimensions.width() * dimensions.height()); | 
|  | 64 | +        this.randomGenerator = randomGenerator; | 
|  | 65 | +    } | 
|  | 66 | + | 
|  | 67 | +    Grid generateMaze() { | 
|  | 68 | +        generate(new Cell(1, 1)); | 
|  | 69 | +        return this; | 
|  | 70 | +    } | 
|  | 71 | + | 
|  | 72 | +    private int random(int bound) { | 
|  | 73 | +        return randomGenerator.nextInt(bound); | 
|  | 74 | +    } | 
|  | 75 | + | 
|  | 76 | +    private Direction pickRandomDirection(Set<Direction> directions) { | 
|  | 77 | +        int size = directions.size(); | 
|  | 78 | +        int itemIndex = random(size); | 
|  | 79 | +        var direction = directions.toArray(new Direction[size])[itemIndex]; | 
|  | 80 | +        directions.remove(direction); | 
|  | 81 | +        return direction; | 
|  | 82 | +    } | 
|  | 83 | + | 
|  | 84 | +    Grid placeDoors() { | 
|  | 85 | +        new Cell(1 + 2 * random(dimensions.rows()), 0).erase(); | 
|  | 86 | +        new Cell(1 + 2 * random(dimensions.rows()), dimensions.width() - 1).erase(); | 
|  | 87 | +        return this; | 
|  | 88 | +    } | 
|  | 89 | + | 
|  | 90 | +    private void generate(Cell cell) { | 
|  | 91 | +        cell.erase(); | 
|  | 92 | + | 
|  | 93 | +        var directions = EnumSet.allOf(Direction.class); | 
|  | 94 | +        do { | 
|  | 95 | +            var direction = pickRandomDirection(directions); | 
|  | 96 | +            var wall = cell.move(direction); | 
|  | 97 | +            var next = wall.move(direction); | 
|  | 98 | +            if (next.isValid() && next.isNotEmpty()) { | 
|  | 99 | +                wall.erase(); | 
|  | 100 | +                generate(next); | 
|  | 101 | +            } | 
|  | 102 | +        } while (!directions.isEmpty()); | 
|  | 103 | +    } | 
|  | 104 | + | 
|  | 105 | +    char[][] print() { | 
|  | 106 | +        return range(0, dimensions.height()) | 
|  | 107 | +                .mapToObj(this::line) | 
|  | 108 | +                .toArray(char[][]::new); | 
|  | 109 | +    } | 
|  | 110 | + | 
|  | 111 | +    private char[] line(int x) { | 
|  | 112 | +        return range(0, dimensions.width()) | 
|  | 113 | +                .map(y -> new Cell(x, y).symbol()) | 
|  | 114 | +                .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) | 
|  | 115 | +                .toString() | 
|  | 116 | +                .toCharArray(); | 
|  | 117 | +    } | 
|  | 118 | + | 
|  | 119 | +    private final class Cell { | 
|  | 120 | +        final int x; | 
|  | 121 | +        final int y; | 
|  | 122 | + | 
|  | 123 | +        private Cell(int x, int y) { | 
|  | 124 | +            this.x = x; | 
|  | 125 | +            this.y = y; | 
|  | 126 | +        } | 
|  | 127 | + | 
|  | 128 | +        boolean isValid() { | 
|  | 129 | +            return x > 0 && x < dimensions.height() && y > 0 && y < dimensions.width(); | 
|  | 130 | +        } | 
|  | 131 | + | 
|  | 132 | +        void erase() { | 
|  | 133 | +            grid.set(index()); | 
|  | 134 | +        } | 
|  | 135 | + | 
|  | 136 | +        boolean isNotEmpty() { | 
|  | 137 | +            return !isEmpty(); | 
|  | 138 | +        } | 
|  | 139 | + | 
|  | 140 | +        boolean isEmpty() { | 
|  | 141 | +            return grid.get(index()); | 
|  | 142 | +        } | 
|  | 143 | + | 
|  | 144 | +        int index() { | 
|  | 145 | +            return x * dimensions.width() + y; | 
|  | 146 | +        } | 
|  | 147 | + | 
|  | 148 | +        boolean isDoor() { | 
|  | 149 | +            return isEmpty() && (y == 0 || y == dimensions.width() - 1); | 
|  | 150 | +        } | 
|  | 151 | + | 
|  | 152 | +        Cell move(Direction direction) { | 
|  | 153 | +            return new Cell(x + direction.dx(), y + direction.dy()); | 
|  | 154 | +        } | 
|  | 155 | + | 
|  | 156 | +        char symbol() { | 
|  | 157 | +            if (isDoor()) { | 
|  | 158 | +                return '⇨'; | 
|  | 159 | +            } | 
|  | 160 | +            if (isEmpty()) { | 
|  | 161 | +                return ' '; | 
|  | 162 | +            } | 
|  | 163 | +            var n = x > 0 && new Cell(x - 1, y).isNotEmpty() ? 1 : 0; | 
|  | 164 | +            var e = y < dimensions.width() - 1 && new Cell(x, y + 1).isNotEmpty() ? 1 : 0; | 
|  | 165 | +            var s = x < dimensions.height() - 1 && new Cell(x + 1, y).isNotEmpty() ? 1 : 0; | 
|  | 166 | +            var w = y > 0 && new Cell(x, y - 1).isNotEmpty() ? 1 : 0; | 
|  | 167 | +            var i = n + 2 * e + 4 * s + 8 * w; | 
|  | 168 | +            return switch (i) { | 
|  | 169 | +                case 0 -> ' '; | 
|  | 170 | +                case 1, 5, 4 -> '│'; | 
|  | 171 | +                case 2, 8, 10 -> '─'; | 
|  | 172 | +                case 3 -> '└'; | 
|  | 173 | +                case 6 -> '┌'; | 
|  | 174 | +                case 7 -> '├'; | 
|  | 175 | +                case 9 -> '┘'; | 
|  | 176 | +                case 11 -> '┴'; | 
|  | 177 | +                case 12 -> '┐'; | 
|  | 178 | +                case 13 -> '┤'; | 
|  | 179 | +                case 14 -> '┬'; | 
|  | 180 | +                case 15 -> '┼'; | 
|  | 181 | +                default -> throw new IllegalStateException("Unexpected value: " + i); | 
|  | 182 | +            }; | 
|  | 183 | +        } | 
|  | 184 | +    } | 
|  | 185 | +} | 
0 commit comments