diff --git a/.drone.yml b/.drone.yml index de0cf20..993edeb 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5,14 +5,14 @@ name: Check_Requirement steps: - name: base_check - image: gradle:jdk11-alpine + image: gradle:jdk19-alpine commands: - ./gradlew clean - ./gradlew build - ./gradlew test - name: syntax_check - image: gradle:jdk11-alpine + image: gradle:jdk19-alpine commands: - ./gradlew check @@ -44,6 +44,6 @@ depends_on: - Check_Requirement --- kind: signature -hmac: 9ca9095fdb69d7b89fda6b4db867877e76666c109607cc7b1e513814ad42bb7e +hmac: f7588a8f835401820f6f596cad344ab01794dc0abcf9f81c989c625844ab4cc3 ... diff --git a/JournalDeBord/src/SUMMARY.md b/JournalDeBord/src/SUMMARY.md index 278a10e..0d1bd53 100644 --- a/JournalDeBord/src/SUMMARY.md +++ b/JournalDeBord/src/SUMMARY.md @@ -6,6 +6,10 @@ - [Deuxième entrevue](./rapports/200223.md) - [Troisième entrevue](./rapports/230323.md) +# Specification + +- [File Parser](./spec/FileParser.md) +- # Histoire - [Plot Story](./histoire/plot_story.md) \ No newline at end of file diff --git a/JournalDeBord/src/spec/FileParser.md b/JournalDeBord/src/spec/FileParser.md new file mode 100644 index 0000000..f7b2942 --- /dev/null +++ b/JournalDeBord/src/spec/FileParser.md @@ -0,0 +1,50 @@ +--- +title: File Parser +author: Debucquoy Anthony (tonitch) +date: 5 March 2023 +--- +# File Parser Specification + +For the Project, I wanted to challenge myself, I decided to do my own file parser with my own specification that would +have the special objective of being really small. + +## The File format + +The file would use the .level file extension. + +The file can contain anything, the used data is enclosed between a header and a footer. +This could be used to add: musics, images and other stuff in the level file itself + +Only one Header and One Footer should be present in the file. +The parser will only read the first one it finds so to avoid problem, it is best practise to put the +level data at the top of the file. + +- The HEADER will be defined by the succesion of the characters 'S', 'M' then 'S' +- The FOOTER will be defined by the succesion of the characters 'S', 'M', then 'E' +- The bytes in between are the level data + - byte 1: Width of the map + - byte 2: Height of the map + - bytes 3 -> Width * Height (+1 if Width * Height % 8 is not 0) + - byte after Map Data: Pieces amount + - for each pieces + - 1 byte: size of the piece + - 4 first bits : width + - 4 last bits: height + - next bytes -> Width * Height (+1 if Width * Height % 8 is not 0) + +### Saved file + +For saved file, the extension will be .slevel +The only difference is that at the end of the map data (after the pieces and before the +Footer. there will be the position of each pieces from their top-left corner in the map. +following this pattern for each pieces + + - 'F' and 'L' on 2 bytes for floating positions when the piece is not placed + - x and y on 2 bytes for position if the piece is placed + +## Known Limitation + +1) by putting the piece size on one byte. We limit the maximum piece size to 15 x 15 (1111 | 1111) +I don't think we will ever need a piece larger than 5x5 so this is clearly a feature, not a bug! :-) +We might use the same methods for the pieces positions but there could be a posibility to have +larger map if I use 2 bytes for the positions. diff --git a/app/build.gradle b/app/build.gradle index 3f8d793..b253582 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,7 +19,9 @@ repositories { dependencies { // Use JUnit Jupiter for testing. - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.2' // This dependency is used by the application. implementation 'com.google.guava:guava:31.1-jre' diff --git a/app/src/main/java/school_project/Map.java b/app/src/main/java/school_project/Map.java index 69a99b3..2beea88 100644 --- a/app/src/main/java/school_project/Map.java +++ b/app/src/main/java/school_project/Map.java @@ -14,11 +14,20 @@ public class Map extends Shape{ super(matrix); } + public Map() { + super(); + } + public void addPiece(Piece piece){ piece.setLinked_map(this); pieces.add(piece); } + public void addPiece(Piece[] pieces) { + for (Piece p : pieces) + this.addPiece(p); + } + /** * Return a matrix with all used space on the map to see if a piece can fit in a space * @@ -33,6 +42,8 @@ public class Map extends Shape{ } for (Piece p : pieces) { + if(p.getPosition() == null) + continue; for(int x = 0; x < p.height; x++){ for(int y = 0; y < p.width; y++){ if (p.getShape()[x][y]){ @@ -43,4 +54,22 @@ public class Map extends Shape{ } return used; } + + public ArrayList getPieces() { + return pieces; + } + + /** + * return a new Clean Map without any pieces on it for saving purpose + * @return a New Map Object without any pieces or saved data + */ + public Map getCleanedMap() { + try { + Map ret = (Map) this.clone(); + ret.getPieces().clear(); + return ret; + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } } diff --git a/app/src/main/java/school_project/Parsers/BinaryParser.java b/app/src/main/java/school_project/Parsers/BinaryParser.java new file mode 100644 index 0000000..98e32a1 --- /dev/null +++ b/app/src/main/java/school_project/Parsers/BinaryParser.java @@ -0,0 +1,220 @@ +package school_project.Parsers; + +import school_project.Map; +import school_project.Piece; +import school_project.Utils.Bitwise; +import school_project.Vec2; + +import java.io.*; +import java.util.Arrays; + +public class BinaryParser implements FileParser { + + @Override + public Map getLevel(File file, boolean saved_data) throws IOException { + Map ret; + + FileInputStream fileStream = new FileInputStream(file); + + byte[] level_data = ExtractLevelData(fileStream); + + ret = new Map(ExtractMapFromLevelData(level_data)); + + ret.addPiece(ExtractPiecesFromLevelData(level_data, saved_data)); + + fileStream.close(); + return ret; + } + + @Override + public void saveLevel(File file, Map level_data, boolean save_data) throws IOException { + int byteSize = getByteSizeForMap(level_data, save_data); + byte[] data = new byte[byteSize]; + int i = 0; + data[i++] = 'S'; data[i++] = 'M'; data[i++] = 'S'; + data[i++] = (byte) level_data.getWidth(); data[i++] = (byte) level_data.getHeight(); + for(byte b : BuildByteFromMatrix(level_data.getShape())){ + data[i++] = b; + } + data[i++] = (byte) level_data.getPieces().size(); + for (Piece p : level_data.getPieces()) { + data[i++] = Bitwise.NibbleToByte((byte) p.getWidth(), (byte) p.getHeight()); + for(byte b : BuildByteFromMatrix(p.getShape())){ + data[i++] = b; + } + } + if (save_data){ + for (Piece p : level_data.getPieces()) { + Vec2 _piece_pos = p.getPosition(); + if(_piece_pos == null){ + data[i++] = 'F'; + data[i++] = 'L'; + }else{ + data[i++] = (byte) _piece_pos.x; + data[i++] = (byte) _piece_pos.y; + } + } + } + data[i++] = 'S'; data[i++] = 'M'; data[i++] = 'E'; + FileOutputStream save_file = new FileOutputStream(file); + save_file.write(data); + save_file.flush(); + save_file.close(); + } + + /** + * Extract Level data from file content + * @param fileStream file stream to read extract data from + * @return Level data as an array of byte + * @throws IOException Expected if we can't read the file + */ + static byte[] ExtractLevelData(InputStream fileStream) throws IOException { + + byte[] bytes = fileStream.readAllBytes(); + + int start_position = 0, end_position = 0; + for (int i = 0; i < bytes.length; i++) { + if(bytes[i] == 83 && bytes[i+1] == 77 && bytes[i+2] == 83){ // SMS + start_position = i+3; + break; + } + } + + for (int i = start_position; i < bytes.length - 2; i++) { + if(bytes[i] == 83 && bytes[i+1] == 77 && bytes[i+2] == 69){ // SME + end_position = i; + break; + } + } + + return Arrays.copyOfRange(bytes, start_position, end_position); + } + + /** + * Get Pieces out of the level data + * + * @param levelData full data of the level without header and footer + * @param saved_data Should extract saved data and included it in the pieces + * @return array of Piece from level data + */ + static Piece[] ExtractPiecesFromLevelData(byte[] levelData, boolean saved_data) { + byte map_width = levelData[0], map_height = levelData[1]; + byte piece_count = levelData[2 + map_width * map_height / 8 + (map_height * map_width % 8 != 0 ? 1 : 0)]; + Piece[] ret = new Piece[piece_count]; + byte[] pieces_data = Arrays.copyOfRange(levelData, 3 + map_width * map_height / 8 + (map_height * map_width % 8 != 0 ? 1 : 0), levelData.length); + byte[] pieces_positions = saved_data ? Arrays.copyOfRange(levelData, levelData.length - piece_count*2,levelData.length ): null; + int piece_offset = 0; + for (int piece_index = 0; piece_index < piece_count; piece_index++) { + Vec2 _piece_size = Bitwise.ByteToNible(pieces_data[piece_index + piece_offset]); + + byte[] _piece_data = Arrays.copyOfRange(pieces_data, piece_index + piece_offset + 1, piece_index + piece_offset + 1 + _piece_size.x * _piece_size.y / 8 + (_piece_size.x * _piece_size.y % 8 != 0 ? 1 : 0)); + + boolean[][] _piece_matrix = BuildMatrixFromBytes(_piece_size.x, _piece_size.y, _piece_data); + + ret[piece_index] = new Piece(_piece_matrix); + + if(saved_data){ + Vec2 _piece_pos = new Vec2(pieces_positions[piece_index*2], pieces_positions[piece_index*2 + 1]); + ret[piece_index].setPosition(_piece_pos); + } + + piece_offset += _piece_size.x * _piece_size.y / 8 + (_piece_size.x * _piece_size.y % 8 != 0 ? 1 : 0); + } + return ret; + } + + /** + * Get the Map Matrix out of the level data + * @param level_data full data of the level without header and footer + * @return boolean matrix of the map + */ + static boolean[][] ExtractMapFromLevelData(byte[] level_data){ + int map_width = level_data[0], map_height = level_data[1]; + byte[] map_data = Arrays.copyOfRange(level_data, 2, 2 + map_width * map_height / 8 + (map_height * map_width % 8 != 0 ? 1 : 0)); + return BuildMatrixFromBytes(map_width, map_height, map_data); + } + + /** + * take a boolean matrix and build an array of byte following the specs of the parser + * @param shape bolean matrix where true are 1 and false are 0 + * @return byte array with each element compiled for file format + */ + static byte[] BuildByteFromMatrix(boolean[][] shape){ + int width = shape[0].length , height = shape.length; + boolean[] b_list = new boolean[width * height]; + for (int x = 0; x < shape.length; x++) { + for (int y = 0; y < shape[x].length; y++) { + b_list[x * shape[x].length + y] = shape[x][y]; + } + } + byte[] ret = new byte[width * height / 8 + (width * height % 8 == 0 ? 0 : 1)]; + for (int i = 0; i < ret.length; i++) { + byte current_byte = 0; + boolean[] current_byte_data = Arrays.copyOfRange(b_list, i * 8, i * 8 + 8); + for (boolean curr_data: current_byte_data) { + current_byte = (byte) (current_byte << 1); + current_byte = (byte) (current_byte | (curr_data ? 1 : 0)); + } + ret[i] = current_byte; + } + return ret; + } + + /** + * Build a boolean Matrix From a byte array + * Each Byte is composed of 8 bit, each bit is 1 or 0 + * if the bit is 0 then it's a false for this cell + * else it's true for this cell + * @param matrix_width width of the matrix + * @param matrix_height height of the matrix + * @param matrix_data byte array of the data to export + * @return boolean Matrix of the data decompiled + */ + static boolean[][] BuildMatrixFromBytes(int matrix_width, int matrix_height, byte[] matrix_data){ + boolean[][] ret = new boolean[matrix_height][matrix_width]; + + // Transforming the bit from matrix_data's byte into boolean array for better manipulation + boolean[] b_array = new boolean[matrix_height * matrix_width]; + int index = 0; + for(byte b: matrix_data){ + for (int i = 0; i < 8; i++) { // because 8 bit in a byte + b_array[index] = Bitwise.IsBitSetAt(b, i); + index++; + if(index >= matrix_height * matrix_width) + break; + } + } + + // Transforming b_array to a 2D matrix + for (int x = 0; x < matrix_height; x++) { + for (int y = 0; y < matrix_width; y++) { + ret[x][y] = b_array[y + x * matrix_width]; + } + } + return ret; + } + + /** + * give the amount of byte needed to store the given Map + * following the binary file format + * @param level the map to check + * @param data should add save data or only level data + * @return integer of the ammount of byte needed + */ + public static int getByteSizeForMap(Map level, boolean data){ + int ret = 6; // header + footer + ret += 2; //size of the piece + ret += ((level.getWidth() * level.getHeight()) / 8); // size of the map + ret += level.getHeight() * level.getWidth() % 8 == 0 ? 0 : 1; // Add 1 if the size of map is not a mult of 8 + ret += 1; // amount of pieces + for(Piece p: level.getPieces()){ + ret += 1; // size of the piece + ret += p.getHeight() * p.getWidth() / 8; + ret += p.getHeight() * p.getWidth() % 8 == 0 ? 0 : 1; // add 1 if the size of the piece is not mult of 8 + if(data){ + ret += 2; // if the piece is not placed, only one byte else 2 + } + } + return ret; + } +} diff --git a/app/src/main/java/school_project/Parsers/FileParser.java b/app/src/main/java/school_project/Parsers/FileParser.java new file mode 100644 index 0000000..c20d0bc --- /dev/null +++ b/app/src/main/java/school_project/Parsers/FileParser.java @@ -0,0 +1,33 @@ +package school_project.Parsers; + +import school_project.Map; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +public interface FileParser { + + /** + * Parse the file and create a Map with its shape and pieces setup + * + * @param file file to parse + * @param saved_data does the saved data should be added to the map + * @return Map Object parsed with file data + * @see "TODO: Add Specification when done" + * @throws FileNotFoundException if the file was not found or was not accessible + * @throws IOException if an I/O occurs + */ + Map getLevel(File file, boolean saved_data) throws IOException; + + /** + * Save Map to a file without all it's data + * Could be used for generating level file. might not be used in game. + * @param file the file where to save + * @param levelData the map to save + * @param save_data should save the map data (need to be false only in development I think) + * @throws FileNotFoundException The file could not be created + * @throws IOException if an I/O occurs + */ + void saveLevel(File file, Map levelData, boolean save_data) throws IOException; +} diff --git a/app/src/main/java/school_project/Parsers/FileParserFactory.java b/app/src/main/java/school_project/Parsers/FileParserFactory.java new file mode 100644 index 0000000..a7f4f68 --- /dev/null +++ b/app/src/main/java/school_project/Parsers/FileParserFactory.java @@ -0,0 +1,83 @@ +package school_project.Parsers; + +import javafx.util.Pair; +import school_project.Map; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.NotSerializableException; + +/** + * This is used to find the right parser to parser a save/level file. + * This should be the only right way to save/load a file! you can just use `Map loadMapFromFile(File)` to load a file + * and `void saveFileFromMap(File, Map)` to save a file + * + *

