Merge pull request 'feature/make-start-and-end-configurable' (#7) from feature/make-start-and-end-configurable into master
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			Reviewed-on: java/labyrinth-generator#7
This commit is contained in:
		
						commit
						2e07fa2981
					
				
					 17 changed files with 644 additions and 234 deletions
				
			
		
							
								
								
									
										103
									
								
								src/main/java/ch/fritteli/labyrinth/generator/Generator.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/main/java/ch/fritteli/labyrinth/generator/Generator.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,103 @@ | ||||||
|  | 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 Position end = this.labyrinth.getEnd(); | ||||||
|  |         final Tile endTile = this.labyrinth.getEndTile(); | ||||||
|  | 
 | ||||||
|  |         if (end.getY() == 0) { | ||||||
|  |             endTile.enableDiggingToOrFrom(Direction.TOP); | ||||||
|  |             endTile.digFrom(Direction.TOP); | ||||||
|  |         } else if (end.getX() == 0) { | ||||||
|  |             endTile.enableDiggingToOrFrom(Direction.LEFT); | ||||||
|  |             endTile.digFrom(Direction.LEFT); | ||||||
|  |         } else if (end.getY() == this.labyrinth.getHeight() - 1) { | ||||||
|  |             endTile.enableDiggingToOrFrom(Direction.BOTTOM); | ||||||
|  |             endTile.digFrom(Direction.BOTTOM); | ||||||
|  |         } else if (end.getX() == this.labyrinth.getWidth() - 1) { | ||||||
|  |             endTile.enableDiggingToOrFrom(Direction.RIGHT); | ||||||
|  |             endTile.digFrom(Direction.RIGHT); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.positions.push(end); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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 Position start = this.labyrinth.getStart(); | ||||||
|  |         final Tile startTile = this.labyrinth.getStartTile(); | ||||||
|  | 
 | ||||||
|  |         if (start.getY() == 0) { | ||||||
|  |             startTile.enableDiggingToOrFrom(Direction.TOP); | ||||||
|  |             startTile.digTo(Direction.TOP); | ||||||
|  |         } else if (start.getX() == 0) { | ||||||
|  |             startTile.enableDiggingToOrFrom(Direction.LEFT); | ||||||
|  |             startTile.digTo(Direction.LEFT); | ||||||
|  |         } else if (start.getY() == this.labyrinth.getHeight() - 1) { | ||||||
|  |             startTile.enableDiggingToOrFrom(Direction.BOTTOM); | ||||||
|  |             startTile.digTo(Direction.BOTTOM); | ||||||
|  |         } else if (start.getX() == this.labyrinth.getWidth() - 1) { | ||||||
|  |             startTile.enableDiggingToOrFrom(Direction.RIGHT); | ||||||
|  |             startTile.digTo(Direction.RIGHT); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -22,6 +22,7 @@ public class Main { | ||||||
|         final int width = 20; |         final int width = 20; | ||||||
|         final int height = 30; |         final int height = 30; | ||||||
|         final Labyrinth labyrinth = new Labyrinth(width, height/*, 0*/); |         final Labyrinth labyrinth = new Labyrinth(width, height/*, 0*/); | ||||||
|  |         new Generator(labyrinth).run(); | ||||||
|         final TextRenderer textRenderer = TextRenderer.newInstance(); |         final TextRenderer textRenderer = TextRenderer.newInstance(); | ||||||
|         final HTMLRenderer htmlRenderer = HTMLRenderer.newInstance(); |         final HTMLRenderer htmlRenderer = HTMLRenderer.newInstance(); | ||||||
|         final JsonRenderer jsonRenderer = JsonRenderer.newInstance(); |         final JsonRenderer jsonRenderer = JsonRenderer.newInstance(); | ||||||
|  |  | ||||||
|  | @ -6,13 +6,10 @@ import lombok.Getter; | ||||||
| import lombok.NonNull; | import lombok.NonNull; | ||||||
| import lombok.ToString; | import lombok.ToString; | ||||||
| 
 | 
 | ||||||
| import java.util.Deque; |  | ||||||
| import java.util.LinkedList; |  | ||||||
| import java.util.Random; |  | ||||||
| 
 |  | ||||||
| @EqualsAndHashCode | @EqualsAndHashCode | ||||||
| @ToString | @ToString | ||||||
| public class Labyrinth { | public class Labyrinth { | ||||||
|  | 
 | ||||||
|     private final Tile[][] field; |     private final Tile[][] field; | ||||||
|     @Getter |     @Getter | ||||||
|     private final int width; |     private final int width; | ||||||
|  | @ -20,8 +17,6 @@ public class Labyrinth { | ||||||
|     private final int height; |     private final int height; | ||||||
|     @Getter |     @Getter | ||||||
|     private final long randomSeed; |     private final long randomSeed; | ||||||
|     @EqualsAndHashCode.Exclude |  | ||||||
|     private final Random random; |  | ||||||
|     @Getter |     @Getter | ||||||
|     private final Position start; |     private final Position start; | ||||||
|     @Getter |     @Getter | ||||||
|  | @ -31,35 +26,60 @@ public class Labyrinth { | ||||||
|         this(width, height, System.nanoTime()); |         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) { |     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) { |         if (width <= 1 || height <= 1) { | ||||||
|             throw new IllegalArgumentException("width and height must be >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("'end' must be at the edge of the labyrinth"); | ||||||
|  |         } | ||||||
|         this.width = width; |         this.width = width; | ||||||
|         this.height = height; |         this.height = height; | ||||||
|         this.randomSeed = randomSeed; |         this.randomSeed = randomSeed; | ||||||
|         this.random = new Random(randomSeed); |  | ||||||
|         this.field = new Tile[width][height]; |         this.field = new Tile[width][height]; | ||||||
|         this.start = new Position(0, 0); |         this.start = start; | ||||||
|         this.end = new Position(this.width - 1, this.height - 1); |         this.end = end; | ||||||
|         this.initField(); |         this.initField(); | ||||||
|         this.generate(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * INTERNAL API. |      * INTERNAL API. Exists only for deserialization. Not to be called from user code. | ||||||
|      * 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) { |     private Labyrinth(@NonNull final Tile[][] field, final int width, final int height, final long randomSeed) { | ||||||
|         this.field = field; |         this.field = field; | ||||||
|         this.width = width; |         this.width = width; | ||||||
|         this.height = height; |         this.height = height; | ||||||
|         this.randomSeed = randomSeed; |         this.randomSeed = randomSeed; | ||||||
|         this.random = new Random(randomSeed); |  | ||||||
|         this.start = new Position(0, 0); |         this.start = new Position(0, 0); | ||||||
|         this.end = new Position(this.width - 1, this.height - 1); |         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 |     @NonNull | ||||||
|     public Option<Tile> getTileAt(@NonNull final Position position) { |     public Option<Tile> getTileAt(@NonNull final Position position) { | ||||||
|         return this.getTileAt(position.getX(), position.getY()); |         return this.getTileAt(position.getX(), position.getY()); | ||||||
|  | @ -74,12 +94,12 @@ public class Labyrinth { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @NonNull |     @NonNull | ||||||
|     Tile getStartTile() { |     public Tile getStartTile() { | ||||||
|         return this.getTileAt(this.start).get(); |         return this.getTileAt(this.start).get(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @NonNull |     @NonNull | ||||||
|     Tile getEndTile() { |     public Tile getEndTile() { | ||||||
|         return this.getTileAt(this.end).get(); |         return this.getTileAt(this.end).get(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -108,62 +128,4 @@ public class Labyrinth { | ||||||
|             tile.preventDiggingToOrFrom(Direction.BOTTOM); |             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; | package ch.fritteli.labyrinth.generator.renderer.json; | ||||||
| 
 | 
 | ||||||
| import ch.fritteli.labyrinth.generator.json.JsonCell; | 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.json.JsonLabyrinth; | ||||||
| import ch.fritteli.labyrinth.generator.model.Direction; | import ch.fritteli.labyrinth.generator.model.Direction; | ||||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||||
|  | @ -43,6 +44,14 @@ class Generator { | ||||||
|             rows.add(row); |             rows.add(row); | ||||||
|         } |         } | ||||||
|         result.setGrid(rows); |         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; |         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.Direction; | ||||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||||
|  | @ -45,7 +45,7 @@ import java.util.EnumSet; | ||||||
|  * Extraneous space (poss. last nibble) is ignored. |  * Extraneous space (poss. last nibble) is ignored. | ||||||
|  */ |  */ | ||||||
| @UtilityClass | @UtilityClass | ||||||
| public class SerializerDeserializer { | public class SerializerDeserializerV1 { | ||||||
|     final byte MAGIC_BYTE_1 = 0x1a; |     final byte MAGIC_BYTE_1 = 0x1a; | ||||||
|     final byte MAGIC_BYTE_2 = (byte) 0xb1; |     final byte MAGIC_BYTE_2 = (byte) 0xb1; | ||||||
|     final byte VERSION_BYTE = 0x01; |     final byte VERSION_BYTE = 0x01; | ||||||
|  | @ -64,7 +64,7 @@ public class SerializerDeserializer { | ||||||
|      */ |      */ | ||||||
|     @NonNull |     @NonNull | ||||||
|     public byte[] serialize(@NonNull final Labyrinth labyrinth) { |     public byte[] serialize(@NonNull final Labyrinth labyrinth) { | ||||||
|         final LabyrinthOutputStream stream = new LabyrinthOutputStream(); |         final LabyrinthOutputStreamV1 stream = new LabyrinthOutputStreamV1(); | ||||||
|         stream.writeHeader(); |         stream.writeHeader(); | ||||||
|         stream.writeLabyrinthData(labyrinth); |         stream.writeLabyrinthData(labyrinth); | ||||||
|         return stream.toByteArray(); |         return stream.toByteArray(); | ||||||
|  | @ -78,7 +78,7 @@ public class SerializerDeserializer { | ||||||
|      */ |      */ | ||||||
|     @NonNull |     @NonNull | ||||||
|     public Labyrinth deserialize(@NonNull final byte[] bytes) { |     public Labyrinth deserialize(@NonNull final byte[] bytes) { | ||||||
|         final LabyrinthInputStream stream = new LabyrinthInputStream(bytes); |         final LabyrinthInputStreamV1 stream = new LabyrinthInputStreamV1(bytes); | ||||||
|         stream.checkHeader(); |         stream.checkHeader(); | ||||||
|         return stream.readLabyrinthData(); |         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", |   "$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", |   "javaType": "ch.fritteli.labyrinth.generator.json.JsonLabyrinth", | ||||||
|   "type": "object", |   "type": "object", | ||||||
|   "additionalProperties": false, |   "additionalProperties": false, | ||||||
|  | @ -8,6 +8,8 @@ | ||||||
|     "id", |     "id", | ||||||
|     "width", |     "width", | ||||||
|     "height", |     "height", | ||||||
|  |     "start", | ||||||
|  |     "end", | ||||||
|     "grid" |     "grid" | ||||||
|   ], |   ], | ||||||
|   "properties": { |   "properties": { | ||||||
|  | @ -23,6 +25,12 @@ | ||||||
|       "type": "integer", |       "type": "integer", | ||||||
|       "minimum": 1 |       "minimum": 1 | ||||||
|     }, |     }, | ||||||
|  |     "start": { | ||||||
|  |       "$ref": "#/$defs/coordinates" | ||||||
|  |     }, | ||||||
|  |     "end": { | ||||||
|  |       "$ref": "#/$defs/coordinates" | ||||||
|  |     }, | ||||||
|     "grid": { |     "grid": { | ||||||
|       "$ref": "#/$defs/grid" |       "$ref": "#/$defs/grid" | ||||||
|     } |     } | ||||||
|  | @ -64,6 +72,25 @@ | ||||||
|           "type": "boolean" |           "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