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.pdffile.PDFFileRenderer;
import ch.fritteli.labyrinth.renderer.text.TextRenderer; import ch.fritteli.labyrinth.renderer.text.TextRenderer;
import ch.fritteli.labyrinth.renderer.textfile.TextFileRenderer; import ch.fritteli.labyrinth.renderer.textfile.TextFileRenderer;
import io.vavr.control.Try; import io.vavr.control.Option;
import lombok.NonNull; import lombok.NonNull;
import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
public class Main { public class Main {
public static void main(@NonNull final String[] args) throws IOException { public static void main(@NonNull final String[] args) {
final String listenerPort = System.getProperty("fritteli.labyrinth.listenerport"); final Option<TheListener> listener = TheListener.createListener();
if (listenerPort != null) { if (listener.isEmpty()) {
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 {
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"); 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 width = 100;
int height = 100; int height = 100;

View file

@ -3,7 +3,6 @@ package ch.fritteli.labyrinth.model;
import io.vavr.control.Option; import io.vavr.control.Option;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import org.jetbrains.annotations.Nullable;
import java.util.Deque; import java.util.Deque;
import java.util.LinkedList; import java.util.LinkedList;
@ -43,36 +42,26 @@ public class Labyrinth {
} }
@NonNull @NonNull
public Tile getTileAt(@NonNull final Position position) { public Option<Tile> getTileAt(@NonNull final Position position) {
return this.getTileAt(position.getX(), position.getY()); return this.getTileAt(position.getX(), position.getY());
} }
@NonNull @NonNull
public Tile getTileAtOrNull(@NonNull final Position position) { public Option<Tile> getTileAt(final int x, final int y) {
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) {
if (x < 0 || y < 0 || x >= this.width || y >= this.height) { 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 @NonNull
Tile getStartTile() { Tile getStartTile() {
return this.getTileAt(this.start); return this.getTileAt(this.start).get();
} }
@NonNull @NonNull
Tile getEndTile() { Tile getEndTile() {
return this.getTileAt(this.end); return this.getTileAt(this.end).get();
} }
private void initField() { private void initField() {
@ -124,13 +113,13 @@ public class Labyrinth {
private void dig() { private void dig() {
while (!this.positions.isEmpty()) { while (!this.positions.isEmpty()) {
final Position currentPosition = this.positions.peek(); 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); final Option<Direction> directionToDigTo = currentTile.getRandomAvailableDirection(Labyrinth.this.random);
if (directionToDigTo.isDefined()) { if (directionToDigTo.isDefined()) {
final Direction digTo = directionToDigTo.get(); final Direction digTo = directionToDigTo.get();
final Direction digFrom = digTo.invert(); final Direction digFrom = digTo.invert();
final Position neighborPosition = currentPosition.move(digTo); 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)) { if (currentTile.digTo(digTo) && neighborTile.digFrom(digFrom)) {
// all ok! // all ok!
this.positions.push(neighborPosition); this.positions.push(neighborPosition);
@ -149,7 +138,7 @@ public class Labyrinth {
} }
private void markSolution() { 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() { 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) { 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); return input.toTry().map(mapper).getOrElse(defaultValue);
} }
@ -111,11 +126,15 @@ public class TheListener {
} }
public void start() { public void start() {
Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "listener-stopper"));
this.httpServer.start(); this.httpServer.start();
System.out.println("Listening on " + this.httpServer.getAddress());
} }
public void stop() { public void stop() {
this.httpServer.stop(10); System.out.println("Stopping listener ...");
this.httpServer.stop(5);
System.out.println("Listener stopped.");
} }
private enum RequestParameter { private enum RequestParameter {

View file

@ -21,7 +21,7 @@ class Generator {
String next() { String next() {
StringBuilder sb = new StringBuilder("<tr>"); StringBuilder sb = new StringBuilder("<tr>");
for (int x = 0; x < this.labyrinth.getWidth(); x++) { 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("<td class=\"");
sb.append(this.getClasses(currentTile).mkString(" ")); sb.append(this.getClasses(currentTile).mkString(" "));
sb.append("\">&nbsp;</td>"); 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.Labyrinth;
import ch.fritteli.labyrinth.model.Position; import ch.fritteli.labyrinth.model.Position;
import ch.fritteli.labyrinth.model.Tile; import ch.fritteli.labyrinth.model.Tile;
import io.vavr.control.Option;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Value; import lombok.Value;
@ -74,7 +75,7 @@ class Generator {
boolean isPainting = false; boolean isPainting = false;
coordinate = coordinate.withY(y); coordinate = coordinate.withY(y);
for (int x = 0; x < this.labyrinth.getWidth(); x++) { 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); coordinate = coordinate.withX(x);
if (currentTile.hasWallAt(Direction.TOP)) { if (currentTile.hasWallAt(Direction.TOP)) {
if (!isPainting) { if (!isPainting) {
@ -105,7 +106,7 @@ class Generator {
int y = this.labyrinth.getHeight(); int y = this.labyrinth.getHeight();
coordinate = coordinate.withY(this.labyrinth.getHeight()); coordinate = coordinate.withY(this.labyrinth.getHeight());
for (int x = 0; x < this.labyrinth.getWidth(); x++) { 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); coordinate = coordinate.withX(x);
if (currentTile.hasWallAt(Direction.BOTTOM)) { if (currentTile.hasWallAt(Direction.BOTTOM)) {
if (!isPainting) { if (!isPainting) {
@ -140,7 +141,7 @@ class Generator {
boolean isPainting = false; boolean isPainting = false;
coordinate = coordinate.withX(x); coordinate = coordinate.withX(x);
for (int y = 0; y < this.labyrinth.getHeight(); y++) { 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); coordinate = coordinate.withY(y);
if (currentTile.hasWallAt(Direction.LEFT)) { if (currentTile.hasWallAt(Direction.LEFT)) {
if (!isPainting) { if (!isPainting) {
@ -171,7 +172,7 @@ class Generator {
int x = this.labyrinth.getWidth(); int x = this.labyrinth.getWidth();
coordinate = coordinate.withX(this.labyrinth.getWidth()); coordinate = coordinate.withX(this.labyrinth.getWidth());
for (int y = 0; y < this.labyrinth.getHeight(); y++) { 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); coordinate = coordinate.withY(y);
if (currentTile.hasWallAt(Direction.RIGHT)) { if (currentTile.hasWallAt(Direction.RIGHT)) {
if (!isPainting) { if (!isPainting) {
@ -220,15 +221,15 @@ class Generator {
@NonNull @NonNull
private Position findNextSolutionPosition(@Nullable final Position previousPosition, @NonNull final Position currentPosition) { 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()) { for (final Direction direction : Direction.values()) {
if (!currentTile.hasWallAt(direction)) { if (!currentTile.hasWallAt(direction)) {
final Position position = currentPosition.move(direction); final Position position = currentPosition.move(direction);
final Tile tileAtPosition = this.labyrinth.getTileAtOrNull(position); final Option<Tile> tileAtPosition = this.labyrinth.getTileAt(position);
if (position.equals(previousPosition) || tileAtPosition == null) { if (position.equals(previousPosition)) {
continue; continue;
} }
if (tileAtPosition.isSolution()) { if (tileAtPosition.map(Tile::isSolution).getOrElse(false)) {
return position; 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.Direction;
import ch.fritteli.labyrinth.model.Labyrinth; import ch.fritteli.labyrinth.model.Labyrinth;
import ch.fritteli.labyrinth.model.Tile; import ch.fritteli.labyrinth.model.Tile;
import io.vavr.control.Option;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.Nullable;
@RequiredArgsConstructor(access = AccessLevel.PACKAGE) @RequiredArgsConstructor(access = AccessLevel.PACKAGE)
class Generator { class Generator {
@ -22,11 +22,11 @@ class Generator {
} }
String next() { String next() {
final Tile currentTile = this.labyrinth.getTileAt(this.x, this.y); final Tile currentTile = this.labyrinth.getTileAt(this.x, this.y).get();
final Tile topTile = this.labyrinth.getTileAtOrNull(this.x, this.y - 1); final Option<Tile> topTile = this.labyrinth.getTileAt(this.x, this.y - 1);
final Tile rightTile = this.labyrinth.getTileAtOrNull(this.x + 1, this.y); final Option<Tile> rightTile = this.labyrinth.getTileAt(this.x + 1, this.y);
final Tile bottomTile = this.labyrinth.getTileAtOrNull(this.x, this.y + 1); final Option<Tile> bottomTile = this.labyrinth.getTileAt(this.x, this.y + 1);
final Tile leftTile = this.labyrinth.getTileAtOrNull(this.x - 1, this.y); final Option<Tile> leftTile = this.labyrinth.getTileAt(this.x - 1, this.y);
final String s; final String s;
switch (this.line) { switch (this.line) {
case 0: 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 charDef1 = new CharDefinition();
final CharDefinition charDef2 = new CharDefinition(); final CharDefinition charDef2 = new CharDefinition();
final CharDefinition charDef3 = new CharDefinition(); final CharDefinition charDef3 = new CharDefinition();
@ -82,7 +82,7 @@ class Generator {
charDef2.horizontal(); charDef2.horizontal();
charDef3.left(); charDef3.left();
} else { } else {
if (this.isSolution(currentTile) && (this.isSolution(topTile) || topTile == null)) { if (this.isSolution(currentTile) && (this.isSolution(topTile) || topTile.isEmpty())) {
charDef2.solution().vertical(); charDef2.solution().vertical();
} }
} }
@ -106,10 +106,10 @@ class Generator {
} }
private String renderCenterLine(@NonNull final Tile currentTile, private String renderCenterLine(@NonNull final Tile currentTile,
@Nullable final Tile topTile, @NonNull final Option<Tile> topTile,
@Nullable final Tile rightTile, @NonNull final Option<Tile> rightTile,
@Nullable final Tile bottomTile, @NonNull final Option<Tile> bottomTile,
@Nullable final Tile leftTile) { @NonNull final Option<Tile> leftTile) {
final CharDefinition charDef1 = new CharDefinition(); final CharDefinition charDef1 = new CharDefinition();
final CharDefinition charDef2 = new CharDefinition(); final CharDefinition charDef2 = new CharDefinition();
final CharDefinition charDef3 = new CharDefinition(); final CharDefinition charDef3 = new CharDefinition();
@ -123,16 +123,16 @@ class Generator {
} }
if (this.isSolution(currentTile)) { if (this.isSolution(currentTile)) {
charDef2.solution(); charDef2.solution();
if (!currentTile.hasWallAt(Direction.LEFT) && (this.isSolution(leftTile) || leftTile == null)) { if (!currentTile.hasWallAt(Direction.LEFT) && (this.isSolution(leftTile) || leftTile.isEmpty())) {
charDef2.left(); charDef2.left();
} }
if (!currentTile.hasWallAt(Direction.TOP) && (this.isSolution(topTile) || topTile == null)) { if (!currentTile.hasWallAt(Direction.TOP) && (this.isSolution(topTile) || topTile.isEmpty())) {
charDef2.up(); charDef2.up();
} }
if (!currentTile.hasWallAt(Direction.RIGHT) && (this.isSolution(rightTile) || rightTile == null)) { if (!currentTile.hasWallAt(Direction.RIGHT) && (this.isSolution(rightTile) || rightTile.isEmpty())) {
charDef2.right(); charDef2.right();
} }
if (!currentTile.hasWallAt(Direction.BOTTOM) && (this.isSolution(bottomTile) || bottomTile == null)) { if (!currentTile.hasWallAt(Direction.BOTTOM) && (this.isSolution(bottomTile) || bottomTile.isEmpty())) {
charDef2.down(); charDef2.down();
} }
} }
@ -153,7 +153,7 @@ class Generator {
return result; 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; String result;
final CharDefinition charDef1 = new CharDefinition(); final CharDefinition charDef1 = new CharDefinition();
final CharDefinition charDef2 = new CharDefinition(); final CharDefinition charDef2 = new CharDefinition();
@ -186,11 +186,15 @@ class Generator {
return result; return result;
} }
private boolean hasWallAt(@Nullable final Tile tile, @NonNull final Direction direction) { private boolean hasWallAt(@NonNull final Option<Tile> tile, @NonNull final Direction direction) {
return tile != null && tile.hasWallAt(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(); 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());
}
}