diff --git a/Project.App/Project.Api/DTOs/BlackjackActionDTO.cs b/Project.App/Project.Api/DTOs/BlackjackActionDTO.cs new file mode 100644 index 0000000..2e29b8d --- /dev/null +++ b/Project.App/Project.Api/DTOs/BlackjackActionDTO.cs @@ -0,0 +1,45 @@ +using System.Text.Json; + +namespace Project.Api.DTOs; + +/// +/// The base class for all blackjack-specific action DTOs. +/// +public abstract record BlackjackActionDTO : GameActionDTO; + +public record BetAction(long Amount) : BlackjackActionDTO; + +public record HitAction : BlackjackActionDTO; + +public record StandAction : BlackjackActionDTO; + +public record DoubleAction : BlackjackActionDTO; + +public record SplitAction : BlackjackActionDTO; + +public record SurrenderAction : BlackjackActionDTO; + +public static class JsonElementExtensions +{ + /// + /// Extension method to deserialize a from a . + /// Any errors thrown during deserialization will be passed to the caller. + /// + public static BlackjackActionDTO ToBlackjackAction(this JsonElement element, string action) + { + return ( + action switch + { + "bet" => element.Deserialize(), + "hit" => element.Deserialize(), + "stand" => element.Deserialize(), + "double" => element.Deserialize(), + "split" => element.Deserialize(), + "surrender" => (BlackjackActionDTO?)element.Deserialize(), + _ => throw new NotSupportedException( + $"Action '{action}' is not a valid action for Blackjack." + ), + } + ) ?? throw new InvalidOperationException($"Could not deserialize action {action}."); + } +} diff --git a/Project.App/Project.Api/DTOs/GameActionDTO.cs b/Project.App/Project.Api/DTOs/GameActionDTO.cs new file mode 100644 index 0000000..0f1f13f --- /dev/null +++ b/Project.App/Project.Api/DTOs/GameActionDTO.cs @@ -0,0 +1,6 @@ +namespace Project.Api.DTOs; + +/// +/// The base class for all game action DTOs. Does nothing. +/// +public abstract record GameActionDTO; diff --git a/Project.App/Project.Api/Services/BlackjackService.cs b/Project.App/Project.Api/Services/BlackjackService.cs new file mode 100644 index 0000000..1c1861e --- /dev/null +++ b/Project.App/Project.Api/Services/BlackjackService.cs @@ -0,0 +1,110 @@ +using System.Text.Json; +using Project.Api.DTOs; +using Project.Api.Services.Interface; + +namespace Project.Api.Services; + +/* + +set up deck API connection +set up game configs + +loop + shuffle deck(?) + + loop + if everyone has bet/left or time is up + break + end if + wait + end loop + deduct bets + + deal 2 cards to each player + deal 2 cards to dealer (one hidden) + + loop (foreach player) + loop + if hit + deal card + else if stand + break + end if + end loop + end loop + + deal to dealer (hit until 17) + calculate scores + determine outcomes + distribute winnings +end loop + +teardown +close room + +*/ + +#region Blackjack Game Stages + +// initial setup +// initialize deck, set game configs +public record BlackjackInitStage : BlackjackStage; + +// doing pre-round setup +public record BlackjackSetupStage : BlackjackStage; + +// waiting for players to bet +public record BlackjackBettingStage : BlackjackStage; + +// dealing +public record BlackjackDealingStage : BlackjackStage; + +// player turn +// TODO: figure out how turn order will work +public record BlackjackPlayerActionStage(int Index) : BlackjackStage; + +// dealer turn and distribute winnings +public record BlackjackFinishRoundStage : BlackjackStage; + +// teardown, close room +public record BlackjackTeardownStage : BlackjackStage; + +#endregion + +public class BlackjackService : IBlackjackService +{ + public Task GetGamestateAsync(string gameId) + { + throw new NotImplementedException(); + } + + public Task PerformActionAsync( + string gameId, + string playerId, + string action, + JsonElement data + ) + { + BlackjackActionDTO actionDTO = data.ToBlackjackAction(action); + + switch (actionDTO) + { + case BetAction betAction: + throw new NotImplementedException(); + case HitAction hitAction: + throw new NotImplementedException(); + case StandAction standAction: + throw new NotImplementedException(); + case DoubleAction doubleAction: + throw new NotImplementedException(); + case SplitAction splitAction: + throw new NotImplementedException(); + case SurrenderAction surrenderAction: + throw new NotImplementedException(); + default: + throw new NotImplementedException(); + } + + throw new NotImplementedException(); + } +} diff --git a/Project.App/Project.Api/Services/Interface/IBlackjackService.cs b/Project.App/Project.Api/Services/Interface/IBlackjackService.cs index 86e9e6c..c4b4211 100644 --- a/Project.App/Project.Api/Services/Interface/IBlackjackService.cs +++ b/Project.App/Project.Api/Services/Interface/IBlackjackService.cs @@ -1,3 +1,13 @@ namespace Project.Api.Services.Interface; -public interface IBlackjackService : IGameService { } +public abstract record BlackjackStage : GameStage; + +public record BlackjackState : GameState +{ + public required List DealerHand { get; set; } = []; +} + +/// +/// Represents a service for handling blackjack logic. +/// +public interface IBlackjackService : IGameService { } diff --git a/Project.App/Project.Api/Services/Interface/IGameService.cs b/Project.App/Project.Api/Services/Interface/IGameService.cs index 1d47c95..1eeafac 100644 --- a/Project.App/Project.Api/Services/Interface/IGameService.cs +++ b/Project.App/Project.Api/Services/Interface/IGameService.cs @@ -1,72 +1,38 @@ -namespace Project.Api.Services.Interface; - -// -// start -> pre-game -> ||: pre-round -> | player turn (foreach player) | -> post-round :|| -> post-game -// +using System.Text.Json; -public enum GameStage -{ - PreGame, - PreRound, - PlayerTurn, - PostRound, - PostGame, -} +namespace Project.Api.Services.Interface; -public record GameState +/// +/// Used to represent the current state of a game. +/// Each game should have their own "parent" abstract class that extends this class to ensure type safety. +/// +public abstract record GameStage; + +/// +/// Represents the current state of a game, including all relevant information. +/// Can be extended to include more information for a specific game type. +/// +public record GameState + where TStage : GameStage { - public GameStage Stage { get; set; } + public required TStage CurrentStage { get; set; } } -public interface IGameService +/// +/// A service for handling game logic. +/// Actions are async and stateless, so for each request, the service needs to get the current game state and +/// act accordingly, either by updating the game state or returning an error. +/// +public interface IGameService { - Task DoPreGameAsync(); - Task DoPreRoundAsync(); - Task DoPlayerTurnAsync(); - Task DoPostRoundAsync(); - Task DoPostGameAsync(); - - Task GetGamestateAsync(string gameId); - - // async Task GetGamestateAsync(string gameId) - // { - // return _gameRepository.GetGamestateAsync(gameId); - // } - - async Task DoGameActionAsync(string gameId, string playerId, string action) - { - GameState gamestate = await GetGamestateAsync(gameId); - return gamestate.Stage switch - { - GameStage.PreGame => await DoPreGameAsync(), - GameStage.PreRound => await DoPreRoundAsync(), - GameStage.PlayerTurn => await DoPlayerTurnAsync(), - GameStage.PostRound => await DoPostRoundAsync(), - GameStage.PostGame => await DoPostGameAsync(), - _ => throw new Exception("Unknown game stage"), - }; - } - - // if this was not distributed, this is how it would work - // BUT gamestate is tracked in the database, so it needs to be able to load the gamestate at any point - // async Task MainLoopAsync() - // { - // await DoPreGameAsync(); - - // // main loop - // bool isRunning = true; - // while (isRunning) - // { - // await DoPreRoundAsync(); - - // // TODO: foreach player - // // { - // // await DoPlayerTurnAsync(); - // // } - - // await DoPostRoundAsync(); - // } - - // await DoPostGameAsync(); - // } + Task GetGamestateAsync(string gameId); + + /// + /// Performs a user action on the game, if valid, then updates the game state. + /// Each game implementation provides their own main loop logic implementation, instead of being restricted to a + /// specific structure. + /// + /// The action to perform + /// A JSON object containing the action data + Task PerformActionAsync(string gameId, string playerId, string action, JsonElement data); }