Implement Wilson's algorithm and let the caller choose the algorithm.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing

This commit is contained in:
Manuel Friedli 2024-12-24 03:28:35 +01:00
parent c4d707d64a
commit 3c2fca9a74
Signed by: manuel
GPG key ID: 41D08ABA75634DA1
8 changed files with 176 additions and 12 deletions

View file

@ -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>

View file

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

View file

@ -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)

View file

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

View file

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

View file

@ -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) {
} }
} }

View file

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

View file

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