using System.Text.Json.Serialization; namespace WebAssembly.Throwaway; public sealed class ResourceAmounts { public int Draft { get; set; } public int Food { get; set; } public int Knowledge { get; set; } public int Industry { get; set; } public int Magic { get; set; } public int Gold { get; set; } public int Imperial { get; set; } public int Stability { get; set; } [JsonIgnore] public bool IsZero => Draft == 0 && Food == 0 && Knowledge == 0 && Industry == 0 && Magic == 0 && Gold == 0 && Imperial == 0 && Stability == 0; public void Add(ResourceAmounts other) { Draft += other.Draft; Food += other.Food; Knowledge += other.Knowledge; Industry += other.Industry; Magic += other.Magic; Gold += other.Gold; Imperial += other.Imperial; Stability += other.Stability; } public void Subtract(ResourceAmounts other) { Draft -= other.Draft; Food -= other.Food; Knowledge -= other.Knowledge; Industry -= other.Industry; Magic -= other.Magic; Gold -= other.Gold; Imperial -= other.Imperial; Stability -= other.Stability; } public static ResourceAmounts operator +(ResourceAmounts a, ResourceAmounts b) { return new ResourceAmounts { Draft = a.Draft + b.Draft, Food = a.Food + b.Food, Knowledge = a.Knowledge + b.Knowledge, Industry = a.Industry + b.Industry, Magic = a.Magic + b.Magic, Gold = a.Gold + b.Gold, Imperial = a.Imperial + b.Imperial, Stability = a.Stability + b.Stability }; } public static ResourceAmounts operator -(ResourceAmounts a, ResourceAmounts b) { return new ResourceAmounts { Draft = a.Draft - b.Draft, Food = a.Food - b.Food, Knowledge = a.Knowledge - b.Knowledge, Industry = a.Industry - b.Industry, Magic = a.Magic - b.Magic, Gold = a.Gold - b.Gold, Imperial = a.Imperial - b.Imperial, Stability = a.Stability - b.Stability }; } } public sealed class BuildingDefinition { public string Id { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string? Description { get; set; } public string? Image { get; set; } public string SourceId { get; set; } = "General"; public List RequirementIds { get; set; } = new(); public ResourceAmounts Income { get; set; } = new(); public ResourceAmounts Upkeep { get; set; } = new(); public ResourceAmounts Cost { get; set; } = new(); public int BoostPopulation { get; set; } public int BoostForester { get; set; } public int BoostFarm { get; set; } public int BoostQuarry { get; set; } public int BoostGoldMine { get; set; } public int BoostConduit { get; set; } } public sealed class BuildOrderEntry { public Guid Id { get; set; } = Guid.NewGuid(); public string ItemId { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public int RequestedTurn { get; set; } public int BuildStartTurn { get; set; } public int BuiltFinishTurn { get; set; } public int IndustryCostRemaining { get; set; } public BuildingDefinition Definition { get; set; } = new(); } public sealed class ResourceSnapshot { public int Turn { get; set; } public ResourceAmounts Stored { get; set; } = new(); public ResourceAmounts TotalIncome { get; set; } = new(); public ResourceAmounts TotalUpkeep { get; set; } = new(); public string? Notes { get; set; } } public sealed class BuildPlanResult { public List BuildOrder { get; set; } = new(); public List ResourceHistory { get; set; } = new(); public ResourceAmounts StartingPool { get; set; } = new(); } public sealed class BuildOrderRequest { public string ItemId { get; init; } = string.Empty; public BuildingDefinition Definition { get; init; } = new(); public int RequestedTurn { get; init; } } public static class BuildingPlanCalculator { public static BuildPlanResult CalculateBuildPlan( int totalTurns, IEnumerable buildRequests, IEnumerable? startingBuildings = null, ResourceAmounts? startingResources = null) { var activeBuildings = new List(); var result = new BuildPlanResult { StartingPool = startingResources ?? new ResourceAmounts() }; var stored = new ResourceAmounts(); if (startingResources is not null) stored = new ResourceAmounts { Draft = startingResources.Draft, Food = startingResources.Food, Knowledge = startingResources.Knowledge, Industry = startingResources.Industry, Magic = startingResources.Magic, Gold = startingResources.Gold, Imperial = startingResources.Imperial, Stability = startingResources.Stability }; if (startingBuildings is not null) foreach (var startingBuilding in startingBuildings) { var entry = new BuildOrderEntry { ItemId = startingBuilding.Id, Name = startingBuilding.Name, RequestedTurn = 1, BuildStartTurn = 1, BuiltFinishTurn = 0, IndustryCostRemaining = 0, Definition = startingBuilding }; activeBuildings.Add(entry); result.BuildOrder.Add(entry); } var requestsByTurn = buildRequests .OrderBy(r => r.RequestedTurn) .GroupBy(r => r.RequestedTurn) .ToDictionary(g => g.Key, g => g.ToList()); var projects = new List(); for (var turn = 1; turn <= totalTurns; turn++) { var incomeThisTurn = new ResourceAmounts(); var upkeepThisTurn = new ResourceAmounts(); foreach (var building in activeBuildings) if (building.BuiltFinishTurn == 0 || turn > building.BuiltFinishTurn) { incomeThisTurn.Add(building.Definition.Income); upkeepThisTurn.Add(building.Definition.Upkeep); } stored.Add(incomeThisTurn); stored.Subtract(upkeepThisTurn); if (requestsByTurn.TryGetValue(turn, out var requests)) foreach (var request in requests) { var nextProject = new BuildOrderEntry { ItemId = request.ItemId, Name = request.Definition.Name, RequestedTurn = turn, BuildStartTurn = turn, BuiltFinishTurn = 0, IndustryCostRemaining = request.Definition.Cost.Industry, Definition = request.Definition }; stored.Subtract(new ResourceAmounts { Draft = request.Definition.Cost.Draft, Food = request.Definition.Cost.Food, Knowledge = request.Definition.Cost.Knowledge, Magic = request.Definition.Cost.Magic, Gold = request.Definition.Cost.Gold, Imperial = request.Definition.Cost.Imperial, Stability = 0 }); projects.Add(nextProject); result.BuildOrder.Add(nextProject); } var availableIndustry = incomeThisTurn.Industry; foreach (var project in projects.Where(p => p.BuiltFinishTurn == 0).OrderBy(p => p.RequestedTurn)) { if (availableIndustry <= 0 || project.IndustryCostRemaining <= 0) continue; var applied = Math.Min(availableIndustry, project.IndustryCostRemaining); project.IndustryCostRemaining -= applied; availableIndustry -= applied; if (project.IndustryCostRemaining <= 0) { project.BuiltFinishTurn = turn; activeBuildings.Add(project); } } result.ResourceHistory.Add(new ResourceSnapshot { Turn = turn, Stored = new ResourceAmounts { Draft = stored.Draft, Food = stored.Food, Knowledge = stored.Knowledge, Industry = stored.Industry, Magic = stored.Magic, Gold = stored.Gold, Imperial = stored.Imperial, Stability = stored.Stability }, TotalIncome = incomeThisTurn, TotalUpkeep = upkeepThisTurn, Notes = null }); } return result; } public static IReadOnlyList GetDefaultStartingBuildings() { return new List { new() { Id = "town-hall-1", Name = "Town Hall I", Description = "Starting civic center that produces basic resources and morale.", SourceId = "General", Income = new ResourceAmounts { Draft = 20, Food = 30, Industry = 20, Gold = 0, Stability = 10 }, Upkeep = new ResourceAmounts(), Cost = new ResourceAmounts() }, new() { Id = "throne", Name = "Throne", Description = "Royal treasury building that provides steady gold income.", SourceId = "General", Income = new ResourceAmounts { Gold = 120 }, Upkeep = new ResourceAmounts(), Cost = new ResourceAmounts() } }; } public static BuildPlanResult CreateSampleBuildPlan(int totalTurns = 60) { var sampleRequests = new List { new() { ItemId = "farm-1", Definition = new BuildingDefinition { Id = "farm-1", Name = "Farm", Description = "Provides food income and supports future growth.", SourceId = "General", Income = new ResourceAmounts { Food = 15 }, Upkeep = new ResourceAmounts { Gold = 2 }, Cost = new ResourceAmounts { Gold = 80, Industry = 40 } }, RequestedTurn = 2 }, new() { ItemId = "workshop-1", Definition = new BuildingDefinition { Id = "workshop-1", Name = "Workshop", Description = "Improves production and generates industry.", SourceId = "General", Income = new ResourceAmounts { Industry = 20 }, Upkeep = new ResourceAmounts { Gold = 4 }, Cost = new ResourceAmounts { Gold = 110, Industry = 80 } }, RequestedTurn = 5 }, new() { ItemId = "market-1", Definition = new BuildingDefinition { Id = "market-1", Name = "Market", Description = "Provides ongoing gold income and supports trade.", SourceId = "General", Income = new ResourceAmounts { Gold = 40 }, Upkeep = new ResourceAmounts { Gold = 6 }, Cost = new ResourceAmounts { Gold = 180, Industry = 100 } }, RequestedTurn = 12 } }; return CalculateBuildPlan(totalTurns, sampleRequests, GetDefaultStartingBuildings(), new ResourceAmounts { Gold = 0 }); } }