diff --git a/src/api/ApiUtils.cs b/src/api/ApiUtils.cs index 51702c71..578b8ba7 100644 --- a/src/api/ApiUtils.cs +++ b/src/api/ApiUtils.cs @@ -91,6 +91,7 @@ public static (int turnLimitVal, string? error) ParseTurnLimit(int? turnLimit, G { // Deserialization method invocation and configuration as explained by: // https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/parameter-binding?view=aspnetcore-8.0#configure-json-deserialization-options-for-an-endpoint + // kja this should use GameSessionTurn.SerializerOptions not GameState parsedTurn = (requestBody.FromJsonTo(GameState.StateJsonSerializerOptions)); // If the input game session turn would have advance time event, besides conceptually not making sense, // it would throw off the NextEventId calculation, as game session could start appending player action events to current turn diff --git a/src/game-lib/Events/GameEvent.cs b/src/game-lib/Events/GameEvent.cs index d9c0953f..f727ebcf 100644 --- a/src/game-lib/Events/GameEvent.cs +++ b/src/game-lib/Events/GameEvent.cs @@ -5,13 +5,18 @@ namespace UfoGameLib.Events; public abstract class GameEvent : IIdentifiable { public int Id { get; } - public readonly string Type; + public readonly GameEventType Type; - public GameEvent(int id, string type) + public GameEvent(int id, string type) : this(id, new GameEventType(type)) + { + } + + public GameEvent(int id, GameEventType type) { Id = id; Type = type; } + public abstract GameEvent Clone(); -} +} \ No newline at end of file diff --git a/src/game-lib/Events/GameEventType.cs b/src/game-lib/Events/GameEventType.cs index 930da9a7..3c06398d 100644 --- a/src/game-lib/Events/GameEventType.cs +++ b/src/game-lib/Events/GameEventType.cs @@ -27,4 +27,9 @@ public GameEventType(string type) Contract.Assert(GameEventTypes.Contains(type) || PlayerAction.IsValidName(type)); _type = type; } -} + + public override string ToString() + { + return $"{_type}"; + } +} \ No newline at end of file diff --git a/src/game-lib/Events/GameEventTypeConverter.cs b/src/game-lib/Events/GameEventTypeConverter.cs new file mode 100644 index 00000000..5cf70990 --- /dev/null +++ b/src/game-lib/Events/GameEventTypeConverter.cs @@ -0,0 +1,19 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace UfoGameLib.Events; + +public class GameEventTypeConverter : JsonConverter +{ + public override GameEventType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + Console.WriteLine("KJA GameEventTypeConverter.Read"); + return new GameEventType(reader.GetString()!); + } + + public override void Write(Utf8JsonWriter writer, GameEventType value, JsonSerializerOptions options) + { + Console.WriteLine("KJA GameEventTypeConverter.Write"); + writer.WriteStringValue(value.ToString()); + } +} \ No newline at end of file diff --git a/src/game-lib/Events/PlayerActionEvent.cs b/src/game-lib/Events/PlayerActionEvent.cs index 6fa403bb..71ad2409 100644 --- a/src/game-lib/Events/PlayerActionEvent.cs +++ b/src/game-lib/Events/PlayerActionEvent.cs @@ -1,7 +1,7 @@ // ReSharper disable MemberCanBePrivate.Global // Reason: public fields are being serialized to JSON. -using UfoGameLib.Controller; +using System.Text.Json.Serialization; namespace UfoGameLib.Events; @@ -10,11 +10,19 @@ public class PlayerActionEvent : GameEvent public readonly List? Ids; public readonly int? TargetId; - public PlayerActionEvent(int id, string type, List? ids = null, int? targetId = null) : base(id, type) + [JsonConstructor] + public PlayerActionEvent(int id, string type, List? ids = null, int? targetId = null) : this( + id, + new GameEventType(type), + ids, + targetId) + { + } + + public PlayerActionEvent(int id, GameEventType type, List? ids = null, int? targetId = null) : base(id, type) { Ids = ids; TargetId = targetId; - // kja PlayerAction.ValidateName(type); } public override PlayerActionEvent Clone() diff --git a/src/game-lib/Events/WorldEvent.cs b/src/game-lib/Events/WorldEvent.cs index 4b010b9b..bd973a1d 100644 --- a/src/game-lib/Events/WorldEvent.cs +++ b/src/game-lib/Events/WorldEvent.cs @@ -1,5 +1,8 @@ // ReSharper disable MemberCanBePrivate.Global // Reason: public fields are being serialized to JSON. + +using System.Text.Json.Serialization; + namespace UfoGameLib.Events; public class WorldEvent : GameEvent @@ -7,11 +10,19 @@ public class WorldEvent : GameEvent public readonly List? Ids; public readonly int? TargetId; - public WorldEvent(int id, string type, List? ids = null, int? targetId = null) : base(id, type) + [JsonConstructor] + public WorldEvent(int id, string type, List? ids = null, int? targetId = null) : this( + id, + new GameEventType(type), + ids, + targetId) + { + } + + public WorldEvent(int id, GameEventType type, List? ids = null, int? targetId = null) : base(id, type) { Ids = ids; - TargetId = targetId; - // kja2-assert: that 'type' is one of the valid WorldEvents + TargetId = targetId; } public override WorldEvent Clone() diff --git a/src/game-lib/State/GameSessionTurn.cs b/src/game-lib/State/GameSessionTurn.cs index c8813951..e875a20f 100644 --- a/src/game-lib/State/GameSessionTurn.cs +++ b/src/game-lib/State/GameSessionTurn.cs @@ -50,7 +50,7 @@ public void AssertInvariants() EventsInTurn.Count == EndState.UpdateCount - StartState.UpdateCount, "Number of events in turn must match the number of updates between the game states."); - Contract.Assert(EventsUntilStartState.Last().Type == GameEventType.ReportEvent); + Contract.Assert(EventsUntilStartState.Last().Type.ToString() == GameEventType.ReportEvent); IdGen.AssertConsecutiveIds(GameEvents.ToList()); // kja all events in turn except the last one must be not AdvanceTime player action // Contract.Assert(EventsInTurn.SkipLast(1).All(@event => @event.Type == "a")) diff --git a/src/game-lib/State/GameState.cs b/src/game-lib/State/GameState.cs index 93acea3b..0e5965fb 100644 --- a/src/game-lib/State/GameState.cs +++ b/src/game-lib/State/GameState.cs @@ -2,6 +2,7 @@ using System.Text.Json.Serialization; using Lib.Contracts; using Lib.Json; +using UfoGameLib.Events; using UfoGameLib.Lib; using UfoGameLib.Model; using File = Lib.OS.File; @@ -136,10 +137,10 @@ private static JsonSerializerOptions GetJsonSerializerOptions() { // We return 'options' that: // 1. use the 'converterOptions' as base options - // 2. have GameStateJsonConverter as a converter - // 3. the converter also uses 'converterOptions' as base options + // 2. have GameStateJsonConverter as a 'converter' + // 3. the 'converter' also uses 'converterOptions' as base options // - // In other words, both 'options' and its converter use the same base options: 'converterOptions'. + // In other words, both 'options' and its 'converter' use the same base options: 'converterOptions'. // // Note: we couldn't collapse 'options' and 'converterOptions' into one instance, because it would // result in an infinite loop: 'options' would use GameStateJsonConverter, which would use 'options', which @@ -157,8 +158,11 @@ private static JsonSerializerOptions GetJsonSerializerOptions() // Define the "top-level" options to be returned. They use "converterOptions". var options = new JsonSerializerOptions(converterOptions); + options.Converters.Add(new GameEventTypeConverter()); + // Attach GameStateJsonConverter to 'options'. Now both 'options' and its converter use 'converterOptions'. options.Converters.Add(new GameStateJsonConverter()); + return options; }