From 5e6965fa557196d436496bacde43c9c1a5af73ee Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Tue, 24 Dec 2024 02:50:45 +0100 Subject: [PATCH] Refactoring and clean-up. It is better organized, but the very basic data structures of the maze are not really suited for Wilson's algorithm. Another refactoring will be required. --- pom.xml | 5 + .../maze/generator/algorithm/Wilson.java | 312 ------------------ .../algorithm/wilson/MazeSolver.java | 62 ++++ .../maze/generator/algorithm/wilson/Path.java | 91 +++++ .../algorithm/wilson/PathsBuilder.java | 131 ++++++++ .../generator/algorithm/wilson/Wilson.java | 96 ++++++ .../fritteli/maze/generator/model/Tile.java | 19 +- .../fritteli/maze/generator/model/Walls.java | 23 +- .../serialization/MazeConstants.java | 9 + .../serialization/v1/MazeInputStreamV1.java | 5 +- .../serialization/v1/MazeOutputStreamV1.java | 5 +- .../v1/SerializerDeserializerV1.java | 4 +- .../serialization/v2/MazeInputStreamV2.java | 5 +- .../serialization/v2/MazeOutputStreamV2.java | 5 +- .../v2/SerializerDeserializerV2.java | 5 +- .../serialization/v3/MazeInputStreamV3.java | 5 +- .../serialization/v3/MazeOutputStreamV3.java | 5 +- .../v3/SerializerDeserializerV3.java | 5 +- .../maze/generator/algorithm/WilsonTest.java | 44 --- .../algorithm/wilson/WilsonTest.java | 100 ++++++ .../maze/generator/model/WallsTest.java | 4 +- .../v2/SerializerDeserializerV2Test.java | 2 +- .../v3/SerializerDeserializerV3Test.java | 11 +- 23 files changed, 557 insertions(+), 396 deletions(-) delete mode 100644 src/main/java/ch/fritteli/maze/generator/algorithm/Wilson.java create mode 100644 src/main/java/ch/fritteli/maze/generator/algorithm/wilson/MazeSolver.java create mode 100644 src/main/java/ch/fritteli/maze/generator/algorithm/wilson/Path.java create mode 100644 src/main/java/ch/fritteli/maze/generator/algorithm/wilson/PathsBuilder.java create mode 100644 src/main/java/ch/fritteli/maze/generator/algorithm/wilson/Wilson.java create mode 100644 src/main/java/ch/fritteli/maze/generator/serialization/MazeConstants.java delete mode 100644 src/test/java/ch/fritteli/maze/generator/algorithm/WilsonTest.java create mode 100644 src/test/java/ch/fritteli/maze/generator/algorithm/wilson/WilsonTest.java 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/Wilson.java b/src/main/java/ch/fritteli/maze/generator/algorithm/Wilson.java deleted file mode 100644 index af55d7e..0000000 --- a/src/main/java/ch/fritteli/maze/generator/algorithm/Wilson.java +++ /dev/null @@ -1,312 +0,0 @@ -package ch.fritteli.maze.generator.algorithm; - -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.Tuple2; -import io.vavr.collection.Iterator; -import io.vavr.collection.Seq; -import io.vavr.collection.Stream; -import io.vavr.collection.Vector; -import io.vavr.control.Option; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; - -/** - * An implementation of Wilson's Algorithm. - */ -public class Wilson extends AbstractMazeGeneratorAlgorithm { - public Wilson(@NotNull final Maze maze) { - super(maze, "Wilson"); - } - - @Override - public void run() { - final MyMaze myMaze = new MyMaze(this.maze.getWidth(), this.maze.getHeight()); - // 1. Initialization: pick random location, add to maze - final Position position = myMaze.getRandomAvailablePosition(this.random).get(); - myMaze.setPartOfMaze(position); - - // 2. while locations-not-in-maze exist, repeat: - - final List pths = new ArrayList<>(); - Position startPosition; - while ((startPosition = myMaze.getRandomAvailablePosition(this.random).getOrNull()) != null) { - final MyMaze.Path path = myMaze.createPath(startPosition); - while (true) { - final Position nextPosition = path.nextRandomPosition(this.random); - if (path.contains(nextPosition)) { - path.removeLoopUpTo(nextPosition); - } else { - path.append(nextPosition); - if (myMaze.isPartOfMaze(nextPosition)) { - myMaze.setPartOfMaze(path); - pths.add(path); - break; - } - } - } - - } - - applyToMaze(pths, maze); - solve(maze); - // 1. pick random location not in maze - // 2. perform random walk until you reach the maze (*) - // 3. add path to maze - // (*): random walk: - // 1. advance in random direction - // 2. if loop with current path is formed, remove loop, continue from last location before loop - } - - private void applyToMaze(@NotNull final List pths, @NotNull final Maze maze) { - pths.forEach(path -> { - final Iterator moves = Stream.ofAll(path.path) - .sliding(2) - .flatMap(poss -> poss.head().getDirectionTo(poss.get(1))); - Position position = path.path.getFirst(); - while (moves.hasNext()) { - Tile tile = maze.getTileAt(position).get(); - final Direction move = moves.next(); - tile.digTo(move); - position = position.move(move); - maze.getTileAt(position).forEach(t -> t.digTo(move.invert())); - } - }); - Direction direction = determineDirectionForDigging(maze.getStart()); - Tile t = maze.getStartTile(); - this.digTo(t, direction); - direction = determineDirectionForDigging(maze.getEnd()); - t = maze.getEndTile(); - this.digTo(t, direction); - // seal all walls, mark all as visited - for (int x = 0; x < maze.getWidth(); x++) { - for (int y = 0; y < maze.getHeight(); y++) { - maze.getTileAt(x, y).forEach(tile -> { - Stream.of(Direction.values()) - .forEach(d -> { - if (tile.hasWallAt(d)) { - tile.preventDiggingToOrFrom(d); - } else { - tile.digFrom(d); - } - }); - }); - } - } - } - - @Nullable - private Direction determineDirectionForDigging(@NotNull final Position position) { - if (position.y() == 0) { - return Direction.TOP; - } - if (position.x() == 0) { - return Direction.LEFT; - } - if (position.y() == this.maze.getHeight() - 1) { - return Direction.BOTTOM; - } - if (position.x() == this.maze.getWidth() - 1) { - return Direction.RIGHT; - } - return null; - } - - private void digTo(@NotNull final Tile tile, @NotNull final Direction direction) { - tile.enableDiggingToOrFrom(direction); - tile.digTo(direction); - } - - private void solve(@NotNull final Maze maze) { - EnumSet[][] remainingDirs = new EnumSet[maze.getWidth()][maze.getHeight()]; - for (int x = 0; x < remainingDirs.length; x++) { - for (int y = 0; y < remainingDirs[x].length; y++) { - remainingDirs[x][y] = EnumSet.allOf(Direction.class); - } - } - Position p = maze.getStart(); - final Direction direction = this.determineDirectionForDigging(p); - remainingDirs[p.x()][p.y()].remove(direction); - LinkedList solution = new LinkedList<>(); - solution.add(p); - while (!p.equals(maze.getEnd())) { - final Tile tile = maze.getTileAt(p).get(); - EnumSet dirs = remainingDirs[p.x()][p.y()]; - dirs.removeIf(tile::hasWallAt); - if (dirs.isEmpty()) { - solution.pop(); - p = solution.peek(); - } else { - final Direction nextDir = dirs.iterator().next(); - final Position nextPos = p.move(nextDir); - solution.push(nextPos); - remainingDirs[p.x()][p.y()].remove(nextDir); - remainingDirs[nextPos.x()][nextPos.y()].remove(nextDir.invert()); - p = nextPos; - } - } - solution.forEach(s -> maze.getTileAt(s).forEach(Tile::setSolution)); - } - - private static class MyMaze { - private final int width; - private final int height; - private final boolean[][] partOfMaze; - private final boolean[] completeColumns; - - MyMaze(final int width, final int height) { - this.width = width; - this.height = height; - this.partOfMaze = new boolean[this.width][this.height]; - for (int x = 0; x < this.width; x++) { - this.partOfMaze[x] = new boolean[this.height]; - } - this.completeColumns = new boolean[this.width]; - } - - boolean isPartOfMaze(final int x, final int y) { - return this.partOfMaze[x][y]; - } - - boolean isPartOfMaze(@NotNull final Position position) { - return this.isPartOfMaze(position.x(), position.y()); - } - - void setPartOfMaze(final int x, final int y) { - this.partOfMaze[x][y] = true; - this.checkCompleteColumn(x); - } - - void setPartOfMaze(@NotNull final Position position) { - this.setPartOfMaze(position.x(), position.y()); - } - - void setPartOfMaze(@NotNull final Path path) { - path.path.forEach(this::setPartOfMaze); - } - - void checkCompleteColumn(final int x) { - if (this.completeColumns[x]) { - return; - } - for (int y = 0; y < this.height; y++) { - if (!this.isPartOfMaze(x, y)) { - return; - } - } - this.completeColumns[x] = true; - } - - Option getRandomAvailablePosition(@NotNull final Random random) { - final Seq allowedColumns = Vector.ofAll(this.completeColumns) - .zipWithIndex() - .reject(Tuple2::_1) - .map(Tuple2::_2); - if (allowedColumns.isEmpty()) { - return Option.none(); - } - final int x = allowedColumns.get(random.nextInt(allowedColumns.size())); - final boolean[] column = partOfMaze[x]; - - final Seq allowedRows = Vector.ofAll(column) - .zipWithIndex() - .reject(Tuple2::_1) - .map(Tuple2::_2); - if (allowedRows.isEmpty()) { - return Option.none(); - } - final int y = allowedRows.get(random.nextInt(allowedRows.size())); - return Option.some(new Position(x, y)); - } - - public Path createPath(@NotNull final Position position) { - return new Path(position); - } - - private class Path { - @NotNull - private final List path = new LinkedList<>(); - - Path(@NotNull final Position position) { - this.path.add(position); - } - - @NotNull - Position nextRandomPosition(@NotNull final Random random) { - final Direction direction = this.getRandomDirectionFromLastPosition(random); - return this.path.getLast().move(direction); - } - - boolean contains(@NotNull final Position position) { - return this.path.contains(position); - } - - void removeLoopUpTo(@NotNull final Position position) { - while (!this.path.removeLast().equals(position)) { - } - this.path.add(position); - } - - public void append(@NotNull final Position nextPosition) { - this.path.add(nextPosition); - } - - @NotNull - private Direction getRandomDirectionFromLastPosition(@NotNull final Random random) { - final EnumSet validDirections = this.getValidDirectionsFromLastPosition(); - if (validDirections.isEmpty()) { - throw new IllegalStateException("WE MUST NOT GET HERE! analyze why it happened!!!"); - } - if (validDirections.size() == 1) { - return validDirections.iterator().next(); - } - final Direction[] directionArray = validDirections.toArray(Direction[]::new); - final int index = random.nextInt(directionArray.length); - return directionArray[index]; - } - - @NotNull - private EnumSet getValidDirectionsFromLastPosition() { - final Position fromPosition = this.path.getLast(); - final EnumSet validDirections = EnumSet.allOf(Direction.class); - if (this.path.size() > 1) { - final Position prevP = this.path.get(this.path.size() - 2); - fromPosition.getDirectionTo(prevP) - .forEach(validDirections::remove); - } - boolean canLeft = fromPosition.x() > 0; - boolean canRight = fromPosition.x() < width - 1; - boolean canUp = fromPosition.y() > 0; - boolean canDown = fromPosition.y() < height - 1; - if (!canLeft) { - validDirections.remove(Direction.LEFT); - } - if (!canRight) { - validDirections.remove(Direction.RIGHT); - } - if (!canUp) { - validDirections.remove(Direction.TOP); - } - if (!canDown) { - validDirections.remove(Direction.BOTTOM); - } - return validDirections; - } - - @Override - public String toString() { - return Stream.ofAll(this.path) - .map(position -> "(%s,%s)".formatted(position.x(), position.y())) - .mkString("Path[", "->", "]"); - } - } - } -} 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: + *
    + *
  1. Pick random location, add to maze
  2. + *
  3. While locations that are not part of the maze exist, loop: + *
      + *
    1. Pick random location that's not part of the maze
    2. + *
    3. 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.
      • + *
    4. + *
  4. + *
+ */ +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/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/serialization/MazeConstants.java b/src/main/java/ch/fritteli/maze/generator/serialization/MazeConstants.java new file mode 100644 index 0000000..4b9c200 --- /dev/null +++ b/src/main/java/ch/fritteli/maze/generator/serialization/MazeConstants.java @@ -0,0 +1,9 @@ +package ch.fritteli.maze.generator.serialization; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class MazeConstants { + public final byte MAGIC_BYTE_1 = 0x1a; + public final byte MAGIC_BYTE_2 = (byte) 0xb1; +} diff --git a/src/main/java/ch/fritteli/maze/generator/serialization/v1/MazeInputStreamV1.java b/src/main/java/ch/fritteli/maze/generator/serialization/v1/MazeInputStreamV1.java index 5ec5c7c..2532787 100644 --- a/src/main/java/ch/fritteli/maze/generator/serialization/v1/MazeInputStreamV1.java +++ b/src/main/java/ch/fritteli/maze/generator/serialization/v1/MazeInputStreamV1.java @@ -4,6 +4,7 @@ import ch.fritteli.maze.generator.model.Maze; import ch.fritteli.maze.generator.model.Tile; import ch.fritteli.maze.generator.serialization.AbstractMazeInputStream; import ch.fritteli.maze.generator.serialization.CommonTileHandler; +import ch.fritteli.maze.generator.serialization.MazeConstants; import org.jetbrains.annotations.NotNull; public class MazeInputStreamV1 extends AbstractMazeInputStream { @@ -18,11 +19,11 @@ public class MazeInputStreamV1 extends AbstractMazeInputStream { // 01 0xb1 magic // 02 0x01 version final byte magic1 = this.readByte(); - if (magic1 != SerializerDeserializerV1.MAGIC_BYTE_1) { + if (magic1 != MazeConstants.MAGIC_BYTE_1) { throw new IllegalArgumentException("Invalid maze data."); } final byte magic2 = this.readByte(); - if (magic2 != SerializerDeserializerV1.MAGIC_BYTE_2) { + if (magic2 != MazeConstants.MAGIC_BYTE_2) { throw new IllegalArgumentException("Invalid maze data."); } final int version = this.readByte(); diff --git a/src/main/java/ch/fritteli/maze/generator/serialization/v1/MazeOutputStreamV1.java b/src/main/java/ch/fritteli/maze/generator/serialization/v1/MazeOutputStreamV1.java index aa8d3c2..7085b04 100644 --- a/src/main/java/ch/fritteli/maze/generator/serialization/v1/MazeOutputStreamV1.java +++ b/src/main/java/ch/fritteli/maze/generator/serialization/v1/MazeOutputStreamV1.java @@ -4,6 +4,7 @@ import ch.fritteli.maze.generator.model.Maze; import ch.fritteli.maze.generator.model.Tile; import ch.fritteli.maze.generator.serialization.AbstractMazeOutputStream; import ch.fritteli.maze.generator.serialization.CommonTileHandler; +import ch.fritteli.maze.generator.serialization.MazeConstants; import org.jetbrains.annotations.NotNull; public class MazeOutputStreamV1 extends AbstractMazeOutputStream { @@ -13,8 +14,8 @@ public class MazeOutputStreamV1 extends AbstractMazeOutputStream { // 00 0x1a magic // 01 0xb1 magic // 02 0x02 version - this.writeByte(SerializerDeserializerV1.MAGIC_BYTE_1); - this.writeByte(SerializerDeserializerV1.MAGIC_BYTE_2); + this.writeByte(MazeConstants.MAGIC_BYTE_1); + this.writeByte(MazeConstants.MAGIC_BYTE_2); this.writeByte(SerializerDeserializerV1.VERSION_BYTE); } diff --git a/src/main/java/ch/fritteli/maze/generator/serialization/v1/SerializerDeserializerV1.java b/src/main/java/ch/fritteli/maze/generator/serialization/v1/SerializerDeserializerV1.java index f9f02d9..9f4a561 100644 --- a/src/main/java/ch/fritteli/maze/generator/serialization/v1/SerializerDeserializerV1.java +++ b/src/main/java/ch/fritteli/maze/generator/serialization/v1/SerializerDeserializerV1.java @@ -24,9 +24,7 @@ import java.lang.reflect.InvocationTargetException; */ @UtilityClass public class SerializerDeserializerV1 { - final byte MAGIC_BYTE_1 = 0x1a; - final byte MAGIC_BYTE_2 = (byte) 0xb1; - final byte VERSION_BYTE = 0x01; + public final byte VERSION_BYTE = 0x01; /** * Serializes the {@code maze} into a byte array. diff --git a/src/main/java/ch/fritteli/maze/generator/serialization/v2/MazeInputStreamV2.java b/src/main/java/ch/fritteli/maze/generator/serialization/v2/MazeInputStreamV2.java index bbe42b1..036d636 100644 --- a/src/main/java/ch/fritteli/maze/generator/serialization/v2/MazeInputStreamV2.java +++ b/src/main/java/ch/fritteli/maze/generator/serialization/v2/MazeInputStreamV2.java @@ -5,6 +5,7 @@ import ch.fritteli.maze.generator.model.Position; import ch.fritteli.maze.generator.model.Tile; import ch.fritteli.maze.generator.serialization.AbstractMazeInputStream; import ch.fritteli.maze.generator.serialization.CommonTileHandler; +import ch.fritteli.maze.generator.serialization.MazeConstants; import org.jetbrains.annotations.NotNull; public class MazeInputStreamV2 extends AbstractMazeInputStream { @@ -19,11 +20,11 @@ public class MazeInputStreamV2 extends AbstractMazeInputStream { // 01 0xb1 magic // 02 0x02 version final byte magic1 = this.readByte(); - if (magic1 != SerializerDeserializerV2.MAGIC_BYTE_1) { + if (magic1 != MazeConstants.MAGIC_BYTE_1) { throw new IllegalArgumentException("Invalid maze data."); } final byte magic2 = this.readByte(); - if (magic2 != SerializerDeserializerV2.MAGIC_BYTE_2) { + if (magic2 != MazeConstants.MAGIC_BYTE_2) { throw new IllegalArgumentException("Invalid maze data."); } final int version = this.readByte(); diff --git a/src/main/java/ch/fritteli/maze/generator/serialization/v2/MazeOutputStreamV2.java b/src/main/java/ch/fritteli/maze/generator/serialization/v2/MazeOutputStreamV2.java index c39ad3d..1cb8f78 100644 --- a/src/main/java/ch/fritteli/maze/generator/serialization/v2/MazeOutputStreamV2.java +++ b/src/main/java/ch/fritteli/maze/generator/serialization/v2/MazeOutputStreamV2.java @@ -5,6 +5,7 @@ import ch.fritteli.maze.generator.model.Position; import ch.fritteli.maze.generator.model.Tile; import ch.fritteli.maze.generator.serialization.AbstractMazeOutputStream; import ch.fritteli.maze.generator.serialization.CommonTileHandler; +import ch.fritteli.maze.generator.serialization.MazeConstants; import org.jetbrains.annotations.NotNull; public class MazeOutputStreamV2 extends AbstractMazeOutputStream { @@ -14,8 +15,8 @@ public class MazeOutputStreamV2 extends AbstractMazeOutputStream { // 00 0x1a magic // 01 0xb1 magic // 02 0x02 version - this.writeByte(SerializerDeserializerV2.MAGIC_BYTE_1); - this.writeByte(SerializerDeserializerV2.MAGIC_BYTE_2); + this.writeByte(MazeConstants.MAGIC_BYTE_1); + this.writeByte(MazeConstants.MAGIC_BYTE_2); this.writeByte(SerializerDeserializerV2.VERSION_BYTE); } diff --git a/src/main/java/ch/fritteli/maze/generator/serialization/v2/SerializerDeserializerV2.java b/src/main/java/ch/fritteli/maze/generator/serialization/v2/SerializerDeserializerV2.java index 5b7753e..99f8e22 100644 --- a/src/main/java/ch/fritteli/maze/generator/serialization/v2/SerializerDeserializerV2.java +++ b/src/main/java/ch/fritteli/maze/generator/serialization/v2/SerializerDeserializerV2.java @@ -29,10 +29,7 @@ import java.lang.reflect.InvocationTargetException; */ @UtilityClass public class SerializerDeserializerV2 { - - final byte MAGIC_BYTE_1 = 0x1a; - final byte MAGIC_BYTE_2 = (byte) 0xb1; - final byte VERSION_BYTE = 0x02; + public final byte VERSION_BYTE = 0x02; /** * Serializes the {@code maze} into a byte array. diff --git a/src/main/java/ch/fritteli/maze/generator/serialization/v3/MazeInputStreamV3.java b/src/main/java/ch/fritteli/maze/generator/serialization/v3/MazeInputStreamV3.java index af809b9..8a0dc26 100644 --- a/src/main/java/ch/fritteli/maze/generator/serialization/v3/MazeInputStreamV3.java +++ b/src/main/java/ch/fritteli/maze/generator/serialization/v3/MazeInputStreamV3.java @@ -5,6 +5,7 @@ import ch.fritteli.maze.generator.model.Position; import ch.fritteli.maze.generator.model.Tile; import ch.fritteli.maze.generator.serialization.AbstractMazeInputStream; import ch.fritteli.maze.generator.serialization.CommonTileHandler; +import ch.fritteli.maze.generator.serialization.MazeConstants; import org.jetbrains.annotations.NotNull; import java.io.IOException; @@ -22,11 +23,11 @@ public class MazeInputStreamV3 extends AbstractMazeInputStream { // 01 0xb1 magic // 02 0x03 version final byte magic1 = this.readByte(); - if (magic1 != SerializerDeserializerV3.MAGIC_BYTE_1) { + if (magic1 != MazeConstants.MAGIC_BYTE_1) { throw new IllegalArgumentException("Invalid maze data."); } final byte magic2 = this.readByte(); - if (magic2 != SerializerDeserializerV3.MAGIC_BYTE_2) { + if (magic2 != MazeConstants.MAGIC_BYTE_2) { throw new IllegalArgumentException("Invalid maze data."); } final int version = this.readByte(); diff --git a/src/main/java/ch/fritteli/maze/generator/serialization/v3/MazeOutputStreamV3.java b/src/main/java/ch/fritteli/maze/generator/serialization/v3/MazeOutputStreamV3.java index 891f0bb..c9da80e 100644 --- a/src/main/java/ch/fritteli/maze/generator/serialization/v3/MazeOutputStreamV3.java +++ b/src/main/java/ch/fritteli/maze/generator/serialization/v3/MazeOutputStreamV3.java @@ -5,6 +5,7 @@ import ch.fritteli.maze.generator.model.Position; import ch.fritteli.maze.generator.model.Tile; import ch.fritteli.maze.generator.serialization.AbstractMazeOutputStream; import ch.fritteli.maze.generator.serialization.CommonTileHandler; +import ch.fritteli.maze.generator.serialization.MazeConstants; import org.jetbrains.annotations.NotNull; import java.nio.charset.StandardCharsets; @@ -16,8 +17,8 @@ public class MazeOutputStreamV3 extends AbstractMazeOutputStream { // 00 0x1a magic // 01 0xb1 magic // 02 0x03 version - this.writeByte(SerializerDeserializerV3.MAGIC_BYTE_1); - this.writeByte(SerializerDeserializerV3.MAGIC_BYTE_2); + this.writeByte(MazeConstants.MAGIC_BYTE_1); + this.writeByte(MazeConstants.MAGIC_BYTE_2); this.writeByte(SerializerDeserializerV3.VERSION_BYTE); } diff --git a/src/main/java/ch/fritteli/maze/generator/serialization/v3/SerializerDeserializerV3.java b/src/main/java/ch/fritteli/maze/generator/serialization/v3/SerializerDeserializerV3.java index ac9375d..1da19cc 100644 --- a/src/main/java/ch/fritteli/maze/generator/serialization/v3/SerializerDeserializerV3.java +++ b/src/main/java/ch/fritteli/maze/generator/serialization/v3/SerializerDeserializerV3.java @@ -32,10 +32,7 @@ import java.lang.reflect.InvocationTargetException; */ @UtilityClass public class SerializerDeserializerV3 { - - final byte MAGIC_BYTE_1 = 0x1a; - final byte MAGIC_BYTE_2 = (byte) 0xb1; - final byte VERSION_BYTE = 0x03; + public final byte VERSION_BYTE = 0x03; /** * Serializes the {@code maze} into a byte array. diff --git a/src/test/java/ch/fritteli/maze/generator/algorithm/WilsonTest.java b/src/test/java/ch/fritteli/maze/generator/algorithm/WilsonTest.java deleted file mode 100644 index a4f8c9f..0000000 --- a/src/test/java/ch/fritteli/maze/generator/algorithm/WilsonTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package ch.fritteli.maze.generator.algorithm; - -import ch.fritteli.maze.generator.model.Maze; -import ch.fritteli.maze.generator.renderer.text.TextRenderer; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class WilsonTest { - @Test - void foo() { - // arrange - final Maze maze = new Maze(10, 10, 0); - final Wilson wilson = new Wilson(maze); - - // act - wilson.run(); - - // assert - final String textRepresentation = TextRenderer.newInstance().render(maze); - assertThat(textRepresentation).isEqualTo(""" - ╷ ╶─────┬─────┬───┬─┐ - │ │ │ │ │ - │ ╷ ╶─┬─┴─┬─╴ ╵ ╷ │ │ - │ │ │ │ │ │ │ - │ └─┐ │ ╶─┼─┬─┬─┘ │ │ - │ │ │ │ │ │ │ │ - │ ╷ │ └─╴ │ ╵ ╵ ╶─┘ │ - │ │ │ │ │ - ├─┴─┘ ╷ ┌─┴───┐ ╷ ╶─┤ - │ │ │ │ │ │ - │ ╷ ┌─┘ └───╴ │ └─┬─┤ - │ │ │ │ │ │ - ├─┴─┘ ┌───╴ ╷ └─┐ ╵ │ - │ │ │ │ │ - │ ╶─┬─┴─╴ ┌─┘ ╶─┘ ╶─┤ - │ │ │ │ - ├─╴ ├─╴ ┌─┘ ╶─┐ ╶───┤ - │ │ │ │ │ - ├───┴─╴ └─┐ ╶─┴───┐ │ - │ │ │ │ - └─────────┴───────┘ ╵"""); - } -} diff --git a/src/test/java/ch/fritteli/maze/generator/algorithm/wilson/WilsonTest.java b/src/test/java/ch/fritteli/maze/generator/algorithm/wilson/WilsonTest.java new file mode 100644 index 0000000..4457c42 --- /dev/null +++ b/src/test/java/ch/fritteli/maze/generator/algorithm/wilson/WilsonTest.java @@ -0,0 +1,100 @@ +package ch.fritteli.maze.generator.algorithm.wilson; + +import ch.fritteli.maze.generator.model.Maze; +import ch.fritteli.maze.generator.renderer.text.TextRenderer; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class WilsonTest { + @Test + void testTinyGeneration() { + // arrange + final Maze maze = new Maze(3, 3, 0); + final Wilson wilson = new Wilson(maze); + + // act + wilson.run(); + + // assert + final String textRepresentation = TextRenderer.newInstance().render(maze); + assertThat(textRepresentation).isEqualTo(""" + ╷ ╶───┐ + │ │ + │ ┌─┐ │ + │ │ │ │ + │ ╵ │ │ + │ │ │ + └───┘ ╵"""); + } + + @Test + void testSimpleGeneration() { + // arrange + final Maze maze = new Maze(10, 10, 0); + final Wilson wilson = new Wilson(maze); + + // act + wilson.run(); + + // assert + final String textRepresentation = TextRenderer.newInstance().render(maze); + assertThat(textRepresentation).isEqualTo(""" + ╷ ╶─────┬─────┬─────┐ + │ │ │ │ + ├─╴ ╷ ╶─┴─┬─╴ ├─┐ ╶─┤ + │ │ │ │ │ │ + │ ╷ └─┐ ╶─┘ ┌─┘ ╵ ╷ │ + │ │ │ │ │ │ + │ │ ╶─┤ ╶─┐ ╵ ╷ ╶─┤ │ + │ │ │ │ │ │ │ + │ ├─┐ ├─╴ └─┐ │ ╶─┼─┤ + │ │ │ │ │ │ │ │ + ├─┘ └─┘ ╶─┐ └─┤ ╶─┤ │ + │ │ │ │ │ + ├───┐ ╷ ╶─┴───┴─┐ │ │ + │ │ │ │ │ │ + ├─╴ └─┼─╴ ╷ ╷ ┌─┴─┘ │ + │ │ │ │ │ │ + │ ╷ ╶─┴─┬─┤ ├─┘ ╶─┐ │ + │ │ │ │ │ │ │ + │ ├─╴ ╷ ╵ ╵ ╵ ┌─╴ │ │ + │ │ │ │ │ │ + └─┴───┴───────┴───┘ ╵"""); + } + + @Test + void testSimpleGenerationWithSolution() { + // arrange + final Maze maze = new Maze(10, 10, 0); + final Wilson wilson = new Wilson(maze); + + // act + wilson.run(); + + // assert + final String textRepresentation = TextRenderer.newInstance().setRenderSolution(true).render(maze); + assertThat(textRepresentation).isEqualTo(""" + ╷│╶─────┬─────┬─────┐ + │╰───╮ │ │ │ + ├─╴ ╷│╶─┴─┬─╴ ├─┐ ╶─┤ + │ │╰─╮ │ │ │ │ + │ ╷ └─┐│╶─┘ ┌─┘ ╵ ╷ │ + │ │ ││ │ │ │ + │ │ ╶─┤│╶─┐ ╵ ╷ ╶─┤ │ + │ │ │╰─╮│ │ │ │ + │ ├─┐ ├─╴│└─┐ │ ╶─┼─┤ + │ │ │ │╭─╯ │ │ │ │ + ├─┘ └─┘│╶─┐ └─┤ ╶─┤ │ + │ │ │ │ │ │ + ├───┐ ╷│╶─┴───┴─┐ │ │ + │ │ │╰───╮ │ │ │ + ├─╴ └─┼─╴ ╷│╷ ┌─┴─┘ │ + │ │ │││ │╭───╮│ + │ ╷ ╶─┴─┬─┤│├─┘│╶─┐││ + │ │ │ │││╭─╯ │││ + │ ├─╴ ╷ ╵ ╵│╵│┌─╴ │││ + │ │ │ ╰─╯│ │││ + └─┴───┴───────┴───┘│╵"""); + } +} diff --git a/src/test/java/ch/fritteli/maze/generator/model/WallsTest.java b/src/test/java/ch/fritteli/maze/generator/model/WallsTest.java index 7be7269..d0f861f 100644 --- a/src/test/java/ch/fritteli/maze/generator/model/WallsTest.java +++ b/src/test/java/ch/fritteli/maze/generator/model/WallsTest.java @@ -3,6 +3,8 @@ package ch.fritteli.maze.generator.model; import io.vavr.collection.Stream; import org.junit.jupiter.api.Test; +import java.util.EnumSet; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -113,7 +115,7 @@ class WallsTest { final Walls sut = new Walls(); // act - Stream result = sut.getUnsealedSet(); + EnumSet result = sut.getUnsealedSet(); // assert assertThat(result).isEmpty(); diff --git a/src/test/java/ch/fritteli/maze/generator/serialization/v2/SerializerDeserializerV2Test.java b/src/test/java/ch/fritteli/maze/generator/serialization/v2/SerializerDeserializerV2Test.java index c4898c7..a89e947 100644 --- a/src/test/java/ch/fritteli/maze/generator/serialization/v2/SerializerDeserializerV2Test.java +++ b/src/test/java/ch/fritteli/maze/generator/serialization/v2/SerializerDeserializerV2Test.java @@ -1,7 +1,7 @@ package ch.fritteli.maze.generator.serialization.v2; import ch.fritteli.maze.generator.algorithm.RandomDepthFirst; -import ch.fritteli.maze.generator.algorithm.Wilson; +import ch.fritteli.maze.generator.algorithm.wilson.Wilson; import ch.fritteli.maze.generator.model.Maze; import ch.fritteli.maze.generator.model.Position; import org.junit.jupiter.api.Test; diff --git a/src/test/java/ch/fritteli/maze/generator/serialization/v3/SerializerDeserializerV3Test.java b/src/test/java/ch/fritteli/maze/generator/serialization/v3/SerializerDeserializerV3Test.java index fde2008..801c677 100644 --- a/src/test/java/ch/fritteli/maze/generator/serialization/v3/SerializerDeserializerV3Test.java +++ b/src/test/java/ch/fritteli/maze/generator/serialization/v3/SerializerDeserializerV3Test.java @@ -1,7 +1,7 @@ package ch.fritteli.maze.generator.serialization.v3; import ch.fritteli.maze.generator.algorithm.RandomDepthFirst; -import ch.fritteli.maze.generator.algorithm.Wilson; +import ch.fritteli.maze.generator.algorithm.wilson.Wilson; import ch.fritteli.maze.generator.model.Maze; import ch.fritteli.maze.generator.model.Position; import org.junit.jupiter.api.Test; @@ -37,4 +37,13 @@ class SerializerDeserializerV3Test { final Maze result = SerializerDeserializerV3.deserialize(bytes); assertThat(result).isEqualTo(expected); } + + @Test + void testSerializeDeserializeLargeRandom() throws IOException { + final Maze expected = new Maze(200, 320, 3141592653589793238L); + new RandomDepthFirst(expected).run(); + final byte[] bytes = SerializerDeserializerV3.serialize(expected); + final Maze result = SerializerDeserializerV3.deserialize(bytes); + assertThat(result).isEqualTo(expected); + } }