Some stub build plan calculator code
This commit is contained in:
@@ -0,0 +1,116 @@
|
|||||||
|
@page "/building-calculator"
|
||||||
|
@using System.Text.Json
|
||||||
|
@using AOW4.Data
|
||||||
|
|
||||||
|
<div class="calculator-page">
|
||||||
|
<h1>Building Plan Calculator</h1>
|
||||||
|
<p>Simulates resource income each turn and tracks build completion times for ordered buildings.</p>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Build Order</h2>
|
||||||
|
<table class="calculator-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Turn Requested</th>
|
||||||
|
<th>Building</th>
|
||||||
|
<th>Finish Turn</th>
|
||||||
|
<th>Industry Remaining</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var entry in Result.BuildOrder)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>@entry.RequestedTurn</td>
|
||||||
|
<td>@entry.Name</td>
|
||||||
|
<td>@(entry.BuiltFinishTurn == 0 ? "Starting" : entry.BuiltFinishTurn.ToString())</td>
|
||||||
|
<td>@entry.IndustryCostRemaining</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Gold Over Time</h2>
|
||||||
|
<table class="calculator-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Turn</th>
|
||||||
|
<th>Stored Gold</th>
|
||||||
|
<th>Income</th>
|
||||||
|
<th>Upkeep</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var snapshot in Result.ResourceHistory)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>@snapshot.Turn</td>
|
||||||
|
<td>@snapshot.Stored.Gold</td>
|
||||||
|
<td>@snapshot.TotalIncome.Gold</td>
|
||||||
|
<td>@snapshot.TotalUpkeep.Gold</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Result JSON</h2>
|
||||||
|
<pre class="json-output">@Json</pre>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private BuildPlanResult Result = new();
|
||||||
|
private string Json = string.Empty;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
Result = BuildingPlanCalculator.CreateSampleBuildPlan(60);
|
||||||
|
Json = JsonSerializer.Serialize(Result, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.calculator-page {
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calculator-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calculator-table th,
|
||||||
|
.calculator-table td {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: 1px solid #d3d3d3;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calculator-table th {
|
||||||
|
background-color: #f3f6fb;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-output {
|
||||||
|
display: block;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background: #1f2937;
|
||||||
|
color: #f8fafc;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: Consolas, "Courier New", monospace;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,360 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace AOW4.Data;
|
||||||
|
|
||||||
|
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)
|
||||||
|
=> 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)
|
||||||
|
=> 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 BuildingDefinition
|
||||||
|
{
|
||||||
|
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 BuildingDefinition
|
||||||
|
{
|
||||||
|
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 BuildOrderRequest
|
||||||
|
{
|
||||||
|
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 BuildOrderRequest
|
||||||
|
{
|
||||||
|
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 BuildOrderRequest
|
||||||
|
{
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,12 @@ public static class SectionsData
|
|||||||
Description = "Useful calculator tools for various computations",
|
Description = "Useful calculator tools for various computations",
|
||||||
Links = new List<SectionLink>
|
Links = new List<SectionLink>
|
||||||
{
|
{
|
||||||
// Add calculator links here in the future
|
new SectionLink
|
||||||
|
{
|
||||||
|
Title = "Building Plan Calculator",
|
||||||
|
Url = "/building-calculator",
|
||||||
|
Description = "Simulate build order timing and gold income over turns."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Section
|
new Section
|
||||||
|
|||||||
Reference in New Issue
Block a user