diff --git a/pom.xml b/pom.xml
index dcdec02..54216db 100644
--- a/pom.xml
+++ b/pom.xml
@@ -74,6 +74,11 @@
io.vavr
vavr
+
+ com.google.guava
+ guava
+ 33.2.1-jre
+
org.apache.pdfbox
pdfbox
diff --git a/src/main/java/ch/fritteli/maze/generator/algorithm/AbstractMazeGeneratorAlgorithm.java b/src/main/java/ch/fritteli/maze/generator/algorithm/AbstractMazeGeneratorAlgorithm.java
new file mode 100644
index 0000000..2037f76
--- /dev/null
+++ b/src/main/java/ch/fritteli/maze/generator/algorithm/AbstractMazeGeneratorAlgorithm.java
@@ -0,0 +1,20 @@
+package ch.fritteli.maze.generator.algorithm;
+
+import ch.fritteli.maze.generator.model.Maze;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public abstract class AbstractMazeGeneratorAlgorithm implements MazeGeneratorAlgorithm {
+ @NotNull
+ protected final Maze maze;
+ @NotNull
+ protected final Random random;
+
+
+ protected AbstractMazeGeneratorAlgorithm(@NotNull final Maze maze, @NotNull final String algorithmName) {
+ this.maze = maze;
+ this.random = new Random(maze.getRandomSeed());
+ this.maze.setAlgorithm(algorithmName);
+ }
+}
diff --git a/src/main/java/ch/fritteli/maze/generator/algorithm/MazeGeneratorAlgorithm.java b/src/main/java/ch/fritteli/maze/generator/algorithm/MazeGeneratorAlgorithm.java
new file mode 100644
index 0000000..0cc091a
--- /dev/null
+++ b/src/main/java/ch/fritteli/maze/generator/algorithm/MazeGeneratorAlgorithm.java
@@ -0,0 +1,5 @@
+package ch.fritteli.maze.generator.algorithm;
+
+public interface MazeGeneratorAlgorithm {
+ void run();
+}
diff --git a/src/main/java/ch/fritteli/maze/generator/algorithm/RandomDepthFirst.java b/src/main/java/ch/fritteli/maze/generator/algorithm/RandomDepthFirst.java
index a65b15e..eab53a9 100644
--- a/src/main/java/ch/fritteli/maze/generator/algorithm/RandomDepthFirst.java
+++ b/src/main/java/ch/fritteli/maze/generator/algorithm/RandomDepthFirst.java
@@ -10,20 +10,13 @@ import org.jetbrains.annotations.Nullable;
import java.util.Deque;
import java.util.LinkedList;
-import java.util.Random;
-public class RandomDepthFirst {
-
- @NotNull
- private final Maze maze;
- @NotNull
- private final Random random;
+public class RandomDepthFirst extends AbstractMazeGeneratorAlgorithm {
@NotNull
private final Deque positions = new LinkedList<>();
public RandomDepthFirst(@NotNull final Maze maze) {
- this.maze = maze;
- this.random = new Random(maze.getRandomSeed());
+ super(maze, "Random Depth First");
}
public void run() {
diff --git a/src/main/java/ch/fritteli/maze/generator/algorithm/wilson/MazeSolver.java b/src/main/java/ch/fritteli/maze/generator/algorithm/wilson/MazeSolver.java
new file mode 100644
index 0000000..f84a20c
--- /dev/null
+++ b/src/main/java/ch/fritteli/maze/generator/algorithm/wilson/MazeSolver.java
@@ -0,0 +1,62 @@
+package ch.fritteli.maze.generator.algorithm.wilson;
+
+import ch.fritteli.maze.generator.model.Direction;
+import ch.fritteli.maze.generator.model.Maze;
+import ch.fritteli.maze.generator.model.Position;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+import java.util.stream.Collectors;
+
+public class MazeSolver {
+ @NotNull
+ private final Maze maze;
+
+ MazeSolver(@NotNull final Maze maze) {
+ this.maze = maze;
+ }
+
+ void solve() {
+ final Direction directionToOuterWall = Wilson.getDirectionToOuterWall(
+ this.maze.getStart(),
+ this.maze.getWidth(),
+ this.maze.getHeight()
+ );
+
+ final List solution = this.getSolution(this.maze.getStart(), directionToOuterWall);
+ for (Position position : solution) {
+ this.maze.getTileAt(position).get().setSolution();
+ }
+ }
+
+ private List getSolution(@NotNull Position position,
+ @NotNull Direction forbidden) {
+ record PathElement(@NotNull Position position,
+ @NotNull EnumSet possibleDirections) {
+ }
+ final Stack solution = new Stack<>();
+ final EnumSet directions = this.maze.getTileAt(position).get().getOpenDirections();
+ directions.remove(forbidden);
+ PathElement head = new PathElement(position, directions);
+ solution.push(head);
+ while (!head.position.equals(this.maze.getEnd())) {
+ if (head.possibleDirections.isEmpty()) {
+ solution.pop();
+ head = solution.peek();
+ } else {
+ final Iterator iterator = head.possibleDirections.iterator();
+ final Direction direction = iterator.next();
+ iterator.remove();
+ final Position next = head.position.move(direction);
+ final EnumSet openDirections = this.maze.getTileAt(next).get().getOpenDirections();
+ openDirections.remove(direction.invert());
+ head = new PathElement(next, openDirections);
+ solution.push(head);
+ }
+ }
+ return solution.stream().map(PathElement::position).collect(Collectors.toList());
+ }
+}
diff --git a/src/main/java/ch/fritteli/maze/generator/algorithm/wilson/Path.java b/src/main/java/ch/fritteli/maze/generator/algorithm/wilson/Path.java
new file mode 100644
index 0000000..ab18ec2
--- /dev/null
+++ b/src/main/java/ch/fritteli/maze/generator/algorithm/wilson/Path.java
@@ -0,0 +1,91 @@
+package ch.fritteli.maze.generator.algorithm.wilson;
+
+import ch.fritteli.maze.generator.model.Direction;
+import ch.fritteli.maze.generator.model.Position;
+import io.vavr.collection.List;
+import io.vavr.collection.Stream;
+import io.vavr.collection.Traversable;
+import io.vavr.control.Option;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+class Path {
+ private final int width;
+ private final int height;
+ @NotNull
+ private List positions;
+
+ Path(@NotNull final Position start, int width, int height) {
+ this.positions = List.of(start);
+ this.width = width;
+ this.height = height;
+ }
+
+ @NotNull
+ Position growRandom(@NotNull final Random random) {
+ final Position position = this.nextRandomPosition(random);
+ if (this.contains(position)) {
+ this.removeLoopUpTo(position);
+ return this.growRandom(random);
+ }
+ this.positions = this.positions.prepend(position);
+ return position;
+ }
+
+ @NotNull
+ List getPositions() {
+ return this.positions;
+ }
+
+ @NotNull
+ Position getStart() {
+ return this.positions.last();
+ }
+
+ @NotNull
+ Traversable getMovesFromStart() {
+ return this.positions.reverse().sliding(2)
+ .flatMap(positions1 -> Option.when(
+ positions1.size() == 2,
+ // DEV-NOTE: .get() is safe here, because in the context of a path, there MUST be a direction
+ // from one position to the next.
+ () -> positions1.head().getDirectionTo(positions1.last()).get()
+ ));
+ }
+
+ @NotNull
+ private Position nextRandomPosition(@NotNull final Random random) {
+ final Direction randomDirection = this.getRandomDirection(random);
+ final Position nextPosition = this.positions.head().move(randomDirection);
+ if (this.isWithinBounds(nextPosition) && !nextPosition.equals(this.positions.head())) {
+ return nextPosition;
+ }
+ return this.nextRandomPosition(random);
+ }
+
+ private boolean isWithinBounds(@NotNull final Position position) {
+ return position.x() >= 0 && position.x() < this.width && position.y() >= 0 && position.y() < this.height;
+ }
+
+ private boolean contains(@NotNull final Position position) {
+ return this.positions.contains(position);
+ }
+
+ private void removeLoopUpTo(@NotNull final Position position) {
+ this.positions = this.positions.dropUntil(position::equals);
+ }
+
+ @NotNull
+ private Direction getRandomDirection(@NotNull final Random random) {
+ final Direction[] array = Direction.values();
+ return array[random.nextInt(array.length)];
+ }
+
+ @Override
+ public String toString() {
+ return Stream.ofAll(this.positions)
+ .map(position -> "(%s,%s)".formatted(position.x(), position.y()))
+ .mkString("Path[", "->", "]");
+ }
+}
diff --git a/src/main/java/ch/fritteli/maze/generator/algorithm/wilson/PathsBuilder.java b/src/main/java/ch/fritteli/maze/generator/algorithm/wilson/PathsBuilder.java
new file mode 100644
index 0000000..098ed81
--- /dev/null
+++ b/src/main/java/ch/fritteli/maze/generator/algorithm/wilson/PathsBuilder.java
@@ -0,0 +1,131 @@
+package ch.fritteli.maze.generator.algorithm.wilson;
+
+import ch.fritteli.maze.generator.model.Maze;
+import ch.fritteli.maze.generator.model.Position;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import io.vavr.Tuple;
+import io.vavr.collection.Stream;
+import io.vavr.collection.Traversable;
+import io.vavr.control.Option;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+/**
+ * This class will build paths such that in the end all fields of the maze are covered by exactly one path.
+ */
+class PathsBuilder {
+ private final int width;
+ private final int height;
+ @NotNull
+ private final Random random;
+ @NotNull
+ private final Multimap availablePositions;
+
+ PathsBuilder(@NotNull final Maze maze,
+ @NotNull final Random random) {
+ this.width = maze.getWidth();
+ this.height = maze.getHeight();
+ this.random = random;
+ this.availablePositions = HashMultimap.create(this.width, this.height);
+
+ // Initialize the available positions.
+ for (int x = 0; x < this.width; x++) {
+ for (int y = 0; y < this.height; y++) {
+ this.availablePositions.put(x, y);
+ }
+ }
+ }
+
+ /**
+ * Create all the paths such that the maze will be completely filled and every field of it will be covered by
+ * exactly one path.
+ *
+ * @return A {@link Traversable} of generated {@link Path Paths}.
+ */
+ @NotNull
+ Traversable buildPaths() {
+ this.initializeWithRandomStartingPosition();
+
+ return Stream.unfoldLeft(
+ this,
+ builder -> builder.buildPath()
+ .map(path -> {
+ builder.setPartOfMaze(path);
+ return Tuple.of(builder, path);
+ })
+ );
+ }
+
+ private void initializeWithRandomStartingPosition() {
+ this.popRandomPosition();
+ }
+
+ /**
+ * Creates one new path, if possible. If the maze is already filled, {@link io.vavr.control.Option.None} is
+ * returned.
+ *
+ * @return An {@link Option} of a new {@link Path} instance.
+ */
+ @NotNull
+ private Option buildPath() {
+ return this.initializeNewPath()
+ .map(this::growPath);
+ }
+
+ @NotNull
+ private Option initializeNewPath() {
+ return this.popRandomPosition()
+ .map(position -> new Path(position, this.width, this.height));
+ }
+
+ /**
+ * Randomly grow the {@code path} until it reaches a field that is part of the maze and return it. The resulting
+ * path will contain no loops.
+ *
+ * @param path The {@link Path} to grow.
+ * @return The final {@link Path} that reaches the maze.
+ */
+ @NotNull
+ private Path growPath(@NotNull final Path path) {
+ Position lastPosition;
+ do {
+ lastPosition = path.growRandom(this.random);
+ } while (this.isNotPartOfMaze(lastPosition));
+ return path;
+ }
+
+ private boolean isNotPartOfMaze(@NotNull final Position position) {
+ return this.availablePositions.containsEntry(position.x(), position.y());
+ }
+
+ private void setPartOfMaze(@NotNull final Position position) {
+ this.availablePositions.remove(position.x(), position.y());
+ }
+
+ private void setPartOfMaze(@NotNull final Path path) {
+ path.getPositions().forEach(this::setPartOfMaze);
+ }
+
+ /**
+ * Finds a random {@link Position}, that is not yet part of the maze, marks it as being part of the maze and returns
+ * it. If no position is available, {@link io.vavr.control.Option.None} is returned.
+ *
+ * @return An available position or {@link io.vavr.control.Option.None}.
+ */
+ @NotNull
+ private Option popRandomPosition() {
+ if (this.availablePositions.isEmpty()) {
+ return Option.none();
+ }
+
+ final Integer[] keys = this.availablePositions.keySet().toArray(Integer[]::new);
+ final int key = keys[this.random.nextInt(keys.length)];
+ final Integer[] values = this.availablePositions.get(key).toArray(Integer[]::new);
+ final int value = values[this.random.nextInt(values.length)];
+
+ this.availablePositions.remove(key, value);
+ return Option.some(new Position(key, value));
+ }
+}
diff --git a/src/main/java/ch/fritteli/maze/generator/algorithm/wilson/Wilson.java b/src/main/java/ch/fritteli/maze/generator/algorithm/wilson/Wilson.java
new file mode 100644
index 0000000..1587e6e
--- /dev/null
+++ b/src/main/java/ch/fritteli/maze/generator/algorithm/wilson/Wilson.java
@@ -0,0 +1,96 @@
+package ch.fritteli.maze.generator.algorithm.wilson;
+
+import ch.fritteli.maze.generator.algorithm.AbstractMazeGeneratorAlgorithm;
+import ch.fritteli.maze.generator.model.Direction;
+import ch.fritteli.maze.generator.model.Maze;
+import ch.fritteli.maze.generator.model.Position;
+import ch.fritteli.maze.generator.model.Tile;
+import io.vavr.collection.Traversable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * An implementation of Wilson's Algorithm.
+ * In short:
+ *
+ * - Pick random location, add to maze
+ * - While locations that are not part of the maze exist, loop:
+ *
+ * - Pick random location that's not part of the maze
+ * - Randomly walk from this location, until ...
+ *
+ * - ... either you hit the current path, forming a loop. Then remove the entire loop and continue
+ * walking.
+ * - ... or you hit a position that is part of the maze. Then add the path to the maze and start the next
+ * walk.
+ *
+ *
+ *
+ */
+public class Wilson extends AbstractMazeGeneratorAlgorithm {
+
+ public Wilson(@NotNull final Maze maze) {
+ super(maze, "Wilson");
+ }
+
+ @Nullable
+ static Direction getDirectionToOuterWall(@NotNull final Position position,
+ final int width,
+ final int height) {
+ if (position.y() == 0) {
+ return Direction.TOP;
+ }
+ if (position.y() == height - 1) {
+ return Direction.BOTTOM;
+ }
+ if (position.x() == 0) {
+ return Direction.LEFT;
+ }
+ if (position.x() == width - 1) {
+ return Direction.RIGHT;
+ }
+ return null;
+ }
+
+ @Override
+ public void run() {
+ final Traversable paths = new PathsBuilder(this.maze, this.random)
+ .buildPaths();
+
+ this.applyPathsToMaze(paths);
+ }
+
+ private void applyPathsToMaze(@NotNull final Traversable paths) {
+ this.openStartAndEndWalls();
+ paths.forEach(path -> path.getMovesFromStart()
+ .foldLeft(
+ path.getStart(),
+ (position, direction) -> {
+ this.maze.getTileAt(position).get()
+ .digTo(direction);
+ final Position next = position.move(direction);
+ this.maze.getTileAt(next).get()
+ .digTo(direction.invert());
+ return next;
+ }));
+
+ final MazeSolver solver = new MazeSolver(this.maze);
+ solver.solve();
+ }
+
+ private void openStartAndEndWalls() {
+ this.openWall(this.maze.getStart(), this.maze.getStartTile());
+ this.openWall(this.maze.getEnd(), this.maze.getEndTile());
+ }
+
+ private void openWall(@NotNull final Position position, @NotNull final Tile tile) {
+ final Direction direction = this.getDirectionToOuterWall(position);
+ tile.enableDiggingToOrFrom(direction);
+ tile.digTo(direction);
+ }
+
+ @Nullable
+ private Direction getDirectionToOuterWall(@NotNull final Position position) {
+ return getDirectionToOuterWall(position, this.maze.getWidth(), this.maze.getHeight());
+ }
+}
diff --git a/src/main/java/ch/fritteli/maze/generator/model/Direction.java b/src/main/java/ch/fritteli/maze/generator/model/Direction.java
index e50ba6c..75ee1c4 100644
--- a/src/main/java/ch/fritteli/maze/generator/model/Direction.java
+++ b/src/main/java/ch/fritteli/maze/generator/model/Direction.java
@@ -1,11 +1,14 @@
package ch.fritteli.maze.generator.model;
+import org.jetbrains.annotations.NotNull;
+
public enum Direction {
TOP,
RIGHT,
BOTTOM,
LEFT;
+ @NotNull
public Direction invert() {
return switch (this) {
case TOP -> BOTTOM;
diff --git a/src/main/java/ch/fritteli/maze/generator/model/Maze.java b/src/main/java/ch/fritteli/maze/generator/model/Maze.java
index 14d7af6..633e1b3 100644
--- a/src/main/java/ch/fritteli/maze/generator/model/Maze.java
+++ b/src/main/java/ch/fritteli/maze/generator/model/Maze.java
@@ -3,6 +3,7 @@ package ch.fritteli.maze.generator.model;
import io.vavr.control.Option;
import lombok.EqualsAndHashCode;
import lombok.Getter;
+import lombok.Setter;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
@@ -21,6 +22,9 @@ public class Maze {
private final Position start;
@Getter
private final Position end;
+ @Getter
+ @Setter
+ private String algorithm;
public Maze(final int width, final int height) {
this(width, height, System.nanoTime());
@@ -34,7 +38,11 @@ public class Maze {
this(width, height, randomSeed, new Position(0, 0), new Position(width - 1, height - 1));
}
- public Maze(final int width, final int height, final long randomSeed, @NotNull final Position start, @NotNull final Position end) {
+ public Maze(final int width,
+ final int height,
+ final long randomSeed,
+ @NotNull final Position start,
+ @NotNull final Position end) {
if (width <= 1 || height <= 1) {
throw new IllegalArgumentException("width and height must be >1");
}
@@ -71,7 +79,12 @@ public class Maze {
/**
* INTERNAL API. Exists only for deserialization. Not to be called from user code.
*/
- private Maze(@NotNull final Tile[][] field, final int width, final int height, @NotNull final Position start, @NotNull final Position end, final long randomSeed) {
+ private Maze(@NotNull final Tile[][] field,
+ final int width,
+ final int height,
+ @NotNull final Position start,
+ @NotNull final Position end,
+ final long randomSeed) {
this.field = field;
this.width = width;
this.height = height;
@@ -80,6 +93,25 @@ public class Maze {
this.end = end;
}
+ /**
+ * INTERNAL API. Exists only for deserialization. Not to be called from user code.
+ */
+ private Maze(@NotNull final Tile[][] field,
+ final int width,
+ final int height,
+ @NotNull final Position start,
+ @NotNull final Position end,
+ final long randomSeed,
+ @NotNull final String algorithm) {
+ this.field = field;
+ this.width = width;
+ this.height = height;
+ this.randomSeed = randomSeed;
+ this.algorithm = algorithm;
+ this.start = start;
+ this.end = end;
+ }
+
@NotNull
public Option getTileAt(@NotNull final Position position) {
return this.getTileAt(position.x(), position.y());
diff --git a/src/main/java/ch/fritteli/maze/generator/model/Position.java b/src/main/java/ch/fritteli/maze/generator/model/Position.java
index 4b91543..a6fa788 100644
--- a/src/main/java/ch/fritteli/maze/generator/model/Position.java
+++ b/src/main/java/ch/fritteli/maze/generator/model/Position.java
@@ -1,10 +1,12 @@
package ch.fritteli.maze.generator.model;
+import io.vavr.control.Option;
import lombok.With;
import org.jetbrains.annotations.NotNull;
@With
public record Position(int x, int y) {
+ @NotNull
public Position move(@NotNull final Direction direction) {
return switch (direction) {
case BOTTOM -> this.withY(this.y + 1);
@@ -13,4 +15,20 @@ public record Position(int x, int y) {
case TOP -> this.withY(this.y - 1);
};
}
+
+ @NotNull
+ public Option getDirectionTo(@NotNull final Position position) {
+ final int xDiff = position.x - this.x;
+ final int yDiff = position.y - this.y;
+ return switch (xDiff) {
+ case -1 -> Option.when(yDiff == 0, Direction.LEFT);
+ case 0 -> switch (yDiff) {
+ case -1 -> Option.some(Direction.TOP);
+ case 1 -> Option.some(Direction.BOTTOM);
+ default -> Option.none();
+ };
+ case 1 -> Option.when(yDiff == 0, Direction.RIGHT);
+ default -> Option.none();
+ };
+ }
}
diff --git a/src/main/java/ch/fritteli/maze/generator/model/Tile.java b/src/main/java/ch/fritteli/maze/generator/model/Tile.java
index c18ef5d..b4428f1 100644
--- a/src/main/java/ch/fritteli/maze/generator/model/Tile.java
+++ b/src/main/java/ch/fritteli/maze/generator/model/Tile.java
@@ -1,6 +1,6 @@
package ch.fritteli.maze.generator.model;
-import io.vavr.collection.Stream;
+import io.vavr.collection.Vector;
import io.vavr.control.Option;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
@@ -17,6 +17,7 @@ import java.util.Random;
@ToString
public class Tile {
final Walls walls = new Walls();
+ @EqualsAndHashCode.Exclude
boolean visited = false;
@Getter
boolean solution = false;
@@ -65,13 +66,23 @@ public class Tile {
this.walls.set(direction);
}
+ @NotNull
+ public EnumSet getOpenDirections() {
+ return this.walls.getOpen();
+ }
+
+ @NotNull
public Option getRandomAvailableDirection(@NotNull final Random random) {
- final Stream availableDirections = this.walls.getUnsealedSet();
+ final EnumSet availableDirections = this.walls.getUnsealedSet();
if (availableDirections.isEmpty()) {
return Option.none();
}
- final int index = random.nextInt(availableDirections.length());
- return Option.of(availableDirections.get(index));
+ if (availableDirections.size() == 1) {
+ return Option.some(availableDirections.iterator().next());
+ }
+ final Vector directions = Vector.ofAll(availableDirections);
+ final int index = random.nextInt(directions.size());
+ return Option.of(directions.get(index));
}
public boolean hasWallAt(@NotNull final Direction direction) {
diff --git a/src/main/java/ch/fritteli/maze/generator/model/Walls.java b/src/main/java/ch/fritteli/maze/generator/model/Walls.java
index 8a4b340..4a899c7 100644
--- a/src/main/java/ch/fritteli/maze/generator/model/Walls.java
+++ b/src/main/java/ch/fritteli/maze/generator/model/Walls.java
@@ -1,21 +1,17 @@
package ch.fritteli.maze.generator.model;
-import io.vavr.collection.Stream;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
@EqualsAndHashCode
@ToString
public class Walls {
- private final SortedSet directions = new TreeSet<>();
- private final Set sealed = new HashSet<>();
+ private final EnumSet directions = EnumSet.noneOf(Direction.class);
+ @EqualsAndHashCode.Exclude
+ private final EnumSet sealed = EnumSet.noneOf(Direction.class);
public void set(@NotNull final Direction direction) {
this.directions.add(direction);
@@ -36,9 +32,11 @@ public class Walls {
return this.directions.contains(direction);
}
- public Stream getUnsealedSet() {
- return Stream.ofAll(this.directions)
- .removeAll(this.sealed);
+ @NotNull
+ public EnumSet getUnsealedSet() {
+ final EnumSet result = EnumSet.copyOf(this.directions);
+ result.removeAll(this.sealed);
+ return result;
}
public void seal(@NotNull final Direction direction) {
@@ -51,4 +49,9 @@ public class Walls {
public void unseal(@NotNull final Direction direction) {
this.sealed.remove(direction);
}
+
+ @NotNull
+ public EnumSet getOpen() {
+ return EnumSet.complementOf(this.directions);
+ }
}
diff --git a/src/main/java/ch/fritteli/maze/generator/renderer/html/HTMLRenderer.java b/src/main/java/ch/fritteli/maze/generator/renderer/html/HTMLRenderer.java
index 1242ac3..a2de552 100644
--- a/src/main/java/ch/fritteli/maze/generator/renderer/html/HTMLRenderer.java
+++ b/src/main/java/ch/fritteli/maze/generator/renderer/html/HTMLRenderer.java
@@ -6,108 +6,109 @@ import org.jetbrains.annotations.NotNull;
public class HTMLRenderer implements Renderer {
- private static final String POSTAMBLE = "