@page "/simulation" @inject GameSimulationService SimService Ecology Simulation

Ecology Simulation

@if (!SimService.Data.IsInitialized) {

Simulate 20 turns of predator, prey, and flora ecology across the valley.

} else {
@if (SimService.Data.CurrentTurn == 0) { Initial Setup } else { After Turn @SimService.Data.CurrentTurn / 20 }
@if (CurrentEvent != null) { @foreach (var act in CurrentEvent.Activations) { @act.MeepleType in @act.RegionType } }
@{ var currentRegions = SimService.GetCurrentState(); var prevRegions = SimService.GetPreviousState(); var prevLookup = prevRegions.ToDictionary(r => r.Name); var activatedRegionNames = new HashSet(); if (CurrentEvent != null) { var activatedTerrain = CurrentEvent.Activations.Select(a => a.RegionType).ToHashSet(); activatedRegionNames = currentRegions .Where(r => activatedTerrain.Contains(r.Terrain)) .Select(r => r.Name) .ToHashSet(); } } @foreach (var line in GetConnectionLines(currentRegions)) { } @foreach (var region in currentRegions) { var isActivated = activatedRegionNames.Contains(region.Name); var terrainColor = GetTerrainColor(region.Terrain); var labelX = region.X + 24; var prev = prevLookup.GetValueOrDefault(region.Name); var dp = prev != null ? region.PredatorMeeples - prev.PredatorMeeples : 0; var dr = prev != null ? region.PreyMeeples - prev.PreyMeeples : 0; var df = prev != null ? region.FloraMeeples - prev.FloraMeeples : 0; var hasDelta = dp != 0 || dr != 0 || df != 0; @region.Name P:@region.PredatorMeeples R:@region.PreyMeeples F:@region.FloraMeeples @if (hasDelta && SimService.Data.CurrentTurn > 0) { var dy = region.Y + 34; var parts = new List(); if (dp != 0) parts.Add($" 0 ? "#4caf50" : "#e76f51")}\">{(dp > 0 ? "+" : "")}{dp}P"); if (dr != 0) parts.Add($" 0 ? "#4caf50" : "#e76f51")}\">{(dr > 0 ? "+" : "")}{dr}R"); if (df != 0) parts.Add($" 0 ? "#4caf50" : "#e76f51")}\">{(df > 0 ? "+" : "")}{df}F"); var deltaHtml = $"{string.Join(" ", parts)}"; @((MarkupString)deltaHtml) } }
@if (CurrentEvent != null && CurrentEvent.Details.Count > 0) {
Turn @CurrentEvent.TurnNumber Details
@foreach (var detail in CurrentEvent.Details) {
@detail
}
}
@{ var totals = SimService.GetCurrentState(); }
Predators: @totals.Sum(r => r.PredatorMeeples)
Prey: @totals.Sum(r => r.PreyMeeples)
Flora: @totals.Sum(r => r.FloraMeeples)
Population Trend
@{ var history = SimService.GetPopulationHistory(); var maxPop = Math.Max(history.Max(h => h.predators + h.prey + h.flora), 10); var chartW = 700d; var chartH = 120d; var chartY = 20d; var stepX = history.Length > 1 ? chartW / (history.Length - 1) : chartW; string MakePath(Func getVal, string color) { var pts = history.Select((h, i) => { var x = i * stepX; var y = chartY + chartH - (getVal(i) / (double)maxPop) * chartH; return $"{x:F1},{y:F1}"; }); var d = "M" + string.Join(" L", pts); return $""; } // Y axis labels @((MarkupString)$"{maxPop}") @((MarkupString)$"0") // Grid lines // Current turn marker var markerX = SimService.Data.CurrentTurn * stepX; @((MarkupString)MakePath(i => history[i].predators, "#e76f51")) @((MarkupString)MakePath(i => history[i].prey, "#e9c46a")) @((MarkupString)MakePath(i => history[i].flora, "#4caf50")) // Turn markers on x-axis @for (int i = 0; i < history.Length; i += 5) { var x = i * stepX; @((MarkupString)$"{i}") } }
Predators Prey Flora
} @code { private TurnEvent? CurrentEvent => SimService.Data.CurrentTurn > 0 && SimService.Data.CurrentTurn <= SimService.Data.Events.Count ? SimService.Data.Events[SimService.Data.CurrentTurn - 1] : null; private static string BadgeClass(string meepleType) => meepleType switch { "Predator" => "danger", "Prey" => "warning", "Flora" => "success", _ => "secondary" }; protected override void OnInitialized() { SimService.RunSimulation(); } private void StartSimulation() { SimService.RunSimulation(); } private void PrevTurn() { if (SimService.Data.CurrentTurn > 0) SimService.Data.CurrentTurn--; } private void NextTurn() { if (SimService.Data.CurrentTurn < 20) SimService.Data.CurrentTurn++; } private void RandomizeEvents() { SimService.RandomizeEvents(); } private static string GetTerrainColor(string terrain) { return terrain switch { "Grass" => "#4caf50", "Forest" => "#2e7d32", "Mountain" => "#78909c", "Water" => "#42a5f5", "Wasteland" => "#8d6e63", _ => "#888" }; } private record ConnectionLine(int X1, int Y1, int X2, int Y2); private List GetConnectionLines(List regions) { var lookup = regions.ToDictionary(r => r.Name); var lines = new List(); var drawn = new HashSet(); foreach (var region in regions) { foreach (var conn in region.Connections) { var key = string.Compare(region.Name, conn, StringComparison.OrdinalIgnoreCase) < 0 ? $"{region.Name}|{conn}" : $"{conn}|{region.Name}"; if (!drawn.Add(key)) continue; if (!lookup.TryGetValue(conn, out var target)) continue; lines.Add(new ConnectionLine(region.X, region.Y, target.X, target.Y)); } } return lines; } }