using GoodWood.Application.Games.DeathBox; using GoodWood.Application.Interfaces; using GoodWood.Domain.Enums; using Microsoft.Extensions.DependencyInjection; using Org.BouncyCastle.Cms; using System; using System.Text.Json; using GoodWood.Infrastructure.Data.Migrations; namespace GoodWood.Application.Games.FoxHunt { /// /// Game logic service for Fuchsjagd-Spiel (Fox Hunt) game type. /// One Player starts as fox, and all others try to hunt him. /// public class FoxHuntGameLogicService : IGameLogicService { /// public object CreateInitialModel(Guid[] playerIds, object? setupOptions) { var options = ParseSetupOptions(setupOptions); var playerOrder = playerIds.ToArray(); var playerStates = playerIds.ToDictionary( id => id, _ => new FoxHuntPlayerState()); var model = new FoxHuntGameModel { FoxIndex = 0, // aktueller Fuchs (Index in PlayerOrder) 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, PinCountGoal = options.PinCountGoal, PlayerStates = playerStates, PlayerOrder = playerOrder, WinnerId = null, IsGameOver = false, FoxCountLeft = playerStates.Count }; return model; } /// public (object UpdatedModel, ThrowResult Result) ProcessThrow(object gameModel, AfterThrowState afterThrow) { var model = this.CastModel(gameModel); var playerId = afterThrow.CurrentPlayerId; var pinsKnocked = afterThrow.PinsKnocked; if (model.IsGameOver) { return (model, new ThrowResult { PointsScored = 0, ShouldRotatePlayer = false, IsGameOver = true, WinnerId = model.WinnerId }); } var playerStates = new Dictionary(model.PlayerStates); var triggers = new List(); var gameEvents = new List(); var foxId = model.PlayerOrder[model.FoxIndex]; //var lastHunterId = model.GetPrev(model.PlayerOrder, model.FoxIndex); //var isLastHunter = lastHunterId == playerId; var chooseNextFox = false; if (model.FoxTurn) { playerStates[foxId].PinCountFox+= afterThrow.PinsKnocked; if (afterThrow.IsCircle) { playerStates[foxId].PinCountFox += 2; } if (playerStates[foxId].PinCountFox >= model.PinCountGoal) { playerStates[foxId].FoxEscaped = true; chooseNextFox = true; } } else { playerStates[foxId].PinCountHunters+= afterThrow.PinsKnocked; if (afterThrow.IsCircle) { playerStates[foxId].PinCountHunters += 2; } if (playerStates[foxId].PinCountHunters >= playerStates[foxId].PinCountFox ) { // fox has been caught chooseNextFox = true; // will cause selection of the next fox playerStates[foxId].FoxCaught = true; // Calculate penalty (point difference the hunters achieved) var pointDifference = playerStates[foxId].PinCountHunters - playerStates[foxId].PinCountFox; if (pointDifference > 0) { triggers.Add(new TriggerEvent { TriggerType = ExpenseTriggerType.ExpensePoint.ToString(), PersonId = foxId, Multiplier = pointDifference }); } } } if (chooseNextFox) { model.FoxCountLeft--; } var nextPlayerId = GetNextId(model, chooseNextFox); // Check GAME END var isGameOver = model.FoxCountLeft == 0; if (isGameOver) { var escapedFoxes = playerStates.Where(kv => !kv.Value.FoxCaught).ToList(); if (escapedFoxes.Count == 0) { // All foxes caught - no winner gameEvents.Add(new GameEndedEvent { WinnerId = null, IsDraw = false, Message = "Kein Sieger - alle Füchse wurden gefangen!" }); } else { // looking for winner(s) among escaped foxes var maxLeading = escapedFoxes .Select(kv => kv.Value.PinCountFox - kv.Value.PinCountHunters).Max(); foreach (var kv in escapedFoxes) { if (kv.Value.PinCountFox - kv.Value.PinCountHunters == maxLeading) { playerStates[kv.Key].IsWinner = true; gameEvents.Add(new PlayerWonEvent { PlayerId = kv.Key, Message = "Bester Fuchs!" }); } } } } // Update model model = model with { PlayerStates = playerStates, FoxIndex = model.FoxIndex, FoxTurn = model.FoxTurn, FoxTurnsRemaining = model.FoxTurnsRemaining, NonFoxIndex = model.NonFoxIndex, PendingGameEvents = gameEvents }; var result = new ThrowResult { PointsScored = pinsKnocked, ShouldRotatePlayer = true, IsGameOver = isGameOver, WinnerId = null, Triggers = triggers, Overrides = isGameOver ? null : new GameLogicOverrides { NextPlayerId = nextPlayerId } }; return (model, result); } /// /// algorithm: /// A, A, B, A, C, A, D, /// B, B, A, B, C, B, D, /// C, C, A, C, B, C, D, /// D, D, A, D, B, D, C /// /// /// private Guid GetNextId(FoxHuntGameModel model, bool chooseNextFox) { // Abbruch: alle waren einmal Fuchs if (model.FoxIndex >= model.PlayerOrder.Length) throw new InvalidOperationException("Alle Spieler waren bereits Fuchs."); var foxId = model.PlayerOrder[model.FoxIndex]; if (chooseNextFox) { model.FoxIndex = model.GetNextIndex(model.PlayerOrder, model.FoxIndex); model.FoxTurnsRemaining = model.LeadingThrows - 1; // nächster Wurf zählt schon model.FoxTurn = true; model.NonFoxIndex = model.FoxIndex; // NonFoxIndex = FoxIndex -> Increment will choose next person after fox return model.PlayerOrder[model.FoxIndex]; } // Fuchs ist 2x hintereinander dran if (model.FoxTurnsRemaining > 0) { model.FoxTurnsRemaining--; model.FoxTurn = true; return foxId; } model.FoxTurn = !model.FoxTurn; // danach abwechselnd // Abwechselnd Nicht-Fuchs → Fuchs if (!model.FoxTurn) { var nextNonFoxIndex = model.GetNextIndex(model.PlayerOrder, model.NonFoxIndex); if (nextNonFoxIndex == model.FoxIndex) { throw new InvalidOperationException("this should never happen"); //nextNoneFoxIndex = GetNextIndex(model, model.NonFoxIndex); } model.NonFoxIndex = nextNonFoxIndex; return model.PlayerOrder[nextNonFoxIndex]; } // Fuchs-Zug im Wechsel return foxId; } /// 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) { return GameSetupValidationResult.Valid(); } var options = ParseSetupOptions(setupOptions); var errors = new List(); if (options.LeadingThrows < 2 || options.LeadingThrows > 4) { errors.Add("Vorsprung muss zwischen 2 und 4 liegen."); } if (options.PinCountGoal < 12 || options.PinCountGoal > 50) { errors.Add("Ziel-Punkte muss zwischen 12 und 50 liegen."); } //TODO: validate player-count, at lease 2 players needed return errors.Count > 0 ? GameSetupValidationResult.Invalid(errors.ToArray()) : 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) { return GameActionResult.Failure($"Unknown action: {actionId}"); } private static FoxHuntGameSetup ParseSetupOptions(object? setupOptions) { if (setupOptions is null) { return new FoxHuntGameSetup(); } if (setupOptions is FoxHuntGameSetup typedSetup) { return typedSetup; } if (setupOptions is JsonElement jsonElement) { try { return JsonSerializer.Deserialize( jsonElement.GetRawText(), GameModelFactory.JsonSerializerOptions) ?? new FoxHuntGameSetup(); } catch { return new FoxHuntGameSetup(); } } return new FoxHuntGameSetup(); } } }