Refactoring, more tests.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Manuel Friedli 2020-10-06 01:37:47 +02:00
parent ad0759b36f
commit d6029471f0
7 changed files with 93 additions and 64 deletions

View file

@ -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<Integer> 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<TheListener> 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;

View file

@ -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<Tile> 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<Tile> 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<Direction> 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() {

View file

@ -76,6 +76,21 @@ public class TheListener {
});
}
public static Option<TheListener> createListener() {
final String listenerPort = System.getProperty("fritteli.labyrinth.listenerport");
final Option<TheListener> 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> T getOrDefault(@NonNull final Option<String> input, @NonNull final Function<String, T> 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 {

View file

@ -21,7 +21,7 @@ class Generator {
String next() {
StringBuilder sb = new StringBuilder("<tr>");
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("<td class=\"");
sb.append(this.getClasses(currentTile).mkString(" "));
sb.append("\">&nbsp;</td>");

View file

@ -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<Tile> tileAtPosition = this.labyrinth.getTileAt(position);
if (position.equals(previousPosition)) {
continue;
}
if (tileAtPosition.isSolution()) {
if (tileAtPosition.map(Tile::isSolution).getOrElse(false)) {
return position;
}
}

View file

@ -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<Tile> topTile = this.labyrinth.getTileAt(this.x, this.y - 1);
final Option<Tile> rightTile = this.labyrinth.getTileAt(this.x + 1, this.y);
final Option<Tile> bottomTile = this.labyrinth.getTileAt(this.x, this.y + 1);
final Option<Tile> 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<Tile> leftTile, @NonNull final Option<Tile> 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<Tile> topTile,
@NonNull final Option<Tile> rightTile,
@NonNull final Option<Tile> bottomTile,
@NonNull final Option<Tile> 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<Tile> 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> 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> tile) {
return this.renderSolution && tile.map(Tile::isSolution).getOrElse(false);
}
}

View file

@ -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());
}
}