diff --git a/CLAUDE.md b/CLAUDE.md index f4507e8..6519910 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,6 +17,8 @@ The name for the App is KOOGLE. Koogle is an app for club management. - Automatic or rule-based cost allocation - Evaluation per person/day/game - User-related actions (CurrentUserService) +- cash book +- plugin system for different game logics ### Technical - ASP.NET Core backend @@ -29,7 +31,7 @@ The name for the App is KOOGLE. Koogle is an app for club management. - maybe in future also for darts, soccer betting pools, Leisure groups, Sport clubs, etc. **Phase 1 MVP:** See [docs/IMPLEMENTATION_PLAN.md](./docs/IMPLEMENTATION_PLAN.md) -- 23 granular phases (A1-F3) +- granular phases (A1-K23) - Foundation → Clubs → Users → Persons → Days → Dashboard - Track progress via checkboxes in plan - ~75 files total diff --git a/src/Koogle.Application/Games/FoxHunt/FoxHuntGameLogicService.cs b/src/Koogle.Application/Games/FoxHunt/FoxHuntGameLogicService.cs index 8c47c5d..3da9532 100644 --- a/src/Koogle.Application/Games/FoxHunt/FoxHuntGameLogicService.cs +++ b/src/Koogle.Application/Games/FoxHunt/FoxHuntGameLogicService.cs @@ -14,6 +14,7 @@ namespace Koogle.Application.Games.FoxHunt /// public class FoxHuntGameLogicService : IGameLogicService { + /// public object CreateInitialModel(Guid[] playerIds, object? setupOptions) { var options = ParseSetupOptions(setupOptions); @@ -30,13 +31,9 @@ namespace Koogle.Application.Games.FoxHunt NonFoxIndex = 0, // Index für Nicht-Fuchs-Spieler FoxTurnsRemaining = options.LeadingThrows - 1, // -1 weil erster Wurf bereits aus PlayerOrder[FoxIndex] FoxTurn = true, // ein fuchs fängt an - LeadingThrows = options.LeadingThrows, PlayerStates = playerStates, PlayerOrder = playerOrder, - //CurrentPlayerIndex = 0, - //FoxLeadingThrowCount = 0, - //CurrentFoxIndex = 0, WinnerId = null, IsGameOver = false, }; @@ -44,6 +41,7 @@ namespace Koogle.Application.Games.FoxHunt return model; } + /// public (object UpdatedModel, ThrowResult Result) ProcessThrow(object gameModel, AfterThrowState afterThrow) { var model = this.CastModel(gameModel); @@ -61,9 +59,7 @@ namespace Koogle.Application.Games.FoxHunt }); } - //var actualName = GetPlayerName(playerId).Result; var playerStates = new Dictionary(model.PlayerStates); - //var eliminatedPlayers = new List(model.EliminatedPlayers); var triggers = new List(); var foxId = model.PlayerOrder[model.FoxIndex]; @@ -73,19 +69,28 @@ namespace Koogle.Application.Games.FoxHunt if (model.FoxTurn) { playerStates[foxId].PinCountFox+= afterThrow.PinsKnocked; + if (afterThrow.IsCircle) + { + playerStates[foxId].PinCountFox += 2; + } } else { playerStates[foxId].PinCountHunters+= afterThrow.PinsKnocked; + if (afterThrow.IsCircle) + { + playerStates[foxId].PinCountHunters += 2; + } + if (playerStates[foxId].PinCountHunters >= playerStates[foxId].PinCountFox ) { // fox has been caught - isLastHunter = true; + isLastHunter = true; // will cause selection of the next fox playerStates[foxId].FoxCaught = true; // Calculate penalty (point difference the hunters achieved) - int pointDifference = playerStates[foxId].PinCountHunters - playerStates[foxId].PinCountFox; + var pointDifference = playerStates[foxId].PinCountHunters - playerStates[foxId].PinCountFox; if (pointDifference > 0) { triggers.Add(new TriggerEvent @@ -109,7 +114,7 @@ namespace Koogle.Application.Games.FoxHunt var nextPlayerId = GetNextId(model, isLastHunter); // Check GAME END - bool isGameOver = isLastHunter && model.FoxIndex == 0; + var isGameOver = isLastHunter && model.FoxIndex == 0; if (isGameOver) { @@ -178,7 +183,7 @@ namespace Koogle.Application.Games.FoxHunt return model.PlayerOrder[model.FoxIndex]; } - // 1️⃣ Fuchs ist 2x hintereinander dran + // Fuchs ist 2x hintereinander dran if (model.FoxTurnsRemaining > 0) { model.FoxTurnsRemaining--; @@ -188,7 +193,7 @@ namespace Koogle.Application.Games.FoxHunt model.FoxTurn = !model.FoxTurn; // danach abwechselnd - // 2️⃣ Abwechselnd Nicht-Fuchs → Fuchs + // Abwechselnd Nicht-Fuchs → Fuchs if (!model.FoxTurn) { var nextNonFoxIndex = model.GetNextIndex(model.PlayerOrder, model.NonFoxIndex); @@ -199,56 +204,26 @@ namespace Koogle.Application.Games.FoxHunt } model.NonFoxIndex = nextNonFoxIndex; return model.PlayerOrder[nextNonFoxIndex]; - - // nächsten Nicht-Fuchs suchen - //while (model.NonFoxIndex == model.FoxIndex) - // model.NonFoxIndex++; - - //if (model.NonFoxIndex < model.PlayerOrder.Length) - //{ - // Guid next = model.PlayerOrder[model.NonFoxIndex]; - // model.NonFoxIndex++; - // //model.FoxTurn = true; - // return next; - //} - //else - //{ - // // Alle Nicht-Füchse durch → neuer Fuchs - // model.FoxIndex++; - // model.NonFoxIndex = 0; - // model.FoxTurnsRemaining = model.LeadingThrows - 1; - // model.FoxTurn = true; - // return GetNextId(model); - //} } - - - - // 3️⃣ Fuchs-Zug im Wechsel - //model.FoxTurn = false; + // Fuchs-Zug im Wechsel return foxId; } - private static int GetNextActivePlayerIndex( - Guid[] playerOrder, - int currentIndex, - Dictionary playerStates) - { - return 0; - } - + /// public (bool IsGameOver, Guid? WinnerId) CheckGameEnd(object gameModel) { var model = this.CastModel(gameModel); return (model.IsGameOver, model.WinnerId); } + /// public IReadOnlyDictionary GetPlayerStats(object gameModel) { throw new NotImplementedException(); } + /// public GameSetupValidationResult ValidateSetup(object? setupOptions) { if (setupOptions is null) @@ -261,7 +236,7 @@ namespace Koogle.Application.Games.FoxHunt if (options.LeadingThrows < 2 || options.LeadingThrows > 4) { - errors.Add("Vorsprung muss zwischen 6 und 12 liegen."); + errors.Add("Vorsprung muss zwischen 2 und 4 liegen."); } //TODO: validate player-count, at lease 2 players needed @@ -271,12 +246,14 @@ namespace Koogle.Application.Games.FoxHunt : GameSetupValidationResult.Valid(); } + /// public IReadOnlyList GetAvailableActions(object gameModel, Guid currentPlayerId) { // FoxHunt has no custom actions return []; } + /// public GameActionResult ExecuteAction(object gameModel, string actionId, Guid currentPlayerId, IReadOnlyDictionary? parameters = null) { diff --git a/src/Koogle.Application/Games/FoxHunt/FoxHuntGameModel.cs b/src/Koogle.Application/Games/FoxHunt/FoxHuntGameModel.cs index ef6283a..d6b114d 100644 --- a/src/Koogle.Application/Games/FoxHunt/FoxHuntGameModel.cs +++ b/src/Koogle.Application/Games/FoxHunt/FoxHuntGameModel.cs @@ -1,34 +1,46 @@ namespace Koogle.Application.Games.FoxHunt { /// - /// Game model for Fuchsjagd-Spiel (Fox Hunt) game type. + /// Game model for Fox Hunt (Fuchsjagd-Spiel) game type. /// One Player starts as fox, and all others try to hunt him. /// public record FoxHuntGameModel : IGameModel { - + /// + /// State for each player (details if fox survived or not). + /// + public Dictionary PlayerStates { get; init; } = new(); - //public int LeadingThrows { get; set; } - //public int FoxLeadingThrowCount { get; set; } - public Dictionary PlayerStates { get; set; } - - public Guid[] PlayerOrder { get; set; } + /// + /// Fixed player order (default order at game start). + /// + public Guid[] PlayerOrder { get; set; } = []; /// /// count of throw the fox can start with /// copy from setup model /// - public int LeadingThrows { get; set; } + public int LeadingThrows { get; set; } - //public int CurrentPlayerIndex { get; set; } - //public int CurrentFoxIndex { get; set; } + /// + /// index pointing to the current fox (Index in PlayerOrder) + /// + public int FoxIndex { get; set; } + /// + /// index pointing to the current hunter (Index in PlayerOrder) + /// + public int NonFoxIndex { get; set; } - public int FoxIndex { get; set; } // aktueller Fuchs (Index in PlayerOrder) - public int NonFoxIndex { get; set; } // Index für Nicht-Fuchs-Spieler - public int FoxTurnsRemaining { get; set; } // verbleibende Fuchs-Züge - public bool FoxTurn { get; set; } // true = Fuchs dran im Wechsel + /// + /// remaining leading throws a fox has left + /// + public int FoxTurnsRemaining { get; set; } + /// + /// true = a throw by fox, false = a throw by a hunter + /// + public bool FoxTurn { get; set; } /// public Guid? WinnerId { get; set; } @@ -37,12 +49,34 @@ public bool IsGameOver { get; set; } } + /// + /// Info about the last throw for UI display. + /// public record FoxHuntPlayerState { + /// + /// count of pins the fox has knocked. + /// public int PinCountFox { get; set; } + + /// + /// count of pins the hunters have knocked. + /// public int PinCountHunters { get; set; } + + /// + /// Weather the hunters have caught the fox. + /// public bool FoxCaught { get; set; } + + /// + /// Weather the fox survived with most point difference. + /// public bool IsWinner { get; set; } + + /// + /// Weather the fox could escape the hunters. + /// public bool FoxEscaped { get; set; } } } diff --git a/src/Koogle.Application/Games/GameExtensions.cs b/src/Koogle.Application/Games/GameExtensions.cs index 0c8ad11..9106ad2 100644 --- a/src/Koogle.Application/Games/GameExtensions.cs +++ b/src/Koogle.Application/Games/GameExtensions.cs @@ -9,8 +9,19 @@ using System.Threading.Tasks; namespace Koogle.Application.Games { + /// + /// Helper class for GameLogicServices + /// public static class GameLogicServiceExtension { + /// + /// casts an object to game model type + /// + /// Type of the game model + /// + /// + /// + /// public static T CastModel(this IGameLogicService logicService, object gameModel) where T : IGameModel { if (gameModel is T model) @@ -29,8 +40,18 @@ namespace Koogle.Application.Games } } + /// + /// Helper methods for GameModel + /// public static class GameModelExtensions { + /// + /// Get the previous Guid in array, considering loop of players. + /// + /// + /// + /// + /// public static Guid GetPrev(this IGameModel gameModel, Guid[] guids, int index) { if (index == 0) @@ -40,6 +61,13 @@ namespace Koogle.Application.Games return guids[index - 1]; } + /// + /// Get the next Guid in array, considering loop of players. + /// + /// + /// + /// + /// public static Guid GetNext(this IGameModel gameModel, Guid[] guids, int index) { if (index == guids.Length - 1) @@ -49,6 +77,13 @@ namespace Koogle.Application.Games return guids[index + 1]; } + /// + /// Get the next index in array, considering loop of players. + /// + /// + /// + /// + /// public static int GetNextIndex(this IGameModel gameModel,Guid[] guids, int index) { if (index == guids.Length - 1)