Make the generation of a labyrinth reproducible and refactor the
PDFRenderer a bit.
This commit is contained in:
parent
fd38b141a1
commit
6060a08573
6 changed files with 114 additions and 74 deletions
|
@ -6,33 +6,6 @@ import lombok.NonNull;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
public class HTMLRenderer implements Renderer<String> {
|
||||
private static final String PREAMBLE = "<!DOCTYPE html><html lang=\"en\">" +
|
||||
"<head>" +
|
||||
"<title>Labyrinth</title>" +
|
||||
"<meta charset=\"utf-8\">" +
|
||||
"<style>" +
|
||||
"table{border-collapse:collapse;}" +
|
||||
"td{border:0 solid black;height:1em;width:1em;}" +
|
||||
"td.top{border-top-width:1px;}" +
|
||||
"td.right{border-right-width:1px;}" +
|
||||
"td.bottom{border-bottom-width:1px;}" +
|
||||
"td.left{border-left-width:1px;}" +
|
||||
"</style>" +
|
||||
"<script>" +
|
||||
"let solution = false;" +
|
||||
"function toggleSolution() {" +
|
||||
"let stylesheet = document.styleSheets[0];" +
|
||||
"if(solution){" +
|
||||
"stylesheet.deleteRule(0);" +
|
||||
"}else{" +
|
||||
"stylesheet.insertRule(\"td.solution{background-color:lightgray;}\", 0);" +
|
||||
"}" +
|
||||
"solution = !solution;" +
|
||||
"}" +
|
||||
"</script>" +
|
||||
"</head>" +
|
||||
"<body>" +
|
||||
"<input type=\"checkbox\" onclick=\"toggleSolution()\">show solution</input>";
|
||||
private static final String POSTAMBLE = "</body></html>";
|
||||
|
||||
private HTMLRenderer() {
|
||||
|
@ -46,10 +19,10 @@ public class HTMLRenderer implements Renderer<String> {
|
|||
@NonNull
|
||||
public String render(@NonNull final Labyrinth labyrinth) {
|
||||
if (labyrinth.getWidth() == 0 || labyrinth.getHeight() == 0) {
|
||||
return PREAMBLE + POSTAMBLE;
|
||||
return this.getPreamble(labyrinth) + POSTAMBLE;
|
||||
}
|
||||
final Generator generator = new Generator(labyrinth);
|
||||
final StringBuilder sb = new StringBuilder(PREAMBLE);
|
||||
final StringBuilder sb = new StringBuilder(this.getPreamble(labyrinth));
|
||||
sb.append("<table>");
|
||||
while (generator.hasNext()) {
|
||||
sb.append(generator.next());
|
||||
|
@ -59,6 +32,36 @@ public class HTMLRenderer implements Renderer<String> {
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
private String getPreamble(@NonNull final Labyrinth labyrinth) {
|
||||
return "<!DOCTYPE html><html lang=\"en\">" +
|
||||
"<head>" +
|
||||
"<title>Labyrinth " + labyrinth.getWidth() + "x" + labyrinth.getHeight() + ", ID " + labyrinth.getRandomSeed() + "</title>" +
|
||||
"<meta charset=\"utf-8\">" +
|
||||
"<style>" +
|
||||
"table{border-collapse:collapse;}" +
|
||||
"td{border:0 solid black;height:1em;width:1em;}" +
|
||||
"td.top{border-top-width:1px;}" +
|
||||
"td.right{border-right-width:1px;}" +
|
||||
"td.bottom{border-bottom-width:1px;}" +
|
||||
"td.left{border-left-width:1px;}" +
|
||||
"</style>" +
|
||||
"<script>" +
|
||||
"let solution = false;" +
|
||||
"function toggleSolution() {" +
|
||||
"let stylesheet = document.styleSheets[0];" +
|
||||
"if(solution){" +
|
||||
"stylesheet.deleteRule(0);" +
|
||||
"}else{" +
|
||||
"stylesheet.insertRule(\"td.solution{background-color:lightgray;}\", 0);" +
|
||||
"}" +
|
||||
"solution = !solution;" +
|
||||
"}" +
|
||||
"</script>" +
|
||||
"</head>" +
|
||||
"<body>" +
|
||||
"<input type=\"checkbox\" onclick=\"toggleSolution()\">show solution</input>";
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class Generator {
|
||||
private final Labyrinth labyrinth;
|
||||
|
|
|
@ -6,6 +6,7 @@ import lombok.NonNull;
|
|||
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Random;
|
||||
|
||||
public class Labyrinth {
|
||||
private final Tile[][] field;
|
||||
|
@ -14,13 +15,22 @@ public class Labyrinth {
|
|||
@Getter
|
||||
private final int height;
|
||||
@Getter
|
||||
private final long randomSeed;
|
||||
private final Random random;
|
||||
@Getter
|
||||
private final Position start;
|
||||
@Getter
|
||||
private final Position end;
|
||||
|
||||
public Labyrinth(final int width, final int height) {
|
||||
this(width, height, System.nanoTime());
|
||||
}
|
||||
|
||||
public Labyrinth(final int width, final int height, final long randomSeed) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.randomSeed = randomSeed;
|
||||
this.random = new Random(this.randomSeed);
|
||||
this.field = new Tile[width][height];
|
||||
this.start = new Position(0, 0);
|
||||
this.end = new Position(this.width - 1, this.height - 1);
|
||||
|
@ -94,7 +104,7 @@ public class Labyrinth {
|
|||
while (!this.positions.isEmpty()) {
|
||||
final Position currentPosition = this.positions.peek();
|
||||
final Tile currentTile = Labyrinth.this.getTileAt(currentPosition);
|
||||
final Option<Direction> directionToDigTo = currentTile.getRandomAvailableDirection();
|
||||
final Option<Direction> directionToDigTo = currentTile.getRandomAvailableDirection(Labyrinth.this.random);
|
||||
if (directionToDigTo.isDefined()) {
|
||||
final Direction digTo = directionToDigTo.get();
|
||||
final Direction digFrom = digTo.invert();
|
||||
|
|
|
@ -9,17 +9,20 @@ public class Main {
|
|||
public static void main(@NonNull final String[] args) {
|
||||
int width = 100;
|
||||
int height = 100;
|
||||
final Labyrinth labyrinth = new Labyrinth(width, height);
|
||||
final Labyrinth labyrinth = new Labyrinth(width, height/*, 0*/);
|
||||
final TextRenderer textRenderer = TextRenderer.newInstance();
|
||||
final HTMLRenderer htmlRenderer = HTMLRenderer.newInstance();
|
||||
final Path userHome = Paths.get(System.getProperty("user.home"));
|
||||
final String baseFilename = getBaseFilename(labyrinth);
|
||||
final TextFileRenderer textFileRenderer = TextFileRenderer.newInstance()
|
||||
.setTargetLabyrinthFile(userHome.resolve("labyrinth.txt"))
|
||||
.setTargetSolutionFile(userHome.resolve("labyrinth-solution.txt"));
|
||||
.setTargetLabyrinthFile(userHome.resolve(baseFilename + ".txt"))
|
||||
.setTargetSolutionFile(userHome.resolve(baseFilename + "-solution.txt"));
|
||||
final HTMLFileRenderer htmlFileRenderer = HTMLFileRenderer.newInstance()
|
||||
.setTargetFile(userHome.resolve("labyrinth.html"));
|
||||
final PDFFileRenderer pdfFileRenderer = PDFFileRenderer.newInstance().setTargetFile(userHome.resolve("labyrinth.pdf"));
|
||||
.setTargetFile(userHome.resolve(baseFilename + ".html"));
|
||||
final PDFFileRenderer pdfFileRenderer = PDFFileRenderer.newInstance()
|
||||
.setTargetFile(userHome.resolve(baseFilename + ".pdf"));
|
||||
|
||||
System.out.println("Labyrinth-ID: " + labyrinth.getRandomSeed());
|
||||
// Render Labyrinth to stdout
|
||||
System.out.println(textRenderer.render(labyrinth));
|
||||
// Render Labyrinth solution to stdout
|
||||
|
@ -33,4 +36,8 @@ public class Main {
|
|||
// Render PDF to file
|
||||
System.out.println(pdfFileRenderer.render(labyrinth));
|
||||
}
|
||||
|
||||
private static String getBaseFilename(@NonNull final Labyrinth labyrinth) {
|
||||
return "labyrinth-" + labyrinth.getWidth() + "x" + labyrinth.getHeight() + "-" + labyrinth.getRandomSeed();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package ch.fritteli.labyrinth;
|
|||
|
||||
import lombok.NonNull;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
|
@ -35,30 +36,22 @@ public class PDFRenderer implements Renderer<byte[]> {
|
|||
final float pageHeight = labyrinth.getHeight() * SCALE + 2 * MARGIN;
|
||||
|
||||
final PDDocument pdDocument = new PDDocument();
|
||||
final PDDocumentInformation info = new PDDocumentInformation();
|
||||
info.setTitle("Labyrinth " + labyrinth.getWidth() + "x" + labyrinth.getHeight() + ", ID " + labyrinth.getRandomSeed());
|
||||
pdDocument.setDocumentInformation(info);
|
||||
final PDPage page = new PDPage(new PDRectangle(pageWidth, pageHeight));
|
||||
pdDocument.addPage(page);
|
||||
try (PDPageContentStream pdPageContentStream = new PDPageContentStream(pdDocument, page)) {
|
||||
pdPageContentStream.setLineCapStyle(BasicStroke.CAP_ROUND);
|
||||
pdPageContentStream.setLineJoinStyle(BasicStroke.JOIN_MITER);
|
||||
pdPageContentStream.setLineWidth(1.0f);
|
||||
pdPageContentStream.setStrokingColor(Color.BLACK);
|
||||
pdPageContentStream.setNonStrokingColor(Color.BLACK);
|
||||
this.drawHorizonzalLines(labyrinth, pdPageContentStream);
|
||||
this.drawVerticalLines(labyrinth, pdPageContentStream);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
final PDPage solution = new PDPage(new PDRectangle(pageWidth, pageHeight));
|
||||
pdDocument.addPage(page);
|
||||
pdDocument.addPage(solution);
|
||||
try (PDPageContentStream pdPageContentStream = new PDPageContentStream(pdDocument, solution)) {
|
||||
pdPageContentStream.setLineCapStyle(BasicStroke.CAP_ROUND);
|
||||
pdPageContentStream.setLineJoinStyle(BasicStroke.JOIN_MITER);
|
||||
pdPageContentStream.setLineWidth(1.0f);
|
||||
pdPageContentStream.setStrokingColor(Color.BLACK);
|
||||
pdPageContentStream.setNonStrokingColor(Color.BLACK);
|
||||
this.drawHorizonzalLines(labyrinth, pdPageContentStream);
|
||||
this.drawVerticalLines(labyrinth, pdPageContentStream);
|
||||
this.drawSolution(labyrinth, pdPageContentStream);
|
||||
try (final PDPageContentStream labyrinthPageContentStream = new PDPageContentStream(pdDocument, page);
|
||||
final PDPageContentStream solutionPageContentStream = new PDPageContentStream(pdDocument, solution)) {
|
||||
setUpPageContentStream(labyrinthPageContentStream);
|
||||
setUpPageContentStream(solutionPageContentStream);
|
||||
this.drawHorizonzalLines(labyrinth, labyrinthPageContentStream);
|
||||
this.drawVerticalLines(labyrinth, labyrinthPageContentStream);
|
||||
this.drawHorizonzalLines(labyrinth, solutionPageContentStream);
|
||||
this.drawVerticalLines(labyrinth, solutionPageContentStream);
|
||||
this.drawSolution(labyrinth, solutionPageContentStream);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -72,51 +65,65 @@ public class PDFRenderer implements Renderer<byte[]> {
|
|||
return output.toByteArray();
|
||||
}
|
||||
|
||||
private void setUpPageContentStream(@NonNull final PDPageContentStream pageContentStream) throws IOException {
|
||||
pageContentStream.setLineCapStyle(BasicStroke.CAP_ROUND);
|
||||
pageContentStream.setLineJoinStyle(BasicStroke.JOIN_ROUND);
|
||||
pageContentStream.setLineWidth(1.0f);
|
||||
pageContentStream.setStrokingColor(Color.BLACK);
|
||||
pageContentStream.setNonStrokingColor(Color.BLACK);
|
||||
}
|
||||
|
||||
private void drawHorizonzalLines(@NonNull final Labyrinth labyrinth,
|
||||
@NonNull final PDPageContentStream pdPageContentStream) throws IOException {
|
||||
// PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required.
|
||||
final float labyrinthHeight = labyrinth.getHeight() * SCALE;
|
||||
for (int y = 0; y < labyrinth.getHeight(); y++) {
|
||||
boolean isPainting = false;
|
||||
final float yCoordinate = labyrinthHeight - y * SCALE + MARGIN;
|
||||
for (int x = 0; x < labyrinth.getWidth(); x++) {
|
||||
final Tile currentTile = labyrinth.getTileAt(x, y);
|
||||
final float xCoordinate = x * SCALE + MARGIN;
|
||||
if (currentTile.hasWallAt(Direction.TOP)) {
|
||||
if (!isPainting) {
|
||||
pdPageContentStream.moveTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN);
|
||||
pdPageContentStream.moveTo(xCoordinate, yCoordinate);
|
||||
isPainting = true;
|
||||
}
|
||||
} else {
|
||||
if (isPainting) {
|
||||
pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN);
|
||||
pdPageContentStream.lineTo(xCoordinate, yCoordinate);
|
||||
pdPageContentStream.stroke();
|
||||
isPainting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isPainting) {
|
||||
pdPageContentStream.lineTo(labyrinth.getWidth() * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN);
|
||||
final float xCoordinate = labyrinth.getWidth() * SCALE + MARGIN;
|
||||
pdPageContentStream.lineTo(xCoordinate, yCoordinate);
|
||||
pdPageContentStream.stroke();
|
||||
}
|
||||
}
|
||||
boolean isPainting = false;
|
||||
int y = labyrinth.getHeight();
|
||||
final float yCoordinate = /*labyrinthHeight - y * SCALE +*/ MARGIN;
|
||||
for (int x = 0; x < labyrinth.getWidth(); x++) {
|
||||
final Tile currentTile = labyrinth.getTileAt(x, y - 1);
|
||||
final float xCoordinate = x * SCALE + MARGIN;
|
||||
if (currentTile.hasWallAt(Direction.BOTTOM)) {
|
||||
if (!isPainting) {
|
||||
pdPageContentStream.moveTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN);
|
||||
pdPageContentStream.moveTo(xCoordinate, yCoordinate);
|
||||
isPainting = true;
|
||||
}
|
||||
} else {
|
||||
if (isPainting) {
|
||||
pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN);
|
||||
pdPageContentStream.lineTo(xCoordinate, yCoordinate);
|
||||
pdPageContentStream.stroke();
|
||||
isPainting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
final float xCoordinate = labyrinth.getWidth() * SCALE + MARGIN;
|
||||
if (isPainting) {
|
||||
pdPageContentStream.lineTo(labyrinth.getWidth() * SCALE + MARGIN, labyrinthHeight - labyrinth.getHeight() * SCALE + MARGIN);
|
||||
pdPageContentStream.lineTo(xCoordinate, yCoordinate);
|
||||
pdPageContentStream.stroke();
|
||||
}
|
||||
}
|
||||
|
@ -127,45 +134,49 @@ public class PDFRenderer implements Renderer<byte[]> {
|
|||
final float labyrinthHeight = labyrinth.getHeight() * SCALE;
|
||||
for (int x = 0; x < labyrinth.getWidth(); x++) {
|
||||
boolean isPainting = false;
|
||||
final float xCoordinate = x * SCALE + MARGIN;
|
||||
for (int y = 0; y < labyrinth.getHeight(); y++) {
|
||||
final Tile currentTile = labyrinth.getTileAt(x, y);
|
||||
final float yCoordinate = labyrinthHeight - y * SCALE + MARGIN;
|
||||
if (currentTile.hasWallAt(Direction.LEFT)) {
|
||||
if (!isPainting) {
|
||||
pdPageContentStream.moveTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN);
|
||||
pdPageContentStream.moveTo(xCoordinate, yCoordinate);
|
||||
isPainting = true;
|
||||
}
|
||||
} else {
|
||||
if (isPainting) {
|
||||
pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN);
|
||||
pdPageContentStream.lineTo(xCoordinate, yCoordinate);
|
||||
pdPageContentStream.stroke();
|
||||
isPainting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isPainting) {
|
||||
pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - labyrinth.getHeight() * SCALE + MARGIN);
|
||||
pdPageContentStream.lineTo(xCoordinate, MARGIN);
|
||||
pdPageContentStream.stroke();
|
||||
}
|
||||
}
|
||||
boolean isPainting = false;
|
||||
int x = labyrinth.getWidth();
|
||||
final float xCoordinate = x * SCALE + MARGIN;
|
||||
for (int y = 0; y < labyrinth.getHeight(); y++) {
|
||||
final Tile currentTile = labyrinth.getTileAt(x - 1, y);
|
||||
final float yCoordinate = labyrinthHeight - y * SCALE + MARGIN;
|
||||
if (currentTile.hasWallAt(Direction.RIGHT)) {
|
||||
if (!isPainting) {
|
||||
pdPageContentStream.moveTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN);
|
||||
pdPageContentStream.moveTo(xCoordinate, yCoordinate);
|
||||
isPainting = true;
|
||||
}
|
||||
} else {
|
||||
if (isPainting) {
|
||||
pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN);
|
||||
pdPageContentStream.lineTo(xCoordinate, yCoordinate);
|
||||
pdPageContentStream.stroke();
|
||||
isPainting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isPainting) {
|
||||
pdPageContentStream.lineTo(labyrinth.getWidth() * SCALE + MARGIN, labyrinthHeight - labyrinth.getHeight() * SCALE + MARGIN);
|
||||
pdPageContentStream.lineTo(xCoordinate, MARGIN);
|
||||
pdPageContentStream.stroke();
|
||||
}
|
||||
}
|
||||
|
@ -184,11 +195,14 @@ public class PDFRenderer implements Renderer<byte[]> {
|
|||
Position newCurrent = this.findNextSolutionPosition(labyrinth, previousPosition, currentPosition);
|
||||
previousPosition = currentPosition;
|
||||
currentPosition = newCurrent;
|
||||
pdPageContentStream.lineTo(MARGIN + currentPosition.getX() * SCALE + SCALE / 2, MARGIN + labyrinthHeight - (currentPosition.getY() * SCALE + SCALE / 2));
|
||||
final float xCoordinate = MARGIN + currentPosition.getX() * SCALE + SCALE / 2;
|
||||
final float yCoordinate = MARGIN + labyrinthHeight - (currentPosition.getY() * SCALE + SCALE / 2);
|
||||
pdPageContentStream.lineTo(xCoordinate, yCoordinate);
|
||||
} while (!currentPosition.equals(end));
|
||||
pdPageContentStream.stroke();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Position findNextSolutionPosition(@NonNull final Labyrinth labyrinth, @Nullable final Position previousPosition, @NonNull final Position currentPosition) {
|
||||
final Tile currentTile = labyrinth.getTileAt(currentPosition);
|
||||
for (final Direction direction : Direction.values()) {
|
||||
|
@ -202,7 +216,6 @@ public class PDFRenderer implements Renderer<byte[]> {
|
|||
}
|
||||
}
|
||||
}
|
||||
// We *SHOULD* never get here. ... famous last words ...
|
||||
return null;
|
||||
throw new IllegalStateException("We *SHOULD* never have gotten here. ... famous last words ...");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import lombok.Getter;
|
|||
import lombok.NonNull;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
public class Tile {
|
||||
final Walls walls = new Walls();
|
||||
|
@ -42,8 +44,13 @@ public class Tile {
|
|||
this.walls.set(direction);
|
||||
}
|
||||
|
||||
public Option<Direction> getRandomAvailableDirection() {
|
||||
return Stream.ofAll(this.walls.getUnhardenedSet()).shuffle().headOption();
|
||||
public Option<Direction> getRandomAvailableDirection(@NonNull final Random random) {
|
||||
final Stream<Direction> availableDirections = this.walls.getUnhardenedSet();
|
||||
if (availableDirections.isEmpty()) {
|
||||
return Option.none();
|
||||
}
|
||||
final int index = random.nextInt(availableDirections.length());
|
||||
return Option.of(availableDirections.get(index));
|
||||
}
|
||||
|
||||
public boolean hasWallAt(@NonNull final Direction direction) {
|
||||
|
|
|
@ -30,7 +30,7 @@ public class Walls {
|
|||
return this.directions.contains(direction);
|
||||
}
|
||||
|
||||
public Iterable<Direction> getUnhardenedSet() {
|
||||
public Stream<Direction> getUnhardenedSet() {
|
||||
return Stream.ofAll(this.directions)
|
||||
.removeAll(this.hardened);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue