diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/IntVector2.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/IntVector2.java index 5ef49c9..ef4b951 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/IntVector2.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/IntVector2.java @@ -1,13 +1,16 @@ package uulm.teamname.marvelous.gamelibrary; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import uulm.teamname.marvelous.gamelibrary.json.ingame.IntVector2Deserializer; +import uulm.teamname.marvelous.gamelibrary.json.ingame.IntVector2Serializer; import java.io.Serializable; import java.util.Objects; /** Represents a 2d vector of integers. */ @JsonDeserialize(using = IntVector2Deserializer.class) +@JsonSerialize(using = IntVector2Serializer.class) public class IntVector2 implements Serializable { private int x; private int y; diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/EntityID.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/EntityID.java index c6eb5f1..af7465b 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/EntityID.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/EntityID.java @@ -1,15 +1,18 @@ package uulm.teamname.marvelous.gamelibrary.entities; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import uulm.teamname.marvelous.gamelibrary.json.ingame.EntityIDDeserializer; import java.util.Objects; /** Represents a distinct identification for every {@link Entity} in a game. */ +@JsonPropertyOrder({"entityID", "ID"}) @JsonDeserialize(using = EntityIDDeserializer.class) public class EntityID { /** The index of the entity */ + @JsonProperty("ID") public final int id; /** The type of the entity */ diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/JSON.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/JSON.java index 0d75cf6..8428d54 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/JSON.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/JSON.java @@ -3,17 +3,21 @@ package uulm.teamname.marvelous.gamelibrary.json; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import jdk.jshell.spi.ExecutionControl; +import uulm.teamname.marvelous.gamelibrary.json.basic.BasicMessage; import uulm.teamname.marvelous.gamelibrary.json.basic.EventMessage; /** Contains JSON encoding and decoding. */ public class JSON { + + private static final ObjectMapper mapper = new ObjectMapper(); + + /** Deserializes an incoming network message into a {@link EventMessage}. * @param input The JSON to deserialize. * @return The parsed message. */ public static EventMessage parse(String input) { EventMessage result = null; - ObjectMapper mapper = new ObjectMapper(); try { result = mapper.readValue(input, EventMessage.class); } catch (JsonProcessingException e) { @@ -25,8 +29,13 @@ public class JSON { /** Serializes a {@link EventMessage} into a JSON string. * @param input The message to serialize. * @return The message as JSON. */ - public static String stringify(EventMessage input) throws ExecutionControl.NotImplementedException { - //TODO: implement JSON.stringify - throw new ExecutionControl.NotImplementedException("JSON.stringify is not implemented"); + public static String stringify(BasicMessage input) throws ExecutionControl.NotImplementedException { + String result = null; + try { + result = mapper.writeValueAsString(input); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + return result; } } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/JsonNodeUnwrapper.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/JsonNodeUnwrapper.java index 80c803d..a0dea7f 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/JsonNodeUnwrapper.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/JsonNodeUnwrapper.java @@ -1,17 +1,17 @@ package uulm.teamname.marvelous.gamelibrary.json; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; public class JsonNodeUnwrapper { - private static final ObjectMapper mapper = new ObjectMapper(); - - public static T unwrap (JsonNode rootNode, String key, Class type) throws JsonProcessingException { - JsonNode subNode; - if ((subNode = rootNode.get(key)) != null) { - return mapper.readValue(subNode.toString(), type); + public static T unwrap(JsonNode subNode, Class type, ObjectCodec codec) + throws JsonProcessingException { + if (subNode != null) { + return codec.treeToValue(subNode, type); } return null; } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/EntityDeserializer.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/EntityDeserializer.java index 1c9b9a8..0335eac 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/EntityDeserializer.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/EntityDeserializer.java @@ -21,9 +21,6 @@ public class EntityDeserializer extends JsonDeserializer { Rock } - // static so that no reinitializations are needed - private static final ObjectMapper mapper = new ObjectMapper(); - @Override public Entity deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { ObjectCodec codec = p.getCodec(); @@ -40,7 +37,7 @@ public class EntityDeserializer extends JsonDeserializer { node.get("ID").intValue()); result = new Character( id, - mapper.readValue(node.get("position").toString(), IntVector2.class), + codec.treeToValue(node.get("position"), IntVector2.class), node.get("name").asText(), node.get("HP").asInt(), node.get("MP").asInt(), @@ -50,21 +47,21 @@ public class EntityDeserializer extends JsonDeserializer { -1 ); - for (var i: mapper.readValue(node.get("stones").toString(), Integer[].class)) { + for (var i: codec.treeToValue(node.get("stones"), Integer[].class)) { ((Character) result).inventory.addStone(StoneType.valueOf(i)); } } case InfinityStone -> { result = new InfinityStone( new EntityID(EntityType.InfinityStones, node.get("ID").asInt()), - mapper.readValue(node.get("position").toString(), IntVector2.class), + codec.treeToValue(node.get("position"), IntVector2.class), StoneType.valueOf(node.get("ID").asInt()) ); } case Rock -> { result = new Rock( new EntityID(EntityType.Rocks, node.get("ID").asInt()), - mapper.readValue(node.get("position").toString(), IntVector2.class), + codec.treeToValue(node.get("position"), IntVector2.class), node.get("HP").asInt() ); } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/EventDeserializer.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/EventDeserializer.java index f86257f..6af5183 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/EventDeserializer.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/EventDeserializer.java @@ -1,12 +1,10 @@ package uulm.teamname.marvelous.gamelibrary.json.ingame; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import uulm.teamname.marvelous.gamelibrary.IntVector2; import uulm.teamname.marvelous.gamelibrary.entities.Entity; import uulm.teamname.marvelous.gamelibrary.entities.EntityID; @@ -14,19 +12,14 @@ import uulm.teamname.marvelous.gamelibrary.entities.StoneType; import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.events.EventBuilder; import uulm.teamname.marvelous.gamelibrary.events.EventType; -import uulm.teamname.marvelous.gamelibrary.events.GamestateEvent; import static uulm.teamname.marvelous.gamelibrary.json.JsonNodeUnwrapper.unwrap; import java.io.IOException; -import java.util.Arrays; import java.util.HashMap; public class EventDeserializer extends JsonDeserializer { - // static so that no reinitializations are needed - private static final ObjectMapper mapper = new ObjectMapper(); - @Override public Event deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { ObjectCodec codec = p.getCodec(); @@ -76,40 +69,40 @@ public class EventDeserializer extends JsonDeserializer { // } // necessary because of exact type mismatch - var stoneTypeID = unwrap(node, "stoneType", EntityID.class); + var stoneTypeID = unwrap(node.get("stoneType"), EntityID.class, codec); - builder.withTargetEntity(unwrap(node, "targetEntity", EntityID.class)) - .withTargetField(unwrap(node, "targetField", IntVector2.class)) - .withAmount(unwrap(node, "targetField", Integer.class)) - .withEntity(unwrap(node, "entity", Entity.class)) + builder.withTargetEntity(unwrap(node.get("targetEntity"), EntityID.class, codec)) + .withTargetField(unwrap(node.get("targetField"), IntVector2.class, codec)) + .withAmount(unwrap(node.get("targetField"), Integer.class, codec)) + .withEntity(unwrap(node.get("entity"), Entity.class, codec)) - .withOriginEntity(unwrap(node, "originEntity", EntityID.class)) - .withOriginField(unwrap(node, "originField", IntVector2.class)) + .withOriginEntity(unwrap(node.get("originEntity"), EntityID.class, codec)) + .withOriginField(unwrap(node.get("originField"), IntVector2.class, codec)) .withStoneType(stoneTypeID != null ? StoneType.valueOf(stoneTypeID.id) : null) - .withRoundCount(unwrap(node, "roundCount", Integer.class)) - .withTurnCount(unwrap(node, "turnCount", Integer.class)) - .withCharacterOrder(unwrap(node, "characterOrder", EntityID[].class)) - .withNextCharacter(unwrap(node, "nextCharacter", EntityID.class)) + .withRoundCount(unwrap(node.get("roundCount"), Integer.class, codec)) + .withTurnCount(unwrap(node.get("turnCount"), Integer.class, codec)) + .withCharacterOrder(unwrap(node.get("characterOrder"), EntityID[].class, codec)) + .withNextCharacter(unwrap(node.get("nextCharacter"), EntityID.class, codec)) - .withPlayerWon(unwrap(node, "playerWon", Integer.class)) + .withPlayerWon(unwrap(node.get("playerWon"), Integer.class, codec)) - .withMessage(unwrap(node, "message", String.class)) - .withTimeLeft(unwrap(node, "timeLeft", Integer.class)) + .withMessage(unwrap(node.get("message"), String.class, codec)) + .withTimeLeft(unwrap(node.get("timeLeft"), Integer.class, codec)) - .withEntities(unwrap(node, "entities", Entity[].class)) - .withTurnOrder(unwrap(node, "turnOrder", EntityID[].class)) - .withMapSize(unwrap(node, "mapSize", IntVector2.class)) - .withActiveCharacter(unwrap(node, "activeCharacter", EntityID.class)) - .withStoneCooldowns(unwrap(node, "stoneCooldowns",Integer[].class)) - .withWinCondition(unwrap(node, "winCondition",Boolean.class)) + .withEntities(unwrap(node.get("entities"), Entity[].class, codec)) + .withTurnOrder(unwrap(node.get("turnOrder"), EntityID[].class, codec)) + .withMapSize(unwrap(node.get("mapSize"), IntVector2.class, codec)) + .withActiveCharacter(unwrap(node.get("activeCharacter"), EntityID.class, codec)) + .withStoneCooldowns(unwrap(node.get("stoneCooldowns"), Integer[].class, codec)) + .withWinCondition(unwrap(node.get("winCondition"), Boolean.class, codec)) - .withTeamIdentifier(unwrap(node, "teamIdentifier", String.class)) - .withCustomContent(unwrap(node,"customContent", HashMap.class)); + .withTeamIdentifier(unwrap(node.get("teamIdentifier"), String.class, codec)) + .withCustomContent(unwrap(node.get("customContent"), HashMap.class, codec)); - builder.withTargetEntity(unwrap(node, "targetEntity", EntityID.class)); - builder.withTargetField(unwrap(node, "targetField", IntVector2.class)); + builder.withTargetEntity(unwrap(node.get("targetEntity"), EntityID.class, codec)); + builder.withTargetField(unwrap(node.get("targetField"), IntVector2.class, codec)); switch (eventType) { case Ack, Nack, GamestateEvent -> { return builder.buildGameStateEvent(); } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/IntVector2Deserializer.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/IntVector2Deserializer.java index a01af72..cf35971 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/IntVector2Deserializer.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/IntVector2Deserializer.java @@ -12,13 +12,9 @@ import uulm.teamname.marvelous.gamelibrary.IntVector2; import java.io.IOException; public class IntVector2Deserializer extends JsonDeserializer { - - // static so that no reinitializations are needed - private static final ObjectMapper mapper = new ObjectMapper(); - @Override public IntVector2 deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - var values = mapper.readValue(p, Integer[].class); + var values = p.readValueAs(Integer[].class); IntVector2 result = new IntVector2(values[0], values[1]); return result; } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/IntVector2Serializer.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/IntVector2Serializer.java new file mode 100644 index 0000000..9d02f60 --- /dev/null +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/IntVector2Serializer.java @@ -0,0 +1,27 @@ +package uulm.teamname.marvelous.gamelibrary.json.ingame; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import uulm.teamname.marvelous.gamelibrary.IntVector2; + +import java.io.IOException; + +public class IntVector2Serializer extends StdSerializer { + + public IntVector2Serializer() { + this(null); + } + + protected IntVector2Serializer(Class t) { + super(t); + } + + @Override + public void serialize(IntVector2 value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartArray(); + gen.writeNumber(value.getX()); + gen.writeNumber(value.getY()); + gen.writeEndArray(); + } +} diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/RequestDeserializer.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/RequestDeserializer.java index 3dcccd8..5696e33 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/RequestDeserializer.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/RequestDeserializer.java @@ -6,12 +6,9 @@ import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import uulm.teamname.marvelous.gamelibrary.IntVector2; -import uulm.teamname.marvelous.gamelibrary.entities.Entity; import uulm.teamname.marvelous.gamelibrary.entities.EntityID; import uulm.teamname.marvelous.gamelibrary.entities.StoneType; -import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.requests.*; import java.io.IOException; @@ -21,9 +18,6 @@ import static uulm.teamname.marvelous.gamelibrary.json.JsonNodeUnwrapper.unwrap; public class RequestDeserializer extends JsonDeserializer { - // static so that no reinitializations are needed - private static final ObjectMapper mapper = new ObjectMapper(); - @Override public Request deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { ObjectCodec codec = p.getCodec(); @@ -38,12 +32,12 @@ public class RequestDeserializer extends JsonDeserializer { } var requestBuilder = new RequestBuilder(requestType) - .withValue(unwrap(node, "value", int.class)) - .withOriginEntity(unwrap(node, "originEntity", EntityID.class)) - .withTargetEntity(unwrap(node, "targetEntity", EntityID.class)) - .withOriginField(unwrap(node, "originField", IntVector2.class)) - .withTargetField(unwrap(node, "targetField", IntVector2.class)) - .withStoneType(Optional.ofNullable(unwrap(node, "stoneType", EntityID.class)) + .withValue(unwrap(node.get("value"), int.class, codec)) + .withOriginEntity(unwrap(node.get("originEntity"), EntityID.class, codec)) + .withTargetEntity(unwrap(node.get("targetEntity"), EntityID.class, codec)) + .withOriginField(unwrap(node.get("originField"), IntVector2.class, codec)) + .withTargetField(unwrap(node.get("targetField"), IntVector2.class, codec)) + .withStoneType(Optional.ofNullable(unwrap(node.get("stoneType"), EntityID.class, codec)) .map(x -> StoneType.valueOf(x.id)) .orElseGet(() -> null)); diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/RequestSerializer.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/RequestSerializer.java new file mode 100644 index 0000000..04dd8ff --- /dev/null +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/json/ingame/RequestSerializer.java @@ -0,0 +1,83 @@ +package uulm.teamname.marvelous.gamelibrary.json.ingame; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import uulm.teamname.marvelous.gamelibrary.entities.EntityID; +import uulm.teamname.marvelous.gamelibrary.entities.EntityType; +import uulm.teamname.marvelous.gamelibrary.requests.CharacterRequest; +import uulm.teamname.marvelous.gamelibrary.requests.GameRequest; +import uulm.teamname.marvelous.gamelibrary.requests.Request; +import uulm.teamname.marvelous.gamelibrary.requests.RequestType; + +import java.io.IOException; + +public class RequestSerializer extends StdSerializer { + + public RequestSerializer() { + this(null); + } + + protected RequestSerializer(Class t) { + super(t); + } + + @Override + public void serialize(Request value, JsonGenerator gen, SerializerProvider provider) throws IOException { + + gen.writeStartObject(); + gen.writeObjectField("requestType", value.type); + + + if (value instanceof GameRequest) { + serializeGameRequest((GameRequest) value, gen, provider); + } else if (value instanceof CharacterRequest) { + serializeCharacterRequest((CharacterRequest) value, gen, provider); + } + + gen.writeEndObject(); + } + + /** Method invoked after writing the RequestType, and used to write any additional values required */ + private void serializeGameRequest(GameRequest value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + return; // does nothing, but still there for consistency + } + + /** Method invoked after writing the RequestType, and used to write any additional values required */ + private void serializeCharacterRequest(CharacterRequest value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + // The idea here is: if ([GUARD]) {gen.write...} for all fields. + + boolean hasTargetEntity = + value.type == RequestType.MeleeAttackRequest || + value.type == RequestType.RangedAttackRequest || + value.type == RequestType.ExchangeInfinityStoneRequest; + + boolean hasStoneType = + value.type == RequestType.ExchangeInfinityStoneRequest || + value.type == RequestType.UseInfinityStoneRequest; + + boolean hasValue = + value.type == RequestType.MeleeAttackRequest || + value.type == RequestType.RangedAttackRequest; + + + gen.writeObjectField("originEntity", value.originEntity); + + if (hasTargetEntity) { + gen.writeObjectField("targetEntity", value.targetEntity); + } + + gen.writeObjectField("originField", value.originField); + gen.writeObjectField("targetField", value.targetField); + + if (hasStoneType) { + gen.writeObjectField("stoneType", new EntityID(EntityType.InfinityStones, value.stoneType.getID())); + } + + if (hasValue) { + gen.writeObjectField("value", value.value); + } + } +} diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/requests/CharacterRequest.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/requests/CharacterRequest.java index 6a37235..6edcfc2 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/requests/CharacterRequest.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/requests/CharacterRequest.java @@ -1,5 +1,6 @@ package uulm.teamname.marvelous.gamelibrary.requests; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import uulm.teamname.marvelous.gamelibrary.IntVector2; import uulm.teamname.marvelous.gamelibrary.entities.EntityID; import uulm.teamname.marvelous.gamelibrary.entities.StoneType; @@ -7,6 +8,7 @@ import uulm.teamname.marvelous.gamelibrary.entities.StoneType; import java.util.Objects; /** Represents a character event for: {@link RequestType#MeleeAttackRequest}, {@link RequestType#RangedAttackRequest}, {@link RequestType#MoveRequest}, {@link RequestType#ExchangeInfinityStoneRequest}, {@link RequestType#UseInfinityStoneRequest}. */ +@JsonPropertyOrder({"requestType", "originEntity", "targetEntity", "originField", "targetField", "stoneType", "value"}) public class CharacterRequest extends Request { public EntityID originEntity = null; public EntityID targetEntity = null; diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/requests/Request.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/requests/Request.java index ace94f9..136e5ac 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/requests/Request.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/requests/Request.java @@ -1,15 +1,22 @@ package uulm.teamname.marvelous.gamelibrary.requests; +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonKey; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import uulm.teamname.marvelous.gamelibrary.json.basic.EventMessage; import uulm.teamname.marvelous.gamelibrary.json.ingame.RequestDeserializer; +import uulm.teamname.marvelous.gamelibrary.json.ingame.RequestSerializer; import java.util.Objects; /** Represents an abstract request sent inside a {@link EventMessage} between client and server. */ @JsonDeserialize(using = RequestDeserializer.class) +@JsonSerialize(using = RequestSerializer.class) public abstract class Request { - public RequestType type; + + @JsonProperty("requestType") public RequestType type; @Override diff --git a/src/test/java/uulm/teamname/marvelous/gamelibrary/json/JSONTest.java b/src/test/java/uulm/teamname/marvelous/gamelibrary/json/JSONTest.java index e70c8e2..13f45ff 100644 --- a/src/test/java/uulm/teamname/marvelous/gamelibrary/json/JSONTest.java +++ b/src/test/java/uulm/teamname/marvelous/gamelibrary/json/JSONTest.java @@ -2,19 +2,27 @@ package uulm.teamname.marvelous.gamelibrary.json; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import net.jqwik.api.*; +import net.jqwik.api.lifecycle.BeforeContainer; +import net.jqwik.api.lifecycle.BeforeProperty; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; + import static org.assertj.core.api.Assertions.*; +import uulm.teamname.marvelous.gamelibrary.IntVector2; import uulm.teamname.marvelous.gamelibrary.entities.EntityID; +import uulm.teamname.marvelous.gamelibrary.entities.EntityType; +import uulm.teamname.marvelous.gamelibrary.entities.Rock; import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.events.EventBuilder; import uulm.teamname.marvelous.gamelibrary.events.EventType; import uulm.teamname.marvelous.gamelibrary.json.basic.EventMessage; +import uulm.teamname.marvelous.gamelibrary.requests.*; -import java.util.HashMap; +import java.util.*; class JSONTest { @@ -29,11 +37,11 @@ class JSONTest { @BeforeEach void setUp() { messageStructureStart = """ - { - "messages":[ - """; - messageStructureEnd = new String[] { - """ + { + "messages":[ + """; + messageStructureEnd = new String[]{ + """ ], "messageType": "REQUESTS", "customContentType": "TestCustomContent", @@ -43,7 +51,7 @@ class JSONTest { "customProperty" = true } """, - """ + """ ], "messageType": "EVENTS", "customContentType": "TestCustomContent", @@ -75,9 +83,9 @@ class JSONTest { }; String eventRepresentation = """ - {"eventType": "Ack"}, - {"eventType": "Nack"} - """; + {"eventType": "Ack"}, + {"eventType": "Nack"} + """; String completeMessageStructure = messageStructureStart + eventRepresentation + messageStructureEnd[1]; target.messageType = MessageType.EVENTS; @@ -90,19 +98,19 @@ class JSONTest { @Test void parseMoreComplicatedEvents() { String eventRepresentation = """ - { - "eventType": "GamestateEvent", - "activeCharacter": {"entityID": "P2", "ID": 4}, - "turnOrder": [ - {"entityID": "P2", "ID": 4}, - {"entityID": "P2", "ID": 2}, - {"entityID": "P1", "ID": 3}, - {"entityID": "NPC", "ID": 1}, - {"entityID": "InfinityStones", "ID": 5}, - {"entityID": "Rocks", "ID": 0} - ] - } - """; + { + "eventType": "GamestateEvent", + "activeCharacter": {"entityID": "P2", "ID": 4}, + "turnOrder": [ + {"entityID": "P2", "ID": 4}, + {"entityID": "P2", "ID": 2}, + {"entityID": "P1", "ID": 3}, + {"entityID": "NPC", "ID": 1}, + {"entityID": "InfinityStones", "ID": 5}, + {"entityID": "Rocks", "ID": 0} + ] + } + """; String completeMessageStructure = messageStructureStart + eventRepresentation + messageStructureEnd[1]; System.out.println(JSON.parse(completeMessageStructure)); @@ -112,11 +120,11 @@ class JSONTest { @Disabled void simpleInDevTest() throws JsonProcessingException { String simpleEntity = """ - { - "entityID": "P2", - "ID": 4 - } - """; + { + "entityID": "P2", + "ID": 4 + } + """; System.out.println(new ObjectMapper().readValue(simpleEntity, EntityID.class)); } diff --git a/src/test/java/uulm/teamname/marvelous/gamelibrary/json/ingame/IntVector2SerializerTest.java b/src/test/java/uulm/teamname/marvelous/gamelibrary/json/ingame/IntVector2SerializerTest.java new file mode 100644 index 0000000..2f530ff --- /dev/null +++ b/src/test/java/uulm/teamname/marvelous/gamelibrary/json/ingame/IntVector2SerializerTest.java @@ -0,0 +1,37 @@ +package uulm.teamname.marvelous.gamelibrary.json.ingame; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import net.jqwik.api.*; +import net.jqwik.api.lifecycle.BeforeProperty; +import uulm.teamname.marvelous.gamelibrary.IntVector2; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.*; + +class IntVector2SerializerTest { + + ObjectMapper mapper = new ObjectMapper(); + + @BeforeProperty + void setup() { + mapper = new ObjectMapper(); + } + + @Property + void vectorsGetProperlyDeserialized(@ForAll @From("positions") IntVector2 vector) throws JsonProcessingException { + var jsonRepresentingVector = String.format("[%d,%d]", vector.getX(), vector.getY()); + assertThat(mapper.writeValueAsString(vector)) + .isEqualTo(jsonRepresentingVector); + + } + + @Provide("positions") + Arbitrary positions() { + return Arbitraries.integers() + .tuple2() + .map(pos -> new IntVector2(pos.get1(), pos.get2())); + } + +} \ No newline at end of file diff --git a/src/test/java/uulm/teamname/marvelous/gamelibrary/json/ingame/RequestSerializerTest.java b/src/test/java/uulm/teamname/marvelous/gamelibrary/json/ingame/RequestSerializerTest.java new file mode 100644 index 0000000..7f3fd5e --- /dev/null +++ b/src/test/java/uulm/teamname/marvelous/gamelibrary/json/ingame/RequestSerializerTest.java @@ -0,0 +1,223 @@ +package uulm.teamname.marvelous.gamelibrary.json.ingame; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import net.jqwik.api.*; +import net.jqwik.api.lifecycle.BeforeProperty; +import uulm.teamname.marvelous.gamelibrary.IntVector2; +import uulm.teamname.marvelous.gamelibrary.entities.EntityID; +import uulm.teamname.marvelous.gamelibrary.entities.EntityType; +import uulm.teamname.marvelous.gamelibrary.entities.StoneType; +import uulm.teamname.marvelous.gamelibrary.requests.CharacterRequest; +import uulm.teamname.marvelous.gamelibrary.requests.GameRequest; +import uulm.teamname.marvelous.gamelibrary.requests.RequestBuilder; +import uulm.teamname.marvelous.gamelibrary.requests.RequestType; + +import java.util.HashSet; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RequestSerializerTest { + + private ObjectMapper mapper; + @BeforeProperty + void setup() { + mapper = new ObjectMapper(); + } + + + + @Property + void gameRequestsGetSerializedProperly( + @ForAll @From("gameRequests") GameRequest request) + throws JsonProcessingException { + var jsonRepresentingRequest = String.format( + "{\"requestType\":\"%s\"}", + request.type); + + assertThat(mapper.writeValueAsString(request)) + .isEqualTo(jsonRepresentingRequest); + } + + @Property + void characterRequestsGetSerializedProperly( + @ForAll @From("characterRequests") CharacterRequest request) + throws JsonProcessingException { + + var jsonRepresentingValue = request.type == RequestType.MeleeAttackRequest || + request.type == RequestType.RangedAttackRequest ? + String.format("\"value\":%d", request.value) : ""; + + var jsonRepresentingTargetEntity = request.type == RequestType.MeleeAttackRequest || + request.type == RequestType.RangedAttackRequest || + request.type == RequestType.ExchangeInfinityStoneRequest ? + String.format( + "\"targetEntity\":{\"entityID\":\"%s\",\"ID\":%d},", + request.targetEntity.type, + request.targetEntity.id) : ""; + + var jsonRepresentingStoneType = request.type == RequestType.UseInfinityStoneRequest || + request.type == RequestType.ExchangeInfinityStoneRequest ? + String.format( + "\"stoneType\":{\"entityID\":\"%s\",\"ID\":%d}", + EntityType.InfinityStones, + request.stoneType.getID()) : ""; + + var jsonRepresentingRequest = String.format( + """ + { + "requestType":"%s", + "originEntity":{"entityID":"%s","ID":%d}, + %s + "originField":[%d,%d], + "targetField":[%d,%d]%s + %s%s + %s + } + """, + request.type, + request.originEntity.type, + request.originEntity.id, + jsonRepresentingTargetEntity, + request.originField.getX(), + request.originField.getY(), + request.targetField.getX(), + request.targetField.getY(), + jsonRepresentingStoneType.length() != 0 ? "," : "", + jsonRepresentingStoneType, + jsonRepresentingValue.length() != 0 ? "," : "", + jsonRepresentingValue + ).replace("\n", ""); + + assertThat(mapper.writeValueAsString(request)) + .isEqualTo(jsonRepresentingRequest); + } + + // Note that everything that follows could be extracted into another class, + // but that's complicated, so I won't do it + + static Set characterRequestTypes; + static Set gameRequestTypes; + + static { + characterRequestTypes = new HashSet<>(); + characterRequestTypes.add(RequestType.MeleeAttackRequest); + characterRequestTypes.add(RequestType.RangedAttackRequest); + characterRequestTypes.add(RequestType.MoveRequest); + characterRequestTypes.add(RequestType.ExchangeInfinityStoneRequest); + characterRequestTypes.add(RequestType.UseInfinityStoneRequest); + + gameRequestTypes = new HashSet<>(); + gameRequestTypes.add(RequestType.PauseStopRequest); + gameRequestTypes.add(RequestType.PauseStartRequest); + gameRequestTypes.add(RequestType.EndRoundRequest); + gameRequestTypes.add(RequestType.DisconnectRequest); + gameRequestTypes.add(RequestType.Req); + } + + @Provide("gameRequests") + public static Arbitrary gameRequests() { + return requestBuilders() + .map(RequestBuilder::buildGameRequest) + .filter(request -> gameRequestTypes.contains(request.type)); + } + + @Provide("characterRequests") + public static Arbitrary characterRequests() { + return requestBuilders() + .map(RequestBuilder::buildCharacterRequest) + .filter(request -> characterRequestTypes.contains(request.type)); + } + + + @Provide("filledRequestBuilders") + public static Arbitrary requestBuilders() { + var emptyBuilders = Arbitraries.of(RequestType.class) + .map(RequestBuilder::new); + + var buildersWithEntitiesAndValue = Combinators.combine( + emptyBuilders, + entityIDs().filter(x -> x.type == EntityType.P1 || x.type == EntityType.P2).tuple2(), + entityIDs().filter(x -> x.type == EntityType.Rocks), + Arbitraries.integers()) + .as((builder, characterIDs, rockID, value) -> builder + .withOriginEntity(characterIDs.get1()) + .withTargetEntity(value % 7 == 0 ? characterIDs.get2() : rockID) + .withValue(value) + ); + + var buildersWithStoneTypes = Combinators.combine( + buildersWithEntitiesAndValue, + Arbitraries.of(StoneType.class)) + .as(RequestBuilder::withStoneType); + + return Combinators.combine( + buildersWithStoneTypes, + randomPositions(), + directions(), + attackPositions()) + .as((builder, position, direction, attackPos) -> { + var type = builder.buildGameRequest().type; // hacky but whatever + Tuple.Tuple2 originAndTargetPosition; + if (type == RequestType.RangedAttackRequest) { + originAndTargetPosition = attackPos; + } else { + originAndTargetPosition = Tuple.of(position, position.copy().add(direction)); + } + return builder.withOriginField(originAndTargetPosition.get1()) + .withTargetField(originAndTargetPosition.get2()); + }); + } + + @Provide("entityIDs") + public static Arbitrary entityIDs() { + var entityTypes = Arbitraries.of(EntityType.class); + return Combinators.combine(entityTypes, Arbitraries.integers().greaterOrEqual(0)) + .as((type, id) -> { + int actualID; + if (type == EntityType.P1 || type == EntityType.P2 || type == EntityType.InfinityStones) { + actualID = id % 6; + } else if (type == EntityType.NPC) { + actualID = id % 3; + } else { + actualID = id; + } + return new EntityID(type, id); + }); + } + + @Provide("randomPositions") + public static Arbitrary randomPositions() { + return Arbitraries.integers() + .greaterOrEqual(0) + .tuple2() + .map((pos) -> new IntVector2(pos.get1(), pos.get2())); + } + + @Provide("directions") + public static Arbitrary directions() { + return Arbitraries.integers() + .between(-1, 1) + .tuple2() + .filter(pos -> pos.get1() != 0 && pos.get2() != 0) // eliminate zero vectors + .map(pos -> new IntVector2(pos.get1(), pos.get2())); + } + + @Provide("attackPositions") + /** Returns tuples of origin vectors (of an attack), and the vector pointing to the attack dir */ + public static Arbitrary> attackPositions() { + return Combinators.combine(randomPositions(), directions().tuple5()) + .as((origin, dir) -> { + var attackField = origin.copy() + .add(dir.get1().scale(3)) + .add(dir.get2().scale(3)) + .add(dir.get3().scale(3)) + .add(dir.get4().scale(3)); + if (attackField.length() == 0) { + attackField.add(dir.get5().scale(3)); + } + return Tuple.of(origin, attackField); + }); + } +}