feature/make-start-and-end-configurable #7
					 17 changed files with 639 additions and 234 deletions
				
			
		
							
								
								
									
										98
									
								
								src/main/java/ch/fritteli/labyrinth/generator/Generator.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/main/java/ch/fritteli/labyrinth/generator/Generator.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,98 @@ | |||
| package ch.fritteli.labyrinth.generator; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.model.Direction; | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
| import ch.fritteli.labyrinth.generator.model.Position; | ||||
| import ch.fritteli.labyrinth.generator.model.Tile; | ||||
| import io.vavr.control.Option; | ||||
| import java.util.Deque; | ||||
| import java.util.LinkedList; | ||||
| import java.util.Random; | ||||
| import lombok.NonNull; | ||||
| 
 | ||||
| public class Generator { | ||||
| 
 | ||||
|     @NonNull | ||||
|     private final Labyrinth labyrinth; | ||||
|     @NonNull | ||||
|     private final Random random; | ||||
|     @NonNull | ||||
|     private final Deque<Position> positions = new LinkedList<>(); | ||||
| 
 | ||||
|     public Generator(@NonNull final Labyrinth labyrinth) { | ||||
|         this.labyrinth = labyrinth; | ||||
|         this.random = new Random(labyrinth.getRandomSeed()); | ||||
|     } | ||||
| 
 | ||||
|     public void run() { | ||||
|         this.preDig(); | ||||
|         this.dig(); | ||||
|         this.postDig(); | ||||
|     } | ||||
| 
 | ||||
|     private void preDig() { | ||||
|         final Tile endTile = this.labyrinth.getEndTile(); | ||||
|         if (endTile.hasWallAt(Direction.BOTTOM)) { | ||||
|             endTile.enableDiggingToOrFrom(Direction.BOTTOM); | ||||
|             endTile.digFrom(Direction.BOTTOM); | ||||
|         } else if (endTile.hasWallAt(Direction.RIGHT)) { | ||||
|             endTile.enableDiggingToOrFrom(Direction.RIGHT); | ||||
|             endTile.digFrom(Direction.RIGHT); | ||||
|         } else if (endTile.hasWallAt(Direction.TOP)) { | ||||
|             endTile.enableDiggingToOrFrom(Direction.TOP); | ||||
|             endTile.digFrom(Direction.TOP); | ||||
|         } else if (endTile.hasWallAt(Direction.LEFT)) { | ||||
|             endTile.enableDiggingToOrFrom(Direction.LEFT); | ||||
|             endTile.digFrom(Direction.LEFT); | ||||
|         } | ||||
|         this.positions.push(this.labyrinth.getEnd()); | ||||
|     } | ||||
| 
 | ||||
|     private void dig() { | ||||
|         while (!this.positions.isEmpty()) { | ||||
|             final Position currentPosition = this.positions.peek(); | ||||
|             final Tile currentTile = this.labyrinth.getTileAt(currentPosition).get(); | ||||
|             final Option<Direction> directionToDigTo = currentTile.getRandomAvailableDirection(this.random); | ||||
|             if (directionToDigTo.isDefined()) { | ||||
|                 final Direction digTo = directionToDigTo.get(); | ||||
|                 final Direction digFrom = digTo.invert(); | ||||
|                 final Position neighborPosition = currentPosition.move(digTo); | ||||
|                 final Tile neighborTile = this.labyrinth.getTileAt(neighborPosition).get(); | ||||
|                 if (currentTile.digTo(digTo) && neighborTile.digFrom(digFrom)) { | ||||
|                     // all ok! | ||||
|                     this.positions.push(neighborPosition); | ||||
|                     if (neighborPosition.equals(this.labyrinth.getStart())) { | ||||
|                         this.markSolution(); | ||||
|                     } | ||||
|                 } else { | ||||
|                     // Hm, didn't work. | ||||
|                     currentTile.undigTo(digTo); | ||||
|                     currentTile.preventDiggingToOrFrom(digTo); | ||||
|                 } | ||||
|             } else { | ||||
|                 this.positions.pop(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void markSolution() { | ||||
|         this.positions.forEach(position -> this.labyrinth.getTileAt(position).get().setSolution()); | ||||
|     } | ||||
| 
 | ||||
|     private void postDig() { | ||||
|         final Tile startTile = this.labyrinth.getStartTile(); | ||||
|         if (startTile.hasWallAt(Direction.TOP)) { | ||||
|             startTile.enableDiggingToOrFrom(Direction.TOP); | ||||
|             startTile.digTo(Direction.TOP); | ||||
|         } else if (startTile.hasWallAt(Direction.LEFT)) { | ||||
|             startTile.enableDiggingToOrFrom(Direction.LEFT); | ||||
|             startTile.digTo(Direction.LEFT); | ||||
|         } else if (startTile.hasWallAt(Direction.BOTTOM)) { | ||||
|             startTile.enableDiggingToOrFrom(Direction.BOTTOM); | ||||
|             startTile.digTo(Direction.BOTTOM); | ||||
|         } else if (startTile.hasWallAt(Direction.RIGHT)) { | ||||
|             startTile.enableDiggingToOrFrom(Direction.RIGHT); | ||||
|             startTile.digTo(Direction.RIGHT); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -22,6 +22,7 @@ public class Main { | |||
|         final int width = 20; | ||||
|         final int height = 30; | ||||
|         final Labyrinth labyrinth = new Labyrinth(width, height/*, 0*/); | ||||
|         new Generator(labyrinth).run(); | ||||
|         final TextRenderer textRenderer = TextRenderer.newInstance(); | ||||
|         final HTMLRenderer htmlRenderer = HTMLRenderer.newInstance(); | ||||
|         final JsonRenderer jsonRenderer = JsonRenderer.newInstance(); | ||||
|  |  | |||
|  | @ -6,13 +6,10 @@ 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 | ||||
|     private final int width; | ||||
|  | @ -20,8 +17,6 @@ public class Labyrinth { | |||
|     private final int height; | ||||
|     @Getter | ||||
|     private final long randomSeed; | ||||
|     @EqualsAndHashCode.Exclude | ||||
|     private final Random random; | ||||
|     @Getter | ||||
|     private final Position start; | ||||
|     @Getter | ||||
|  | @ -31,35 +26,60 @@ public class Labyrinth { | |||
|         this(width, height, System.nanoTime()); | ||||
|     } | ||||
| 
 | ||||
|     public Labyrinth(final int width, final int height, @NonNull final Position start, @NonNull final Position end) { | ||||
|         this(width, height, System.nanoTime(), start, end); | ||||
|     } | ||||
| 
 | ||||
|     public Labyrinth(final int width, final int height, final long randomSeed) { | ||||
|         this(width, height, randomSeed, new Position(0, 0), new Position(width - 1, height - 1)); | ||||
|     } | ||||
| 
 | ||||
|     public Labyrinth(final int width, final int height, final long randomSeed, @NonNull final Position start, @NonNull final Position end) { | ||||
|         if (width <= 1 || height <= 1) { | ||||
|             throw new IllegalArgumentException("width and height must be >1"); | ||||
|         } | ||||
|         if (start.equals(end)) { | ||||
|             throw new IllegalArgumentException("'start' must not be equal to 'end'"); | ||||
|         } | ||||
|         if (start.getX() != 0 && start.getX() != width - 1 && start.getY() != 0 && start.getY() != height - 1) { | ||||
|             throw new IllegalArgumentException("'start' must be at the edge of the labyrinth"); | ||||
|         } | ||||
|         if (end.getX() != 0 && end.getX() != width - 1 && end.getY() != 0 && end.getY() != height - 1) { | ||||
|             throw new IllegalArgumentException("'start' must be at the edge of the labyrinth"); | ||||
|         } | ||||
|         this.width = width; | ||||
|         this.height = height; | ||||
|         this.randomSeed = randomSeed; | ||||
|         this.random = new Random(randomSeed); | ||||
|         this.field = new Tile[width][height]; | ||||
|         this.start = new Position(0, 0); | ||||
|         this.end = new Position(this.width - 1, this.height - 1); | ||||
|         this.start = start; | ||||
|         this.end = end; | ||||
|         this.initField(); | ||||
|         this.generate(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * INTERNAL API. | ||||
|      * Exists only for deserialization. Not to be called from user code. | ||||
|      * INTERNAL API. Exists only for deserialization. Not to be called from user code. | ||||
|      */ | ||||
|     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); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * INTERNAL API. Exists only for deserialization. Not to be called from user code. | ||||
|      */ | ||||
|     private Labyrinth(@NonNull final Tile[][] field, final int width, final int height, @NonNull final Position start, @NonNull final Position end, final long randomSeed) { | ||||
|         this.field = field; | ||||
|         this.width = width; | ||||
|         this.height = height; | ||||
|         this.randomSeed = randomSeed; | ||||
|         this.start = start; | ||||
|         this.end = end; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public Option<Tile> getTileAt(@NonNull final Position position) { | ||||
|         return this.getTileAt(position.getX(), position.getY()); | ||||
|  | @ -74,12 +94,12 @@ public class Labyrinth { | |||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     Tile getStartTile() { | ||||
|     public Tile getStartTile() { | ||||
|         return this.getTileAt(this.start).get(); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     Tile getEndTile() { | ||||
|     public Tile getEndTile() { | ||||
|         return this.getTileAt(this.end).get(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -108,62 +128,4 @@ public class Labyrinth { | |||
|             tile.preventDiggingToOrFrom(Direction.BOTTOM); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void generate() { | ||||
|         new Generator().run(); | ||||
|     } | ||||
| 
 | ||||
|     private class Generator { | ||||
|         private final Deque<Position> positions = new LinkedList<>(); | ||||
| 
 | ||||
|         void run() { | ||||
|             this.preDig(); | ||||
|             this.dig(); | ||||
|             this.postDig(); | ||||
|         } | ||||
| 
 | ||||
|         private void preDig() { | ||||
|             final Tile endTile = Labyrinth.this.getEndTile(); | ||||
|             endTile.enableDiggingToOrFrom(Direction.BOTTOM); | ||||
|             endTile.digFrom(Direction.BOTTOM); | ||||
|             this.positions.push(Labyrinth.this.end); | ||||
|         } | ||||
| 
 | ||||
|         private void dig() { | ||||
|             while (!this.positions.isEmpty()) { | ||||
|                 final Position currentPosition = this.positions.peek(); | ||||
|                 final Tile currentTile = Labyrinth.this.getTileAt(currentPosition).get(); | ||||
|                 final Option<Direction> directionToDigTo = currentTile.getRandomAvailableDirection(Labyrinth.this.random); | ||||
|                 if (directionToDigTo.isDefined()) { | ||||
|                     final Direction digTo = directionToDigTo.get(); | ||||
|                     final Direction digFrom = digTo.invert(); | ||||
|                     final Position neighborPosition = currentPosition.move(digTo); | ||||
|                     final Tile neighborTile = Labyrinth.this.getTileAt(neighborPosition).get(); | ||||
|                     if (currentTile.digTo(digTo) && neighborTile.digFrom(digFrom)) { | ||||
|                         // all ok! | ||||
|                         this.positions.push(neighborPosition); | ||||
|                         if (neighborPosition.equals(Labyrinth.this.start)) { | ||||
|                             this.markSolution(); | ||||
|                         } | ||||
|                     } else { | ||||
|                         // Hm, didn't work. | ||||
|                         currentTile.undigTo(digTo); | ||||
|                         currentTile.preventDiggingToOrFrom(digTo); | ||||
|                     } | ||||
|                 } else { | ||||
|                     this.positions.pop(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void markSolution() { | ||||
|             this.positions.forEach(position -> Labyrinth.this.getTileAt(position).get().setSolution()); | ||||
|         } | ||||
| 
 | ||||
|         private void postDig() { | ||||
|             final Tile startTile = Labyrinth.this.getStartTile(); | ||||
|             startTile.enableDiggingToOrFrom(Direction.TOP); | ||||
|             startTile.digTo(Direction.TOP); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| package ch.fritteli.labyrinth.generator.renderer.json; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.json.JsonCell; | ||||
| import ch.fritteli.labyrinth.generator.json.JsonCoordinates; | ||||
| import ch.fritteli.labyrinth.generator.json.JsonLabyrinth; | ||||
| import ch.fritteli.labyrinth.generator.model.Direction; | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
|  | @ -43,6 +44,14 @@ class Generator { | |||
|             rows.add(row); | ||||
|         } | ||||
|         result.setGrid(rows); | ||||
|         final JsonCoordinates start = new JsonCoordinates(); | ||||
|         start.setX(this.labyrinth.getStart().getX()); | ||||
|         start.setY(this.labyrinth.getStart().getY()); | ||||
|         result.setStart(start); | ||||
|         final JsonCoordinates end = new JsonCoordinates(); | ||||
|         end.setX(this.labyrinth.getEnd().getX()); | ||||
|         end.setY(this.labyrinth.getEnd().getY()); | ||||
|         result.setEnd(end); | ||||
|         return result; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,42 @@ | |||
| package ch.fritteli.labyrinth.generator.serialization; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
| import java.io.ByteArrayInputStream; | ||||
| import lombok.NonNull; | ||||
| 
 | ||||
| public abstract class AbstractLabyrinthInputStream extends ByteArrayInputStream { | ||||
| 
 | ||||
|     public AbstractLabyrinthInputStream(@NonNull final byte[] buf) { | ||||
|         super(buf); | ||||
|     } | ||||
| 
 | ||||
|     public abstract void checkHeader(); | ||||
| 
 | ||||
|     @NonNull | ||||
|     public abstract Labyrinth readLabyrinthData(); | ||||
| 
 | ||||
|     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; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,28 @@ | |||
| package ch.fritteli.labyrinth.generator.serialization; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
| import java.io.ByteArrayOutputStream; | ||||
| import lombok.NonNull; | ||||
| 
 | ||||
| public abstract class AbstractLabyrinthOutputStream extends ByteArrayOutputStream { | ||||
| 
 | ||||
|     public abstract void writeHeader(); | ||||
| 
 | ||||
|     public abstract void writeLabyrinthData(@NonNull final Labyrinth labyrinth); | ||||
| 
 | ||||
|     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); | ||||
|     } | ||||
| } | ||||
|  | @ -1,74 +0,0 @@ | |||
| package ch.fritteli.labyrinth.generator.serialization; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
| import ch.fritteli.labyrinth.generator.model.Tile; | ||||
| import lombok.NonNull; | ||||
| 
 | ||||
| import java.io.ByteArrayInputStream; | ||||
| 
 | ||||
| public class LabyrinthInputStream extends ByteArrayInputStream { | ||||
|     public LabyrinthInputStream(@NonNull final byte[] buf) { | ||||
|         super(buf); | ||||
|     } | ||||
| 
 | ||||
|     public long readLong() { | ||||
|         long result = 0; | ||||
|         result |= ((long) this.readInt()) << 32; | ||||
|         result |= 0xffffffffL & this.readInt(); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     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 void checkHeader() { | ||||
|         final byte magic1 = this.readByte(); | ||||
|         if (magic1 != SerializerDeserializer.MAGIC_BYTE_1) { | ||||
|             throw new IllegalArgumentException("Invalid labyrinth data."); | ||||
|         } | ||||
|         final byte magic2 = this.readByte(); | ||||
|         if (magic2 != SerializerDeserializer.MAGIC_BYTE_2) { | ||||
|             throw new IllegalArgumentException("Invalid labyrinth data."); | ||||
|         } | ||||
|         final int version = this.readByte(); | ||||
|         if (version != SerializerDeserializer.VERSION_BYTE) { | ||||
|             throw new IllegalArgumentException("Unknown Labyrinth data version: " + version); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public Labyrinth readLabyrinthData() { | ||||
|         final long randomSeed = this.readLong(); | ||||
|         final int width = this.readInt(); | ||||
|         final int height = this.readInt(); | ||||
| 
 | ||||
|         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 = this.readByte(); | ||||
|                 tiles[x][y] = SerializerDeserializer.getTileForBitmask(bitmask); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return SerializerDeserializer.createLabyrinth(tiles, width, height, randomSeed); | ||||
|     } | ||||
| } | ||||
|  | @ -1,50 +0,0 @@ | |||
| package ch.fritteli.labyrinth.generator.serialization; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
| import ch.fritteli.labyrinth.generator.model.Tile; | ||||
| import lombok.NonNull; | ||||
| 
 | ||||
| import java.io.ByteArrayOutputStream; | ||||
| 
 | ||||
| 
 | ||||
| public class LabyrinthOutputStream extends ByteArrayOutputStream { | ||||
|     public void writeHeader() { | ||||
|         this.writeByte(SerializerDeserializer.MAGIC_BYTE_1); | ||||
|         this.writeByte(SerializerDeserializer.MAGIC_BYTE_2); | ||||
|         this.writeByte(SerializerDeserializer.VERSION_BYTE); | ||||
|     } | ||||
| 
 | ||||
|     public void writeByte(final byte value) { | ||||
|         this.write(value); | ||||
|     } | ||||
| 
 | ||||
|     public void writeLabyrinthData(@NonNull final Labyrinth labyrinth) { | ||||
|         final long randomSeed = labyrinth.getRandomSeed(); | ||||
|         final int width = labyrinth.getWidth(); | ||||
|         final int height = labyrinth.getHeight(); | ||||
|         this.writeLong(randomSeed); | ||||
|         this.writeInt(width); | ||||
|         this.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 = SerializerDeserializer.getBitmaskForTile(tile); | ||||
|                 this.writeByte(bitmask); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void writeLong(final long value) { | ||||
|         this.writeInt((int) (value >> 32)); | ||||
|         this.writeInt((int) value); | ||||
|     } | ||||
| 
 | ||||
|     public void writeInt(final int value) { | ||||
|         this.write(value >> 24); | ||||
|         this.write(value >> 16); | ||||
|         this.write(value >> 8); | ||||
|         this.write(value); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,51 @@ | |||
| package ch.fritteli.labyrinth.generator.serialization.v1; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
| import ch.fritteli.labyrinth.generator.model.Tile; | ||||
| import ch.fritteli.labyrinth.generator.serialization.AbstractLabyrinthInputStream; | ||||
| import lombok.NonNull; | ||||
| 
 | ||||
| public class LabyrinthInputStreamV1 extends AbstractLabyrinthInputStream { | ||||
| 
 | ||||
|     public LabyrinthInputStreamV1(@NonNull final byte[] buf) { | ||||
|         super(buf); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void checkHeader() { | ||||
|         final byte magic1 = this.readByte(); | ||||
|         if (magic1 != SerializerDeserializerV1.MAGIC_BYTE_1) { | ||||
|             throw new IllegalArgumentException("Invalid labyrinth data."); | ||||
|         } | ||||
|         final byte magic2 = this.readByte(); | ||||
|         if (magic2 != SerializerDeserializerV1.MAGIC_BYTE_2) { | ||||
|             throw new IllegalArgumentException("Invalid labyrinth data."); | ||||
|         } | ||||
|         final int version = this.readByte(); | ||||
|         if (version != SerializerDeserializerV1.VERSION_BYTE) { | ||||
|             throw new IllegalArgumentException("Unknown Labyrinth data version: " + version); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public Labyrinth readLabyrinthData() { | ||||
|         final long randomSeed = this.readLong(); | ||||
|         final int width = this.readInt(); | ||||
|         final int height = this.readInt(); | ||||
| 
 | ||||
|         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 = this.readByte(); | ||||
|                 tiles[x][y] = SerializerDeserializerV1.getTileForBitmask(bitmask); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return SerializerDeserializerV1.createLabyrinth(tiles, width, height, randomSeed); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,35 @@ | |||
| package ch.fritteli.labyrinth.generator.serialization.v1; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
| import ch.fritteli.labyrinth.generator.model.Tile; | ||||
| import ch.fritteli.labyrinth.generator.serialization.AbstractLabyrinthOutputStream; | ||||
| import lombok.NonNull; | ||||
| 
 | ||||
| public class LabyrinthOutputStreamV1 extends AbstractLabyrinthOutputStream { | ||||
| 
 | ||||
|     @Override | ||||
|     public void writeHeader() { | ||||
|         this.writeByte(SerializerDeserializerV1.MAGIC_BYTE_1); | ||||
|         this.writeByte(SerializerDeserializerV1.MAGIC_BYTE_2); | ||||
|         this.writeByte(SerializerDeserializerV1.VERSION_BYTE); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void writeLabyrinthData(@NonNull final Labyrinth labyrinth) { | ||||
|         final long randomSeed = labyrinth.getRandomSeed(); | ||||
|         final int width = labyrinth.getWidth(); | ||||
|         final int height = labyrinth.getHeight(); | ||||
|         this.writeLong(randomSeed); | ||||
|         this.writeInt(width); | ||||
|         this.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 = SerializerDeserializerV1.getBitmaskForTile(tile); | ||||
|                 this.writeByte(bitmask); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,4 +1,4 @@ | |||
| package ch.fritteli.labyrinth.generator.serialization; | ||||
| package ch.fritteli.labyrinth.generator.serialization.v1; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.model.Direction; | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
|  | @ -45,7 +45,7 @@ import java.util.EnumSet; | |||
|  * Extraneous space (poss. last nibble) is ignored. | ||||
|  */ | ||||
| @UtilityClass | ||||
| public class SerializerDeserializer { | ||||
| public class SerializerDeserializerV1 { | ||||
|     final byte MAGIC_BYTE_1 = 0x1a; | ||||
|     final byte MAGIC_BYTE_2 = (byte) 0xb1; | ||||
|     final byte VERSION_BYTE = 0x01; | ||||
|  | @ -64,7 +64,7 @@ public class SerializerDeserializer { | |||
|      */ | ||||
|     @NonNull | ||||
|     public byte[] serialize(@NonNull final Labyrinth labyrinth) { | ||||
|         final LabyrinthOutputStream stream = new LabyrinthOutputStream(); | ||||
|         final LabyrinthOutputStreamV1 stream = new LabyrinthOutputStreamV1(); | ||||
|         stream.writeHeader(); | ||||
|         stream.writeLabyrinthData(labyrinth); | ||||
|         return stream.toByteArray(); | ||||
|  | @ -78,7 +78,7 @@ public class SerializerDeserializer { | |||
|      */ | ||||
|     @NonNull | ||||
|     public Labyrinth deserialize(@NonNull final byte[] bytes) { | ||||
|         final LabyrinthInputStream stream = new LabyrinthInputStream(bytes); | ||||
|         final LabyrinthInputStreamV1 stream = new LabyrinthInputStreamV1(bytes); | ||||
|         stream.checkHeader(); | ||||
|         return stream.readLabyrinthData(); | ||||
|     } | ||||
|  | @ -0,0 +1,69 @@ | |||
| package ch.fritteli.labyrinth.generator.serialization.v2; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
| import ch.fritteli.labyrinth.generator.model.Position; | ||||
| import ch.fritteli.labyrinth.generator.model.Tile; | ||||
| import ch.fritteli.labyrinth.generator.serialization.AbstractLabyrinthInputStream; | ||||
| import lombok.NonNull; | ||||
| 
 | ||||
| public class LabyrinthInputStreamV2 extends AbstractLabyrinthInputStream { | ||||
| 
 | ||||
|     public LabyrinthInputStreamV2(@NonNull final byte[] buf) { | ||||
|         super(buf); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void checkHeader() { | ||||
|         // 00 0x1a magic | ||||
|         // 01 0xb1 magic | ||||
|         // 02 0x02 version | ||||
|         final byte magic1 = this.readByte(); | ||||
|         if (magic1 != SerializerDeserializerV2.MAGIC_BYTE_1) { | ||||
|             throw new IllegalArgumentException("Invalid labyrinth data."); | ||||
|         } | ||||
|         final byte magic2 = this.readByte(); | ||||
|         if (magic2 != SerializerDeserializerV2.MAGIC_BYTE_2) { | ||||
|             throw new IllegalArgumentException("Invalid labyrinth data."); | ||||
|         } | ||||
|         final int version = this.readByte(); | ||||
|         if (version != SerializerDeserializerV2.VERSION_BYTE) { | ||||
|             throw new IllegalArgumentException("Unknown Labyrinth data version: " + version); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public Labyrinth readLabyrinthData() { | ||||
|         // 03..06  width (int) | ||||
|         // 07..10  height (int) | ||||
|         // 11..14  start-x (int) | ||||
|         // 15..18  start-y (int) | ||||
|         // 19..22  end-x (int) | ||||
|         // 23..26  end-y (int) | ||||
|         // 27..34  random seed number (long) | ||||
|         // 35..    tiles | ||||
|         final int width = this.readInt(); | ||||
|         final int height = this.readInt(); | ||||
|         final int startX = this.readInt(); | ||||
|         final int startY = this.readInt(); | ||||
|         final int endX = this.readInt(); | ||||
|         final int endY = this.readInt(); | ||||
|         final long randomSeed = this.readLong(); | ||||
| 
 | ||||
|         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 = this.readByte(); | ||||
|                 tiles[x][y] = SerializerDeserializerV2.getTileForBitmask(bitmask); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         final Position start = new Position(startX, startY); | ||||
|         final Position end = new Position(endX, endY); | ||||
|         return SerializerDeserializerV2.createLabyrinth(tiles, width, height, start, end, randomSeed); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,53 @@ | |||
| package ch.fritteli.labyrinth.generator.serialization.v2; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
| import ch.fritteli.labyrinth.generator.model.Position; | ||||
| import ch.fritteli.labyrinth.generator.model.Tile; | ||||
| import ch.fritteli.labyrinth.generator.serialization.AbstractLabyrinthOutputStream; | ||||
| import lombok.NonNull; | ||||
| 
 | ||||
| public class LabyrinthOutputStreamV2 extends AbstractLabyrinthOutputStream { | ||||
| 
 | ||||
|     @Override | ||||
|     public void writeHeader() { | ||||
|         // 00 0x1a magic | ||||
|         // 01 0xb1 magic | ||||
|         // 02 0x02 version | ||||
|         this.writeByte(SerializerDeserializerV2.MAGIC_BYTE_1); | ||||
|         this.writeByte(SerializerDeserializerV2.MAGIC_BYTE_2); | ||||
|         this.writeByte(SerializerDeserializerV2.VERSION_BYTE); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void writeLabyrinthData(@NonNull final Labyrinth labyrinth) { | ||||
|         // 03..06  width (int) | ||||
|         // 07..10  height (int) | ||||
|         // 11..14  start-x (int) | ||||
|         // 15..18  start-y (int) | ||||
|         // 19..22  end-x (int) | ||||
|         // 23..26  end-y (int) | ||||
|         // 27..34  random seed number (long) | ||||
|         // 35..    tiles | ||||
|         final long randomSeed = labyrinth.getRandomSeed(); | ||||
|         final int width = labyrinth.getWidth(); | ||||
|         final int height = labyrinth.getHeight(); | ||||
|         final Position start = labyrinth.getStart(); | ||||
|         final Position end = labyrinth.getEnd(); | ||||
|         this.writeInt(width); | ||||
|         this.writeInt(height); | ||||
|         this.writeInt(start.getX()); | ||||
|         this.writeInt(start.getY()); | ||||
|         this.writeInt(end.getX()); | ||||
|         this.writeInt(end.getY()); | ||||
|         this.writeLong(randomSeed); | ||||
| 
 | ||||
|         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 = SerializerDeserializerV2.getBitmaskForTile(tile); | ||||
|                 this.writeByte(bitmask); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,150 @@ | |||
| package ch.fritteli.labyrinth.generator.serialization.v2; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.model.Direction; | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
| import ch.fritteli.labyrinth.generator.model.Position; | ||||
| import ch.fritteli.labyrinth.generator.model.Tile; | ||||
| import java.lang.reflect.Constructor; | ||||
| import java.lang.reflect.InvocationTargetException; | ||||
| import java.util.EnumSet; | ||||
| import lombok.NonNull; | ||||
| import lombok.experimental.UtilityClass; | ||||
| 
 | ||||
| /** | ||||
|  * <pre> | ||||
|  * 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 | ||||
|  * </pre> | ||||
|  * ==> bits 0..2: always 0; bit 3: 1=solution, 0=not solution; bits 4..7: encode walls ==> first bytes are: | ||||
|  * <pre> | ||||
|  *   byte  hex meaning | ||||
|  *     00 0x1a magic | ||||
|  *     01 0xb1 magic | ||||
|  *     02 0x02 version (0x00 -> dev, 0x01 -> deprecated, 0x02 -> stable) | ||||
|  *     03..06  width (int) | ||||
|  *     07..10  height (int) | ||||
|  *     11..14  start-x (int) | ||||
|  *     15..18  start-y (int) | ||||
|  *     19..22  end-x (int) | ||||
|  *     23..26  end-y (int) | ||||
|  *     27..34  random seed number (long) | ||||
|  *     35..    tiles | ||||
|  * </pre> | ||||
|  * Extraneous space (poss. last nibble) is ignored. | ||||
|  */ | ||||
| @UtilityClass | ||||
| public class SerializerDeserializerV2 { | ||||
| 
 | ||||
|     final byte MAGIC_BYTE_1 = 0x1a; | ||||
|     final byte MAGIC_BYTE_2 = (byte) 0xb1; | ||||
|     final byte VERSION_BYTE = 0x02; | ||||
| 
 | ||||
|     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) { | ||||
|         final LabyrinthOutputStreamV2 stream = new LabyrinthOutputStreamV2(); | ||||
|         stream.writeHeader(); | ||||
|         stream.writeLabyrinthData(labyrinth); | ||||
|         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 LabyrinthInputStreamV2 stream = new LabyrinthInputStreamV2(bytes); | ||||
|         stream.checkHeader(); | ||||
|         return stream.readLabyrinthData(); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     Labyrinth createLabyrinth(@NonNull final Tile[][] field, final int width, final int height, @NonNull final Position start, @NonNull final Position end, final long randomSeed) { | ||||
|         try { | ||||
|             final Constructor<Labyrinth> constructor = Labyrinth.class.getDeclaredConstructor(Tile[][].class, Integer.TYPE, Integer.TYPE, Position.class, Position.class, Long.TYPE); | ||||
|             constructor.setAccessible(true); | ||||
|             return constructor.newInstance(field, width, height, start, end, randomSeed); | ||||
|         } catch (@NonNull final NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { | ||||
|             throw new RuntimeException("Can not deserialize Labyrinth from labyrinth data.", e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private Tile createTile(@NonNull final EnumSet<Direction> walls, boolean solution) { | ||||
|         try { | ||||
|             final Constructor<Tile> constructor = Tile.class.getDeclaredConstructor(EnumSet.class, Boolean.TYPE); | ||||
|             constructor.setAccessible(true); | ||||
|             return constructor.newInstance(walls, solution); | ||||
|         } catch (@NonNull final NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { | ||||
|             throw new RuntimeException("Can not deserialize Tile from labyrinth data.", e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     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 | ||||
|     Tile getTileForBitmask(final byte bitmask) { | ||||
|         final EnumSet<Direction> 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); | ||||
|     } | ||||
| } | ||||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "$schema": "https://json-schema.org/draft/2020-12/schema", | ||||
|   "$id": "https://manuel.friedli.info/labyrinth-1/labyrinth.schema.json", | ||||
|   "$id": "https://manuel.friedli.info/labyrinth-2/labyrinth.schema.json", | ||||
|   "javaType": "ch.fritteli.labyrinth.generator.json.JsonLabyrinth", | ||||
|   "type": "object", | ||||
|   "additionalProperties": false, | ||||
|  | @ -8,6 +8,8 @@ | |||
|     "id", | ||||
|     "width", | ||||
|     "height", | ||||
|     "start", | ||||
|     "end", | ||||
|     "grid" | ||||
|   ], | ||||
|   "properties": { | ||||
|  | @ -23,6 +25,12 @@ | |||
|       "type": "integer", | ||||
|       "minimum": 1 | ||||
|     }, | ||||
|     "start": { | ||||
|       "$ref": "#/$defs/coordinates" | ||||
|     }, | ||||
|     "end": { | ||||
|       "$ref": "#/$defs/coordinates" | ||||
|     }, | ||||
|     "grid": { | ||||
|       "$ref": "#/$defs/grid" | ||||
|     } | ||||
|  | @ -64,6 +72,25 @@ | |||
|           "type": "boolean" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "coordinates": { | ||||
|       "type": "object", | ||||
|       "javaType": "ch.fritteli.labyrinth.generator.json.JsonCoordinates", | ||||
|       "additionalProperties": false, | ||||
|       "required": [ | ||||
|         "x", | ||||
|         "y" | ||||
|       ], | ||||
|       "properties": { | ||||
|         "x": { | ||||
|           "type": "integer", | ||||
|           "minimum": 0 | ||||
|         }, | ||||
|         "y": { | ||||
|           "type": "integer", | ||||
|           "minimum": 0 | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,32 +0,0 @@ | |||
| package ch.fritteli.labyrinth.generator.serialization; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
| import org.junit.jupiter.api.Test; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| 
 | ||||
| class SerializerDeserializerTest { | ||||
|     @Test | ||||
|     void testSerializeDeserializeTiny() { | ||||
|         final Labyrinth expected = new Labyrinth(2, 2, 255); | ||||
|         final byte[] bytes = SerializerDeserializer.serialize(expected); | ||||
|         final Labyrinth result = SerializerDeserializer.deserialize(bytes); | ||||
|         assertThat(result).isEqualTo(expected); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testSerializeDeserializeMedium() { | ||||
|         final Labyrinth expected = new Labyrinth(20, 20, -271828182846L); | ||||
|         final byte[] bytes = SerializerDeserializer.serialize(expected); | ||||
|         final Labyrinth result = SerializerDeserializer.deserialize(bytes); | ||||
|         assertThat(result).isEqualTo(expected); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testSerializeDeserializeLarge() { | ||||
|         final Labyrinth expected = new Labyrinth(200, 320, 3141592653589793238L); | ||||
|         final byte[] bytes = SerializerDeserializer.serialize(expected); | ||||
|         final Labyrinth result = SerializerDeserializer.deserialize(bytes); | ||||
|         assertThat(result).isEqualTo(expected); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,36 @@ | |||
| package ch.fritteli.labyrinth.generator.serialization.v1; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.Generator; | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
| import org.junit.jupiter.api.Test; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| 
 | ||||
| class SerializerDeserializerV1Test { | ||||
|     @Test | ||||
|     void testSerializeDeserializeTiny() { | ||||
|         final Labyrinth expected = new Labyrinth(2, 2, 255); | ||||
|         new Generator(expected).run(); | ||||
|         final byte[] bytes = SerializerDeserializerV1.serialize(expected); | ||||
|         final Labyrinth result = SerializerDeserializerV1.deserialize(bytes); | ||||
|         assertThat(result).isEqualTo(expected); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testSerializeDeserializeMedium() { | ||||
|         final Labyrinth expected = new Labyrinth(20, 20, -271828182846L); | ||||
|         new Generator(expected).run(); | ||||
|         final byte[] bytes = SerializerDeserializerV1.serialize(expected); | ||||
|         final Labyrinth result = SerializerDeserializerV1.deserialize(bytes); | ||||
|         assertThat(result).isEqualTo(expected); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testSerializeDeserializeLarge() { | ||||
|         final Labyrinth expected = new Labyrinth(200, 320, 3141592653589793238L); | ||||
|         new Generator(expected).run(); | ||||
|         final byte[] bytes = SerializerDeserializerV1.serialize(expected); | ||||
|         final Labyrinth result = SerializerDeserializerV1.deserialize(bytes); | ||||
|         assertThat(result).isEqualTo(expected); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue