1c735b124a
- Rewrite ProductionSolver: system of linear equations A×x=b via LU/QR decomposition - Add ResourceClassifier: classify resources as target/intermediate/raw - Add RecipeMatrixBuilder: build coefficient matrix and RHS vector - Add DI container (Microsoft.Extensions.DependencyInjection) in CLI - Fix resource flow calculation (RecipeRate is total, not per-machine) - Fix recipes.json: moduleCategory → machineCategory (recipe #19) - Update tests: 30 tests covering solver, classifier, matrix builder - Update README: document matrix model, architecture, principles
99 lines
3.3 KiB
C#
99 lines
3.3 KiB
C#
using FactorioCalc.Domain;
|
|
|
|
namespace FactorioCalc.Solver;
|
|
|
|
/// <summary>
|
|
/// Classifies resources into targets (user-requested), craftable (have producers),
|
|
/// and raw (must be extracted from external sources).
|
|
/// </summary>
|
|
public sealed class ResourceClassifier
|
|
{
|
|
/// <summary>
|
|
/// Resources that have at least one recipe producing them.
|
|
/// </summary>
|
|
public IReadOnlySet<int> Craftable { get; private set; } = default!;
|
|
|
|
/// <summary>
|
|
/// Resources explicitly requested by the user.
|
|
/// </summary>
|
|
public IReadOnlySet<int> Targets { get; private set; } = default!;
|
|
|
|
/// <summary>
|
|
/// Craftable resources not directly targeted — produced only as intermediates.
|
|
/// </summary>
|
|
public IReadOnlySet<int> Intermediate { get; private set; } = default!;
|
|
|
|
/// <summary>
|
|
/// Resources with no known producer — must come from mining, gathering, etc.
|
|
/// </summary>
|
|
public IReadOnlySet<int> Raw { get; private set; } = default!;
|
|
|
|
/// <summary>
|
|
/// All resource IDs touched by the problem (targets + every ingredient in relevant recipes).
|
|
/// </summary>
|
|
public IReadOnlySet<int> AllResourceIds { get; private set; } = default!;
|
|
|
|
public void Classify(
|
|
IReadOnlyDictionary<int, Recipe> recipes,
|
|
IReadOnlyCollection<ProductionTarget> targets)
|
|
{
|
|
var targetIds = new HashSet<int>(targets.Select(t => t.ResourceId));
|
|
|
|
// Find every resource that some recipe produces
|
|
var craftable = new HashSet<int>();
|
|
foreach (var recipe in recipes.Values)
|
|
{
|
|
foreach (var product in recipe.Products)
|
|
craftable.Add(product.ResourceId);
|
|
}
|
|
|
|
var raw = new HashSet<int>();
|
|
var allIds = new HashSet<int>(targetIds);
|
|
|
|
foreach (var rid in targetIds)
|
|
{
|
|
if (!craftable.Contains(rid))
|
|
raw.Add(rid);
|
|
}
|
|
|
|
// Walk ingredients of every recipe that produces a target or craftable resource
|
|
// to discover all resources involved
|
|
var visited = new HashSet<int>();
|
|
var queue = new Queue<int>(targetIds);
|
|
|
|
while (queue.Count > 0)
|
|
{
|
|
var rid = queue.Dequeue();
|
|
if (visited.Contains(rid))
|
|
continue;
|
|
visited.Add(rid);
|
|
allIds.Add(rid);
|
|
|
|
if (craftable.Contains(rid))
|
|
{
|
|
foreach (var recipe in recipes.Values)
|
|
{
|
|
if (recipe.Products.Any(p => p.ResourceId == rid))
|
|
{
|
|
foreach (var ing in recipe.Ingredients)
|
|
{
|
|
allIds.Add(ing.ResourceId);
|
|
if (!visited.Contains(ing.ResourceId) && !craftable.Contains(ing.ResourceId))
|
|
raw.Add(ing.ResourceId);
|
|
queue.Enqueue(ing.ResourceId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var intermediate = craftable.Where(r => !targetIds.Contains(r) && allIds.Contains(r)).ToHashSet();
|
|
|
|
Craftable = craftable.AsReadOnly();
|
|
Targets = targetIds.AsReadOnly();
|
|
Intermediate = intermediate.AsReadOnly();
|
|
Raw = raw.AsReadOnly();
|
|
AllResourceIds = allIds.AsReadOnly();
|
|
}
|
|
}
|