149 lines
5.0 KiB
Java
149 lines
5.0 KiB
Java
package ch.fritteli.maze.generator.serialization.v1;
|
|
|
|
import ch.fritteli.maze.generator.model.Direction;
|
|
import ch.fritteli.maze.generator.model.Maze;
|
|
import ch.fritteli.maze.generator.model.Tile;
|
|
import lombok.NonNull;
|
|
import lombok.experimental.UtilityClass;
|
|
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.util.EnumSet;
|
|
|
|
/**
|
|
* <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 0x01 version (0x00 -> dev, 0x01 -> stable)
|
|
* 03..06 width (int)
|
|
* 07..10 height (int)
|
|
* 11..18 random seed number (long)
|
|
* 19.. tiles
|
|
* </pre>
|
|
* Extraneous space (poss. last nibble) is ignored.
|
|
*/
|
|
@UtilityClass
|
|
public class SerializerDeserializerV1 {
|
|
final byte MAGIC_BYTE_1 = 0x1a;
|
|
final byte MAGIC_BYTE_2 = (byte) 0xb1;
|
|
final byte VERSION_BYTE = 0x01;
|
|
|
|
private final byte TOP_BIT = 0b0000_0001;
|
|
private final byte RIGHT_BIT = 0b0000_0010;
|
|
private final byte BOTTOM_BIT = 0b0000_0100;
|
|
private final byte LEFT_BIT = 0b0000_1000;
|
|
private final byte SOLUTION_BIT = 0b0001_0000;
|
|
|
|
/**
|
|
* Serializes the {@code maze} into a byte array.
|
|
*
|
|
* @param maze The {@link Maze} to be serialized.
|
|
* @return The resulting byte array.
|
|
*/
|
|
@NonNull
|
|
public byte[] serialize(@NonNull final Maze maze) {
|
|
final MazeOutputStreamV1 stream = new MazeOutputStreamV1();
|
|
stream.writeHeader();
|
|
stream.writeMazeData(maze);
|
|
return stream.toByteArray();
|
|
}
|
|
|
|
/**
|
|
* Deserializes the byte array into an instance of {@link Maze}.
|
|
*
|
|
* @param bytes The byte array to be deserialized.
|
|
* @return An instance of {@link Maze}.
|
|
*/
|
|
@NonNull
|
|
public Maze deserialize(@NonNull final byte[] bytes) {
|
|
final MazeInputStreamV1 stream = new MazeInputStreamV1(bytes);
|
|
stream.checkHeader();
|
|
return stream.readMazeData();
|
|
}
|
|
|
|
@NonNull
|
|
Maze createMaze(@NonNull final Tile[][] field, final int width, final int height, final long randomSeed) {
|
|
try {
|
|
final Constructor<Maze> constructor = Maze.class.getDeclaredConstructor(Tile[][].class, Integer.TYPE, Integer.TYPE, Long.TYPE);
|
|
constructor.setAccessible(true);
|
|
return constructor.newInstance(field, width, height, randomSeed);
|
|
} catch (@NonNull final NoSuchMethodException | IllegalAccessException | InstantiationException |
|
|
InvocationTargetException e) {
|
|
throw new RuntimeException("Can not deserialize Maze from maze 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 maze 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);
|
|
}
|
|
}
|