This commit is contained in:
C H 2021-05-30 18:50:33 +02:00
commit 1e47a63cb7
15 changed files with 476 additions and 94 deletions

View File

@ -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;

View File

@ -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 */

View File

@ -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;
}
}

View File

@ -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> T unwrap (JsonNode rootNode, String key, Class<T> type) throws JsonProcessingException {
JsonNode subNode;
if ((subNode = rootNode.get(key)) != null) {
return mapper.readValue(subNode.toString(), type);
public static <T> T unwrap(JsonNode subNode, Class<T> type, ObjectCodec codec)
throws JsonProcessingException {
if (subNode != null) {
return codec.treeToValue(subNode, type);
}
return null;
}

View File

@ -21,9 +21,6 @@ public class EntityDeserializer extends JsonDeserializer<Entity> {
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<Entity> {
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<Entity> {
-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()
);
}

View File

@ -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<Event> {
// 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<Event> {
// }
// 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(); }

View File

@ -12,13 +12,9 @@ import uulm.teamname.marvelous.gamelibrary.IntVector2;
import java.io.IOException;
public class IntVector2Deserializer extends JsonDeserializer<IntVector2> {
// 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;
}

View File

@ -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<IntVector2> {
public IntVector2Serializer() {
this(null);
}
protected IntVector2Serializer(Class<IntVector2> 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();
}
}

View File

@ -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<Request> {
// 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<Request> {
}
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));

View File

@ -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<Request> {
public RequestSerializer() {
this(null);
}
protected RequestSerializer(Class<Request> 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);
}
}
}

View File

@ -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;

View File

@ -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

View File

@ -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));
}

View File

@ -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<IntVector2> positions() {
return Arbitraries.integers()
.tuple2()
.map(pos -> new IntVector2(pos.get1(), pos.get2()));
}
}

View File

@ -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<RequestType> characterRequestTypes;
static Set<RequestType> 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<GameRequest> gameRequests() {
return requestBuilders()
.map(RequestBuilder::buildGameRequest)
.filter(request -> gameRequestTypes.contains(request.type));
}
@Provide("characterRequests")
public static Arbitrary<CharacterRequest> characterRequests() {
return requestBuilders()
.map(RequestBuilder::buildCharacterRequest)
.filter(request -> characterRequestTypes.contains(request.type));
}
@Provide("filledRequestBuilders")
public static Arbitrary<RequestBuilder> 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<IntVector2, IntVector2> 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<EntityID> 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<IntVector2> randomPositions() {
return Arbitraries.integers()
.greaterOrEqual(0)
.tuple2()
.map((pos) -> new IntVector2(pos.get1(), pos.get2()));
}
@Provide("directions")
public static Arbitrary<IntVector2> 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<Tuple.Tuple2<IntVector2, IntVector2>> 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.<IntVector2, IntVector2>of(origin, attackField);
});
}
}