diff --git a/src/main/java/ch/fritteli/labyrinth/Main.java b/src/main/java/ch/fritteli/labyrinth/Main.java index b64a270..61b888b 100644 --- a/src/main/java/ch/fritteli/labyrinth/Main.java +++ b/src/main/java/ch/fritteli/labyrinth/Main.java @@ -7,26 +7,16 @@ import ch.fritteli.labyrinth.renderer.htmlfile.HTMLFileRenderer; import ch.fritteli.labyrinth.renderer.pdffile.PDFFileRenderer; import ch.fritteli.labyrinth.renderer.text.TextRenderer; import ch.fritteli.labyrinth.renderer.textfile.TextFileRenderer; -import io.vavr.control.Try; +import io.vavr.control.Option; import lombok.NonNull; -import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; public class Main { - public static void main(@NonNull final String[] args) throws IOException { - final String listenerPort = System.getProperty("fritteli.labyrinth.listenerport"); - if (listenerPort != null) { - final Try portTry = Try.of(() -> Integer.valueOf(listenerPort)); - if (portTry.isFailure()) { - System.err.println("Invalid port specified via sysprop 'fritteli.labyrinth.listenerport': " + listenerPort + ". Not starting webserver."); - } else { - final TheListener listener = new TheListener(portTry.get()); - listener.start(); - System.out.println("Listening on port " + portTry.get()); - } - } else { + public static void main(@NonNull final String[] args) { + final Option listener = TheListener.createListener(); + if (listener.isEmpty()) { System.out.println("In order to start the webserver, specify the port to listen to via the system property 'fritteli.labyrinth.listenerport', i.e.: -Dfritteli.labyrinth.listenerport=12345"); int width = 100; int height = 100; diff --git a/src/main/java/ch/fritteli/labyrinth/model/Labyrinth.java b/src/main/java/ch/fritteli/labyrinth/model/Labyrinth.java index 1cc6b35..2754770 100644 --- a/src/main/java/ch/fritteli/labyrinth/model/Labyrinth.java +++ b/src/main/java/ch/fritteli/labyrinth/model/Labyrinth.java @@ -3,7 +3,6 @@ package ch.fritteli.labyrinth.model; import io.vavr.control.Option; import lombok.Getter; import lombok.NonNull; -import org.jetbrains.annotations.Nullable; import java.util.Deque; import java.util.LinkedList; @@ -43,36 +42,26 @@ public class Labyrinth { } @NonNull - public Tile getTileAt(@NonNull final Position position) { + public Option getTileAt(@NonNull final Position position) { return this.getTileAt(position.getX(), position.getY()); } @NonNull - public Tile getTileAtOrNull(@NonNull final Position position) { - return this.getTileAtOrNull(position.getX(), position.getY()); - } - - @NonNull - public Tile getTileAt(final int x, final int y) { - return this.field[x][y]; - } - - @Nullable - public Tile getTileAtOrNull(final int x, final int y) { + public Option getTileAt(final int x, final int y) { if (x < 0 || y < 0 || x >= this.width || y >= this.height) { - return null; + return Option.none(); } - return this.getTileAt(x, y); + return Option.of(this.field[x][y]); } @NonNull Tile getStartTile() { - return this.getTileAt(this.start); + return this.getTileAt(this.start).get(); } @NonNull Tile getEndTile() { - return this.getTileAt(this.end); + return this.getTileAt(this.end).get(); } private void initField() { @@ -124,13 +113,13 @@ public class Labyrinth { private void dig() { while (!this.positions.isEmpty()) { final Position currentPosition = this.positions.peek(); - final Tile currentTile = Labyrinth.this.getTileAt(currentPosition); + final Tile currentTile = Labyrinth.this.getTileAt(currentPosition).get(); final Option directionToDigTo = currentTile.getRandomAvailableDirection(Labyrinth.this.random); if (directionToDigTo.isDefined()) { final Direction digTo = directionToDigTo.get(); final Direction digFrom = digTo.invert(); final Position neighborPosition = currentPosition.move(digTo); - final Tile neighborTile = Labyrinth.this.getTileAt(neighborPosition); + final Tile neighborTile = Labyrinth.this.getTileAt(neighborPosition).get(); if (currentTile.digTo(digTo) && neighborTile.digFrom(digFrom)) { // all ok! this.positions.push(neighborPosition); @@ -149,7 +138,7 @@ public class Labyrinth { } private void markSolution() { - this.positions.forEach(position -> Labyrinth.this.getTileAt(position).setSolution()); + this.positions.forEach(position -> Labyrinth.this.getTileAt(position).get().setSolution()); } private void postDig() { diff --git a/src/main/java/ch/fritteli/labyrinth/net/TheListener.java b/src/main/java/ch/fritteli/labyrinth/net/TheListener.java index d5f61c9..4b2b3ab 100644 --- a/src/main/java/ch/fritteli/labyrinth/net/TheListener.java +++ b/src/main/java/ch/fritteli/labyrinth/net/TheListener.java @@ -76,6 +76,21 @@ public class TheListener { }); } + public static Option createListener() { + final String listenerPort = System.getProperty("fritteli.labyrinth.listenerport"); + final Option listenerOption = Option.of(listenerPort) + .toTry() + .map(Integer::valueOf) + .onFailure(cause -> System.err.println("Invalid port specified via system property 'fritteli.labyrinth.listenerport': " + + listenerPort + + ". Not starting webserver.")) + .mapTry(TheListener::new) + .onFailure(cause -> System.err.println("Failed to create Listener: " + cause)) + .toOption(); + listenerOption.forEach(TheListener::start); + return listenerOption; + } + private T getOrDefault(@NonNull final Option input, @NonNull final Function mapper, @Nullable final T defaultValue) { return input.toTry().map(mapper).getOrElse(defaultValue); } @@ -111,11 +126,15 @@ public class TheListener { } public void start() { + Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "listener-stopper")); this.httpServer.start(); + System.out.println("Listening on " + this.httpServer.getAddress()); } public void stop() { - this.httpServer.stop(10); + System.out.println("Stopping listener ..."); + this.httpServer.stop(5); + System.out.println("Listener stopped."); } private enum RequestParameter { diff --git a/src/main/java/ch/fritteli/labyrinth/renderer/html/Generator.java b/src/main/java/ch/fritteli/labyrinth/renderer/html/Generator.java index 40f69eb..b521949 100644 --- a/src/main/java/ch/fritteli/labyrinth/renderer/html/Generator.java +++ b/src/main/java/ch/fritteli/labyrinth/renderer/html/Generator.java @@ -21,7 +21,7 @@ class Generator { String next() { StringBuilder sb = new StringBuilder(""); for (int x = 0; x < this.labyrinth.getWidth(); x++) { - final Tile currentTile = this.labyrinth.getTileAt(x, this.y); + final Tile currentTile = this.labyrinth.getTileAt(x, this.y).get(); sb.append(" "); diff --git a/src/main/java/ch/fritteli/labyrinth/renderer/pdf/Generator.java b/src/main/java/ch/fritteli/labyrinth/renderer/pdf/Generator.java index 2e95f9b..9a7445d 100644 --- a/src/main/java/ch/fritteli/labyrinth/renderer/pdf/Generator.java +++ b/src/main/java/ch/fritteli/labyrinth/renderer/pdf/Generator.java @@ -4,6 +4,7 @@ import ch.fritteli.labyrinth.model.Direction; import ch.fritteli.labyrinth.model.Labyrinth; import ch.fritteli.labyrinth.model.Position; import ch.fritteli.labyrinth.model.Tile; +import io.vavr.control.Option; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.Value; @@ -74,7 +75,7 @@ class Generator { boolean isPainting = false; coordinate = coordinate.withY(y); for (int x = 0; x < this.labyrinth.getWidth(); x++) { - final Tile currentTile = this.labyrinth.getTileAt(x, y); + final Tile currentTile = this.labyrinth.getTileAt(x, y).get(); coordinate = coordinate.withX(x); if (currentTile.hasWallAt(Direction.TOP)) { if (!isPainting) { @@ -105,7 +106,7 @@ class Generator { int y = this.labyrinth.getHeight(); coordinate = coordinate.withY(this.labyrinth.getHeight()); for (int x = 0; x < this.labyrinth.getWidth(); x++) { - final Tile currentTile = this.labyrinth.getTileAt(x, y - 1); + final Tile currentTile = this.labyrinth.getTileAt(x, y - 1).get(); coordinate = coordinate.withX(x); if (currentTile.hasWallAt(Direction.BOTTOM)) { if (!isPainting) { @@ -140,7 +141,7 @@ class Generator { boolean isPainting = false; coordinate = coordinate.withX(x); for (int y = 0; y < this.labyrinth.getHeight(); y++) { - final Tile currentTile = this.labyrinth.getTileAt(x, y); + final Tile currentTile = this.labyrinth.getTileAt(x, y).get(); coordinate = coordinate.withY(y); if (currentTile.hasWallAt(Direction.LEFT)) { if (!isPainting) { @@ -171,7 +172,7 @@ class Generator { int x = this.labyrinth.getWidth(); coordinate = coordinate.withX(this.labyrinth.getWidth()); for (int y = 0; y < this.labyrinth.getHeight(); y++) { - final Tile currentTile = this.labyrinth.getTileAt(x - 1, y); + final Tile currentTile = this.labyrinth.getTileAt(x - 1, y).get(); coordinate = coordinate.withY(y); if (currentTile.hasWallAt(Direction.RIGHT)) { if (!isPainting) { @@ -220,15 +221,15 @@ class Generator { @NonNull private Position findNextSolutionPosition(@Nullable final Position previousPosition, @NonNull final Position currentPosition) { - final Tile currentTile = this.labyrinth.getTileAt(currentPosition); + final Tile currentTile = this.labyrinth.getTileAt(currentPosition).get(); for (final Direction direction : Direction.values()) { if (!currentTile.hasWallAt(direction)) { final Position position = currentPosition.move(direction); - final Tile tileAtPosition = this.labyrinth.getTileAtOrNull(position); - if (position.equals(previousPosition) || tileAtPosition == null) { + final Option tileAtPosition = this.labyrinth.getTileAt(position); + if (position.equals(previousPosition)) { continue; } - if (tileAtPosition.isSolution()) { + if (tileAtPosition.map(Tile::isSolution).getOrElse(false)) { return position; } } diff --git a/src/main/java/ch/fritteli/labyrinth/renderer/text/Generator.java b/src/main/java/ch/fritteli/labyrinth/renderer/text/Generator.java index 65ae821..afb8ccc 100644 --- a/src/main/java/ch/fritteli/labyrinth/renderer/text/Generator.java +++ b/src/main/java/ch/fritteli/labyrinth/renderer/text/Generator.java @@ -3,10 +3,10 @@ package ch.fritteli.labyrinth.renderer.text; import ch.fritteli.labyrinth.model.Direction; import ch.fritteli.labyrinth.model.Labyrinth; import ch.fritteli.labyrinth.model.Tile; +import io.vavr.control.Option; import lombok.AccessLevel; import lombok.NonNull; import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.Nullable; @RequiredArgsConstructor(access = AccessLevel.PACKAGE) class Generator { @@ -22,11 +22,11 @@ class Generator { } String next() { - final Tile currentTile = this.labyrinth.getTileAt(this.x, this.y); - final Tile topTile = this.labyrinth.getTileAtOrNull(this.x, this.y - 1); - final Tile rightTile = this.labyrinth.getTileAtOrNull(this.x + 1, this.y); - final Tile bottomTile = this.labyrinth.getTileAtOrNull(this.x, this.y + 1); - final Tile leftTile = this.labyrinth.getTileAtOrNull(this.x - 1, this.y); + final Tile currentTile = this.labyrinth.getTileAt(this.x, this.y).get(); + final Option topTile = this.labyrinth.getTileAt(this.x, this.y - 1); + final Option rightTile = this.labyrinth.getTileAt(this.x + 1, this.y); + final Option bottomTile = this.labyrinth.getTileAt(this.x, this.y + 1); + final Option leftTile = this.labyrinth.getTileAt(this.x - 1, this.y); final String s; switch (this.line) { case 0: @@ -69,7 +69,7 @@ class Generator { } } - private String renderTopLine(@NonNull final Tile currentTile, @Nullable final Tile leftTile, @Nullable final Tile topTile) { + private String renderTopLine(@NonNull final Tile currentTile, @NonNull final Option leftTile, @NonNull final Option topTile) { final CharDefinition charDef1 = new CharDefinition(); final CharDefinition charDef2 = new CharDefinition(); final CharDefinition charDef3 = new CharDefinition(); @@ -82,7 +82,7 @@ class Generator { charDef2.horizontal(); charDef3.left(); } else { - if (this.isSolution(currentTile) && (this.isSolution(topTile) || topTile == null)) { + if (this.isSolution(currentTile) && (this.isSolution(topTile) || topTile.isEmpty())) { charDef2.solution().vertical(); } } @@ -106,10 +106,10 @@ class Generator { } private String renderCenterLine(@NonNull final Tile currentTile, - @Nullable final Tile topTile, - @Nullable final Tile rightTile, - @Nullable final Tile bottomTile, - @Nullable final Tile leftTile) { + @NonNull final Option topTile, + @NonNull final Option rightTile, + @NonNull final Option bottomTile, + @NonNull final Option leftTile) { final CharDefinition charDef1 = new CharDefinition(); final CharDefinition charDef2 = new CharDefinition(); final CharDefinition charDef3 = new CharDefinition(); @@ -123,16 +123,16 @@ class Generator { } if (this.isSolution(currentTile)) { charDef2.solution(); - if (!currentTile.hasWallAt(Direction.LEFT) && (this.isSolution(leftTile) || leftTile == null)) { + if (!currentTile.hasWallAt(Direction.LEFT) && (this.isSolution(leftTile) || leftTile.isEmpty())) { charDef2.left(); } - if (!currentTile.hasWallAt(Direction.TOP) && (this.isSolution(topTile) || topTile == null)) { + if (!currentTile.hasWallAt(Direction.TOP) && (this.isSolution(topTile) || topTile.isEmpty())) { charDef2.up(); } - if (!currentTile.hasWallAt(Direction.RIGHT) && (this.isSolution(rightTile) || rightTile == null)) { + if (!currentTile.hasWallAt(Direction.RIGHT) && (this.isSolution(rightTile) || rightTile.isEmpty())) { charDef2.right(); } - if (!currentTile.hasWallAt(Direction.BOTTOM) && (this.isSolution(bottomTile) || bottomTile == null)) { + if (!currentTile.hasWallAt(Direction.BOTTOM) && (this.isSolution(bottomTile) || bottomTile.isEmpty())) { charDef2.down(); } } @@ -153,7 +153,7 @@ class Generator { return result; } - private String renderBottomLine(@NonNull final Tile currentTile, @Nullable final Tile leftTile) { + private String renderBottomLine(@NonNull final Tile currentTile, @NonNull final Option leftTile) { String result; final CharDefinition charDef1 = new CharDefinition(); final CharDefinition charDef2 = new CharDefinition(); @@ -186,11 +186,15 @@ class Generator { return result; } - private boolean hasWallAt(@Nullable final Tile tile, @NonNull final Direction direction) { - return tile != null && tile.hasWallAt(direction); + private boolean hasWallAt(@NonNull final Option tile, @NonNull final Direction direction) { + return tile.map(t -> t.hasWallAt(direction)).getOrElse(false); } - private boolean isSolution(@Nullable final Tile tile) { + private boolean isSolution(@NonNull final Tile tile) { return this.renderSolution && tile != null && tile.isSolution(); } + + private boolean isSolution(@NonNull final Option tile) { + return this.renderSolution && tile.map(Tile::isSolution).getOrElse(false); + } } diff --git a/src/test/java/ch/fritteli/labyrinth/model/LabyrinthTest.java b/src/test/java/ch/fritteli/labyrinth/model/LabyrinthTest.java new file mode 100644 index 0000000..eb5968a --- /dev/null +++ b/src/test/java/ch/fritteli/labyrinth/model/LabyrinthTest.java @@ -0,0 +1,26 @@ +package ch.fritteli.labyrinth.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class LabyrinthTest { + @Test + void testConstruct() { + // act / assert on simple cases + assertThrows(IllegalArgumentException.class, () -> new Labyrinth(0, 0)); + assertThrows(IllegalArgumentException.class, () -> new Labyrinth(0, 0, 0)); + + // now for the real work: + // arrange + final Labyrinth sut = new Labyrinth(2, 3, 5); + + // assert + assertEquals(2, sut.getWidth()); + assertEquals(3, sut.getHeight()); + assertEquals(5, sut.getRandomSeed()); + assertEquals(new Position(0, 0), sut.getStart()); + assertEquals(new Position(1, 2), sut.getEnd()); + } +}