+ * there is currently 2 file format with 2 variation each (save file or level file) + * - BinaryParser + * - ".level" + * - ".slevel" + * - SerializeParser + * - ".serialized" + * - ".sserialized" + *

+ * + *

+ * More file format can be added in the future by adding a new class that implement parser + * and adding it to this file + *

+ * + * @author tonitch + */ +public class FileParserFactory { + /** + * Load a file and return a map + * If this is a save map, return the map with its save data + * @param file file to get data from + * @return Map generated from the file + * @throws FileNotFoundException if the file was not found or was not accessible + * @throws IOException if an I/O occurs + */ + public static Map loadMapFromFile(File file) throws IOException { + Pair parser= getFileParser(file); + return parser.getKey().getLevel(file, parser.getValue()); + } + + /** + * Save a file in a specific format, this format is defined by the file extension + * This file extention could be: ".level", ".slevel", ".serialized", ".sserialized" + * for save file use the .s variations + * @param file file name to be saved to with the right extension + * @param map map file to save + * @throws NotSerializableException the file extension is not recognised + * @throws FileNotFoundException The file could not be created + * @throws IOException if an I/O occurs + */ + public static void saveFileFromMap(File file, Map map) throws IOException { + Pair parser= getFileParser(file); + parser.getKey().saveLevel(file, map, parser.getValue()); + } + + private static Pair getFileParser(File file) throws NotSerializableException { + FileParser fileParser; + boolean save_data; + + if (file.toString().toLowerCase().endsWith(".level")){ + fileParser = new BinaryParser(); + save_data = false; + }else if(file.toString().toLowerCase().endsWith(".slevel")){ + fileParser = new BinaryParser(); + save_data = true; + }else if(file.toString().toLowerCase().endsWith(".serialized")){ + fileParser = new SerializeParser(); + save_data = false; + }else if(file.toString().toLowerCase().endsWith(".sserialized")) { + fileParser = new SerializeParser(); + save_data = true; + }else { + throw new NotSerializableException("This file format is not supported"); + } + return new Pair(fileParser, save_data); + } +} \ No newline at end of file diff --git a/app/src/main/java/school_project/Parsers/SerializeParser.java b/app/src/main/java/school_project/Parsers/SerializeParser.java new file mode 100644 index 0000000..d49abbd --- /dev/null +++ b/app/src/main/java/school_project/Parsers/SerializeParser.java @@ -0,0 +1,32 @@ +package school_project.Parsers; + +import school_project.Map; + +import java.io.*; + +public class SerializeParser implements FileParser{ + + @Override + public Map getLevel(File file, boolean saved_data) throws IOException { + // saved_data is ignored in this case because the file is serialized data and it already knows if should have saved_data or not at this point + FileInputStream fileStream = new FileInputStream(file); + ObjectInputStream objectStream = new ObjectInputStream(fileStream); + try { + return (Map) objectStream.readObject(); + } catch (ClassNotFoundException e) { + throw new IOException("the serialized file format has not found any object in the file"); + } + } + + @Override + public void saveLevel(File file, Map levelData, boolean save_data) throws IOException { + FileOutputStream fileStream = new FileOutputStream(file); + ObjectOutputStream objectStream = new ObjectOutputStream(fileStream); + objectStream.writeObject(save_data ? levelData : levelData.getCleanedMap()); + + objectStream.close(); + fileStream.close(); + } + + +} diff --git a/app/src/main/java/school_project/Piece.java b/app/src/main/java/school_project/Piece.java index a358dd2..51df463 100644 --- a/app/src/main/java/school_project/Piece.java +++ b/app/src/main/java/school_project/Piece.java @@ -24,10 +24,6 @@ public class Piece extends Shape{ } public void setPosition(Vec2 position){ - if (linked_map == null) { - return; - } - this.Position = position; } @@ -56,4 +52,14 @@ public class Piece extends Shape{ matrix = temp_matrix; } } + + @Override + public boolean equals(Object obj) { + if(obj instanceof Piece pieceObj){ + if( pieceObj.getPosition().equals(this.getPosition()) && pieceObj.getShape().equals(getShape())) { + return true; + } + } + return false; + } } \ No newline at end of file diff --git a/app/src/main/java/school_project/Shape.java b/app/src/main/java/school_project/Shape.java index eda3a9c..3a436d6 100644 --- a/app/src/main/java/school_project/Shape.java +++ b/app/src/main/java/school_project/Shape.java @@ -1,12 +1,14 @@ package school_project; +import java.io.Serializable; + /** * Base class for everything that is a shape kind, like map and pieces * it contain a matrix of boolean where the shape is defined by the true's */ -public class Shape { - +public class Shape implements Serializable, Cloneable{ + protected boolean[][] matrix; protected int height, width; @@ -41,4 +43,17 @@ public class Shape { public boolean[][] getShape() { return matrix; } -} \ No newline at end of file + + @Override + public String toString() { + String ret = ""; + for (boolean[] row : matrix) { + for (boolean el : row) { + if(el) ret = ret.concat("⬛"); + else ret = ret.concat("⬜"); + } + ret = ret.concat("\n"); + } + return ret; + } +} diff --git a/app/src/main/java/school_project/Utils/Bitwise.java b/app/src/main/java/school_project/Utils/Bitwise.java new file mode 100644 index 0000000..1e808b2 --- /dev/null +++ b/app/src/main/java/school_project/Utils/Bitwise.java @@ -0,0 +1,43 @@ +package school_project.Utils; + +import school_project.Vec2; + +public class Bitwise { + + /** + * Check if the bit at pos is 1 or 0 + * @param b byte to test + * @param pos position in b to check + * @return true if the bit at pos is 1 or false if it is 0 + */ + public static boolean IsBitSetAt(byte b, int pos){ + pos = 7 - pos; + return (b & (1 << pos))!= 0; + } + + /** + * Transform a byte (8 bit) to two Nible (4 bit) with a split in the middle + * Exemple: + * in = 01000101 (=69) + * out = { 00000100, 00000101 } (={4, 5}) + * + * @param in the byte to split + * @return an arrya of 2 byte ret[0] = left part; ret[1] = right part + */ + public static Vec2 ByteToNible(byte in){ + Vec2 ret = new Vec2(); + ret.x = (byte) (in >> 4); + ret.y = (byte) (in & 15); // apply the mask '00001111' + return ret; + } + + /** + * Transform 2 byte into 1 with a left part ( 4 bits ) and a right part ( 4 bits) + * @param left first 4 bits + * @param right last 4 bits + * @return concatenated byte + */ + public static byte NibbleToByte(byte left, byte right){ + return (byte) ((left << 4) | right); + } +} diff --git a/app/src/main/java/school_project/Vec2.java b/app/src/main/java/school_project/Vec2.java index 5de0978..7bf6ed9 100644 --- a/app/src/main/java/school_project/Vec2.java +++ b/app/src/main/java/school_project/Vec2.java @@ -1,10 +1,12 @@ package school_project; +import java.io.Serializable; + /** * This is used to represent a position/vector/... any ensemble of 2 elements that have to work together in * a plan. This way we can use some basic operations over them. */ -public class Vec2 { +public class Vec2 implements Serializable { public int x, y; public Vec2() { @@ -16,4 +18,12 @@ public class Vec2 { this.x = x; this.y = y; } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Vec2 vec) { + return this.x == vec.x && this.y == vec.y; + } + return false; + } } diff --git a/app/src/test/java/school_project/Parsers/BinaryParserTest.java b/app/src/test/java/school_project/Parsers/BinaryParserTest.java new file mode 100644 index 0000000..ea28f6c --- /dev/null +++ b/app/src/test/java/school_project/Parsers/BinaryParserTest.java @@ -0,0 +1,177 @@ +package school_project.Parsers; + +import org.junit.jupiter.api.Test; +import school_project.Map; +import school_project.Piece; +import school_project.Vec2; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + +class BinaryParserTest { + + static byte[] file_data = { + 'S', 'M', 'S', + 6, 5, (byte) 0x31, (byte) 0xEC, (byte) 0xF3, (byte) 0xFC, + 4, + (byte) 0x22, (byte) 0x70, + (byte) 0x33, (byte) 0x99, (byte) 0x80, + (byte) 0x32, (byte) 0x7C, + (byte) 0x33, (byte) 0xDB, (byte) 0x80, + 'S', 'M', 'E' + }; + + @Test + void getByteSizeForMap() { + boolean[][] map_shape = { + { true, true, true }, + { true, false, true }, + { true, true, true }, + + }; + boolean[][] piece1_shape = { + { true, true }, + { true, false }, + { true, true }, + + }; + boolean[][] piece2_shape = { + { true }, + { true }, + { true }, + + }; + Map map = new Map(map_shape); + Piece piece1 = new Piece(piece1_shape); + Piece piece2 = new Piece(piece2_shape); + + map.addPiece(new Piece[]{piece1, piece2}); + piece2.setPosition(new Vec2(0, 2)); + + + assertEquals(15, BinaryParser.getByteSizeForMap(map, false)); + assertEquals(19, BinaryParser.getByteSizeForMap(map, true)); + } + + + @Test + void BuildByteFromMatrix(){ + byte[] map_data = Arrays.copyOfRange(file_data, 5, 9); + boolean[][] map_shape = { + {false, false, true, true, false, false}, + {false, true, true, true, true, false}, + {true, true, false, false, true, true}, + {true, true, false, false, true, true}, + {true, true, true, true, true, true}, + }; + assertArrayEquals(map_data, BinaryParser.BuildByteFromMatrix(map_shape)); + } + + @Test + void BuildMatrixFromByte_map(){ + byte[] map_data = Arrays.copyOfRange(file_data, 5, 9); + boolean[][] map_shape = { + {false, false, true, true, false, false}, + {false, true, true, true, true, false}, + {true, true, false, false, true, true}, + {true, true, false, false, true, true}, + {true, true, true, true, true, true}, + }; + assertArrayEquals(map_shape, BinaryParser.BuildMatrixFromBytes(6, 5, map_data)); + } + + @Test + void BuildMatrixFromByte_piece1(){ + byte[] piece1_data = Arrays.copyOfRange(file_data, 11, 12); + boolean[][] piece1_shape = { + {false, true}, + {true, true}, + }; + assertArrayEquals(piece1_shape, BinaryParser.BuildMatrixFromBytes(2, 2, piece1_data)); + } + + @Test + void BuildMatrixFromByte_piece2(){ + byte[] piece2_data = Arrays.copyOfRange(file_data, 13, 15); + boolean[][] piece2_shape = { + {true, false, false}, + {true, true, false}, + {false, true, true}, + }; + assertArrayEquals(piece2_shape, BinaryParser.BuildMatrixFromBytes(3, 3, piece2_data)); + } + + @Test + void BuildMatrixFromByte_piece3(){ + byte[] piece3_data = Arrays.copyOfRange(file_data, 16, 17); + boolean[][] piece3_shape = { + {false, true, true}, + {true, true, true}, + }; + assertArrayEquals(piece3_shape, BinaryParser.BuildMatrixFromBytes(3, 2, piece3_data)); + } + + @Test + void BuildMatrixFromByte_piece4(){ + byte[] piece4_data = Arrays.copyOfRange(file_data, 18, 20); + boolean[][] piece4_shape = { + {true, true, false}, + {true, true, false}, + {true, true, true}, + }; + assertArrayEquals(piece4_shape, BinaryParser.BuildMatrixFromBytes(3, 3, piece4_data)); + } + + @Test + void ExtractLevelData() throws IOException { + boolean[][] expected_map_shape = { + {false, false, true, true, false, false}, + {false, true, true, true, true, false}, + {true, true, false, false, true, true}, + {true, true, false, false, true, true}, + {true, true, true, true, true, true}, + }; + + byte[] level_data = BinaryParser.ExtractLevelData(new ByteArrayInputStream(file_data)); + boolean[][] map = BinaryParser.ExtractMapFromLevelData(level_data); + + assertArrayEquals(expected_map_shape, map); + } + + @Test + void ExtractPiecesFronLevelDataTest() throws IOException { + boolean[][] piece1_shape = { + {false, true}, + {true, true}, + }; + boolean[][] piece2_shape = { + {true, false, false}, + {true, true, false}, + {false, true, true}, + }; + boolean[][] piece3_shape = { + {false, true, true}, + {true, true, true}, + }; + boolean[][] piece4_shape = { + {true, true, false}, + {true, true, false}, + {true, true, true}, + }; + boolean[][][] pieces_shapes = { + piece1_shape, + piece2_shape, + piece3_shape, + piece4_shape + }; + byte[] level_data = BinaryParser.ExtractLevelData(new ByteArrayInputStream(file_data)); + Piece[] pieces = BinaryParser.ExtractPiecesFromLevelData(level_data, false); + + for (int i = 0; i < pieces_shapes.length; i++) { + assertArrayEquals(pieces_shapes[i], pieces[i].getShape()); + } + } +} \ No newline at end of file diff --git a/app/src/test/java/school_project/Parsers/FileParserFactoryTest.java b/app/src/test/java/school_project/Parsers/FileParserFactoryTest.java new file mode 100644 index 0000000..e703db6 --- /dev/null +++ b/app/src/test/java/school_project/Parsers/FileParserFactoryTest.java @@ -0,0 +1,103 @@ +package school_project.Parsers; + +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.io.TempDir; +import school_project.Map; +import school_project.Piece; +import school_project.Vec2; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.*; + +class FileParserFactoryTest { + + static Map generateMapTest(){ + boolean[][] map_shape = { + {false, false, true, true, false, false}, + {false, true, true, true, true, false}, + {true, true, false, false, true, true}, + {true, true, false, false, true, true}, + {true, true, true, true, true, true}, + }; + boolean[][] piece1_shape = { + {false, true}, + {true, true}, + }; + boolean[][] piece2_shape = { + {true, false, false}, + {true, true, false}, + {false, true, true}, + }; + boolean[][] piece3_shape = { + {false, true, true}, + {true, true, true}, + }; + boolean[][] piece4_shape = { + {true, true, false}, + {true, true, false}, + {true, true, true}, + }; + Piece[] pieces = { new Piece(piece1_shape), new Piece(piece2_shape), new Piece(piece3_shape), new Piece(piece4_shape) }; + Map map = new Map(map_shape); + map.addPiece(pieces); + + pieces[0].setPosition(new Vec2(1, 0)); + pieces[1].setPosition(new Vec2(3, 0)); + pieces[2].setPosition(new Vec2(3, 3)); + pieces[3].setPosition(new Vec2(0, 2)); + return map; + } + + @Test + void saveLoadFileFromMap_Binary(@TempDir Path tmpFolder) throws IOException { + Map map = generateMapTest(); + FileParserFactory.saveFileFromMap(tmpFolder.resolve("TestBinaryLevel.level").toFile(), map); + + Map testMap = FileParserFactory.loadMapFromFile(tmpFolder.resolve("TestBinaryLevel.level").toFile()); + assertArrayEquals(map.getCleanedMap().getShape(), testMap.getShape()); + for (int i = 0; i < map.getPieces().size(); i++) { + assertArrayEquals(map.getPieces().get(i).getShape(), testMap.getPieces().get(i).getShape()); + } + } + + @Test + void saveLoadFileFromMap_save_Binary(@TempDir Path tmpFolder) throws IOException { + Map map = generateMapTest(); + FileParserFactory.saveFileFromMap(tmpFolder.resolve("TestBinarySave.slevel").toFile(), map); + + Map testMap = FileParserFactory.loadMapFromFile(tmpFolder.resolve("TestBinarySave.slevel").toFile()); + assertArrayEquals(map.getShape(), testMap.getShape()); + for (int i = 0; i < map.getPieces().size(); i++) { + assertArrayEquals(map.getPieces().get(i).getShape(), testMap.getPieces().get(i).getShape()); + assertEquals(map.getPieces().get(i).getPosition(), testMap.getPieces().get(i).getPosition()); + } + } + @Test + void saveLoadFileFromMap_Serialized(@TempDir Path tmpFolder) throws IOException { + Map map = generateMapTest(); + FileParserFactory.saveFileFromMap( tmpFolder.resolve("TestSerializedLevel.serialized").toFile(), map); + + Map testMap = FileParserFactory.loadMapFromFile( tmpFolder.resolve("TestSerializedLevel.serialized").toFile()); + assertArrayEquals(map.getShape(), testMap.getShape()); + for (int i = 0; i < map.getPieces().size(); i++) { + assertArrayEquals(map.getPieces().get(i).getShape(), testMap.getPieces().get(i).getShape()); + } + + } + + @Test + void saveLoadFileFromMap_save_Serialized(@TempDir Path tmpFolder) throws IOException{ + Map map = generateMapTest(); + FileParserFactory.saveFileFromMap(tmpFolder.resolve("TestSerializedSave.sserialized").toFile(), map); + + Map testMap = FileParserFactory.loadMapFromFile(tmpFolder.resolve("TestSerializedSave.sserialized").toFile()); + assertArrayEquals(map.getShape(), testMap.getShape()); + for (int i = 0; i < map.getPieces().size(); i++) { + assertArrayEquals(map.getPieces().get(i).getShape(), testMap.getPieces().get(i).getShape()); + assertEquals(map.getPieces().get(i).getPosition(), testMap.getPieces().get(i).getPosition()); + } + } +}