From 41330de4f414cf69fca52dc54221bb5573f257be Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Thu, 11 Feb 2021 21:29:02 +0100 Subject: [PATCH 1/7] Clean up some code, add comments. Tiny stuff only. --- .../generator/renderer/pdf/Generator.java | 24 +++++++----- .../generator/renderer/pdf/PDFRenderer.java | 3 -- .../renderer/pdffile/PDFFileRenderer.java | 38 ++++++++++++------- src/main/main.iml | 11 ------ src/test/test.iml | 11 ------ 5 files changed, 38 insertions(+), 49 deletions(-) delete mode 100644 src/main/main.iml delete mode 100644 src/test/test.iml diff --git a/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/Generator.java b/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/Generator.java index a45d60d..7f15ba1 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/Generator.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/Generator.java @@ -36,16 +36,16 @@ class Generator { final PDDocumentInformation info = new PDDocumentInformation(); info.setTitle("Labyrinth " + this.labyrinth.getWidth() + "x" + this.labyrinth.getHeight() + ", ID " + this.labyrinth.getRandomSeed()); pdDocument.setDocumentInformation(info); - final PDPage page = new PDPage(new PDRectangle(pageWidth, pageHeight)); - final PDPage solution = new PDPage(new PDRectangle(pageWidth, pageHeight)); - pdDocument.addPage(page); - pdDocument.addPage(solution); - try (final PDPageContentStream labyrinthPageContentStream = new PDPageContentStream(pdDocument, page); - final PDPageContentStream solutionPageContentStream = new PDPageContentStream(pdDocument, solution)) { - setUpPageContentStream(labyrinthPageContentStream); + final PDPage puzzlePage = new PDPage(new PDRectangle(pageWidth, pageHeight)); + final PDPage solutionPage = new PDPage(new PDRectangle(pageWidth, pageHeight)); + pdDocument.addPage(puzzlePage); + pdDocument.addPage(solutionPage); + try (final PDPageContentStream puzzlePageContentStream = new PDPageContentStream(pdDocument, puzzlePage); + final PDPageContentStream solutionPageContentStream = new PDPageContentStream(pdDocument, solutionPage)) { + setUpPageContentStream(puzzlePageContentStream); setUpPageContentStream(solutionPageContentStream); - this.drawHorizonzalLines(labyrinthPageContentStream, solutionPageContentStream); - this.drawVerticalLines(labyrinthPageContentStream, solutionPageContentStream); + this.drawHorizontalLines(puzzlePageContentStream, solutionPageContentStream); + this.drawVerticalLines(puzzlePageContentStream, solutionPageContentStream); this.drawSolution(solutionPageContentStream); } catch (IOException e) { e.printStackTrace(); @@ -68,9 +68,10 @@ class Generator { pageContentStream.setNonStrokingColor(Color.BLACK); } - private void drawHorizonzalLines(@NonNull final PDPageContentStream... contentStreams) throws IOException { + private void drawHorizontalLines(@NonNull final PDPageContentStream... contentStreams) throws IOException { // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. Coordinate coordinate = new Coordinate(0f, 0f); + // Draw the TOP borders of all tiles. for (int y = 0; y < this.labyrinth.getHeight(); y++) { boolean isPainting = false; coordinate = coordinate.withY(y); @@ -102,6 +103,7 @@ class Generator { } } } + // Draw the BOTTOM border of the last row of tiles. boolean isPainting = false; int y = this.labyrinth.getHeight(); coordinate = coordinate.withY(this.labyrinth.getHeight()); @@ -137,6 +139,7 @@ class Generator { private void drawVerticalLines(@NonNull final PDPageContentStream... contentStreams) throws IOException { // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. Coordinate coordinate = new Coordinate(0f, 0f); + // Draw the LEFT borders of all tiles. for (int x = 0; x < this.labyrinth.getWidth(); x++) { boolean isPainting = false; coordinate = coordinate.withX(x); @@ -168,6 +171,7 @@ class Generator { } } } + // Draw the RIGHT border of the last column of tiles. boolean isPainting = false; int x = this.labyrinth.getWidth(); coordinate = coordinate.withX(this.labyrinth.getWidth()); diff --git a/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/PDFRenderer.java b/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/PDFRenderer.java index 099d2ec..dd4183c 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/PDFRenderer.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/PDFRenderer.java @@ -9,7 +9,6 @@ public class PDFRenderer implements Renderer { static final float SCALE = 10; private PDFRenderer() { - } @NonNull @@ -23,6 +22,4 @@ public class PDFRenderer implements Renderer { final Generator generator = new Generator(labyrinth); return generator.generate(); } - - } diff --git a/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdffile/PDFFileRenderer.java b/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdffile/PDFFileRenderer.java index 8523c5b..781d33f 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdffile/PDFFileRenderer.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdffile/PDFFileRenderer.java @@ -3,24 +3,30 @@ package ch.fritteli.labyrinth.generator.renderer.pdffile; import ch.fritteli.labyrinth.generator.model.Labyrinth; import ch.fritteli.labyrinth.generator.renderer.Renderer; import ch.fritteli.labyrinth.generator.renderer.pdf.PDFRenderer; +import io.vavr.control.Option; +import io.vavr.control.Try; import lombok.NonNull; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.NoSuchElementException; public class PDFFileRenderer implements Renderer { @NonNull private static final PDFRenderer PDF_RENDERER = PDFRenderer.newInstance(); - private Path targetFile; + @NonNull + private Option targetFile; private PDFFileRenderer() { - try { - this.targetFile = Files.createTempFile("labyrinth_", ".pdf"); - } catch (IOException e) { - System.err.println("Unable to set default target file."); - e.printStackTrace(); - } + this.targetFile = Try + .of(() -> Files.createTempFile("labyrinth_", ".pdf")) + .onFailure(ex -> { + System.err.println("Unable to set default target file."); + ex.printStackTrace(); + }) + .toOption(); } @NonNull @@ -29,12 +35,15 @@ public class PDFFileRenderer implements Renderer { } public boolean isTargetFileDefinedAndWritable() { - return this.targetFile != null && this.targetFile.toFile().canWrite(); + return this.targetFile + .map(Path::toFile) + .map(File::canWrite) + .getOrElse(false); } @NonNull public PDFFileRenderer setTargetFile(@NonNull final Path targetFile) { - this.targetFile = targetFile; + this.targetFile = Option.of(targetFile); return this; } @@ -42,18 +51,19 @@ public class PDFFileRenderer implements Renderer { public Path render(@NonNull final Labyrinth labyrinth) { if (!this.isTargetFileDefinedAndWritable()) { try { - Files.createFile(this.targetFile); - } catch (IOException e) { + Files.createFile(this.targetFile.get()); + } catch (IOException | NoSuchElementException e) { throw new IllegalArgumentException("Cannot write to target file.", e); } } final byte[] bytes = PDF_RENDERER.render(labyrinth); + final Path targetFile = this.targetFile.get(); try { - Files.write(this.targetFile, bytes); + Files.write(targetFile, bytes); } catch (IOException e) { - System.err.println("Failed writing to file " + this.targetFile.normalize().toString()); + System.err.println("Failed writing to file " + targetFile.normalize().toString()); e.printStackTrace(); } - return this.targetFile; + return targetFile; } } diff --git a/src/main/main.iml b/src/main/main.iml deleted file mode 100644 index 908ad4f..0000000 --- a/src/main/main.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/test.iml b/src/test/test.iml deleted file mode 100644 index a0e49a3..0000000 --- a/src/test/test.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file -- 2.45.2 From 185114bcf402c821192e39430971b3c651c12d6b Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Fri, 12 Feb 2021 01:51:17 +0100 Subject: [PATCH 2/7] Add serialization and deserialization to a compact byte-based format. --- .../labyrinth/generator/model/Labyrinth.java | 24 +++ .../labyrinth/generator/model/Tile.java | 21 ++ .../labyrinth/generator/model/Walls.java | 4 + .../serialization/LabyrinthInputStream.java | 36 ++++ .../serialization/LabyrinthOutputStream.java | 21 ++ .../serialization/SerializerDeserializer.java | 197 ++++++++++++++++++ .../SerializerDeserializerTest.java | 33 +++ 7 files changed, 336 insertions(+) create mode 100644 src/main/java/ch/fritteli/labyrinth/generator/serialization/LabyrinthInputStream.java create mode 100644 src/main/java/ch/fritteli/labyrinth/generator/serialization/LabyrinthOutputStream.java create mode 100644 src/main/java/ch/fritteli/labyrinth/generator/serialization/SerializerDeserializer.java create mode 100644 src/test/java/ch/fritteli/labyrinth/generator/serialization/SerializerDeserializerTest.java diff --git a/src/main/java/ch/fritteli/labyrinth/generator/model/Labyrinth.java b/src/main/java/ch/fritteli/labyrinth/generator/model/Labyrinth.java index 03b0e99..3390a3f 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/model/Labyrinth.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/model/Labyrinth.java @@ -1,13 +1,17 @@ package ch.fritteli.labyrinth.generator.model; import io.vavr.control.Option; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NonNull; +import lombok.ToString; import java.util.Deque; import java.util.LinkedList; import java.util.Random; +@EqualsAndHashCode +@ToString public class Labyrinth { private final Tile[][] field; @Getter @@ -16,6 +20,7 @@ public class Labyrinth { private final int height; @Getter private final long randomSeed; + @EqualsAndHashCode.Exclude private final Random random; @Getter private final Position start; @@ -41,6 +46,25 @@ public class Labyrinth { this.generate(); } + /** + * INTERNAL API. + * Exists only for deserialization. Not to be called from user code. + * + * @param field + * @param width + * @param height + * @param randomSeed + */ + private Labyrinth(@NonNull final Tile[][] field, final int width, final int height, final long randomSeed) { + this.field = field; + this.width = width; + this.height = height; + this.randomSeed = randomSeed; + this.random = new Random(randomSeed); + this.start = new Position(0, 0); + this.end = new Position(this.width - 1, this.height - 1); + } + @NonNull public Option getTileAt(@NonNull final Position position) { return this.getTileAt(position.getX(), position.getY()); diff --git a/src/main/java/ch/fritteli/labyrinth/generator/model/Tile.java b/src/main/java/ch/fritteli/labyrinth/generator/model/Tile.java index c7fb01b..b3e9735 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/model/Tile.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/model/Tile.java @@ -3,13 +3,18 @@ package ch.fritteli.labyrinth.generator.model; import io.vavr.collection.Stream; import io.vavr.control.Option; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NonNull; +import lombok.ToString; import lombok.experimental.FieldDefaults; +import java.util.EnumSet; import java.util.Random; @FieldDefaults(level = AccessLevel.PRIVATE) +@EqualsAndHashCode +@ToString public class Tile { final Walls walls = new Walls(); boolean visited = false; @@ -20,6 +25,22 @@ public class Tile { this.walls.setAll(); } + /** + * INTERNAL API. + * Exists only for deserialization. Not to be called from user code. + * + * @param walls + * @param solution + */ + private Tile(@NonNull final EnumSet walls, final boolean solution) { + for (final Direction direction : walls) { + this.walls.set(direction); + this.walls.harden(direction); + } + this.visited = true; + this.solution = solution; + } + public void preventDiggingToOrFrom(@NonNull final Direction direction) { this.walls.harden(direction); } diff --git a/src/main/java/ch/fritteli/labyrinth/generator/model/Walls.java b/src/main/java/ch/fritteli/labyrinth/generator/model/Walls.java index 0dced66..c7ce5e7 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/model/Walls.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/model/Walls.java @@ -1,7 +1,9 @@ package ch.fritteli.labyrinth.generator.model; import io.vavr.collection.Stream; +import lombok.EqualsAndHashCode; import lombok.NonNull; +import lombok.ToString; import java.util.EnumSet; import java.util.HashSet; @@ -9,6 +11,8 @@ import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +@EqualsAndHashCode +@ToString public class Walls { private final SortedSet directions = new TreeSet<>(); private final Set hardened = new HashSet<>(); diff --git a/src/main/java/ch/fritteli/labyrinth/generator/serialization/LabyrinthInputStream.java b/src/main/java/ch/fritteli/labyrinth/generator/serialization/LabyrinthInputStream.java new file mode 100644 index 0000000..1d13101 --- /dev/null +++ b/src/main/java/ch/fritteli/labyrinth/generator/serialization/LabyrinthInputStream.java @@ -0,0 +1,36 @@ +package ch.fritteli.labyrinth.generator.serialization; + +import lombok.NonNull; + +import java.io.ByteArrayInputStream; + +public class LabyrinthInputStream extends ByteArrayInputStream { + public LabyrinthInputStream(@NonNull final byte[] buf) { + super(buf); + } + + public byte readByte() { + final int read = this.read(); + if (read == -1) { + // end of stream reached + throw new ArrayIndexOutOfBoundsException("End of stream reached. Cannot read more bytes."); + } + return (byte) read; + } + + public int readInt() { + int result = 0; + result |= (0xff & this.readByte()) << 24; + result |= (0xff & this.readByte()) << 16; + result |= (0xff & this.readByte()) << 8; + result |= 0xff & this.readByte(); + return result; + } + + public long readLong() { + long result = 0; + result |= ((long) this.readInt()) << 32; + result |= 0xffffffffL & this.readInt(); + return result; + } +} diff --git a/src/main/java/ch/fritteli/labyrinth/generator/serialization/LabyrinthOutputStream.java b/src/main/java/ch/fritteli/labyrinth/generator/serialization/LabyrinthOutputStream.java new file mode 100644 index 0000000..6d2327f --- /dev/null +++ b/src/main/java/ch/fritteli/labyrinth/generator/serialization/LabyrinthOutputStream.java @@ -0,0 +1,21 @@ +package ch.fritteli.labyrinth.generator.serialization; + +import java.io.ByteArrayOutputStream; + +public class LabyrinthOutputStream extends ByteArrayOutputStream { + public void writeByte(final byte value) { + this.write(value); + } + + public void writeInt(final int value) { + this.write(value >> 24); + this.write(value >> 16); + this.write(value >> 8); + this.write(value); + } + + public void writeLong(final long value) { + this.writeInt((int) (value >> 32)); + this.writeInt((int) value); + } +} diff --git a/src/main/java/ch/fritteli/labyrinth/generator/serialization/SerializerDeserializer.java b/src/main/java/ch/fritteli/labyrinth/generator/serialization/SerializerDeserializer.java new file mode 100644 index 0000000..3315202 --- /dev/null +++ b/src/main/java/ch/fritteli/labyrinth/generator/serialization/SerializerDeserializer.java @@ -0,0 +1,197 @@ +package ch.fritteli.labyrinth.generator.serialization; + +import ch.fritteli.labyrinth.generator.model.Direction; +import ch.fritteli.labyrinth.generator.model.Labyrinth; +import ch.fritteli.labyrinth.generator.model.Tile; +import lombok.NonNull; +import lombok.experimental.UtilityClass; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.EnumSet; + +/** + *
+ * decimal hex border
+ *       0   0 no border
+ *       1   1 top
+ *       2   2 right
+ *       3   3 top+right
+ *       4   4 bottom
+ *       5   5 top+bottom
+ *       6   6 right+bottom
+ *       7   7 top+right+bottom
+ *       8   8 left
+ *       9   9 top+left
+ *      10   a right+left
+ *      11   b top+right+left
+ *      12   c bottom+left
+ *      13   d top+bottom+left
+ *      14   e right+bottom+left
+ *      15   f top+right+bottom+left
+ * 
+ * ==> bits 0..2: always 0; bit 3: 1=solution, 0=not solution; bits 4..7: encode walls + * ==> first bytes are: + *
+ *   byte  hex meaning
+ *     00 0x1a magic
+ *     01 0xb1 magic
+ *     02 0x00 version (0x00 -> dev / unstable; will be bumped to 0x01 once stabilized)
+ * 03..06      width (int)
+ * 07..10      height (int)
+ * 11..18      random seed number (long)
+ * 19..        tiles
+ * 
+ * exteaneous space (poss. last nibble) is ignored. + */ +@UtilityClass +public class SerializerDeserializer { + private final byte MAGIC_BYTE_1 = 0x1a; + private final byte MAGIC_BYTE_2 = (byte) 0xb1; + private final byte VERSION_BYTE = 0x01; + + private final byte TOP_BIT = 0b0000_0001; + private final byte RIGHT_BIT = 0b0000_0010; + private final byte BOTTOM_BIT = 0b0000_0100; + private final byte LEFT_BIT = 0b0000_1000; + private final byte SOLUTION_BIT = 0b0001_0000; + + /** + * Serializes the {@code labyrinth} into a byte array. + * + * @param labyrinth The labyrinth to be serialized. + * @return The resulting byte array. + */ + @NonNull + public byte[] serialize(@NonNull final Labyrinth labyrinth) { + @NonNull final LabyrinthOutputStream stream = new LabyrinthOutputStream(); + final int width = labyrinth.getWidth(); + final int height = labyrinth.getHeight(); + final long randomSeed = labyrinth.getRandomSeed(); + stream.writeByte(MAGIC_BYTE_1); + stream.writeByte(MAGIC_BYTE_2); + stream.writeByte(VERSION_BYTE); + stream.writeLong(randomSeed); + stream.writeInt(width); + stream.writeInt(height); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // We .get() it, because we want to crash hard if it is not available. + @NonNull final Tile tile = labyrinth.getTileAt(x, y).get(); + final byte bitmask = getBitmaskForTile(tile); + stream.writeByte(bitmask); + } + } + return stream.toByteArray(); + } + + /** + * Deserializes the byte array into an instance of {@link Labyrinth}. + * + * @param bytes The byte array to be deserialized. + * @return An instance of {@link Labyrinth}. + */ + @NonNull + public Labyrinth deserialize(@NonNull final byte[] bytes) { + final LabyrinthInputStream stream = new LabyrinthInputStream(bytes); + checkHeader(stream); + return readLabyrinthData(stream); + } + + private static void checkHeader(@NonNull final LabyrinthInputStream stream) { + final byte magic1 = stream.readByte(); + if (magic1 != MAGIC_BYTE_1) { + throw new IllegalArgumentException("Invalid labyrinth data."); + } + final byte magic2 = stream.readByte(); + if (magic2 != MAGIC_BYTE_2) { + throw new IllegalArgumentException("Invalid labyrinth data."); + } + final int version = stream.readByte(); + if (version != VERSION_BYTE) { + throw new IllegalArgumentException("Unknown Labyrinth data version: " + version); + } + } + + @NonNull + private static Labyrinth readLabyrinthData(@NonNull final LabyrinthInputStream stream) { + final long randomSeed = stream.readLong(); + final int width = stream.readInt(); + final int height = stream.readInt(); + + @NonNull final Tile[][] tiles = new Tile[width][height]; + for (int x = 0; x < width; x++) { + tiles[x] = new Tile[height]; + } + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + final byte bitmask = stream.readByte(); + tiles[x][y] = getTileForBitmask(bitmask); + } + } + + return createLabyrinth(tiles, width, height, randomSeed); + } + + @NonNull + private Labyrinth createLabyrinth(@NonNull final Tile[][] field, final int width, final int height, final long randomSeed) { + try { + @NonNull final Constructor constructor = Labyrinth.class.getDeclaredConstructor(Tile[][].class, Integer.TYPE, Integer.TYPE, Long.TYPE); + constructor.setAccessible(true); + return constructor.newInstance(field, width, height, randomSeed); + } catch (final NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + throw new RuntimeException("Can not deserialize Labyrinth from labyrinth data.", e); + } + } + + @NonNull + private Tile createTile(@NonNull final EnumSet walls, boolean solution) { + try { + @NonNull final Constructor constructor = Tile.class.getDeclaredConstructor(EnumSet.class, Boolean.TYPE); + constructor.setAccessible(true); + return constructor.newInstance(walls, solution); + } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Can not deserialize Tile from labyrinth data.", e); + } + } + + private byte getBitmaskForTile(@NonNull final Tile tile) { + byte bitmask = 0; + if (tile.hasWallAt(Direction.TOP)) { + bitmask |= TOP_BIT; + } + if (tile.hasWallAt(Direction.RIGHT)) { + bitmask |= RIGHT_BIT; + } + if (tile.hasWallAt(Direction.BOTTOM)) { + bitmask |= BOTTOM_BIT; + } + if (tile.hasWallAt((Direction.LEFT))) { + bitmask |= LEFT_BIT; + } + if (tile.isSolution()) { + bitmask |= SOLUTION_BIT; + } + return bitmask; + } + + @NonNull + private Tile getTileForBitmask(final byte bitmask) { + final EnumSet walls = EnumSet.noneOf(Direction.class); + if ((bitmask & TOP_BIT) == TOP_BIT) { + walls.add(Direction.TOP); + } + if ((bitmask & RIGHT_BIT) == RIGHT_BIT) { + walls.add(Direction.RIGHT); + } + if ((bitmask & BOTTOM_BIT) == BOTTOM_BIT) { + walls.add(Direction.BOTTOM); + } + if ((bitmask & LEFT_BIT) == LEFT_BIT) { + walls.add(Direction.LEFT); + } + final boolean solution = (bitmask & SOLUTION_BIT) == SOLUTION_BIT; + return createTile(walls, solution); + } +} diff --git a/src/test/java/ch/fritteli/labyrinth/generator/serialization/SerializerDeserializerTest.java b/src/test/java/ch/fritteli/labyrinth/generator/serialization/SerializerDeserializerTest.java new file mode 100644 index 0000000..4839e9b --- /dev/null +++ b/src/test/java/ch/fritteli/labyrinth/generator/serialization/SerializerDeserializerTest.java @@ -0,0 +1,33 @@ +package ch.fritteli.labyrinth.generator.serialization; + +import ch.fritteli.labyrinth.generator.model.Labyrinth; +import lombok.NonNull; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SerializerDeserializerTest { + @Test + void testSerializeDeserializeTiny() { + @NonNull final Labyrinth expected = new Labyrinth(2, 2, 255); + @NonNull final byte[] bytes = SerializerDeserializer.serialize(expected); + @NonNull final Labyrinth result = SerializerDeserializer.deserialize(bytes); + assertEquals(expected, result); + } + + @Test + void testSerializeDeserializeMedium() { + @NonNull final Labyrinth expected = new Labyrinth(20, 20, -271828182846L); + @NonNull final byte[] bytes = SerializerDeserializer.serialize(expected); + @NonNull final Labyrinth result = SerializerDeserializer.deserialize(bytes); + assertEquals(expected, result); + } + + @Test + void testSerializeDeserializeLarge() { + @NonNull final Labyrinth expected = new Labyrinth(200, 320, 3141592653589793238L); + @NonNull final byte[] bytes = SerializerDeserializer.serialize(expected); + @NonNull final Labyrinth result = SerializerDeserializer.deserialize(bytes); + assertEquals(expected, result); + } +} -- 2.45.2 From c8619f80e89777ac5b6475468b091608e1594266 Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Thu, 27 Jan 2022 22:04:07 +0100 Subject: [PATCH 3/7] Some dcumentation improvements and minor refactorings. --- labyrinth-generator.iml | 4 +- pom.xml | 24 +++++- .../generator/renderer/text/Generator.java | 7 ++ .../serialization/SerializerDeserializer.java | 83 +++++++++++-------- 4 files changed, 77 insertions(+), 41 deletions(-) diff --git a/labyrinth-generator.iml b/labyrinth-generator.iml index 86ee68f..815f5ae 100644 --- a/labyrinth-generator.iml +++ b/labyrinth-generator.iml @@ -16,8 +16,8 @@ - - + + diff --git a/pom.xml b/pom.xml index 2ad3f6d..fd7a6c9 100644 --- a/pom.xml +++ b/pom.xml @@ -8,9 +8,15 @@ fritteli-build-parent 2.0.4 + ch.fritteli.labyrinth labyrinth-generator 0.0.2-SNAPSHOT + + + 2.0.25 + + org.projectlombok @@ -27,7 +33,7 @@ org.apache.pdfbox pdfbox - 2.0.20 + ${pdfbox.version} org.junit.jupiter @@ -81,12 +87,24 @@ - repo.gittr.ch - https://repo.gittr.ch/ + repo.gittr.ch.releases + https://repo.gittr.ch/releases/ true never + + false + never + + + + repo.gittr.ch.snapshots + https://repo.gittr.ch/snapshots/ + + false + never + true always diff --git a/src/main/java/ch/fritteli/labyrinth/generator/renderer/text/Generator.java b/src/main/java/ch/fritteli/labyrinth/generator/renderer/text/Generator.java index 8378018..b8be526 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/renderer/text/Generator.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/renderer/text/Generator.java @@ -15,6 +15,13 @@ class Generator { private final boolean renderSolution; private int x = 0; private int y = 0; + // Each row has three lines: + // - The top border + // - The central pathway + // - The bottom border + // The bottom border of one row is identical to the top border of the following row. We use this variable here in + // order to be able to render the bottom border of the last row, which obviously does not have a following row with + // a top border. private int line = 0; boolean hasNext() { diff --git a/src/main/java/ch/fritteli/labyrinth/generator/serialization/SerializerDeserializer.java b/src/main/java/ch/fritteli/labyrinth/generator/serialization/SerializerDeserializer.java index 3315202..ae4df70 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/serialization/SerializerDeserializer.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/serialization/SerializerDeserializer.java @@ -12,23 +12,23 @@ import java.util.EnumSet; /** *
- * decimal hex border
- *       0   0 no border
- *       1   1 top
- *       2   2 right
- *       3   3 top+right
- *       4   4 bottom
- *       5   5 top+bottom
- *       6   6 right+bottom
- *       7   7 top+right+bottom
- *       8   8 left
- *       9   9 top+left
- *      10   a right+left
- *      11   b top+right+left
- *      12   c bottom+left
- *      13   d top+bottom+left
- *      14   e right+bottom+left
- *      15   f top+right+bottom+left
+ * decimal hex bin  border
+ *       0   0 0000 no border
+ *       1   1 0001 top
+ *       2   2 0010 right
+ *       3   3 0011 top+right
+ *       4   4 0100 bottom
+ *       5   5 0101 top+bottom
+ *       6   6 0110 right+bottom
+ *       7   7 0111 top+right+bottom
+ *       8   8 1000 left
+ *       9   9 1001 top+left
+ *      10   a 1010 right+left
+ *      11   b 1011 top+right+left
+ *      12   c 1100 bottom+left
+ *      13   d 1101 top+bottom+left
+ *      14   e 1110 right+bottom+left
+ *      15   f 1111 top+right+bottom+left
  * 
* ==> bits 0..2: always 0; bit 3: 1=solution, 0=not solution; bits 4..7: encode walls * ==> first bytes are: @@ -64,24 +64,9 @@ public class SerializerDeserializer { */ @NonNull public byte[] serialize(@NonNull final Labyrinth labyrinth) { - @NonNull final LabyrinthOutputStream stream = new LabyrinthOutputStream(); - final int width = labyrinth.getWidth(); - final int height = labyrinth.getHeight(); - final long randomSeed = labyrinth.getRandomSeed(); - stream.writeByte(MAGIC_BYTE_1); - stream.writeByte(MAGIC_BYTE_2); - stream.writeByte(VERSION_BYTE); - stream.writeLong(randomSeed); - stream.writeInt(width); - stream.writeInt(height); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - // We .get() it, because we want to crash hard if it is not available. - @NonNull final Tile tile = labyrinth.getTileAt(x, y).get(); - final byte bitmask = getBitmaskForTile(tile); - stream.writeByte(bitmask); - } - } + final LabyrinthOutputStream stream = new LabyrinthOutputStream(); + writeHeader(stream); + writeLabyrinthData(stream, labyrinth); return stream.toByteArray(); } @@ -98,6 +83,13 @@ public class SerializerDeserializer { return readLabyrinthData(stream); } + private static void writeHeader(@NonNull final LabyrinthOutputStream stream) { + stream.writeByte(MAGIC_BYTE_1); + stream.writeByte(MAGIC_BYTE_2); + stream.writeByte(VERSION_BYTE); + + } + private static void checkHeader(@NonNull final LabyrinthInputStream stream) { final byte magic1 = stream.readByte(); if (magic1 != MAGIC_BYTE_1) { @@ -113,13 +105,32 @@ public class SerializerDeserializer { } } + private static void writeLabyrinthData(@NonNull final LabyrinthOutputStream stream, @NonNull final Labyrinth labyrinth) { + final long randomSeed = labyrinth.getRandomSeed(); + final int width = labyrinth.getWidth(); + final int height = labyrinth.getHeight(); + stream.writeLong(randomSeed); + stream.writeInt(width); + stream.writeInt(height); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // We .get() it, because we want to crash hard if it is not available. + final Tile tile = labyrinth.getTileAt(x, y).get(); + final byte bitmask = getBitmaskForTile(tile); + stream.writeByte(bitmask); + } + } + + } + @NonNull private static Labyrinth readLabyrinthData(@NonNull final LabyrinthInputStream stream) { final long randomSeed = stream.readLong(); final int width = stream.readInt(); final int height = stream.readInt(); - @NonNull final Tile[][] tiles = new Tile[width][height]; + final Tile[][] tiles = new Tile[width][height]; for (int x = 0; x < width; x++) { tiles[x] = new Tile[height]; } -- 2.45.2 From 28c78325fee7bdf3a32e514ed007dead3157bc8c Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Wed, 2 Feb 2022 00:44:23 +0100 Subject: [PATCH 4/7] Do some refactoring and reformatting. Also, use logback instead of plain ol' System.out.println(). --- pom.xml | 13 +++- .../ch/fritteli/labyrinth/generator/Main.java | 16 +++-- .../generator/renderer/html/HTMLRenderer.java | 1 + .../renderer/htmlfile/HTMLFileRenderer.java | 36 ++++++---- .../generator/renderer/pdf/Generator.java | 11 ++- .../generator/renderer/pdf/PDFRenderer.java | 2 +- .../renderer/pdffile/PDFFileRenderer.java | 14 ++-- .../generator/renderer/text/TextRenderer.java | 1 + .../renderer/textfile/TextFileRenderer.java | 72 ++++++++++++------- src/main/resources/logback.xml | 16 +++++ 10 files changed, 121 insertions(+), 61 deletions(-) create mode 100644 src/main/resources/logback.xml diff --git a/pom.xml b/pom.xml index fd7a6c9..029d0b5 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,9 @@ 0.0.2-SNAPSHOT + 1.2.10 2.0.25 + 1.7.35 @@ -35,10 +37,19 @@ pdfbox ${pdfbox.version}
+ + org.slf4j + slf4j-api + ${slf4j.version} + + + ch.qos.logback + logback-classic + ${logback.version} + org.junit.jupiter junit-jupiter-api - test
diff --git a/src/main/java/ch/fritteli/labyrinth/generator/Main.java b/src/main/java/ch/fritteli/labyrinth/generator/Main.java index 1acf787..df3ebef 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/Main.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/Main.java @@ -7,10 +7,12 @@ import ch.fritteli.labyrinth.generator.renderer.pdffile.PDFFileRenderer; import ch.fritteli.labyrinth.generator.renderer.text.TextRenderer; import ch.fritteli.labyrinth.generator.renderer.textfile.TextFileRenderer; import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; import java.nio.file.Path; import java.nio.file.Paths; +@Slf4j public class Main { public static void main(@NonNull final String[] args) { int width = 100; @@ -28,19 +30,19 @@ public class Main { final PDFFileRenderer pdfFileRenderer = PDFFileRenderer.newInstance() .setTargetFile(userHome.resolve(baseFilename + ".pdf")); - System.out.println("Labyrinth-ID: " + labyrinth.getRandomSeed()); + log.info("Labyrinth-ID: {}", labyrinth.getRandomSeed()); // Render Labyrinth to stdout - System.out.println(textRenderer.render(labyrinth)); + log.info("Text rendering:\n{}", textRenderer.render(labyrinth)); // Render Labyrinth solution to stdout - System.out.println(textRenderer.setRenderSolution(true).render(labyrinth)); + log.info("Text rendering with solution:\n{}", textRenderer.setRenderSolution(true).render(labyrinth)); // Render HTML to stdout - System.out.println(htmlRenderer.render(labyrinth)); + log.info("HTML rendering:\n{}", htmlRenderer.render(labyrinth)); // Render Labyrinth and solution to (separate) files - System.out.println(textFileRenderer.render(labyrinth)); + log.info("Text rendering to file:\n{}", textFileRenderer.render(labyrinth)); // Render HTML to file - System.out.println(htmlFileRenderer.render(labyrinth)); + log.info("HTML rendering to file:\n{}", htmlFileRenderer.render(labyrinth)); // Render PDF to file - System.out.println(pdfFileRenderer.render(labyrinth)); + log.info("PDF rendering to file:\n{}", pdfFileRenderer.render(labyrinth)); } private static String getBaseFilename(@NonNull final Labyrinth labyrinth) { diff --git a/src/main/java/ch/fritteli/labyrinth/generator/renderer/html/HTMLRenderer.java b/src/main/java/ch/fritteli/labyrinth/generator/renderer/html/HTMLRenderer.java index 03666a7..fc03f38 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/renderer/html/HTMLRenderer.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/renderer/html/HTMLRenderer.java @@ -16,6 +16,7 @@ public class HTMLRenderer implements Renderer { } @NonNull + @Override public String render(@NonNull final Labyrinth labyrinth) { if (labyrinth.getWidth() == 0 || labyrinth.getHeight() == 0) { return this.getPreamble(labyrinth) + POSTAMBLE; diff --git a/src/main/java/ch/fritteli/labyrinth/generator/renderer/htmlfile/HTMLFileRenderer.java b/src/main/java/ch/fritteli/labyrinth/generator/renderer/htmlfile/HTMLFileRenderer.java index 3407537..db829bc 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/renderer/htmlfile/HTMLFileRenderer.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/renderer/htmlfile/HTMLFileRenderer.java @@ -3,25 +3,30 @@ package ch.fritteli.labyrinth.generator.renderer.htmlfile; import ch.fritteli.labyrinth.generator.model.Labyrinth; import ch.fritteli.labyrinth.generator.renderer.Renderer; import ch.fritteli.labyrinth.generator.renderer.html.HTMLRenderer; +import io.vavr.control.Option; +import io.vavr.control.Try; import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.NoSuchElementException; +@Slf4j public class HTMLFileRenderer implements Renderer { @NonNull private static final HTMLRenderer HTML_RENDERER = HTMLRenderer.newInstance(); - private Path targetFile; + @NonNull + private Option targetFile; private HTMLFileRenderer() { - try { - this.targetFile = Files.createTempFile("labyrinth_", ".html"); - } catch (IOException e) { - System.err.println("Unable to set default target file."); - e.printStackTrace(); - } + this.targetFile = Try + .of(() -> Files.createTempFile("labyrinth_", ".html")) + .onFailure(ex -> log.error("Unable to set default target file.", ex)) + .toOption(); } @NonNull @@ -30,30 +35,33 @@ public class HTMLFileRenderer implements Renderer { } public boolean isTargetFileDefinedAndWritable() { - return this.targetFile != null && this.targetFile.toFile().canWrite(); + return this.targetFile + .map(Path::toFile) + .exists(File::canWrite); } @NonNull public HTMLFileRenderer setTargetFile(@NonNull final Path targetFile) { - this.targetFile = targetFile; + this.targetFile = Option.of(targetFile); return this; } + @NonNull @Override public Path render(@NonNull final Labyrinth labyrinth) { if (!this.isTargetFileDefinedAndWritable()) { try { - Files.createFile(this.targetFile); - } catch (IOException e) { + Files.createFile(this.targetFile.get()); + } catch (IOException | NoSuchElementException e) { throw new IllegalArgumentException("Cannot write to target file.", e); } } final String html = HTML_RENDERER.render(labyrinth); + final Path targetFile = this.targetFile.get(); try { - Files.writeString(this.targetFile, html, StandardCharsets.UTF_8); + Files.writeString(targetFile, html, StandardCharsets.UTF_8); } catch (IOException e) { - System.err.println("Failed writing to file " + this.targetFile.normalize().toString()); - e.printStackTrace(); + log.error("Failed writing to file " + targetFile.normalize(), e); } return targetFile; } diff --git a/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/Generator.java b/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/Generator.java index 7f15ba1..971fed2 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/Generator.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/Generator.java @@ -8,6 +8,7 @@ import io.vavr.control.Option; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.Value; +import lombok.extern.slf4j.Slf4j; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentInformation; import org.apache.pdfbox.pdmodel.PDPage; @@ -20,14 +21,12 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; @RequiredArgsConstructor +@Slf4j class Generator { @NonNull private final Labyrinth labyrinth; - private static boolean isValid(@NonNull final Position position) { - return position.getX() >= 0 && position.getY() >= 0; - } - + @NonNull public byte[] generate() { final float pageWidth = this.labyrinth.getWidth() * PDFRenderer.SCALE + 2 * PDFRenderer.MARGIN; final float pageHeight = this.labyrinth.getHeight() * PDFRenderer.SCALE + 2 * PDFRenderer.MARGIN; @@ -48,14 +47,14 @@ class Generator { this.drawVerticalLines(puzzlePageContentStream, solutionPageContentStream); this.drawSolution(solutionPageContentStream); } catch (IOException e) { - e.printStackTrace(); + log.error("Error while rendering PDF document", e); } final ByteArrayOutputStream output = new ByteArrayOutputStream(); try { pdDocument.save(output); pdDocument.close(); } catch (IOException e) { - e.printStackTrace(); + log.error("Error while writing PDF data", e); } return output.toByteArray(); } diff --git a/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/PDFRenderer.java b/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/PDFRenderer.java index dd4183c..c9ff88a 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/PDFRenderer.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdf/PDFRenderer.java @@ -16,8 +16,8 @@ public class PDFRenderer implements Renderer { return new PDFRenderer(); } - @Override @NonNull + @Override public byte[] render(@NonNull final Labyrinth labyrinth) { final Generator generator = new Generator(labyrinth); return generator.generate(); diff --git a/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdffile/PDFFileRenderer.java b/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdffile/PDFFileRenderer.java index 781d33f..a6753e0 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdffile/PDFFileRenderer.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/renderer/pdffile/PDFFileRenderer.java @@ -6,6 +6,7 @@ import ch.fritteli.labyrinth.generator.renderer.pdf.PDFRenderer; import io.vavr.control.Option; import io.vavr.control.Try; import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; import java.io.File; import java.io.IOException; @@ -13,6 +14,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.NoSuchElementException; +@Slf4j public class PDFFileRenderer implements Renderer { @NonNull private static final PDFRenderer PDF_RENDERER = PDFRenderer.newInstance(); @@ -22,10 +24,7 @@ public class PDFFileRenderer implements Renderer { private PDFFileRenderer() { this.targetFile = Try .of(() -> Files.createTempFile("labyrinth_", ".pdf")) - .onFailure(ex -> { - System.err.println("Unable to set default target file."); - ex.printStackTrace(); - }) + .onFailure(ex -> log.error("Unable to set default target file.", ex)) .toOption(); } @@ -37,8 +36,7 @@ public class PDFFileRenderer implements Renderer { public boolean isTargetFileDefinedAndWritable() { return this.targetFile .map(Path::toFile) - .map(File::canWrite) - .getOrElse(false); + .exists(File::canWrite); } @NonNull @@ -47,6 +45,7 @@ public class PDFFileRenderer implements Renderer { return this; } + @NonNull @Override public Path render(@NonNull final Labyrinth labyrinth) { if (!this.isTargetFileDefinedAndWritable()) { @@ -61,8 +60,7 @@ public class PDFFileRenderer implements Renderer { try { Files.write(targetFile, bytes); } catch (IOException e) { - System.err.println("Failed writing to file " + targetFile.normalize().toString()); - e.printStackTrace(); + log.error("Failed writing to file " + targetFile.normalize(), e); } return targetFile; } diff --git a/src/main/java/ch/fritteli/labyrinth/generator/renderer/text/TextRenderer.java b/src/main/java/ch/fritteli/labyrinth/generator/renderer/text/TextRenderer.java index ecb605f..b7ce398 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/renderer/text/TextRenderer.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/renderer/text/TextRenderer.java @@ -23,6 +23,7 @@ public class TextRenderer implements Renderer { } @NonNull + @Override public String render(@NonNull final Labyrinth labyrinth) { if (labyrinth.getWidth() == 0 || labyrinth.getHeight() == 0) { return ""; diff --git a/src/main/java/ch/fritteli/labyrinth/generator/renderer/textfile/TextFileRenderer.java b/src/main/java/ch/fritteli/labyrinth/generator/renderer/textfile/TextFileRenderer.java index 9f0d959..d274f37 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/renderer/textfile/TextFileRenderer.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/renderer/textfile/TextFileRenderer.java @@ -4,29 +4,42 @@ import ch.fritteli.labyrinth.generator.model.Labyrinth; import ch.fritteli.labyrinth.generator.renderer.Renderer; import ch.fritteli.labyrinth.generator.renderer.text.TextRenderer; import io.vavr.collection.List; +import io.vavr.control.Option; +import io.vavr.control.Try; import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.NoSuchElementException; +@Slf4j public class TextFileRenderer implements Renderer> { @NonNull private static final TextRenderer TEXT_RENDERER = TextRenderer.newInstance(); - private Path targetLabyrinthFile; - private Path targetSolutionFile; + @NonNull + private Option targetLabyrinthFile; + @NonNull + private Option targetSolutionFile; private TextFileRenderer() { - try { - this.targetLabyrinthFile = Files.createTempFile("labyrinth_", ".txt"); - this.targetSolutionFile = this.targetLabyrinthFile.getParent().resolve( - this.targetLabyrinthFile.getFileName().toString().replace(".txt", "-solution.txt") - ); - } catch (IOException e) { - System.err.println("Unable to set default target file."); - e.printStackTrace(); - } + this.targetLabyrinthFile = Try + .of(() -> Files.createTempFile("labyrinth_", ".txt")) + .onFailure(ex -> log.error("Unable to set default target file", ex)) + .toOption(); + + this.targetSolutionFile = this.targetLabyrinthFile.toTry() + .map(Path::getParent) + .flatMap(parent -> this.targetLabyrinthFile.toTry() + .map(Path::getFileName) + .map(Path::toString) + .map(a -> a.replace(".txt", "-solution.txt")) + .map(parent::resolve)) + .onFailure(ex -> log.error("Unable to set default solution target file", ex)) + .toOption(); } @NonNull @@ -35,38 +48,44 @@ public class TextFileRenderer implements Renderer> { } public boolean isTargetLabyrinthFileDefinedAndWritable() { - return this.targetLabyrinthFile != null && this.targetLabyrinthFile.toFile().canWrite(); + return this.targetLabyrinthFile + .map(Path::toFile) + .exists(File::canWrite); } public boolean isTargetSolutionFileDefinedAndWritable() { - return this.targetSolutionFile != null && this.targetSolutionFile.toFile().canWrite(); + return this.targetSolutionFile + .map(Path::toFile) + .exists(File::canWrite); } @NonNull public TextFileRenderer setTargetLabyrinthFile(@NonNull final Path targetLabyrinthFile) { - this.targetLabyrinthFile = targetLabyrinthFile; + this.targetLabyrinthFile = Option.of(targetLabyrinthFile); return this; } @NonNull public TextFileRenderer setTargetSolutionFile(@NonNull final Path targetSolutionFile) { - this.targetSolutionFile = targetSolutionFile; + this.targetSolutionFile = Option.of(targetSolutionFile); return this; } + @NonNull @Override public List render(@NonNull final Labyrinth labyrinth) { if (!this.isTargetLabyrinthFileDefinedAndWritable()) { try { - Files.createFile(this.targetLabyrinthFile); - } catch (IOException e) { + Files.createFile(this.targetLabyrinthFile.get()); + } catch (IOException | NoSuchElementException e) { + log.error("Cannot write to target labyrinth file.", e); throw new IllegalArgumentException("Cannot write to target labyrinth file.", e); } } if (!this.isTargetSolutionFileDefinedAndWritable()) { try { - Files.createFile(this.targetSolutionFile); - } catch (IOException e) { + Files.createFile(this.targetSolutionFile.get()); + } catch (IOException | NoSuchElementException e) { throw new IllegalArgumentException("Cannot write to target solution file.", e); } } @@ -76,13 +95,18 @@ public class TextFileRenderer implements Renderer> { text = TEXT_RENDERER.setRenderSolution(false).render(labyrinth).strip(); solution = TEXT_RENDERER.setRenderSolution(true).render(labyrinth).strip(); } + final Path targetLabyrinthFile = this.targetLabyrinthFile.get(); + final Path targetSolutionFile = this.targetSolutionFile.get(); try { - Files.write(this.targetLabyrinthFile, text.getBytes(StandardCharsets.UTF_8)); - Files.write(this.targetSolutionFile, solution.getBytes(StandardCharsets.UTF_8)); + Files.write(targetLabyrinthFile, text.getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { - System.err.println("Failed writing to file " + this.targetLabyrinthFile.normalize().toString()); - e.printStackTrace(); + log.error("Failed writing to file " + targetLabyrinthFile.normalize(), e); } - return List.of(this.targetLabyrinthFile, this.targetSolutionFile); + try { + Files.write(targetSolutionFile, solution.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + log.error("Failed writing to file " + targetSolutionFile.normalize()); + } + return List.of(targetLabyrinthFile, targetSolutionFile); } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..fd7cd55 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + -- 2.45.2 From 67c07ff2cee9883de9d80009426004c1bce88265 Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Wed, 2 Feb 2022 00:53:42 +0100 Subject: [PATCH 5/7] Cosmetics (because the test is trivial). --- .../generator/renderer/text/CharDefinition.java | 4 +++- .../generator/renderer/text/CharDefinitionTest.java | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/fritteli/labyrinth/generator/renderer/text/CharDefinition.java b/src/main/java/ch/fritteli/labyrinth/generator/renderer/text/CharDefinition.java index a8c0513..2d0245e 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/renderer/text/CharDefinition.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/renderer/text/CharDefinition.java @@ -2,12 +2,14 @@ package ch.fritteli.labyrinth.generator.renderer.text; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.experimental.FieldDefaults; +@AllArgsConstructor +@EqualsAndHashCode @FieldDefaults(level = AccessLevel.PRIVATE) @NoArgsConstructor -@AllArgsConstructor class CharDefinition { // ─ static final String HORIZONTAL = "\u2500"; diff --git a/src/test/java/ch/fritteli/labyrinth/generator/renderer/text/CharDefinitionTest.java b/src/test/java/ch/fritteli/labyrinth/generator/renderer/text/CharDefinitionTest.java index 818771d..eefc1a9 100644 --- a/src/test/java/ch/fritteli/labyrinth/generator/renderer/text/CharDefinitionTest.java +++ b/src/test/java/ch/fritteli/labyrinth/generator/renderer/text/CharDefinitionTest.java @@ -44,4 +44,16 @@ class CharDefinitionTest { assertEquals("┤", new CharDefinition(true, true, true, false, true).toString()); assertEquals("┼", new CharDefinition(true, true, true, true, true).toString()); } + + @Test + void testBuilderMethods() { + assertEquals(new CharDefinition(true, false, false, false, false), new CharDefinition().up()); + assertEquals(new CharDefinition(false, true, false, false, false), new CharDefinition().down()); + assertEquals(new CharDefinition(false, false, true, false, false), new CharDefinition().left()); + assertEquals(new CharDefinition(false, false, false, true, false), new CharDefinition().right()); + assertEquals(new CharDefinition(false, false, false, false, true), new CharDefinition().solution()); + assertEquals(new CharDefinition(true, true, false, false, false), new CharDefinition().vertical()); + assertEquals(new CharDefinition(false, false, true, true, false), new CharDefinition().horizontal()); + assertEquals(new CharDefinition(true, true, true, true, true), new CharDefinition().vertical().horizontal().solution()); + } } -- 2.45.2 From 195df4f12053b306cffa3d6712bb4a0d3cf03291 Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Wed, 2 Feb 2022 01:03:00 +0100 Subject: [PATCH 6/7] Make this commit to trigger a new CI build. --- .../ch/fritteli/labyrinth/generator/model/Labyrinth.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/ch/fritteli/labyrinth/generator/model/Labyrinth.java b/src/main/java/ch/fritteli/labyrinth/generator/model/Labyrinth.java index 3390a3f..2ef7e3a 100644 --- a/src/main/java/ch/fritteli/labyrinth/generator/model/Labyrinth.java +++ b/src/main/java/ch/fritteli/labyrinth/generator/model/Labyrinth.java @@ -49,11 +49,6 @@ public class Labyrinth { /** * INTERNAL API. * Exists only for deserialization. Not to be called from user code. - * - * @param field - * @param width - * @param height - * @param randomSeed */ private Labyrinth(@NonNull final Tile[][] field, final int width, final int height, final long randomSeed) { this.field = field; -- 2.45.2 From 820521520a0a32fa7cba36631b00cd9429dd8ccf Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Wed, 2 Feb 2022 01:12:31 +0100 Subject: [PATCH 7/7] Try not to deploy to the maven repo simply when a PR is open. --- .drone.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index 251bdd0..e4c41bc 100644 --- a/.drone.yml +++ b/.drone.yml @@ -20,7 +20,9 @@ steps: from_secret: repo-token commands: - mvn -s maven-settings.xml deploy -DskipTests=true - when: + trigger: branch: - include: - - master + - master + event: + exclude: + - pull_request -- 2.45.2