Files
AOW4/WebAssembly/Throwaway/BuildingCalculator.cs
T

355 lines
12 KiB
C#

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<string> 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<BuildOrderEntry> BuildOrder { get; set; } = new();
public List<ResourceSnapshot> 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<BuildOrderRequest> buildRequests,
IEnumerable<BuildingDefinition>? startingBuildings = null,
ResourceAmounts? startingResources = null)
{
var activeBuildings = new List<BuildOrderEntry>();
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<BuildOrderEntry>();
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<BuildingDefinition> GetDefaultStartingBuildings()
{
return new List<BuildingDefinition>
{
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<BuildOrderRequest>
{
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 });
}
}