File Parser for levels #18
@ -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
|
||||
|
||||
...
|
||||
|
@ -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)
|
50
JournalDeBord/src/spec/FileParser.md
Normal file
50
JournalDeBord/src/spec/FileParser.md
Normal file
@ -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.
|
@ -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'
|
||||
|
@ -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<Piece> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
220
app/src/main/java/school_project/Parsers/BinaryParser.java
Normal file
220
app/src/main/java/school_project/Parsers/BinaryParser.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
33
app/src/main/java/school_project/Parsers/FileParser.java
Normal file
33
app/src/main/java/school_project/Parsers/FileParser.java
Normal file
@ -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;
|
||||
}
|
@ -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
|
||||
*
|
||||
* <p>
|
||||
* there is currently 2 file format with 2 variation each (save file or level file)
|
||||
* - BinaryParser
|
||||
* - ".level"
|
||||
* - ".slevel"
|
||||
* - SerializeParser
|
||||
* - ".serialized"
|
||||
* - ".sserialized"
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* More file format can be added in the future by adding a new class that implement parser
|
||||
* and adding it to this file
|
||||
* </p>
|
||||
*
|
||||
* @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<FileParser, Boolean> 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<FileParser, Boolean> parser= getFileParser(file);
|
||||
parser.getKey().saveLevel(file, map, parser.getValue());
|
||||
}
|
||||
|
||||
private static Pair<FileParser, Boolean> 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, Boolean>(fileParser, save_data);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
43
app/src/main/java/school_project/Utils/Bitwise.java
Normal file
43
app/src/main/java/school_project/Utils/Bitwise.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
177
app/src/test/java/school_project/Parsers/BinaryParserTest.java
Normal file
177
app/src/test/java/school_project/Parsers/BinaryParserTest.java
Normal file
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user