using FactorioCalc.Domain; namespace FactorioCalc.Solver; /// /// Classifies resources into targets (user-requested), craftable (have producers), /// and raw (must be extracted from external sources). /// public sealed class ResourceClassifier { /// /// Resources that have at least one recipe producing them. /// public IReadOnlySet Craftable { get; private set; } = default!; /// /// Resources explicitly requested by the user. /// public IReadOnlySet Targets { get; private set; } = default!; /// /// Craftable resources not directly targeted — produced only as intermediates. /// public IReadOnlySet Intermediate { get; private set; } = default!; /// /// Resources with no known producer — must come from mining, gathering, etc. /// public IReadOnlySet Raw { get; private set; } = default!; /// /// All resource IDs touched by the problem (targets + every ingredient in relevant recipes). /// public IReadOnlySet AllResourceIds { get; private set; } = default!; public void Classify( IReadOnlyDictionary recipes, IReadOnlyCollection targets) { var targetIds = new HashSet(targets.Select(t => t.ResourceId)); // Find every resource that some recipe produces var craftable = new HashSet(); foreach (var recipe in recipes.Values) { foreach (var product in recipe.Products) craftable.Add(product.ResourceId); } var raw = new HashSet(); var allIds = new HashSet(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(); var queue = new Queue(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(); } }