From 8da8e973ce11c0624c10721dba46266d348edbc7 Mon Sep 17 00:00:00 2001 From: Konrad Jamrozik Date: Fri, 5 Jul 2024 18:06:49 -0700 Subject: [PATCH] introduce PlayerActionName --- src/api/ApplyPlayerActionRoute.cs | 3 +- src/api/PlayerActionPayload.cs | 19 +++++--- src/game-lib/Controller/PlayerAction.cs | 8 +--- src/game-lib/Controller/PlayerActionName.cs | 44 +++++++++++++++++++ src/game-lib/Events/GameEventType.cs | 3 +- src/game-lib/Events/GameEventTypeConverter.cs | 5 --- .../lib/api/playerActionsPayloadsProviders.ts | 19 ++++---- web/src/lib/codesync/PlayerActionPayload.ts | 2 +- 8 files changed, 70 insertions(+), 33 deletions(-) create mode 100644 src/game-lib/Controller/PlayerActionName.cs delete mode 100644 src/game-lib/Events/GameEventTypeConverter.cs diff --git a/src/api/ApplyPlayerActionRoute.cs b/src/api/ApplyPlayerActionRoute.cs index 38b9c0d4..ddc15d69 100644 --- a/src/api/ApplyPlayerActionRoute.cs +++ b/src/api/ApplyPlayerActionRoute.cs @@ -46,7 +46,7 @@ private static JsonHttpResult ApplyPlayerActionInternal( GameSession gameSession = ApiUtils.NewGameSessionFromTurn(gameSessionTurn); var controller = new GameSessionController(config, log, gameSession); - Contract.Assert(playerActionPayload.ActionName != GameEventType.AdvanceTimePlayerAction); + Contract.Assert(playerActionPayload.Name.ToString() != GameEventType.AdvanceTimePlayerAction); gameSession.CurrentPlayerActionEvents.Add(playerActionPayload.Apply(controller)); @@ -65,7 +65,6 @@ private static JsonHttpResult ApplyPlayerActionInternal( // https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/parameter-binding?view=aspnetcore-8.0#configure-json-deserialization-options-for-an-endpoint parsedBody = (await req.ReadFromJsonAsync(GameSessionTurn.JsonSerializerOptions))!; - PlayerAction.ValidateName(parsedBody.PlayerActionPayload.ActionName); error = null; } else diff --git a/src/api/PlayerActionPayload.cs b/src/api/PlayerActionPayload.cs index 57799ebd..6eba340a 100644 --- a/src/api/PlayerActionPayload.cs +++ b/src/api/PlayerActionPayload.cs @@ -1,4 +1,6 @@ // codesync: UfoGameLib.Api.PlayerActionPayload + +using System.Text.Json.Serialization; using UfoGameLib.Controller; using UfoGameLib.Events; @@ -15,23 +17,26 @@ namespace UfoGameLib.Api; // Reason: used by JSON deserializer. public class PlayerActionPayload { - // kja introduce type: PlayerActionName + public readonly PlayerActionName Name; // ReSharper disable MemberCanBePrivate.Global // Reason: used by JSON deserializer. - public readonly string ActionName; public readonly int[]? Ids; public readonly int? TargetId; + public PlayerActionPayload(string name, int[]? ids, int? targetId) : this(new PlayerActionName(name), ids, targetId) + { + } + /// /// Represents a player action payload. The payload is expected to be deserialized /// from a system boundary, e.g. from a JSON string received from a POST HTTP request. /// /// The payload can be applied to a GameSessionController. See the Apply() method. /// - public PlayerActionPayload(string actionName, int[]? ids, int? targetId) + [JsonConstructor] + public PlayerActionPayload(PlayerActionName name, int[]? ids, int? targetId) { - PlayerAction.ValidateName(actionName); - ActionName = actionName; + Name = name; Ids = ids; TargetId = targetId; } @@ -53,7 +58,7 @@ public PlayerActionEvent Apply(GameSessionController controller) private Func TranslatePlayerActionToControllerAction(GameSessionController controller) // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#property-pattern // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns#positional-pattern - => ActionName switch + => Name.ToString() switch { GameEventType.AdvanceTimePlayerAction => () => controller.AdvanceTime().advaceTimeEvent, nameof(BuyTransportCapacityPlayerAction) => () => controller.CurrentTurnController.BuyTransportCapacity(TargetId!.Value), @@ -65,6 +70,6 @@ private Func TranslatePlayerActionToControllerAction(GameSess nameof(SendAgentsToTrainingPlayerAction) => () => controller.CurrentTurnController.SendAgentsToTraining(Ids!), nameof(RecallAgentsPlayerAction) => () => controller.CurrentTurnController.RecallAgents(Ids!), nameof(LaunchMissionPlayerAction) => () => controller.CurrentTurnController.LaunchMission(TargetId!.Value, Ids!), - _ => () => throw new ArgumentException($"Unsupported player action of '{ActionName}'") + _ => () => throw new ArgumentException($"Unsupported player action of '{Name}'") }; } diff --git a/src/game-lib/Controller/PlayerAction.cs b/src/game-lib/Controller/PlayerAction.cs index 84722fe9..9432a6c3 100644 --- a/src/game-lib/Controller/PlayerAction.cs +++ b/src/game-lib/Controller/PlayerAction.cs @@ -32,12 +32,6 @@ public PlayerActionEvent Apply(GameState state, int nextEventId) } protected abstract (List? ids, int? targetId) ApplyImpl(GameState state); - // kja get rid of ValidattName and IsValidName - public static void ValidateName(string name) - { - bool isValid = IsValidName(name); - Contract.Assert(isValid, $"The type name '{name}' is not a valid name of PlayerAction-derived class."); - } public static bool IsValidName(string name) { @@ -51,4 +45,4 @@ public static bool IsValidName(string name) bool isValid = derivedTypes.Any(t => t.Name == name); return isValid; } -} +} \ No newline at end of file diff --git a/src/game-lib/Controller/PlayerActionName.cs b/src/game-lib/Controller/PlayerActionName.cs new file mode 100644 index 00000000..4870c372 --- /dev/null +++ b/src/game-lib/Controller/PlayerActionName.cs @@ -0,0 +1,44 @@ +using System.Collections.Immutable; +using System.Reflection; +using System.Text.Json.Serialization; +using Lib.Contracts; +using Lib.Json; + +namespace UfoGameLib.Controller; + +[JsonConverter(typeof(StringJsonConverter))] +public class PlayerActionName +{ + private readonly string _name; + + private static readonly ImmutableList ValidNames = GetValidNames(); + + private static ImmutableList GetValidNames() + { + // Get the assembly that contains the PlayerAction class + Assembly assembly = Assembly.GetAssembly(typeof(PlayerAction))!; + + // Get all types in the assembly that inherit from PlayerAction + var derivedTypes = assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(PlayerAction))); + + return derivedTypes.Select(t => t.Name).ToImmutableList(); + } + + public PlayerActionName(string name) + { + Contract.Assert( + ValidNames.Contains(name), + $"The type name '{name}' is not a valid name of PlayerAction-derived class."); + _name = name; + } + + public override string ToString() + { + return $"{_name}"; + } + + // public static implicit operator string(PlayerActionName playerActionName) + // { + // return playerActionName._name; + // } +} \ No newline at end of file diff --git a/src/game-lib/Events/GameEventType.cs b/src/game-lib/Events/GameEventType.cs index 0ffc2c24..8c81a0e7 100644 --- a/src/game-lib/Events/GameEventType.cs +++ b/src/game-lib/Events/GameEventType.cs @@ -1,12 +1,13 @@ using System.Collections.Immutable; using System.Text.Json.Serialization; using Lib.Contracts; +using Lib.Json; using UfoGameLib.Controller; namespace UfoGameLib.Events; -[JsonConverter(typeof(GameEventTypeConverter))] +[JsonConverter(typeof(StringJsonConverter))] public class GameEventType { private readonly string _type; diff --git a/src/game-lib/Events/GameEventTypeConverter.cs b/src/game-lib/Events/GameEventTypeConverter.cs deleted file mode 100644 index 267972fc..00000000 --- a/src/game-lib/Events/GameEventTypeConverter.cs +++ /dev/null @@ -1,5 +0,0 @@ -using Lib.Json; - -namespace UfoGameLib.Events; - -public class GameEventTypeConverter : StringJsonConverter; \ No newline at end of file diff --git a/web/src/lib/api/playerActionsPayloadsProviders.ts b/web/src/lib/api/playerActionsPayloadsProviders.ts index 58378494..fb827fc4 100644 --- a/web/src/lib/api/playerActionsPayloadsProviders.ts +++ b/web/src/lib/api/playerActionsPayloadsProviders.ts @@ -9,42 +9,41 @@ export const playerActionsPayloadsProviders: { } = { // Note: currently Cap always buys 1 capacity. See PlayerActionPayload.cs in backend. BuyTransportCapacityPlayerAction: (TargetId: number) => ({ - ActionName: 'BuyTransportCapacityPlayerAction' as PlayerActionNameInTurn, + Name: 'BuyTransportCapacityPlayerAction' as PlayerActionNameInTurn, TargetId, }), // Note: currently HireAgents always hires 1 agent. See PlayerActionPayload.cs in backend. HireAgentsPlayerAction: (TargetId: number) => ({ - ActionName: 'HireAgentsPlayerAction' as PlayerActionNameInTurn, + Name: 'HireAgentsPlayerAction' as PlayerActionNameInTurn, TargetId, }), SackAgentsPlayerAction: (Ids: number[]) => ({ - ActionName: 'SackAgentsPlayerAction' as PlayerActionNameInTurn, + Name: 'SackAgentsPlayerAction' as PlayerActionNameInTurn, Ids, }), SendAgentsToGenerateIncomePlayerAction: (Ids: number[]) => ({ - ActionName: - 'SendAgentsToGenerateIncomePlayerAction' as PlayerActionNameInTurn, + Name: 'SendAgentsToGenerateIncomePlayerAction' as PlayerActionNameInTurn, Ids, }), SendAgentsToGatherIntelPlayerAction: (Ids: number[]) => ({ - ActionName: 'SendAgentsToGatherIntelPlayerAction' as PlayerActionNameInTurn, + Name: 'SendAgentsToGatherIntelPlayerAction' as PlayerActionNameInTurn, Ids, }), SendAgentsToTrainingPlayerAction: (Ids: number[]) => ({ - ActionName: 'SendAgentsToTrainingPlayerAction' as PlayerActionNameInTurn, + Name: 'SendAgentsToTrainingPlayerAction' as PlayerActionNameInTurn, Ids, }), RecallAgentsPlayerAction: (Ids: number[]) => ({ - ActionName: 'RecallAgentsPlayerAction' as PlayerActionNameInTurn, + Name: 'RecallAgentsPlayerAction' as PlayerActionNameInTurn, Ids, }), LaunchMissionPlayerAction: (Ids: number[], TargetId: number) => ({ - ActionName: 'LaunchMissionPlayerAction' as PlayerActionNameInTurn, + Name: 'LaunchMissionPlayerAction' as PlayerActionNameInTurn, Ids, TargetId, }), InvestIntelPlayerAction: (Ids: number[], TargetId: number) => ({ - ActionName: 'InvestIntelPlayerAction' as PlayerActionNameInTurn, + Name: 'InvestIntelPlayerAction' as PlayerActionNameInTurn, Ids, TargetId, }), diff --git a/web/src/lib/codesync/PlayerActionPayload.ts b/web/src/lib/codesync/PlayerActionPayload.ts index c11e16fb..51a6a83a 100644 --- a/web/src/lib/codesync/PlayerActionPayload.ts +++ b/web/src/lib/codesync/PlayerActionPayload.ts @@ -2,7 +2,7 @@ import type { PlayerActionNameInTurn } from './PlayerActionEvent' export type PlayerActionPayload = { - readonly ActionName: PlayerActionNameInTurn + readonly Name: PlayerActionNameInTurn readonly Ids?: number[] readonly TargetId?: number }