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,7 +2,7 @@ package ch.fritteli.maze.server; | |||
| 
 | ||||
| import ch.fritteli.maze.generator.algorithm.MazeGeneratorAlgorithm; | ||||
| 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 io.vavr.collection.List; | ||||
| 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.RenderV1Handler; | ||||
| 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.server.RoutingHandler; | ||||
| import io.vavr.control.Try; | ||||
|  | @ -24,7 +26,9 @@ public class MazeServer { | |||
|         final RoutingHandler routingHandler = new RoutingHandler() | ||||
|                 .get(CreateHandler.PATH_TEMPLATE, new CreateHandler(config.maxMazeHeight(), config.maxMazeWidth())) | ||||
|                 .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() | ||||
|                 .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.serialization.v1.SerializerDeserializerV1; | ||||
| 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.Stream; | ||||
| import io.vavr.control.Option; | ||||
|  | @ -23,7 +24,8 @@ public enum OutputType { | |||
|             maze -> TextRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8), | ||||
|             false, | ||||
|             "t", | ||||
|             "text"), | ||||
|             "text", | ||||
|             "txt"), | ||||
|     HTML("text/html", | ||||
|             "html", | ||||
|             maze -> HTMLRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8), | ||||
|  | @ -65,7 +67,13 @@ public enum OutputType { | |||
|             SerializerDeserializerV2::serialize, | ||||
|             true, | ||||
|             "v", | ||||
|             "binaryv2"); | ||||
|             "binaryv2"), | ||||
|     BINARY_V3("application/octet-stream", | ||||
|             "maz3", | ||||
|             SerializerDeserializerV3::serialize, | ||||
|             true, | ||||
|             "3", | ||||
|             "binaryv3"); | ||||
|     @Getter | ||||
|     @NotNull | ||||
|     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-Width"), String.valueOf(maze.getWidth())) | ||||
|                             .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)); | ||||
|                     if (outputType.isAttachment()) { | ||||
|                         exchange.getResponseHeaders() | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| 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.Position; | ||||
| import ch.fritteli.maze.server.Algorithm; | ||||
|  | @ -80,10 +80,10 @@ class ParametersToMazeExtractor { | |||
|                 maze = new Maze(desiredWidth, desiredHeight, id.getOrElse(() -> new Random().nextLong())); | ||||
|             } | ||||
| 
 | ||||
|             algorithm.getOrElse(Algorithm.RANDOM_DEPTH_FIRST) | ||||
|                     .createAlgorithm(maze) | ||||
|                     .run(); | ||||
|             return new GeneratedMaze(maze, output.get(), RandomDepthFirst.class.getSimpleName()); | ||||
|             final MazeGeneratorAlgorithm generator = algorithm.getOrElse(Algorithm.WILSON) | ||||
|                     .createAlgorithm(maze); | ||||
|             generator.run(); | ||||
|             return new GeneratedMaze(maze, output.get()); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | @ -92,6 +92,6 @@ class ParametersToMazeExtractor { | |||
|         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…
	
	Add table
		Add a link
		
	
		Reference in a new issue