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)