Implement Wilson's algorithm and let the caller choose the algorithm.
This commit is contained in:
parent
c4d707d64a
commit
3c2fca9a74
8 changed files with 176 additions and 12 deletions
2
pom.xml
2
pom.xml
|
@ -56,7 +56,7 @@
|
||||||
</distributionManagement>
|
</distributionManagement>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maze-generator.version>0.2.2-SNAPSHOT</maze-generator.version>
|
<maze-generator.version>0.3.0</maze-generator.version>
|
||||||
<maven-site-plugin.version>4.0.0-M8</maven-site-plugin.version>
|
<maven-site-plugin.version>4.0.0-M8</maven-site-plugin.version>
|
||||||
<undertow.version>2.3.18.Final</undertow.version>
|
<undertow.version>2.3.18.Final</undertow.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ch.fritteli.maze.server;
|
||||||
|
|
||||||
import ch.fritteli.maze.generator.algorithm.MazeGeneratorAlgorithm;
|
import ch.fritteli.maze.generator.algorithm.MazeGeneratorAlgorithm;
|
||||||
import ch.fritteli.maze.generator.algorithm.RandomDepthFirst;
|
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.Maze;
|
||||||
import io.vavr.collection.List;
|
import io.vavr.collection.List;
|
||||||
import io.vavr.collection.Stream;
|
import io.vavr.collection.Stream;
|
||||||
|
|
|
@ -3,6 +3,8 @@ package ch.fritteli.maze.server;
|
||||||
import ch.fritteli.maze.server.handler.CreateHandler;
|
import ch.fritteli.maze.server.handler.CreateHandler;
|
||||||
import ch.fritteli.maze.server.handler.RenderV1Handler;
|
import ch.fritteli.maze.server.handler.RenderV1Handler;
|
||||||
import ch.fritteli.maze.server.handler.RenderV2Handler;
|
import ch.fritteli.maze.server.handler.RenderV2Handler;
|
||||||
|
import ch.fritteli.maze.server.handler.RenderV3Handler;
|
||||||
|
import ch.fritteli.maze.server.handler.RenderVxHandler;
|
||||||
import io.undertow.Undertow;
|
import io.undertow.Undertow;
|
||||||
import io.undertow.server.RoutingHandler;
|
import io.undertow.server.RoutingHandler;
|
||||||
import io.vavr.control.Try;
|
import io.vavr.control.Try;
|
||||||
|
@ -24,7 +26,9 @@ public class MazeServer {
|
||||||
final RoutingHandler routingHandler = new RoutingHandler()
|
final RoutingHandler routingHandler = new RoutingHandler()
|
||||||
.get(CreateHandler.PATH_TEMPLATE, new CreateHandler(config.maxMazeHeight(), config.maxMazeWidth()))
|
.get(CreateHandler.PATH_TEMPLATE, new CreateHandler(config.maxMazeHeight(), config.maxMazeWidth()))
|
||||||
.post(RenderV1Handler.PATH_TEMPLATE, new RenderV1Handler())
|
.post(RenderV1Handler.PATH_TEMPLATE, new RenderV1Handler())
|
||||||
.post(RenderV2Handler.PATH_TEMPLATE, new RenderV2Handler());
|
.post(RenderV2Handler.PATH_TEMPLATE, new RenderV2Handler())
|
||||||
|
.post(RenderV3Handler.PATH_TEMPLATE, new RenderV3Handler())
|
||||||
|
.post(RenderVxHandler.PATH_TEMPLATE, new RenderVxHandler());
|
||||||
|
|
||||||
this.undertow = Undertow.builder()
|
this.undertow = Undertow.builder()
|
||||||
.addHttpListener(port, hostAddress)
|
.addHttpListener(port, hostAddress)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import ch.fritteli.maze.generator.renderer.pdf.PDFRenderer;
|
||||||
import ch.fritteli.maze.generator.renderer.text.TextRenderer;
|
import ch.fritteli.maze.generator.renderer.text.TextRenderer;
|
||||||
import ch.fritteli.maze.generator.serialization.v1.SerializerDeserializerV1;
|
import ch.fritteli.maze.generator.serialization.v1.SerializerDeserializerV1;
|
||||||
import ch.fritteli.maze.generator.serialization.v2.SerializerDeserializerV2;
|
import ch.fritteli.maze.generator.serialization.v2.SerializerDeserializerV2;
|
||||||
|
import ch.fritteli.maze.generator.serialization.v3.SerializerDeserializerV3;
|
||||||
import io.vavr.collection.List;
|
import io.vavr.collection.List;
|
||||||
import io.vavr.collection.Stream;
|
import io.vavr.collection.Stream;
|
||||||
import io.vavr.control.Option;
|
import io.vavr.control.Option;
|
||||||
|
@ -23,7 +24,8 @@ public enum OutputType {
|
||||||
maze -> TextRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8),
|
maze -> TextRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8),
|
||||||
false,
|
false,
|
||||||
"t",
|
"t",
|
||||||
"text"),
|
"text",
|
||||||
|
"txt"),
|
||||||
HTML("text/html",
|
HTML("text/html",
|
||||||
"html",
|
"html",
|
||||||
maze -> HTMLRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8),
|
maze -> HTMLRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8),
|
||||||
|
@ -65,7 +67,13 @@ public enum OutputType {
|
||||||
SerializerDeserializerV2::serialize,
|
SerializerDeserializerV2::serialize,
|
||||||
true,
|
true,
|
||||||
"v",
|
"v",
|
||||||
"binaryv2");
|
"binaryv2"),
|
||||||
|
BINARY_V3("application/octet-stream",
|
||||||
|
"maz3",
|
||||||
|
SerializerDeserializerV3::serialize,
|
||||||
|
true,
|
||||||
|
"3",
|
||||||
|
"binaryv3");
|
||||||
@Getter
|
@Getter
|
||||||
@NotNull
|
@NotNull
|
||||||
private final String contentType;
|
private final String contentType;
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class CreateHandler extends AbstractHttpHandler {
|
||||||
.put(HttpString.tryFromString("X-Maze-ID"), String.valueOf(maze.getRandomSeed()))
|
.put(HttpString.tryFromString("X-Maze-ID"), String.valueOf(maze.getRandomSeed()))
|
||||||
.put(HttpString.tryFromString("X-Maze-Width"), String.valueOf(maze.getWidth()))
|
.put(HttpString.tryFromString("X-Maze-Width"), String.valueOf(maze.getWidth()))
|
||||||
.put(HttpString.tryFromString("X-Maze-Height"), String.valueOf(maze.getHeight()))
|
.put(HttpString.tryFromString("X-Maze-Height"), String.valueOf(maze.getHeight()))
|
||||||
.put(HttpString.tryFromString("X-Maze-Algorithm"), generatedMaze.generatorName())
|
.put(HttpString.tryFromString("X-Maze-Algorithm"), maze.getAlgorithm())
|
||||||
.put(HttpString.tryFromString("X-Maze-Generation-Duration-millis"), String.valueOf(durationMillis));
|
.put(HttpString.tryFromString("X-Maze-Generation-Duration-millis"), String.valueOf(durationMillis));
|
||||||
if (outputType.isAttachment()) {
|
if (outputType.isAttachment()) {
|
||||||
exchange.getResponseHeaders()
|
exchange.getResponseHeaders()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package ch.fritteli.maze.server.handler;
|
package ch.fritteli.maze.server.handler;
|
||||||
|
|
||||||
import ch.fritteli.maze.generator.algorithm.RandomDepthFirst;
|
import ch.fritteli.maze.generator.algorithm.MazeGeneratorAlgorithm;
|
||||||
import ch.fritteli.maze.generator.model.Maze;
|
import ch.fritteli.maze.generator.model.Maze;
|
||||||
import ch.fritteli.maze.generator.model.Position;
|
import ch.fritteli.maze.generator.model.Position;
|
||||||
import ch.fritteli.maze.server.Algorithm;
|
import ch.fritteli.maze.server.Algorithm;
|
||||||
|
@ -80,10 +80,10 @@ class ParametersToMazeExtractor {
|
||||||
maze = new Maze(desiredWidth, desiredHeight, id.getOrElse(() -> new Random().nextLong()));
|
maze = new Maze(desiredWidth, desiredHeight, id.getOrElse(() -> new Random().nextLong()));
|
||||||
}
|
}
|
||||||
|
|
||||||
algorithm.getOrElse(Algorithm.RANDOM_DEPTH_FIRST)
|
final MazeGeneratorAlgorithm generator = algorithm.getOrElse(Algorithm.WILSON)
|
||||||
.createAlgorithm(maze)
|
.createAlgorithm(maze);
|
||||||
.run();
|
generator.run();
|
||||||
return new GeneratedMaze(maze, output.get(), RandomDepthFirst.class.getSimpleName());
|
return new GeneratedMaze(maze, output.get());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +92,6 @@ class ParametersToMazeExtractor {
|
||||||
return parameter.getParameterValue(this.queryParameters);
|
return parameter.getParameterValue(this.queryParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record GeneratedMaze(@NotNull Maze maze, @NotNull OutputType outputType, @NotNull String generatorName) {
|
public record GeneratedMaze(@NotNull Maze maze, @NotNull OutputType outputType) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package ch.fritteli.maze.server.handler;
|
||||||
|
|
||||||
|
import ch.fritteli.maze.generator.model.Maze;
|
||||||
|
import ch.fritteli.maze.generator.serialization.v3.SerializerDeserializerV3;
|
||||||
|
import ch.fritteli.maze.server.OutputType;
|
||||||
|
import io.undertow.server.HttpServerExchange;
|
||||||
|
import io.undertow.util.HeaderValues;
|
||||||
|
import io.undertow.util.Headers;
|
||||||
|
import io.undertow.util.StatusCodes;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class RenderV3Handler extends AbstractHttpHandler {
|
||||||
|
|
||||||
|
public static final String PATH_TEMPLATE = "/render/v3/{output}";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(@NotNull final HttpServerExchange exchange) {
|
||||||
|
log.debug("Handling render request");
|
||||||
|
|
||||||
|
if (exchange.isInIoThread()) {
|
||||||
|
exchange.dispatch(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
exchange.getRequestReceiver().receiveFullBytes((httpServerExchange, bytes) -> {
|
||||||
|
final OutputType output = this.getOutputType(httpServerExchange);
|
||||||
|
final byte[] render;
|
||||||
|
try {
|
||||||
|
final Maze maze = SerializerDeserializerV3.deserialize(bytes);
|
||||||
|
render = output.render(maze);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Error rendering binary maze data", e);
|
||||||
|
httpServerExchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR)
|
||||||
|
.getResponseSender()
|
||||||
|
.send("Error rendering maze: %s".formatted(e.getMessage()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
httpServerExchange
|
||||||
|
.setStatusCode(StatusCodes.OK)
|
||||||
|
.getResponseHeaders()
|
||||||
|
.put(Headers.CONTENT_TYPE, output.getContentType());
|
||||||
|
httpServerExchange.getResponseSender()
|
||||||
|
.send(ByteBuffer.wrap(render));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private OutputType getOutputType(@NotNull final HttpServerExchange httpServerExchange) {
|
||||||
|
return RequestParameter.OUTPUT.<OutputType>getParameterValue(httpServerExchange.getQueryParameters())
|
||||||
|
.getOrElse(() -> {
|
||||||
|
final HeaderValues accept = httpServerExchange.getRequestHeaders().get(Headers.ACCEPT);
|
||||||
|
if (accept.contains(OutputType.HTML.getContentType())) {
|
||||||
|
return OutputType.HTML;
|
||||||
|
}
|
||||||
|
return OutputType.TEXT_PLAIN;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package ch.fritteli.maze.server.handler;
|
||||||
|
|
||||||
|
import ch.fritteli.maze.generator.model.Maze;
|
||||||
|
import ch.fritteli.maze.generator.serialization.MazeConstants;
|
||||||
|
import ch.fritteli.maze.generator.serialization.v1.SerializerDeserializerV1;
|
||||||
|
import ch.fritteli.maze.generator.serialization.v2.SerializerDeserializerV2;
|
||||||
|
import ch.fritteli.maze.generator.serialization.v3.SerializerDeserializerV3;
|
||||||
|
import ch.fritteli.maze.server.OutputType;
|
||||||
|
import io.undertow.server.HttpHandler;
|
||||||
|
import io.undertow.server.HttpServerExchange;
|
||||||
|
import io.undertow.util.HeaderValues;
|
||||||
|
import io.undertow.util.Headers;
|
||||||
|
import io.undertow.util.StatusCodes;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class RenderVxHandler implements HttpHandler {
|
||||||
|
public static final String PATH_TEMPLATE = "/render/dyn/{output}";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleRequest(@NotNull final HttpServerExchange exchange) {
|
||||||
|
log.debug("Handling render request");
|
||||||
|
|
||||||
|
if (exchange.isInIoThread()) {
|
||||||
|
exchange.dispatch(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
exchange.getRequestReceiver().receiveFullBytes((httpServerExchange, bytes) -> {
|
||||||
|
final OutputType output = this.getOutputType(httpServerExchange);
|
||||||
|
final byte[] render;
|
||||||
|
try {
|
||||||
|
final Version version = this.getVersion(bytes);
|
||||||
|
final Maze maze = switch (version) {
|
||||||
|
case V1 -> SerializerDeserializerV1.deserialize(bytes);
|
||||||
|
case V2 -> SerializerDeserializerV2.deserialize(bytes);
|
||||||
|
case V3 -> SerializerDeserializerV3.deserialize(bytes);
|
||||||
|
};
|
||||||
|
render = output.render(maze);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Error rendering binary maze data", e);
|
||||||
|
httpServerExchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR)
|
||||||
|
.getResponseSender()
|
||||||
|
.send("Error rendering maze: %s".formatted(e.getMessage()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
httpServerExchange
|
||||||
|
.setStatusCode(StatusCodes.OK)
|
||||||
|
.getResponseHeaders()
|
||||||
|
.put(Headers.CONTENT_TYPE, output.getContentType());
|
||||||
|
httpServerExchange.getResponseSender()
|
||||||
|
.send(ByteBuffer.wrap(render));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Version getVersion(@NotNull final byte[] bytes) throws IllegalArgumentException {
|
||||||
|
if (bytes.length < 3) {
|
||||||
|
throw new IllegalArgumentException("Invalid input: too short");
|
||||||
|
}
|
||||||
|
if (bytes[0] == MazeConstants.MAGIC_BYTE_1 && bytes[1] == MazeConstants.MAGIC_BYTE_2) {
|
||||||
|
final byte version = bytes[2];
|
||||||
|
return switch (version) {
|
||||||
|
case SerializerDeserializerV1.VERSION_BYTE -> Version.V1;
|
||||||
|
case SerializerDeserializerV2.VERSION_BYTE -> Version.V2;
|
||||||
|
case SerializerDeserializerV3.VERSION_BYTE -> Version.V3;
|
||||||
|
default -> throw new IllegalArgumentException("Invalid version: " + version);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Invalid input: not a Maze file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private OutputType getOutputType(@NotNull final HttpServerExchange httpServerExchange) {
|
||||||
|
return RequestParameter.OUTPUT.<OutputType>getParameterValue(httpServerExchange.getQueryParameters())
|
||||||
|
.getOrElse(() -> {
|
||||||
|
final HeaderValues accept = httpServerExchange.getRequestHeaders().get(Headers.ACCEPT);
|
||||||
|
if (accept.contains(OutputType.HTML.getContentType())) {
|
||||||
|
return OutputType.HTML;
|
||||||
|
}
|
||||||
|
return OutputType.TEXT_PLAIN;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Version {
|
||||||
|
V1, V2, V3;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue