From f0b9583daaaf2eaa391134e06af9b902c5652ca6 Mon Sep 17 00:00:00 2001 From: Joel Verhagen Date: Fri, 19 Jan 2024 13:32:15 -0500 Subject: [PATCH] Abstract List into ITableList --- Directory.Build.props | 5 + src/FactorioTools/CollectionExtensions.cs | 118 +++++++++-- .../OilField/Algorithms/AStar.cs | 12 +- .../OilField/Algorithms/AStarResult.cs | 6 +- .../OilField/Algorithms/BreadthFirstFinder.cs | 4 +- .../OilField/Algorithms/BresenhamsLine.cs | 4 +- .../OilField/Algorithms/DijkstrasResult.cs | 8 +- .../OilField/Algorithms/Prims.cs | 4 +- .../Containers/DictionaryTableList.cs | 186 ++++++++++++++++++ .../OilField/Containers/IReadOnlyTableList.cs | 12 ++ .../OilField/Containers/ITableList.cs | 20 ++ .../OilField/Containers/ListTableList.cs | 87 ++++++++ .../OilField/Containers/TableList.cs | 39 ++++ src/FactorioTools/OilField/Helpers.cs | 92 ++++----- .../OilField/Models/BeaconSolution.cs | 2 +- src/FactorioTools/OilField/Models/Context.cs | 70 +------ .../OilField/Models/OilFieldPlanSummary.cs | 6 +- .../OilField/Models/SharedInstances.cs | 4 +- src/FactorioTools/OilField/OilFieldOptions.cs | 4 +- src/FactorioTools/OilField/Planner.cs | 18 +- .../OilField/Steps/AddElectricPoles.cs | 26 +-- .../OilField/Steps/AddPipes.0.cs | 119 ++++++----- .../OilField/Steps/AddPipes.1.FBE.cs | 106 +++++----- .../Steps/AddPipes.2.ConnectedCenters.cs | 109 +++++----- .../Steps/AddPipes.3.ConnectedCenters.DT.cs | 8 +- .../AddPipes.4.ConnectedCenters.FLUTE.cs | 9 +- .../OilField/Steps/CleanBlueprint.cs | 10 +- .../OilField/Steps/InitializeContext.cs | 50 ++--- .../OilField/Steps/PlanBeacons.0.cs | 10 +- .../OilField/Steps/PlanBeacons.1.FBE.cs | 46 ++--- .../OilField/Steps/PlanBeacons.2.Snug.cs | 18 +- .../OilField/Steps/PlanUndergroundPipes.cs | 8 +- .../OilField/Steps/RotateOptimize.cs | 50 +++-- src/FactorioTools/OilField/Steps/Validate.cs | 18 +- src/Sandbox/Program.cs | 74 +------ src/Sandbox/Properties/launchSettings.json | 2 +- .../Models/GenericCollectionSchemaFilter.cs | 35 ++++ ...OilFieldPlanRequestDefaultsSchemaFilter.cs | 13 +- src/WebApp/Program.cs | 6 + test/FactorioTools.Test/ExtensionMethods.cs | 19 ++ .../Theory.cs | 2 +- .../OilField/BasePlannerTest.cs | 10 +- test/FactorioTools.Test/OilField/BaseTest.cs | 9 +- .../Collections/DictionaryTableArrayTest.cs | 81 ++++++++ .../OilField/NonStandardBeacon/Theory.cs | 2 +- .../OilField/PlannerIssueTest.cs | 16 +- .../OilField/PlannerTest.cs | 8 +- test/FactorioTools.Test/OilField/Score.cs | 6 +- .../OilField/Steps/HelpersTest.cs | 10 +- .../OilField/Steps/InitializeContextTest.cs | 30 +-- test/FactorioTools.Test/SetVerifySettings.cs | 12 -- 51 files changed, 1064 insertions(+), 559 deletions(-) create mode 100644 src/FactorioTools/OilField/Containers/DictionaryTableList.cs create mode 100644 src/FactorioTools/OilField/Containers/IReadOnlyTableList.cs create mode 100644 src/FactorioTools/OilField/Containers/ITableList.cs create mode 100644 src/FactorioTools/OilField/Containers/ListTableList.cs create mode 100644 src/FactorioTools/OilField/Containers/TableList.cs create mode 100644 src/WebApp/Models/GenericCollectionSchemaFilter.cs create mode 100644 test/FactorioTools.Test/ExtensionMethods.cs create mode 100644 test/FactorioTools.Test/OilField/Collections/DictionaryTableArrayTest.cs diff --git a/Directory.Build.props b/Directory.Build.props index add2a632..e984259b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,6 +27,11 @@ $(DefineConstants);USE_BITARRAY + + true + $(DefineConstants);USE_ARRAY + + true $(DefineConstants);LOCATION_AS_STRUCT diff --git a/src/FactorioTools/CollectionExtensions.cs b/src/FactorioTools/CollectionExtensions.cs index 5523321e..47eff92b 100644 --- a/src/FactorioTools/CollectionExtensions.cs +++ b/src/FactorioTools/CollectionExtensions.cs @@ -27,10 +27,26 @@ public static ILocationDictionary ToDictionary( return dictionary; } - public static List Distinct(this IReadOnlyCollection locations, Context context) + public static ILocationDictionary ToDictionary( + this IReadOnlyTableList items, + Context context, + Func keySelector, + Func valueSelector) + { + var dictionary = context.GetLocationDictionary(items.Count); + for (var i = 0; i < items.Count; i++) + { + var item = items[i]; + dictionary.Add(keySelector(item), valueSelector(item)); + } + + return dictionary; + } + + public static ITableList Distinct(this IReadOnlyCollection locations, Context context) { var set = context.GetLocationSet(locations.Count); - var output = new List(locations.Count); + var output = TableList.New(locations.Count); foreach (var location in locations) { if (set.Add(location)) @@ -41,25 +57,74 @@ public static List Distinct(this IReadOnlyCollection locatio return output; } + public static ILocationSet ToSet(this IReadOnlyCollection locations, Context context) + { + return locations.ToSet(context, allowEnumerate: false); + } + public static ILocationSet ToSet(this IReadOnlyCollection locations, Context context, bool allowEnumerate) { - return context.GetLocationSet(locations, allowEnumerate); + var set = context.GetLocationSet(allowEnumerate); + foreach (var location in locations) + { + set.Add(location); + } + + return set; } public static ILocationSet ToReadOnlySet(this IReadOnlyCollection locations, Context context) { - return context.GetReadOnlyLocationSet(locations); + return locations.ToReadOnlySet(context, allowEnumerate: false); } public static ILocationSet ToReadOnlySet(this IReadOnlyCollection locations, Context context, bool allowEnumerate) { - return context.GetReadOnlyLocationSet(locations, allowEnumerate); + Location firstLocation = Location.Invalid; + int itemCount = 0; + ILocationSet? set = null; + foreach (var location in locations) + { + if (itemCount == 0) + { + firstLocation = location; + } + else if (itemCount == 1) + { + set = context.GetLocationSet(allowEnumerate); + set.Add(firstLocation); + set.Add(location); + } + else + { + set!.Add(location); + } + + itemCount++; + } + + if (set is null) + { + if (itemCount == 0) + { + set = EmptyLocationSet.Instance; + } + else if (itemCount == 1) + { + set = context.GetSingleLocationSet(firstLocation); + } + else + { + throw new NotImplementedException(); + } + } + return set; } #if ENABLE_VISUALIZER - public static List ToDelaunatorPoints(this ILocationSet set) + public static ITableList ToDelaunatorPoints(this ILocationSet set) { - var points = new List(); + var points = TableList.New(); foreach (var item in set.EnumerateItems()) { points.Add(new DelaunatorSharp.Point(item.X, item.Y)); @@ -68,9 +133,9 @@ public static ILocationSet ToReadOnlySet(this IReadOnlyCollection loca return points; } - public static List ToDelaunatorPoints(this ILocationDictionary dictionary) + public static ITableList ToDelaunatorPoints(this ILocationDictionary dictionary) { - var points = new List(); + var points = TableList.New(); foreach (var item in dictionary.Keys) { points.Add(new DelaunatorSharp.Point(item.X, item.Y)); @@ -186,9 +251,27 @@ public static ILocationSet ToReadOnlySet(this IReadOnlyCollection loca return min; } - public static List ToList(this IReadOnlyCollection source) + public static TSource[] ToArray(this IReadOnlyTableList source) + { + var output = new TSource[source.Count]; + for (var i = 0; i < source.Count; i++) + { + output[i] = source[i]; + } + + return output; + } + + public static ITableList ToTableList(this IReadOnlyCollection source) + { + var output = TableList.New(source.Count); + output.AddCollection(source); + return output; + } + + public static ITableList ToTableList(this IReadOnlyTableList source) { - var output = new List(source.Count); + var output = TableList.New(source.Count); output.AddRange(source); return output; } @@ -269,11 +352,10 @@ public static TSource First(this IReadOnlyCollection source, F throw new FactorioToolsException("An item should have matched the predicate."); } - public static TSource? FirstOrDefault(this IReadOnlyList source, Func predicate) + public static TSource? FirstOrDefault(this IReadOnlyCollection source, Func predicate) { - for (int i = 0; i < source.Count; i++) + foreach (var item in source) { - var item = source[i]; if (predicate(item)) { return item; @@ -301,7 +383,7 @@ public static double Average(this IReadOnlyCollection source, return sum / count; } - public static bool SequenceEqual(this IReadOnlyList first, IReadOnlyList second) + public static bool SequenceEqual(this IReadOnlyTableList first, IReadOnlyTableList second) { if (first.Count != second.Count) { @@ -321,12 +403,12 @@ public static bool SequenceEqual(this IReadOnlyList first, IRe return true; } - public static int Sum(this IReadOnlyList source, Func selector) + public static int Sum(this IReadOnlyCollection source, Func selector) { var sum = 0; - for (var i = 0; i < source.Count; i++) + foreach (var item in source) { - sum += selector(source[i]); + sum += selector(item); } return sum; diff --git a/src/FactorioTools/OilField/Algorithms/AStar.cs b/src/FactorioTools/OilField/Algorithms/AStar.cs index 7547ddaa..562d70eb 100644 --- a/src/FactorioTools/OilField/Algorithms/AStar.cs +++ b/src/FactorioTools/OilField/Algorithms/AStar.cs @@ -10,7 +10,15 @@ namespace Knapcode.FactorioTools.OilField; /// public static class AStar { - public static AStarResult GetShortestPath(Context context, SquareGrid grid, Location start, ILocationSet goals, bool preferNoTurns = true, int xWeight = 1, int yWeight = 1, List? outputList = null) + public static AStarResult GetShortestPath( + Context context, + SquareGrid grid, + Location start, + ILocationSet goals, + bool preferNoTurns = true, + int xWeight = 1, + int yWeight = 1, + ITableList? outputList = null) { if (goals.Contains(start)) { @@ -20,7 +28,7 @@ public static AStarResult GetShortestPath(Context context, SquareGrid grid, Loca } else { - outputList = new List { start }; + outputList = TableList.New(start); } return new AStarResult(success: true, start, outputList); diff --git a/src/FactorioTools/OilField/Algorithms/AStarResult.cs b/src/FactorioTools/OilField/Algorithms/AStarResult.cs index 8b9f5688..850586c1 100644 --- a/src/FactorioTools/OilField/Algorithms/AStarResult.cs +++ b/src/FactorioTools/OilField/Algorithms/AStarResult.cs @@ -4,9 +4,9 @@ namespace Knapcode.FactorioTools.OilField; public class AStarResult { - private readonly List? _path; + private readonly ITableList? _path; - public AStarResult(bool success, Location reachedGoal, List? path) + public AStarResult(bool success, Location reachedGoal, ITableList? path) { Success = success; ReachedGoal = reachedGoal; @@ -16,7 +16,7 @@ public AStarResult(bool success, Location reachedGoal, List? path) public bool Success { get; } public Location ReachedGoal { get; } - public List Path + public ITableList Path { get { diff --git a/src/FactorioTools/OilField/Algorithms/BreadthFirstFinder.cs b/src/FactorioTools/OilField/Algorithms/BreadthFirstFinder.cs index e0eb9e5b..e103aa3e 100644 --- a/src/FactorioTools/OilField/Algorithms/BreadthFirstFinder.cs +++ b/src/FactorioTools/OilField/Algorithms/BreadthFirstFinder.cs @@ -5,7 +5,7 @@ namespace Knapcode.FactorioTools.OilField { public static class BreadthFirstFinder { - public static List? GetShortestPath(Context context, Location start, Location goal) + public static ITableList? GetShortestPath(Context context, Location start, Location goal) { #if !USE_SHARED_INSTANCES var toExplore = new Queue(); @@ -40,7 +40,7 @@ public static class BreadthFirstFinder if (current == goal) { - var output = new List { current }; + var output = TableList.New(current); while (parents.TryGetValue(current, out var parent)) { output.Add(parent); diff --git a/src/FactorioTools/OilField/Algorithms/BresenhamsLine.cs b/src/FactorioTools/OilField/Algorithms/BresenhamsLine.cs index 6ae1d8b2..886a6c82 100644 --- a/src/FactorioTools/OilField/Algorithms/BresenhamsLine.cs +++ b/src/FactorioTools/OilField/Algorithms/BresenhamsLine.cs @@ -5,9 +5,9 @@ namespace Knapcode.FactorioTools.OilField; public static class BresenhamsLine { - public static List GetPath(Location a, Location b) + public static ITableList GetPath(Location a, Location b) { - var line = new List(); + var line = TableList.New(); var dx = Math.Abs(b.X - a.X); var sx = a.X < b.X ? 1 : -1; var dy = -1 * Math.Abs(b.Y - a.Y); diff --git a/src/FactorioTools/OilField/Algorithms/DijkstrasResult.cs b/src/FactorioTools/OilField/Algorithms/DijkstrasResult.cs index 7f8e6798..3ebaa6b1 100644 --- a/src/FactorioTools/OilField/Algorithms/DijkstrasResult.cs +++ b/src/FactorioTools/OilField/Algorithms/DijkstrasResult.cs @@ -14,16 +14,16 @@ public DijkstrasResult(ILocationDictionary locationToPrevious, ILo public ILocationDictionary LocationToPrevious { get; } public ILocationSet ReachedGoals { get; } - public List> GetStraightPaths(Location goal) + public ITableList> GetStraightPaths(Location goal) { - var paths = new List>(); + var paths = TableList.New>(); if (LocationToPrevious.TryGetValue(goal, out var previousLocations)) { if (previousLocations.Count == 0) { // This is a special case when the goal matches the starting point. - paths.Add(new List { goal }); + paths.Add(TableList.New(goal)); } foreach (var beforeGoal in previousLocations.EnumerateItems()) @@ -43,7 +43,7 @@ public List> GetStraightPaths(Location goal) }; var current = goal; - var path = new List(); + var path = TableList.New(); while (true) { path.Add(current); diff --git a/src/FactorioTools/OilField/Algorithms/Prims.cs b/src/FactorioTools/OilField/Algorithms/Prims.cs index 77ecc62f..fb8c7a47 100644 --- a/src/FactorioTools/OilField/Algorithms/Prims.cs +++ b/src/FactorioTools/OilField/Algorithms/Prims.cs @@ -57,8 +57,10 @@ public static ILocationDictionary GetMinimumSpanningTree( if (!digraph) { // Make the MST bidirectional (a graph, not a digraph). - foreach (var center in mst.Keys.ToList()) + var keys = mst.Keys.ToTableList(); + for (var i = 0; i < keys.Count; i++) { + var center = keys[i]; foreach (var neighbor in mst[center].EnumerateItems()) { if (!mst.TryGetValue(neighbor, out var otherNeighbors)) diff --git a/src/FactorioTools/OilField/Containers/DictionaryTableList.cs b/src/FactorioTools/OilField/Containers/DictionaryTableList.cs new file mode 100644 index 00000000..85f6f9cd --- /dev/null +++ b/src/FactorioTools/OilField/Containers/DictionaryTableList.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; + +namespace Knapcode.FactorioTools.OilField; + +public class DictionaryTableList : ITableList +{ + private readonly Dictionary _dictionary; + + public DictionaryTableList() + { + _dictionary = new Dictionary(); + } + + public DictionaryTableList(int capacity) + { + _dictionary = new Dictionary(capacity); + } + + public T this[int index] + { + get + { + if (index >= _dictionary.Count) + { + throw new IndexOutOfRangeException(); + } + + return _dictionary[index]; + } + set + { + if (index >= _dictionary.Count) + { + throw new IndexOutOfRangeException(); + } + + _dictionary[index] = value; + } + } + + public int Count => _dictionary.Count; + + public void Add(T item) + { + _dictionary.Add(_dictionary.Count, item); + } + + public void AddCollection(IReadOnlyCollection collection) + { + foreach (var item in collection) + { + _dictionary.Add(_dictionary.Count, item); + } + } + + public void AddRange(IReadOnlyTableList collection) + { + var other = (DictionaryTableList)collection; + for (var i = 0; i < other.Count; i++) + { + _dictionary.Add(_dictionary.Count, other[i]); + } + } + + public void Clear() + { + _dictionary.Clear(); + } + + public bool Contains(T item) + { + var comparer = EqualityComparer.Default; + foreach (var value in _dictionary.Values) + { + if (comparer.Equals(item, value)) + { + return true; + } + } + + return false; + } + + public IReadOnlyCollection EnumerateItems() + { + return _dictionary.Values; + } + + public bool Remove(T item) + { + var comparer = EqualityComparer.Default; + var index = -1; + foreach (var (key, value) in _dictionary) + { + if (comparer.Equals(item, value)) + { + index = key; + break; + } + } + + if (index == -1) + { + return false; + } + + RemoveAt(index); + return true; + } + + public void RemoveAt(int index) + { + RemoveRange(index, 1); + } + + public void RemoveRange(int index, int count) + { + var originalCount = _dictionary.Count; + if (index + count > originalCount) + { + throw new IndexOutOfRangeException(); + } + + var startIndex = index + count; + for (var i = startIndex; i < originalCount; i++) + { + _dictionary[i - count] = _dictionary[i]; + _dictionary.Remove(i); + } + + for (var i = originalCount - count; i < startIndex; i++) + { + _dictionary.Remove(i); + } + } + + public void Reverse() + { + var count = _dictionary.Count; + for (var i = 0; i < count / 2; i++) + { + var temp = _dictionary[i]; + var otherIndex = count - 1 - i; + _dictionary[i] = _dictionary[otherIndex]; + _dictionary[otherIndex] = temp; + } + } + + public void Sort(Comparison comparison) + { + var keys = new int[_dictionary.Count]; + var values = new T[_dictionary.Count]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = i; + values[i] = _dictionary[i]; + } + + Array.Sort(keys, (a, b) => comparison(this[a], this[b])); + + for (var i = 0; i < keys.Length; i++) + { + _dictionary[i] = values[keys[i]]; + } + } + + public void SortRange(int index, int count, IComparer comparer) + { + var keys = new int[_dictionary.Count]; + var values = new T[_dictionary.Count]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = i; + values[i] = _dictionary[i]; + } + + var keyComparer = Comparer.Create((a, b) => comparer.Compare(this[a], this[b])); + Array.Sort(keys, index, count, keyComparer); + + for (var i = 0; i < keys.Length; i++) + { + _dictionary[i] = values[keys[i]]; + } + } +} diff --git a/src/FactorioTools/OilField/Containers/IReadOnlyTableList.cs b/src/FactorioTools/OilField/Containers/IReadOnlyTableList.cs new file mode 100644 index 00000000..58161ec2 --- /dev/null +++ b/src/FactorioTools/OilField/Containers/IReadOnlyTableList.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Knapcode.FactorioTools.OilField; + +public interface IReadOnlyTableList +{ + T this[int index] { get; } + int Count { get; } + + bool Contains(T item); + IReadOnlyCollection EnumerateItems(); +} diff --git a/src/FactorioTools/OilField/Containers/ITableList.cs b/src/FactorioTools/OilField/Containers/ITableList.cs new file mode 100644 index 00000000..366882e3 --- /dev/null +++ b/src/FactorioTools/OilField/Containers/ITableList.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace Knapcode.FactorioTools.OilField; + +public interface ITableList : IReadOnlyTableList +{ + new T this[int index] { get; set; } + + void Add(T item); + void AddCollection(IReadOnlyCollection collection); + void AddRange(IReadOnlyTableList collection); + void Clear(); + bool Remove(T item); + void RemoveAt(int index); + void RemoveRange(int index, int count); + void Reverse(); + void Sort(Comparison comparison); + void SortRange(int index, int count, IComparer comparer); +} diff --git a/src/FactorioTools/OilField/Containers/ListTableList.cs b/src/FactorioTools/OilField/Containers/ListTableList.cs new file mode 100644 index 00000000..6a694010 --- /dev/null +++ b/src/FactorioTools/OilField/Containers/ListTableList.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; + +namespace Knapcode.FactorioTools.OilField; + +public class ListTableList : ITableList +{ + private readonly List _list; + + public ListTableList() + { + _list = new List(); + } + + public ListTableList(int capacity) + { + _list = new List(capacity); + } + + public T this[int index] + { + get => _list[index]; + set => _list[index] = value; + } + + public int Count => _list.Count; + + public void Add(T item) + { + _list.Add(item); + } + + public void AddCollection(IReadOnlyCollection collection) + { + _list.AddRange(collection); + } + + public void AddRange(IReadOnlyTableList collection) + { + _list.AddRange(((ListTableList)collection)._list); + } + + public void Clear() + { + _list.Clear(); + } + + public bool Contains(T item) + { + return _list.Contains(item); + } + + public IReadOnlyCollection EnumerateItems() + { + return _list; + } + + public bool Remove(T item) + { + return _list.Remove(item); + } + + public void RemoveAt(int index) + { + _list.RemoveAt(index); + } + + public void RemoveRange(int index, int count) + { + _list.RemoveRange(index, count); + } + + public void Reverse() + { + _list.Reverse(); + } + + public void Sort(Comparison comparison) + { + _list.Sort(comparison); + } + + public void SortRange(int index, int count, IComparer comparer) + { + _list.Sort(index, count, comparer); + } +} diff --git a/src/FactorioTools/OilField/Containers/TableList.cs b/src/FactorioTools/OilField/Containers/TableList.cs new file mode 100644 index 00000000..6e49a08c --- /dev/null +++ b/src/FactorioTools/OilField/Containers/TableList.cs @@ -0,0 +1,39 @@ +namespace Knapcode.FactorioTools.OilField; + +public static class TableList +{ + public static ITableList New() + { +#if USE_ARRAY + return new ListTableList(); +#else + return new DictionaryTableList(); +#endif + } + + public static ITableList New(int capacity) + { +#if USE_ARRAY + return new ListTableList(capacity); +#else + return new DictionaryTableList(capacity); +#endif + } + + public static ITableList New(T item) + { + var list = New(); + list.Add(item); + return list; + } + + public static IReadOnlyTableList Empty() + { + return EmptyInstances.Instance; + } + + private static class EmptyInstances + { + public static IReadOnlyTableList Instance { get; } = New(capacity: 0); + } +} diff --git a/src/FactorioTools/OilField/Helpers.cs b/src/FactorioTools/OilField/Helpers.cs index fddb8d2a..f2c0c21d 100644 --- a/src/FactorioTools/OilField/Helpers.cs +++ b/src/FactorioTools/OilField/Helpers.cs @@ -40,18 +40,19 @@ public static PumpjackCenter AddPumpjack(SquareGrid grid, Location center) return centerEntity; } - public static ILocationDictionary> GetCenterToTerminals(Context context, SquareGrid grid, IReadOnlyCollection centers) + public static ILocationDictionary> GetCenterToTerminals(Context context, SquareGrid grid, IReadOnlyTableList centers) { - var centerToTerminals = context.GetLocationDictionary>(); + var centerToTerminals = context.GetLocationDictionary>(); PopulateCenterToTerminals(centerToTerminals, grid, centers); return centerToTerminals; } - public static void PopulateCenterToTerminals(ILocationDictionary> centerToTerminals, SquareGrid grid, IReadOnlyCollection centers) + public static void PopulateCenterToTerminals(ILocationDictionary> centerToTerminals, SquareGrid grid, IReadOnlyTableList centers) { - foreach (var center in centers) + for (var i = 0; i < centers.Count; i++) { - var candidateTerminals = new List(); + var center = centers[i]; + var candidateTerminals = TableList.New(); foreach ((var direction, var translation) in TerminalOffsets) { var location = center.Translate(translation); @@ -72,22 +73,23 @@ public static void PopulateCenterToTerminals(ILocationDictionary> GetLocationToTerminals(Context context, ILocationDictionary> centerToTerminals) + public static ILocationDictionary> GetLocationToTerminals(Context context, ILocationDictionary> centerToTerminals) { - var locationToTerminals = context.GetLocationDictionary>(); + var locationToTerminals = context.GetLocationDictionary>(); PopulateLocationToTerminals(locationToTerminals, centerToTerminals); return locationToTerminals; } - public static void PopulateLocationToTerminals(ILocationDictionary> locationToTerminals, ILocationDictionary> centerToTerminals) + public static void PopulateLocationToTerminals(ILocationDictionary> locationToTerminals, ILocationDictionary> centerToTerminals) { foreach (var terminals in centerToTerminals.Values) { - foreach (var terminal in terminals) + for (var i = 0; i < terminals.Count; i++) { + var terminal = terminals[i]; if (!locationToTerminals.TryGetValue(terminal.Terminal, out var list)) { - list = new List(2); + list = TableList.New(2); locationToTerminals.Add(terminal.Terminal, list); } @@ -98,7 +100,7 @@ public static void PopulateLocationToTerminals(ILocationDictionary CandidateToInfo, CountedBitArray CoveredEntities, ILocationDictionary Providers) GetBeaconCandidateToCovered( Context context, - List recipients, + IReadOnlyTableList recipients, ICandidateFactory candidateFactory, bool removeUnused) where TInfo : CandidateInfo @@ -118,7 +120,7 @@ public static (ILocationDictionary CandidateToInfo, CountedBitArray Cover public static (ILocationDictionary CandidateToInfo, CountedBitArray CoveredEntities, ILocationDictionary Providers) GetElectricPoleCandidateToCovered( Context context, - List recipients, + IReadOnlyTableList recipients, ICandidateFactory candidateFactory, bool removeUnused) where TInfo : CandidateInfo @@ -138,7 +140,7 @@ public static (ILocationDictionary CandidateToInfo, CountedBitArray Cover private static (ILocationDictionary CandidateToInfo, CountedBitArray CoveredEntities, ILocationDictionary Providers) GetCandidateToCovered( Context context, - List recipients, + IReadOnlyTableList recipients, ICandidateFactory candidateFactory, int providerWidth, int providerHeight, @@ -294,7 +296,7 @@ private static (ILocationDictionary CandidateToInfo, CountedBitArray Cove if (providers.Count > 0 || unusedProviders.Count > 0) { // Remove candidates that only cover recipients that are already covered. - var toRemove = new List(); + var toRemove = TableList.New(); foreach ((var candidate, var info) in candidateToInfo.EnumeratePairs()) { var subset = new CountedBitArray(info.Covered); @@ -443,7 +445,7 @@ public static bool DoesProviderFit( return true; } - public static double GetEntityDistance(List poweredEntities, Location candidate, CountedBitArray covered) + public static double GetEntityDistance(ITableList poweredEntities, Location candidate, CountedBitArray covered) { double sum = 0; for (var i = 0; i < poweredEntities.Count; i++) @@ -463,7 +465,7 @@ public static void AddProviderAndPreventMultipleProviders( TInfo centerInfo, int providerWidth, int providerHeight, - List recipients, + IReadOnlyTableList recipients, CountedBitArray coveredEntities, Dictionary> coveredToCandidates, ILocationDictionary candidateToInfo) @@ -491,7 +493,7 @@ public static void AddProviderAndPreventMultipleProviders( coveredToCandidates); #if !USE_SHARED_INSTANCES - var toRemove = new List(); + var toRemove = TableList.New(); var updated = context.GetLocationSet(); #else var toRemove = context.SharedInstances.LocationListA; @@ -542,7 +544,7 @@ public static void AddProviderAndAllowMultipleProviders( TInfo centerInfo, int providerWidth, int providerHeight, - List recipients, + IReadOnlyTableList recipients, CountedBitArray coveredEntities, Dictionary> coveredToCandidates, ILocationDictionary candidateToInfo, @@ -578,7 +580,7 @@ public static void AddProviderAndAllowMultipleProviders( } #if !USE_SHARED_INSTANCES - var toRemove = new List(); + var toRemove = TableList.New(); var updated = context.GetLocationSet(); #else var toRemove = context.SharedInstances.LocationListA; @@ -667,7 +669,7 @@ public static void AddProviderAndAllowMultipleProviders( public static (ILocationDictionary PoleCenterToCoveredCenters, ILocationDictionary CoveredCenterToPoleCenters) GetElectricPoleCoverage( Context context, - List poweredEntities, + IReadOnlyTableList poweredEntities, IReadOnlyCollection electricPoleCenters) { var poleCenterToCoveredCenters = GetProviderCenterToCoveredCenters( @@ -690,9 +692,9 @@ public static (ILocationDictionary PoleCenterToCoveredCenters, ILo return (PoleCenterToCoveredCenters: poleCenterToCoveredCenters, CoveredCenterToPoleCenters: coveredCenterToPoleCenters); } - public static (List PoweredEntities, bool HasBeacons) GetPoweredEntities(Context context) + public static (ITableList PoweredEntities, bool HasBeacons) GetPoweredEntities(Context context) { - var poweredEntities = new List(); + var poweredEntities = TableList.New(); var hasBeacons = false; foreach (var location in context.Grid.EntityLocations.EnumerateItems()) @@ -924,13 +926,13 @@ public static void AddProviderToGrid( } } - public static void AddBeaconsToGrid(SquareGrid grid, OilFieldOptions options, IEnumerable centers) + public static void AddBeaconsToGrid(SquareGrid grid, OilFieldOptions options, IReadOnlyTableList centers) { - foreach (var center in centers) + for (var i = 0; i < centers.Count; i++) { AddProviderToGrid( grid, - center, + centers[i], new BeaconCenter(grid.GetId()), c => new BeaconSide(grid.GetId(), c), options.BeaconWidth, @@ -979,15 +981,15 @@ public static void EliminateOtherTerminals(Context context, TerminalLocation sel terminalOptions.Add(selectedTerminal); } - public static List GetPath(ILocationDictionary cameFrom, Location start, Location reachedGoal) + public static ITableList GetPath(ILocationDictionary cameFrom, Location start, Location reachedGoal) { var sizeEstimate = 2 * start.GetManhattanDistance(reachedGoal); - var path = new List(sizeEstimate); + var path = TableList.New(sizeEstimate); AddPath(cameFrom, reachedGoal, path); return path; } - public static void AddPath(ILocationDictionary cameFrom, Location reachedGoal, List outputList) + public static void AddPath(ILocationDictionary cameFrom, Location reachedGoal, ITableList outputList) { var current = reachedGoal; while (true) @@ -1003,7 +1005,7 @@ public static void AddPath(ILocationDictionary cameFrom, Location reac } } - public static bool AreLocationsCollinear(IReadOnlyList locations) + public static bool AreLocationsCollinear(IReadOnlyTableList locations) { double lastSlope = 0; for (var i = 0; i < locations.Count; i++) @@ -1030,7 +1032,7 @@ public static bool AreLocationsCollinear(IReadOnlyList locations) return false; } - public static int CountTurns(List path) + public static int CountTurns(IReadOnlyTableList path) { var previousDirection = -1; var turns = 0; @@ -1051,12 +1053,12 @@ public static int CountTurns(List path) return turns; } - public static List? MakeStraightLineOnEmpty(SquareGrid grid, Location a, Location b) + public static ITableList? MakeStraightLineOnEmpty(SquareGrid grid, Location a, Location b) { if (a.X == b.X) { (var min, var max) = a.Y < b.Y ? (a.Y, b.Y) : (b.Y, a.Y); - var line = new List(max - min + 1); + var line = TableList.New(max - min + 1); for (var y = min; y <= max; y++) { if (!grid.IsEmpty(new Location(a.X, y))) @@ -1073,7 +1075,7 @@ public static int CountTurns(List path) if (a.Y == b.Y) { (var min, var max) = a.X < b.X ? (a.X, b.X) : (b.X, a.X); - var line = new List(max - min + 1); + var line = TableList.New(max - min + 1); for (var x = min; x <= max; x++) { if (!grid.IsEmpty(new Location(x, a.Y))) @@ -1090,12 +1092,12 @@ public static int CountTurns(List path) throw new ArgumentException("The two points must be one the same line either horizontally or vertically."); } - public static List MakeStraightLine(Location a, Location b) + public static ITableList MakeStraightLine(Location a, Location b) { if (a.X == b.X) { (var min, var max) = a.Y < b.Y ? (a.Y, b.Y) : (b.Y, a.Y); - var line = new List(max - min + 1); + var line = TableList.New(max - min + 1); for (var y = min; y <= max; y++) { line.Add(new Location(a.X, y)); @@ -1107,7 +1109,7 @@ public static List MakeStraightLine(Location a, Location b) if (a.Y == b.Y) { (var min, var max) = a.X < b.X ? (a.X, b.X) : (b.X, a.X); - var line = new List(max - min + 1); + var line = TableList.New(max - min + 1); for (var x = min; x <= max; x++) { line.Add(new Location(x, a.Y)); @@ -1119,20 +1121,20 @@ public static List MakeStraightLine(Location a, Location b) throw new ArgumentException("The two points must be one the same line either horizontally or vertically."); } - public static List PointsToLines(IReadOnlyCollection nodes) + public static ITableList PointsToLines(IReadOnlyCollection nodes) { - return PointsToLines(nodes.ToList(), sort: true); + return PointsToLines(nodes.ToTableList(), sort: true); } /// /// Source: https://github.com/teoxoy/factorio-blueprint-editor/blob/21ab873d8316a41b9a05c719697d461d3ede095d/packages/editor/src/core/generators/util.ts#L62 /// - public static List PointsToLines(IReadOnlyList nodes, bool sort) + public static ITableList PointsToLines(IReadOnlyTableList nodes, bool sort) { - IReadOnlyList filteredNodes; + IReadOnlyTableList filteredNodes; if (sort) { - var sortedXY = nodes.ToList(); + var sortedXY = nodes.ToTableList(); sortedXY.Sort((a, b) => { var c = a.X.CompareTo(b.X); @@ -1152,17 +1154,17 @@ public static List PointsToLines(IReadOnlyList nodes, bool if (filteredNodes.Count == 1) { - return new List { new Endpoints(filteredNodes[0], filteredNodes[0]) }; + return TableList.New(new Endpoints(filteredNodes[0], filteredNodes[0])); } else if (filteredNodes.Count == 2) { - return new List { new Endpoints(filteredNodes[0], filteredNodes[1]) }; + return TableList.New(new Endpoints(filteredNodes[0], filteredNodes[1])); } // Check that nodes are not collinear if (AreLocationsCollinear(filteredNodes)) { - var collinearLines = new List(filteredNodes.Count - 1); + var collinearLines = TableList.New(filteredNodes.Count - 1); for (var i = 1; i < filteredNodes.Count; i++) { collinearLines.Add(new Endpoints(filteredNodes[i - 1], filteredNodes[i])); @@ -1179,7 +1181,7 @@ public static List PointsToLines(IReadOnlyList nodes, bool } var delaunator = new Delaunator(points); - var lines = new List(); + var lines = TableList.New(); for (var e = 0; e < delaunator.Triangles.Length; e++) { if (e > delaunator.Halfedges[e]) diff --git a/src/FactorioTools/OilField/Models/BeaconSolution.cs b/src/FactorioTools/OilField/Models/BeaconSolution.cs index 8430b35f..e085e28d 100644 --- a/src/FactorioTools/OilField/Models/BeaconSolution.cs +++ b/src/FactorioTools/OilField/Models/BeaconSolution.cs @@ -2,4 +2,4 @@ namespace Knapcode.FactorioTools.OilField; -public record BeaconSolution(BeaconStrategy Strategy, List Beacons, int Effects); \ No newline at end of file +public record BeaconSolution(BeaconStrategy Strategy, ITableList Beacons, int Effects); \ No newline at end of file diff --git a/src/FactorioTools/OilField/Models/Context.cs b/src/FactorioTools/OilField/Models/Context.cs index 42747ab8..532ad128 100644 --- a/src/FactorioTools/OilField/Models/Context.cs +++ b/src/FactorioTools/OilField/Models/Context.cs @@ -11,10 +11,10 @@ public class Context public required float DeltaX { get; set; } public required float DeltaY { get; set; } public required SquareGrid Grid { get; set; } - public required List Centers { get; set; } - public required ILocationDictionary> CenterToTerminals { get; set; } + public required ITableList Centers { get; set; } + public required ILocationDictionary> CenterToTerminals { get; set; } public required ILocationDictionary CenterToOriginalDirection { get; set; } - public required ILocationDictionary> LocationToTerminals { get; set; } + public required ILocationDictionary> LocationToTerminals { get; set; } public required int[] LocationToAdjacentCount { get; set; } public required SharedInstances SharedInstances { get; set; } @@ -115,70 +115,6 @@ public ILocationSet GetLocationSet(Location location, int capacity, bool allowEn return set; } - public ILocationSet GetLocationSet(IReadOnlyCollection locations) - { - return GetLocationSet(locations, allowEnumerate: false); - } - - public ILocationSet GetLocationSet(IReadOnlyCollection locations, bool allowEnumerate) - { - var set = GetLocationSet(allowEnumerate); - foreach (var location in locations) - { - set.Add(location); - } - - return set; - } - - public ILocationSet GetReadOnlyLocationSet(IReadOnlyCollection locations) - { - return GetReadOnlyLocationSet(locations, allowEnumerate: false); - } - - public ILocationSet GetReadOnlyLocationSet(IReadOnlyCollection locations, bool allowEnumerate) - { - Location firstLocation = Location.Invalid; - int itemCount = 0; - ILocationSet? set = null; - foreach (var location in locations) - { - if (itemCount == 0) - { - firstLocation = location; - } - else if (itemCount == 1) - { - set = GetLocationSet(allowEnumerate); - set.Add(firstLocation); - set.Add(location); - } - else - { - set!.Add(location); - } - - itemCount++; - } - - if (set is null) - { - if (itemCount == 0) - { - set = EmptyLocationSet.Instance; - } - else if (itemCount == 1) - { - set = new SingleLocationSet(firstLocation); - } - else - { - throw new NotImplementedException(); - } - } - return set; - } - public SingleLocationSet GetSingleLocationSet(Location location) { return new SingleLocationSet(location); diff --git a/src/FactorioTools/OilField/Models/OilFieldPlanSummary.cs b/src/FactorioTools/OilField/Models/OilFieldPlanSummary.cs index 956ee019..9a9b7338 100644 --- a/src/FactorioTools/OilField/Models/OilFieldPlanSummary.cs +++ b/src/FactorioTools/OilField/Models/OilFieldPlanSummary.cs @@ -13,6 +13,6 @@ namespace Knapcode.FactorioTools.OilField; public record OilFieldPlanSummary( int MissingPumpjacks, int RotatedPumpjacks, - IReadOnlyList SelectedPlans, - IReadOnlyList AlternatePlans, - IReadOnlyList UnusedPlans); + ITableList SelectedPlans, + ITableList AlternatePlans, + ITableList UnusedPlans); diff --git a/src/FactorioTools/OilField/Models/SharedInstances.cs b/src/FactorioTools/OilField/Models/SharedInstances.cs index 0f430b3f..806e40a0 100644 --- a/src/FactorioTools/OilField/Models/SharedInstances.cs +++ b/src/FactorioTools/OilField/Models/SharedInstances.cs @@ -49,8 +49,8 @@ public void ReturnNeighborArray(Location[] array) public ILocationDictionary LocationToLocation; public ILocationDictionary LocationToDouble; public PriorityQueue LocationPriorityQueue = new(); - public List LocationListA = new(); - public List LocationListB = new(); + public ITableList LocationListA = TableList.New(); + public ITableList LocationListB = TableList.New(); public ILocationSet LocationSetA; public ILocationSet LocationSetB; diff --git a/src/FactorioTools/OilField/OilFieldOptions.cs b/src/FactorioTools/OilField/OilFieldOptions.cs index 5fdd4cbc..e83c13b5 100644 --- a/src/FactorioTools/OilField/OilFieldOptions.cs +++ b/src/FactorioTools/OilField/OilFieldOptions.cs @@ -146,12 +146,12 @@ public static OilFieldOptions ForSubstation /// /// The pipe planning strategies to attempt. /// - public List PipeStrategies { get; set; } = new List(DefaultPipeStrategies); + public ITableList PipeStrategies { get; set; } = DefaultPipeStrategies.ToTableList(); /// /// The beacon planning strategies to attempt. This will have no affect if is false. /// - public List BeaconStrategies { get; set; } = new List(DefaultBeaconStrategies); + public ITableList BeaconStrategies { get; set; } = DefaultBeaconStrategies.ToTableList(); /// /// The internal entity name for the electric pole to use. diff --git a/src/FactorioTools/OilField/Planner.cs b/src/FactorioTools/OilField/Planner.cs index 46d1912b..38635e38 100644 --- a/src/FactorioTools/OilField/Planner.cs +++ b/src/FactorioTools/OilField/Planner.cs @@ -12,8 +12,8 @@ public static PlannerResult ExecuteSample() { var options = OilFieldOptions.ForMediumElectricPole; - options.PipeStrategies = OilFieldOptions.AllPipeStrategies.ToList(); - options.BeaconStrategies = OilFieldOptions.AllBeaconStrategies.ToList(); + options.PipeStrategies = OilFieldOptions.AllPipeStrategies.ToTableList(); + options.BeaconStrategies = OilFieldOptions.AllBeaconStrategies.ToTableList(); options.ValidateSolution = true; var inputBlueprint = new Blueprint @@ -88,10 +88,10 @@ public static PlannerResult Execute(OilFieldOptions options, Blueprint inputBlue return Execute( options, inputBlueprint, - avoid: Array.Empty()); + avoid: TableList.Empty()); } - public static PlannerResult Execute(OilFieldOptions options, Blueprint inputBlueprint, IReadOnlyList avoid) + public static PlannerResult Execute(OilFieldOptions options, Blueprint inputBlueprint, IReadOnlyTableList avoid) { return Execute( options, @@ -104,7 +104,7 @@ public static PlannerResult Execute(OilFieldOptions options, Blueprint inputBlue private static PlannerResult Execute( OilFieldOptions options, Blueprint blueprint, - IReadOnlyList avoid, + IReadOnlyTableList avoid, ILocationSet electricPolesAvoid, EletricPolesMode electricPolesMode) { @@ -210,7 +210,13 @@ private static PlannerResult Execute( var rotatedPumpjacks = 0; foreach ((var location, var originalDirection) in context.CenterToOriginalDirection.EnumeratePairs()) { - var finalDirection = context.CenterToTerminals[location].Single().Direction; + var terminals = context.CenterToTerminals[location]; + if (terminals.Count != 1) + { + throw new FactorioToolsException("There should be exactly one terminal at this point."); + } + + var finalDirection = terminals[0].Direction; if (originalDirection != finalDirection) { rotatedPumpjacks++; diff --git a/src/FactorioTools/OilField/Steps/AddElectricPoles.cs b/src/FactorioTools/OilField/Steps/AddElectricPoles.cs index 1e4958fe..434855ae 100644 --- a/src/FactorioTools/OilField/Steps/AddElectricPoles.cs +++ b/src/FactorioTools/OilField/Steps/AddElectricPoles.cs @@ -81,7 +81,7 @@ private enum RetryStrategy return electricPoles.Keys.ToReadOnlySet(context); } - private static void RemoveExtraElectricPoles(Context context, List poweredEntities, ILocationDictionary electricPoles) + private static void RemoveExtraElectricPoles(Context context, ITableList poweredEntities, ILocationDictionary electricPoles) { (var poleCenterToCoveredCenters, var coveredCenterToPoleCenters) = GetElectricPoleCoverage(context, poweredEntities, electricPoles.Keys); @@ -202,7 +202,7 @@ private static int GetElectricPoleDistanceSquared(Location a, Location b, OilFie return b.GetEuclideanDistanceSquared(a.X + offsetX, a.Y + offsetY); } - private static (List? ElectricPoleList, List PoweredEntities) AddElectricPolesAroundEntities( + private static (ITableList? ElectricPoleList, ITableList PoweredEntities) AddElectricPolesAroundEntities( Context context, bool allowRetries) { @@ -312,9 +312,9 @@ public ElectricPoleCandidateInfo Create(CountedBitArray covered) } } - private static (List? ElectricPoleList, CountedBitArray CoveredEntities) AddElectricPolesAroundEntities( + private static (ITableList? ElectricPoleList, CountedBitArray CoveredEntities) AddElectricPolesAroundEntities( Context context, - List poweredEntities, + ITableList poweredEntities, CountedBitArray? entitiesToPowerFirst) { (var allCandidateToInfo, var coveredEntities, var electricPoles2) = GetElectricPoleCandidateToCovered( @@ -323,7 +323,7 @@ private static (List? ElectricPoleList, CountedBitArray CoveredEntitie CandidateFactory.Instance, removeUnused: true); - var electricPoleList = electricPoles2.Keys.ToList(); + var electricPoleList = electricPoles2.Keys.ToTableList(); PopulateCandidateToInfo(context, allCandidateToInfo, entitiesToPowerFirst, poweredEntities, electricPoleList); @@ -472,8 +472,8 @@ private static void PopulateCandidateToInfo( Context context, ILocationDictionary candidateToInfo, CountedBitArray? entitiesToPowerFirst, - List poweredEntities, - List electricPoleList) + ITableList poweredEntities, + ITableList electricPoleList) { foreach ((var candidate, var info) in candidateToInfo.EnumeratePairs()) { @@ -632,8 +632,8 @@ private static void ConnectElectricPoles(Context context, ILocationDictionary g.Contains(endpoint.A)); - var groupB = groups.Single(g => g.Contains(endpoint.B)); + var groupA = groups.EnumerateItems().Single(g => g.Contains(endpoint.A)); + var groupB = groups.EnumerateItems().Single(g => g.Contains(endpoint.B)); if (groupA == groupB) { continue; @@ -662,7 +662,7 @@ private static void ConnectElectricPoles(Context context, ILocationDictionary electricPoles, List groups, double distance, Endpoints endpoints) + private static void AddSinglePoleForConnection(Context context, ILocationDictionary electricPoles, ITableList groups, double distance, Endpoints endpoints) { var segments = (int)Math.Ceiling(distance / context.Options.ElectricPoleWireReach); var idealLine = BresenhamsLine.GetPath(endpoints.A, endpoints.B); @@ -735,7 +735,7 @@ private static void AddSinglePoleForConnection(Context context, ILocationDiction } var center = AddElectricPole(context, electricPoles, selectedPoint); - var connectedGroups = new List(groups.Count); + var connectedGroups = TableList.New(groups.Count); for (var i = 0; i < groups.Count; i++) { var group = groups[i]; @@ -771,9 +771,9 @@ private static void AddSinglePoleForConnection(Context context, ILocationDiction // Visualizer.Show(context.Grid, Array.Empty(), Array.Empty()); } - private static List GetElectricPoleGroups(Context context, ILocationDictionary electricPoles) + private static ITableList GetElectricPoleGroups(Context context, ILocationDictionary electricPoles) { - var groups = new List(); + var groups = TableList.New(); var remaining = electricPoles.Keys.ToSet(context, allowEnumerate: true); while (remaining.Count > 0) { diff --git a/src/FactorioTools/OilField/Steps/AddPipes.0.cs b/src/FactorioTools/OilField/Steps/AddPipes.0.cs index 020629f2..0ec29e62 100644 --- a/src/FactorioTools/OilField/Steps/AddPipes.0.cs +++ b/src/FactorioTools/OilField/Steps/AddPipes.0.cs @@ -8,7 +8,7 @@ namespace Knapcode.FactorioTools.OilField; public static class AddPipes { - public static (List SelectedPlans, List AlternatePlans, List UnusedPlans) + public static (ITableList SelectedPlans, ITableList AlternatePlans, ITableList UnusedPlans) Execute(Context context, bool eliminateStrandedTerminals) { if (eliminateStrandedTerminals) @@ -16,9 +16,9 @@ public static (List SelectedPlans, List AlternatePla EliminateStrandedTerminals(context); } - List selectedPlans; - List alternatePlans; - List unusedPlans; + ITableList selectedPlans; + ITableList alternatePlans; + ITableList unusedPlans; Solution bestSolution; BeaconSolution? bestBeacons; @@ -49,7 +49,12 @@ public static (List SelectedPlans, List AlternatePla return (selectedPlans, alternatePlans, unusedPlans); } - private record SolutionInfo(List SelectedPlans, List AltnernatePlans, List UnusedPlans, Solution BestSolution, BeaconSolution? BestBeacons); + private record SolutionInfo( + ITableList SelectedPlans, + ITableList AlternatePlans, + ITableList UnusedPlans, + Solution BestSolution, + BeaconSolution? BestBeacons); private static Result GetBestSolution(Context context) { @@ -114,12 +119,14 @@ private static Result GetBestSolution(Context context) PlanInfo? bestPlanInfo = null; var noMoreAlternates = false; - var selectedPlans = new List(); - var alternatePlans = new List(); - var unusedPlans = new List(); + var selectedPlans = TableList.New(); + var alternatePlans = TableList.New(); + var unusedPlans = TableList.New(); - foreach (var planInfo in sortedPlans) + for (var i = 0; i < sortedPlans.Count; i++) { + var planInfo = sortedPlans[i]; + if (noMoreAlternates) { unusedPlans.Add(planInfo.Plan); @@ -159,26 +166,29 @@ private static Result GetBestSolution(Context context) return Result.NewData(new SolutionInfo(selectedPlans, alternatePlans, unusedPlans, bestPlanInfo.Pipes, bestPlanInfo.Beacons)); } - private static Result> GetAllPlans(Context context) + private static Result> GetAllPlans(Context context) { var result = GetSolutionGroups(context); if (result.Exception is not null) { - return Result.NewException>(result.Exception); + return Result.NewException>(result.Exception); } var solutionGroups = result.Data!; - var plans = new List(); + var plans = TableList.New(); foreach ((var solutionGroup, var groupNumber) in solutionGroups) { - foreach (var solution in solutionGroup) + for (var i = 0; i < solutionGroup.Count; i++) { + var solution = solutionGroup[i]; if (solution.BeaconSolutions is null) { - foreach (var strategy in solution.Strategies) + for (var j = 0; j < solution.Strategies.Count; j++) { - foreach (var optimized in solution.Optimized) + var strategy = solution.Strategies[j]; + for (var k = 0; k < solution.Optimized.Count; k++) { + var optimized = solution.Optimized[k]; var plan = new OilFieldPlan( strategy, optimized, @@ -194,15 +204,17 @@ private static Result> GetAllPlans(Context context) } else { - foreach (var beacons in solution.BeaconSolutions) + for (var j = 0; j < solution.BeaconSolutions.Count; j++) { - foreach (var strategy in solution.Strategies) + var beacons = solution.BeaconSolutions[j]; + for (var k = 0; k < solution.Strategies.Count; k++) { - foreach (var optimized in solution.Optimized) + var strategy = solution.Strategies[k]; + for (var l = 0; l < solution.Optimized.Count; l++) { var plan = new OilFieldPlan( strategy, - optimized, + solution.Optimized[l], beacons.Strategy, beacons.Effects, beacons.Beacons.Count, @@ -226,7 +238,7 @@ private static Result> GetSolutionG var originalLocationToTerminals = context.LocationToTerminals; var pipesToSolutions = new Dictionary(LocationSetComparer.Instance); - var connectedCentersToSolutions = new Dictionary, List>(ConnectedCentersComparer.Instance); + var connectedCentersToSolutions = new Dictionary, ITableList>(ConnectedCentersComparer.Instance); if (context.CenterToTerminals.Count == 1) { @@ -234,22 +246,23 @@ private static Result> GetSolutionG EliminateOtherTerminals(context, terminal); var pipes = context.GetSingleLocationSet(terminal.Terminal); var solutions = OptimizeAndAddSolutions(context, pipesToSolutions, default, pipes, centerToConnectedCenters: null); - var solution = solutions.Single(); + var solution = solutions.EnumerateItems().Single(); solution.Strategies.Clear(); solution.Strategies.AddRange(context.Options.PipeStrategies); } else { var completedStrategies = new CountedBitArray((int)PipeStrategy.ConnectedCentersFlute + 1); // max value - foreach (var strategy in context.Options.PipeStrategies) + for (var i = 0; i < context.Options.PipeStrategies.Count; i++) { + var strategy = context.Options.PipeStrategies[i]; if (completedStrategies[(int)strategy]) { continue; } - context.CenterToTerminals = originalCenterToTerminals.EnumeratePairs().ToDictionary(context, x => x.Key, x => x.Value.ToList()); - context.LocationToTerminals = originalLocationToTerminals.EnumeratePairs().ToDictionary(context, x => x.Key, x => x.Value.ToList()); + context.CenterToTerminals = originalCenterToTerminals.EnumeratePairs().ToDictionary(context, x => x.Key, x => x.Value.ToTableList()); + context.LocationToTerminals = originalLocationToTerminals.EnumeratePairs().ToDictionary(context, x => x.Key, x => x.Value.ToTableList()); switch (strategy) { @@ -278,9 +291,9 @@ private static Result> GetSolutionG if (connectedCentersToSolutions.TryGetValue(centerToConnectedCenters, out var solutions)) { - foreach (var solution in solutions) + for (var j = 0; j < solutions.Count; j++) { - solution.Strategies.Add(strategy); + solutions[j].Strategies.Add(strategy); } continue; } @@ -306,7 +319,7 @@ private static Result> GetSolutionG return Result.NewData>(pipesToSolutions.Values); } - private static List OptimizeAndAddSolutions( + private static ITableList OptimizeAndAddSolutions( Context context, Dictionary pipesToSolutions, PipeStrategy strategy, @@ -316,9 +329,9 @@ private static List OptimizeAndAddSolutions( SolutionsAndGroupNumber? solutionsAndIndex; if (pipesToSolutions.TryGetValue(pipes, out solutionsAndIndex)) { - foreach (var solution in solutionsAndIndex.Solutions) + for (var i = 0; i < solutionsAndIndex.Solutions.Count; i++) { - solution.Strategies.Add(strategy); + solutionsAndIndex.Solutions[i].Strategies.Add(strategy); } return solutionsAndIndex.Solutions; @@ -332,22 +345,20 @@ private static List OptimizeAndAddSolutions( ILocationSet optimizedPipes = pipes; if (context.Options.OptimizePipes) { - context.CenterToTerminals = originalCenterToTerminals.EnumeratePairs().ToDictionary(context, x => x.Key, x => x.Value.ToList()); - context.LocationToTerminals = originalLocationToTerminals.EnumeratePairs().ToDictionary(context, x => x.Key, x => x.Value.ToList()); + context.CenterToTerminals = originalCenterToTerminals.EnumeratePairs().ToDictionary(context, x => x.Key, x => x.Value.ToTableList()); + context.LocationToTerminals = originalLocationToTerminals.EnumeratePairs().ToDictionary(context, x => x.Key, x => x.Value.ToTableList()); optimizedPipes = context.GetLocationSet(pipes); RotateOptimize.Execute(context, optimizedPipes); // Visualizer.Show(context.Grid, optimizedPipes.Select(p => (IPoint)new Point(p.X, p.Y)), Array.Empty()); } - List solutions; + ITableList solutions; if (pipes.SetEquals(optimizedPipes)) { optimizedPipes = context.Options.UseUndergroundPipes ? context.GetLocationSet(pipes) : pipes; - solutions = new List - { - GetSolution(context, strategy, optimized: false, centerToConnectedCenters, optimizedPipes) - }; + solutions = TableList.New( + GetSolution(context, strategy, optimized: false, centerToConnectedCenters, optimizedPipes)); if (context.Options.OptimizePipes) { @@ -358,14 +369,16 @@ private static List OptimizeAndAddSolutions( { var solutionA = GetSolution(context, strategy, optimized: true, centerToConnectedCenters, optimizedPipes); - context.CenterToTerminals = originalCenterToTerminals.EnumeratePairs().ToDictionary(context, x => x.Key, x => x.Value.ToList()); - context.LocationToTerminals = originalLocationToTerminals.EnumeratePairs().ToDictionary(context, x => x.Key, x => x.Value.ToList()); + context.CenterToTerminals = originalCenterToTerminals.EnumeratePairs().ToDictionary(context, x => x.Key, x => x.Value.ToTableList()); + context.LocationToTerminals = originalLocationToTerminals.EnumeratePairs().ToDictionary(context, x => x.Key, x => x.Value.ToTableList()); var pipesB = context.Options.UseUndergroundPipes ? context.GetLocationSet(pipes) : pipes; var solutionB = GetSolution(context, strategy, optimized: false, centerToConnectedCenters, pipesB); Validate.PipesDoNotMatch(context, solutionA.Pipes, solutionB.Pipes); - solutions = new List { solutionA, solutionB }; + solutions = TableList.New(); + solutions.Add(solutionA); + solutions.Add(solutionB); } pipesToSolutions.Add(pipes, new SolutionsAndGroupNumber(solutions, pipesToSolutions.Count + 1)); @@ -373,7 +386,7 @@ private static List OptimizeAndAddSolutions( return solutions; } - private record SolutionsAndGroupNumber(List Solutions, int GroupNumber); + private record SolutionsAndGroupNumber(ITableList Solutions, int GroupNumber); private static Solution GetSolution( Context context, @@ -392,7 +405,7 @@ private static Solution GetSolution( undergroundPipes = PlanUndergroundPipes.Execute(context, optimizedPipes); } - List? beaconSolutions = null; + ITableList? beaconSolutions = null; if (context.Options.AddBeacons) { beaconSolutions = PlanBeacons.Execute(context, optimizedPipes); @@ -404,8 +417,8 @@ private static Solution GetSolution( return new Solution { - Strategies = new List { strategy }, - Optimized = new List { optimized }, + Strategies = TableList.New(strategy), + Optimized = TableList.New(optimized), CenterToConnectedCenters = centerToConnectedCenters, CenterToTerminals = context.CenterToTerminals, LocationToTerminals = context.LocationToTerminals, @@ -460,12 +473,14 @@ private static void EliminateStrandedTerminals(Context context) bool foundStranded = false; foreach (var location in terminalsToEliminate.EnumerateItems()) { - foreach (var terminal in context.LocationToTerminals[location]) + var terminals = context.LocationToTerminals[location]; + for (var i = 0; i < terminals.Count; i++) { - var terminals = context.CenterToTerminals[terminal.Center]; - terminals.Remove(terminal); + var terminal = terminals[i]; + var otherTerminals = context.CenterToTerminals[terminal.Center]; + otherTerminals.Remove(terminal); - if (terminals.Count == 0) + if (otherTerminals.Count == 0) { strandedTerminal = terminal.Terminal; foundStranded = true; @@ -490,15 +505,15 @@ private static void EliminateStrandedTerminals(Context context) private class Solution { - public required List Strategies { get; set; } - public required List Optimized { get; set; } + public required ITableList Strategies { get; set; } + public required ITableList Optimized { get; set; } public required ILocationDictionary? CenterToConnectedCenters { get; set; } - public required ILocationDictionary> CenterToTerminals { get; set; } - public required ILocationDictionary> LocationToTerminals { get; set; } + public required ILocationDictionary> CenterToTerminals { get; set; } + public required ILocationDictionary> LocationToTerminals { get; set; } public required int PipeCountWithoutUnderground { get; set; } public required ILocationSet Pipes { get; set; } public required ILocationDictionary? UndergroundPipes { get; set; } - public required List? BeaconSolutions { get; set; } + public required ITableList? BeaconSolutions { get; set; } } private class LocationSetComparer : IEqualityComparer diff --git a/src/FactorioTools/OilField/Steps/AddPipes.1.FBE.cs b/src/FactorioTools/OilField/Steps/AddPipes.1.FBE.cs index 2fc733df..1affea9f 100644 --- a/src/FactorioTools/OilField/Steps/AddPipes.1.FBE.cs +++ b/src/FactorioTools/OilField/Steps/AddPipes.1.FBE.cs @@ -30,30 +30,34 @@ public static Result Execute(Context context, PipeStrategy strategy) (var terminals, var pipes, var finalStrategy) = result.Data!; - foreach (var terminal in terminals) + for (var i = 0; i < terminals.Count; i++) { - EliminateOtherTerminals(context, terminal); + EliminateOtherTerminals(context, terminals[i]); } return Result.NewData(new FbeResult(pipes, finalStrategy)); } - private record FbeResultInfo(IReadOnlyList Terminals, ILocationSet Pipes, PipeStrategy FinalStrategy); + private record FbeResultInfo(IReadOnlyTableList Terminals, ILocationSet Pipes, PipeStrategy FinalStrategy); private static Result DelaunayTriangulation(Context context, Location middle, PipeStrategy strategy) { // GENERATE LINES - var lines = new List(); + var lines = TableList.New(); var allLines = PointsToLines(context.Centers, sort: false); for (var i = 0; i < allLines.Count; i++) { var line = allLines[i]; - var connections = new List(); + var connections = TableList.New(); - foreach (var tA in context.CenterToTerminals[line.A]) + var terminalsA = context.CenterToTerminals[line.A]; + for (var j = 0; j < terminalsA.Count; j++) { - foreach (var tB in context.CenterToTerminals[line.B]) + var tA = terminalsA[j]; + var terminalsB = context.CenterToTerminals[line.B]; + for (var k = 0; k < terminalsB.Count; k++) { + var tB = terminalsB[k]; if (tA.Terminal.X != tB.Terminal.X && tA.Terminal.Y != tB.Terminal.Y) { continue; @@ -78,15 +82,15 @@ private static Result DelaunayTriangulation(Context context, Loca } // GENERATE GROUPS - var groups = new List(); - var addedPumpjacks = new List(); - var leftoverPumpjacks = context.Centers.ToSet(context, allowEnumerate: true); + var groups = TableList.New(); + var addedPumpjacks = TableList.New(); + var leftoverPumpjacks = context.Centers.EnumerateItems().ToSet(context, allowEnumerate: true); while (lines.Count > 0) { var line = GetNextLine(lines, addedPumpjacks); - var addedA = addedPumpjacks.FirstOrDefault(x => x.Center == line.Endpoints.A); - var addedB = addedPumpjacks.FirstOrDefault(x => x.Center == line.Endpoints.B); + var addedA = addedPumpjacks.EnumerateItems().FirstOrDefault(x => x.Center == line.Endpoints.A); + var addedB = addedPumpjacks.EnumerateItems().FirstOrDefault(x => x.Center == line.Endpoints.B); line.Connections.Sort((a, b) => { @@ -99,11 +103,12 @@ private static Result DelaunayTriangulation(Context context, Loca return a.Line.Count.CompareTo(b.Line.Count); }); - foreach (var connection in line.Connections) + for (var i = 0; i < line.Connections.Count; i++) { + var connection = line.Connections[i]; if (addedA is null && addedB is null) { - var group = new Group(context, connection, new List> { connection.Line }); + var group = new Group(context, connection, TableList.New(connection.Line)); groups.Add(group); addedPumpjacks.Add(connection.TerminalA); addedPumpjacks.Add(connection.TerminalB); @@ -114,7 +119,7 @@ private static Result DelaunayTriangulation(Context context, Loca if (addedA is null && addedB is not null && addedB.Direction == connection.TerminalB.Direction) { - var group = groups.First(g => g.HasTerminal(addedB)); + var group = groups.EnumerateItems().First(g => g.HasTerminal(addedB)); group.Add(connection.TerminalA); group.Paths.Add(connection.Line); addedPumpjacks.Add(connection.TerminalA); @@ -124,7 +129,7 @@ private static Result DelaunayTriangulation(Context context, Loca if (addedA is not null && addedB is null && addedA.Direction == connection.TerminalA.Direction) { - var group = groups.First(g => g.HasTerminal(addedA)); + var group = groups.EnumerateItems().First(g => g.HasTerminal(addedA)); group.Add(connection.TerminalB); group.Paths.Add(connection.Line); addedPumpjacks.Add(connection.TerminalB); @@ -189,19 +194,19 @@ private static Result DelaunayTriangulation(Context context, Loca throw new FactorioToolsException("A connection between terminals should have been found."); } - var group = new Group(context, connection, new List> { connection.Line }); + var group = new Group(context, connection, TableList.New(connection.Line)); groups.Add(group); } // CONNECT GROUPS var maxTries = strategy == PipeStrategy.FbeOriginal ? 3 : 20; var tries = maxTries; - var aloneGroups = new List(); + var aloneGroups = TableList.New(); Group? finalGroup = null; while (groups.Count > 0) { - var group = groups.MinBy(x => x.Paths.Sum(p => p.Count))!; + var group = groups.EnumerateItems().MinBy(x => x.Paths.EnumerateItems().Sum(p => p.Count))!; groups.Remove(group); if (groups.Count == 0) @@ -223,7 +228,7 @@ private static Result DelaunayTriangulation(Context context, Loca locationToGroup.Add(group.Location, group); var groupLines = PointsToLines(locationToGroup.Keys); - var par = new List(groupLines.Count); + var par = TableList.New(groupLines.Count); for (var i = 0; i < groupLines.Count; i++) { var line = groupLines[i]; @@ -306,11 +311,11 @@ private static Result DelaunayTriangulation(Context context, Loca { var center = sortedLeftoverPumpjacks[i]; var centerTerminals = context.CenterToTerminals[center]; - var terminalGroups = new List(centerTerminals.Count); + var terminalGroups = TableList.New(centerTerminals.Count); for (var j = 0; j < centerTerminals.Count; j++) { var terminal = centerTerminals[j]; - var group = new Group(context, terminal, new List> { new List { terminal.Terminal } }); + var group = new Group(context, terminal, TableList.New(TableList.New(terminal.Terminal))); terminalGroups.Add(group); } @@ -347,7 +352,12 @@ private static Result DelaunayTriangulation(Context context, Loca continue; } - finalGroup.Add(connection.FirstGroup.Entities.Single()); + if (connection.FirstGroup.Entities.Count != 1) + { + throw new FactorioToolsException("There should be a single entity in the group."); + } + + finalGroup.Add(connection.FirstGroup.Entities[0]); finalGroup.Paths.Add(connection.Lines[0]); break; } @@ -367,7 +377,7 @@ private static Result DelaunayTriangulation(Context context, Loca return Result.NewData(new FbeResultInfo(terminals, pipes, strategy)); } - private static PumpjackConnection GetNextLine(List lines, List addedPumpjacks) + private static PumpjackConnection GetNextLine(ITableList lines, ITableList addedPumpjacks) { PumpjackConnection? next = null; int nextIndex = 0; @@ -452,7 +462,7 @@ private static PumpjackConnection GetNextLine(List lines, Li return next!; } - private static bool LineContainsAnAddedPumpjack(List addedPumpjacks, PumpjackConnection ent) + private static bool LineContainsAnAddedPumpjack(ITableList addedPumpjacks, PumpjackConnection ent) { for (var i = 0; i < addedPumpjacks.Count; i++) { @@ -475,7 +485,7 @@ private static bool LineContainsAnAddedPumpjack(List addedPump return false; } - private static Result GetPathBetweenGroups(Context context, List groups, Group group, int maxTurns, PipeStrategy strategy) + private static Result GetPathBetweenGroups(Context context, ITableList groups, Group group, int maxTurns, PipeStrategy strategy) { TwoConnectedGroups? best = null; for (var i = 0; i < groups.Count; i++) @@ -512,19 +522,19 @@ private static bool LineContainsAnAddedPumpjack(List addedPump private static Result ConnectTwoGroups(Context context, Group a, Group b, int maxTurns, PipeStrategy strategy) { - var aLocations = new List(); + var aLocations = TableList.New(); for (var i = 0; i < a.Paths.Count; i++) { aLocations.AddRange(a.Paths[i]); } - var bLocations = new List(); + var bLocations = TableList.New(); for (var i = 0; i < b.Paths.Count; i++) { bLocations.AddRange(b.Paths[i]); } - var lineInfo = new List(); + var lineInfo = TableList.New(); for (var i = 0; i < aLocations.Count; i++) { var al = aLocations[i]; @@ -574,11 +584,11 @@ private static Result ConnectTwoGroups(Context context, Grou } var aPlusB = context.GetLocationSet(aLocations.Count + bLocations.Count, allowEnumerate: true); - aPlusB.UnionWith(aLocations); - aPlusB.UnionWith(bLocations); + aPlusB.UnionWith(aLocations.EnumerateItems()); + aPlusB.UnionWith(bLocations.EnumerateItems()); var allEndpoints = PointsToLines(aPlusB.EnumerateItems()); - var matches = new List>(allEndpoints.Count); + var matches = TableList.New>(allEndpoints.Count); for (var i = 0; i < allEndpoints.Count; i++) { var pair = allEndpoints[i]; @@ -607,7 +617,7 @@ private static Result ConnectTwoGroups(Context context, Grou { var l = matches[i].Item1; - List? path; + ITableList? path; if (strategy == PipeStrategy.FbeOriginal) { // We can't terminal early based on max turns because this leads to different results since it allows @@ -652,7 +662,7 @@ private static Result ConnectTwoGroups(Context context, Grou return a.OriginalIndex.CompareTo(b.OriginalIndex); }); - var lines = new List>(lineInfo.Count); + var lines = TableList.New>(lineInfo.Count); var minCount = lineInfo.Count == 0 ? 0 : int.MaxValue; for (var i = 0; i < lineInfo.Count; i++) { @@ -667,39 +677,39 @@ private static Result ConnectTwoGroups(Context context, Grou return Result.NewData(new TwoConnectedGroups(lines, minCount, a)); } - private record TwoConnectedGroups(List> Lines, int MinDistance, Group FirstGroup); + private record TwoConnectedGroups(ITableList> Lines, int MinDistance, Group FirstGroup); - private record PathAndTurns(Endpoints Endpoints, List Path, int Turns, int OriginalIndex); + private record PathAndTurns(Endpoints Endpoints, ITableList Path, int Turns, int OriginalIndex); private class Group { private readonly ILocationSet _terminals; - private readonly List _entities; + private readonly ITableList _entities; private double _sumX = 0; private double _sumY = 0; - public Group(Context context, TerminalLocation terminal, List> paths) : this(context, paths) + public Group(Context context, TerminalLocation terminal, ITableList> paths) : this(context, paths) { Add(terminal); UpdateLocation(); } - public Group(Context context, TerminalPair pair, List> paths) : this(context, paths) + public Group(Context context, TerminalPair pair, ITableList> paths) : this(context, paths) { Add(pair.TerminalA); Add(pair.TerminalB); UpdateLocation(); } - private Group(Context context, List> paths) + private Group(Context context, ITableList> paths) { _terminals = context.GetLocationSet(); - _entities = new List(); + _entities = TableList.New(); Paths = paths; } - public IReadOnlyList Entities => _entities; - public List> Paths { get; } + public ITableList Entities => _entities; + public ITableList> Paths { get; } public Location Location { get; private set; } = Location.Invalid; public bool HasTerminal(TerminalLocation location) @@ -739,7 +749,7 @@ private void UpdateLocation() private class PumpjackConnection { - public PumpjackConnection(Endpoints endpoints, List connections, Location middle) + public PumpjackConnection(Endpoints endpoints, ITableList connections, Location middle) { Endpoints = endpoints; Connections = connections; @@ -747,18 +757,18 @@ public PumpjackConnection(Endpoints endpoints, List connections, L } public Endpoints Endpoints { get; } - public List Connections { get; } + public ITableList Connections { get; } public int EndpointDistance { get; } public double GetAverageDistance() { - return Connections.Count > 0 ? Connections.Average(x => x.Line.Count - 1) : 0; + return Connections.Count > 0 ? Connections.EnumerateItems().Average(x => x.Line.Count - 1) : 0; } } private class TerminalPair { - public TerminalPair(TerminalLocation terminalA, TerminalLocation terminalB, List line, Location middle) + public TerminalPair(TerminalLocation terminalA, TerminalLocation terminalB, ITableList line, Location middle) { TerminalA = terminalA; TerminalB = terminalB; @@ -768,7 +778,7 @@ public TerminalPair(TerminalLocation terminalA, TerminalLocation terminalB, List public TerminalLocation TerminalA { get; } public TerminalLocation TerminalB { get; } - public List Line { get; } + public ITableList Line { get; } public int CenterDistance { get; } #if ENABLE_GRID_TOSTRING diff --git a/src/FactorioTools/OilField/Steps/AddPipes.2.ConnectedCenters.cs b/src/FactorioTools/OilField/Steps/AddPipes.2.ConnectedCenters.cs index 2efe84bc..1a147e16 100644 --- a/src/FactorioTools/OilField/Steps/AddPipes.2.ConnectedCenters.cs +++ b/src/FactorioTools/OilField/Steps/AddPipes.2.ConnectedCenters.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using static Knapcode.FactorioTools.OilField.Helpers; namespace Knapcode.FactorioTools.OilField; @@ -78,7 +79,7 @@ private static void VisualizeConnectedCenters(Context context, ILocationDictiona } } - Visualizer.Show(context.Grid, connectedCenters.ToDelaunatorPoints(), edges); + Visualizer.Show(context.Grid, connectedCenters.ToDelaunatorPoints().EnumerateItems(), edges); } #endif @@ -87,7 +88,7 @@ private record GroupCandidate( Location Center, Location IncludedCenter, TerminalLocation Terminal, - List Path); + ITableList Path); public static Result FindTrunksAndConnect(Context context, ILocationDictionary centerToConnectedCenters) { @@ -102,7 +103,7 @@ public static Result FindTrunksAndConnect(Context context, ILocati } } - var groups = new List(selectedTrunks.Count); + var groups = TableList.New(selectedTrunks.Count); for (var i = 0; i < selectedTrunks.Count; i++) { groups.Add(new PumpjackGroup(context, centerToConnectedCenters, allIncludedCenters, selectedTrunks[i])); @@ -125,8 +126,9 @@ public static Result FindTrunksAndConnect(Context context, ILocati double? shortestDistance = null; GroupCandidate? candidate = null; - foreach (var group in groups) + for (int i = 0; i < groups.Count; i++) { + var group = groups[i]; var centroidX = group.Pipes.EnumerateItems().Average(l => l.X); var centroidY = group.Pipes.EnumerateItems().Average(l => l.Y); @@ -140,8 +142,10 @@ public static Result FindTrunksAndConnect(Context context, ILocati // Prefer the terminal that has the shortest path, then prefer the terminal closer to the centroid // of the child (unconnected) pumpjacks. - foreach (var terminal in context.CenterToTerminals[center]) + var terminals = context.CenterToTerminals[center]; + for (var j = 0; j < terminals.Count; j++) { + var terminal = terminals[j]; var result = GetShortestPathToGroup(context, terminal, group, centroidX, centroidY); if (result.Exception is not null) { @@ -192,7 +196,7 @@ public static Result FindTrunksAndConnect(Context context, ILocati if (allIncludedCenters.Contains(candidate.Terminal.Center)) { - var otherGroup = groups.Single(g => g.IncludedCenters.Contains(candidate.Terminal.Center)); + var otherGroup = groups.EnumerateItems().Single(g => g.IncludedCenters.Contains(candidate.Terminal.Center)); candidate.Group.MergeGroup(otherGroup, candidate.Path); groups.Remove(otherGroup); @@ -216,10 +220,15 @@ public static Result FindTrunksAndConnect(Context context, ILocati } } - return Result.NewData(groups.Single().Pipes); + if (groups.Count != 1) + { + throw new FactorioToolsException("There should be a single group at this point."); + } + + return Result.NewData(groups[0].Pipes); } - private static Result> GetShortestPathToGroup(Context context, TerminalLocation terminal, PumpjackGroup group, double groupCentroidX, double groupCentroidY) + private static Result> GetShortestPathToGroup(Context context, TerminalLocation terminal, PumpjackGroup group, double groupCentroidX, double groupCentroidY) { #if !USE_SHARED_INSTANCES var aStarResultV = AStar.GetShortestPath(context, context.Grid, terminal.Terminal, group.Pipes, xWeight: 2); @@ -232,12 +241,12 @@ private static Result> GetShortestPathToGroup(Context context, Te #endif if (!aStarResultV.Success) { - return Result.NewException>(new NoPathBetweenTerminalsException(terminal.Terminal, group.Pipes.EnumerateItems().First())); + return Result.NewException>(new NoPathBetweenTerminalsException(terminal.Terminal, group.Pipes.EnumerateItems().First())); } if (aStarResultV.Path.SequenceEqual(aStarResultH.Path)) { - return Result.NewData(aStarResultV.Path.ToList()); + return Result.NewData(aStarResultV.Path.ToTableList()); } var adjacentPipesV = 0; @@ -290,19 +299,19 @@ private static Result> GetShortestPathToGroup(Context context, Te if (adjacentPipesV > adjacentPipesH) { - return Result.NewData(aStarResultV.Path.ToList()); + return Result.NewData(aStarResultV.Path.ToTableList()); } else if (adjacentPipesV < adjacentPipesH) { - return Result.NewData(aStarResultH.Path.ToList()); + return Result.NewData(aStarResultH.Path.ToTableList()); } else if (centroidDistanceSquaredV < centroidDistanceSquaredH) { - return Result.NewData(aStarResultV.Path.ToList()); + return Result.NewData(aStarResultV.Path.ToTableList()); } else { - return Result.NewData(aStarResultH.Path.ToList()); + return Result.NewData(aStarResultH.Path.ToTableList()); } #if USE_SHARED_INSTANCES } @@ -329,7 +338,7 @@ private static double GetCentroidDistanceSquared( return centroidDistanceSquared; } - private static List FindTrunks(Context context, ILocationDictionary centerToConnectedCenters) + private static ITableList FindTrunks(Context context, ILocationDictionary centerToConnectedCenters) { /* Visualizer.Show(context.Grid, Array.Empty(), centerToConnectedCenters @@ -368,14 +377,15 @@ private static List FindTrunks(Context context, ILocationDictionary(); - foreach (var trunk in trunkCandidates) + var selectedTrunks = TableList.New(); + for (var i = 0; i < trunkCandidates.Count; i++) { + var trunk = trunkCandidates[i]; var path = MakeStraightLine(trunk.Start, trunk.End); - if (!includedPipes.Overlaps(path) && !includedCenters.Overlaps(trunk.Centers.EnumerateItems())) + if (!includedPipes.Overlaps(path.EnumerateItems()) && !includedCenters.Overlaps(trunk.Centers.EnumerateItems())) { selectedTrunks.Add(trunk); - includedPipes.UnionWith(path); + includedPipes.UnionWith(path.EnumerateItems()); includedCenters.UnionWith(trunk.Centers); } } @@ -392,11 +402,12 @@ private static List FindTrunks(Context context, ILocationDictionary FindTrunks(Context context, ILocationDictionary Path, TerminalLocation Terminal, TerminalLocation BestTerminal); + private record BestConnection(ITableList Path, TerminalLocation Terminal, TerminalLocation BestTerminal); private static PumpjackGroup ConnectTwoClosestPumpjacks(Context context, ILocationDictionary centerToConnectedCenters, ILocationSet allIncludedCenters) { @@ -450,7 +461,7 @@ private static PumpjackGroup ConnectTwoClosestPumpjacks(Context context, ILocati } var reachedGoal = result.ReachedGoal; - var closestTerminal = otherTerminals.Single(t => t.Terminal == reachedGoal); + var closestTerminal = otherTerminals.EnumerateItems().Single(t => t.Terminal == reachedGoal); var path = result.Path; if (bestConnection is null) @@ -609,23 +620,26 @@ private static ILocationSet GetChildCenters( return visited; } - private static List GetTrunkCandidates(Context context, ILocationDictionary centerToConnectedCenters) + private static ITableList GetTrunkCandidates(Context context, ILocationDictionary centerToConnectedCenters) { var centerToMaxX = context .Centers - .ToDictionary(context, c => c, c => centerToConnectedCenters[c].EnumerateItems().Max(c => context.CenterToTerminals[c].Max(t => t.Terminal.X))); + .ToDictionary(context, c => c, c => centerToConnectedCenters[c].EnumerateItems().Max(c => context.CenterToTerminals[c].EnumerateItems().Max(t => t.Terminal.X))); var centerToMaxY = context .Centers - .ToDictionary(context, c => c, c => centerToConnectedCenters[c].EnumerateItems().Max(c => context.CenterToTerminals[c].Max(t => t.Terminal.Y))); + .ToDictionary(context, c => c, c => centerToConnectedCenters[c].EnumerateItems().Max(c => context.CenterToTerminals[c].EnumerateItems().Max(t => t.Terminal.Y))); // Find paths that connect the most terminals of neighboring pumpjacks. - var trunkCandidates = new List(); + var trunkCandidates = TableList.New(); foreach (var translation in Translations) { - foreach (var startingCenter in context.Centers) + for (var i = 0; i < context.Centers.Count; i++) { - foreach (var terminal in context.CenterToTerminals[startingCenter]) + var startingCenter = context.Centers[i]; + var terminals = context.CenterToTerminals[startingCenter]; + for (var j = 0; j < terminals.Count; j++) { + var terminal = terminals[j]; var currentCenter = startingCenter; var expandedChildCenters = false; var nextCenters = centerToConnectedCenters[currentCenter]; @@ -638,12 +652,13 @@ private static List GetTrunkCandidates(Context context, ILocationDictiona while (location.X <= maxX && location.Y <= maxY && context.Grid.IsEmpty(location)) { - if (context.LocationToTerminals.TryGetValue(location, out var terminals)) + if (context.LocationToTerminals.TryGetValue(location, out var otherTerminals)) { Location nextCenter = Location.Invalid; bool hasMatch = false; - foreach (var nextTerminal in terminals) + for (var k = 0; k < otherTerminals.Count; k++) { + var nextTerminal = otherTerminals[k]; if (nextCenters.Contains(nextTerminal.Center)) { nextCenter = nextTerminal.Center; @@ -673,8 +688,8 @@ private static List GetTrunkCandidates(Context context, ILocationDictiona break; } - maxX = nextCenters.EnumerateItems().Max(c => context.CenterToTerminals[c].Max(t => t.Terminal.X)); - maxY = nextCenters.EnumerateItems().Max(c => context.CenterToTerminals[c].Max(t => t.Terminal.Y)); + maxX = nextCenters.EnumerateItems().Max(c => context.CenterToTerminals[c].EnumerateItems().Max(t => t.Terminal.X)); + maxY = nextCenters.EnumerateItems().Max(c => context.CenterToTerminals[c].EnumerateItems().Max(t => t.Terminal.Y)); expandedChildCenters = true; } @@ -683,14 +698,12 @@ private static List GetTrunkCandidates(Context context, ILocationDictiona trunk = new Trunk(context, terminal, currentCenter); } - trunk.Terminals.AddRange(terminals); - foreach (var other in terminals) - { - trunk.TerminalLocations.Add(other.Terminal); - } - foreach (var nextTerminal in terminals) + trunk.Terminals.AddRange(otherTerminals); + for (var k = 0; k < otherTerminals.Count; k++) { - trunk.Centers.Add(nextTerminal.Center); + var otherTerminal = otherTerminals[k]; + trunk.TerminalLocations.Add(otherTerminal.Terminal); + trunk.Centers.Add(otherTerminal.Center); } currentCenter = nextCenter; @@ -725,7 +738,7 @@ public Trunk(Context context, TerminalLocation startingTerminal, Location center } public int OriginalIndex { get; set; } - public List Terminals { get; } = new List(2); + public ITableList Terminals { get; } = TableList.New(2); public ILocationSet TerminalLocations { get; } public ILocationSet Centers { get; } public int Length => Start.GetManhattanDistance(End) + 1; @@ -781,7 +794,7 @@ public PumpjackGroup(Context context, ILocationDictionary centerTo { } - public PumpjackGroup(Context context, ILocationDictionary centerToConnectedCenters, ILocationSet allIncludedCenters, IReadOnlyCollection includedCenters, IReadOnlyCollection pipes) + public PumpjackGroup(Context context, ILocationDictionary centerToConnectedCenters, ILocationSet allIncludedCenters, IReadOnlyCollection includedCenters, ITableList pipes) { _context = context; _centerToConnectedCenters = centerToConnectedCenters; @@ -792,7 +805,7 @@ public PumpjackGroup(Context context, ILocationDictionary centerTo FrontierCenters = context.GetLocationSet(allowEnumerate: true); IncludedCenterToChildCenters = context.GetLocationDictionary(); - Pipes = pipes.ToSet(context, allowEnumerate: true); + Pipes = pipes.EnumerateItems().ToSet(context, allowEnumerate: true); UpdateFrontierCenters(); UpdateIncludedCenterToChildCenters(); @@ -821,19 +834,19 @@ public double GetChildCentroidDistanceSquared(Location includedCenter, Location return terminalCandidate.GetEuclideanDistanceSquared(centroidX, centroidY); } - public void ConnectPumpjack(Location center, List path) + public void ConnectPumpjack(Location center, ITableList path) { _allIncludedCenters.Add(center); IncludedCenters.Add(center); - Pipes.UnionWith(path); + Pipes.UnionWith(path.EnumerateItems()); UpdateFrontierCenters(); UpdateIncludedCenterToChildCenters(); } - public void MergeGroup(PumpjackGroup other, List path) + public void MergeGroup(PumpjackGroup other, ITableList path) { IncludedCenters.UnionWith(other.IncludedCenters); - Pipes.UnionWith(path); + Pipes.UnionWith(path.EnumerateItems()); Pipes.UnionWith(other.Pipes); UpdateFrontierCenters(); UpdateIncludedCenterToChildCenters(); diff --git a/src/FactorioTools/OilField/Steps/AddPipes.3.ConnectedCenters.DT.cs b/src/FactorioTools/OilField/Steps/AddPipes.3.ConnectedCenters.DT.cs index 2f2ce767..8e8f8369 100644 --- a/src/FactorioTools/OilField/Steps/AddPipes.3.ConnectedCenters.DT.cs +++ b/src/FactorioTools/OilField/Steps/AddPipes.3.ConnectedCenters.DT.cs @@ -5,7 +5,7 @@ namespace Knapcode.FactorioTools.OilField; public static class AddPipesConnectedCentersDT { - public static ILocationDictionary ExecuteWithDelaunay(Context context, List centers) + public static ILocationDictionary ExecuteWithDelaunay(Context context, ITableList centers) { var delaunator = GetDelauntator(centers); var dlGraph = centers.ToDictionary(context, c => c, c => context.GetLocationSet(allowEnumerate: true)); @@ -25,7 +25,7 @@ public static ILocationDictionary ExecuteWithDelaunay(Context cont return dlGraph; } - public static ILocationDictionary ExecuteWithDelaunayMst(Context context, List centers) + public static ILocationDictionary ExecuteWithDelaunayMst(Context context, ITableList centers) { var delaunator = GetDelauntator(centers); var dlGraph = centers.ToDictionary(context, c => c, c => context.GetLocationDictionary()); @@ -43,13 +43,13 @@ public static ILocationDictionary ExecuteWithDelaunayMst(Context c } } - var closestToMiddle = centers.MinBy(context.Grid.Middle.GetEuclideanDistanceSquared)!; + var closestToMiddle = centers.EnumerateItems().MinBy(context.Grid.Middle.GetEuclideanDistanceSquared)!; var mst = Prims.GetMinimumSpanningTree(context, dlGraph, closestToMiddle, digraph: false); return mst; } - private static Delaunator GetDelauntator(List centers) + private static Delaunator GetDelauntator(ITableList centers) { var points = new IPoint[centers.Count]; for (var i = 0; i < centers.Count; i++) diff --git a/src/FactorioTools/OilField/Steps/AddPipes.4.ConnectedCenters.FLUTE.cs b/src/FactorioTools/OilField/Steps/AddPipes.4.ConnectedCenters.FLUTE.cs index 6e7152ec..6704ecfd 100644 --- a/src/FactorioTools/OilField/Steps/AddPipes.4.ConnectedCenters.FLUTE.cs +++ b/src/FactorioTools/OilField/Steps/AddPipes.4.ConnectedCenters.FLUTE.cs @@ -16,9 +16,9 @@ public static ILocationDictionary Execute(Context context) var otherCenters = context.GetLocationSet(allowEnumerate: true); var visitedPoints = context.GetLocationSet(); var queue = new Queue(); - foreach (var terminal in terminals) + for (var i = 0; i < terminals.Count; i++) { - queue.Enqueue(locationToPoint[terminal.Terminal]); + queue.Enqueue(locationToPoint[terminals[i].Terminal]); } while (queue.Count > 0) @@ -65,7 +65,7 @@ public FlutePoint(Context context, Location location) public bool IsSteinerPoint => Centers.Count == 0; public Location Location { get; } public ILocationSet Centers { get; } - public List Terminals { get; } = new List(); + public ITableList Terminals { get; } = TableList.New(); public ILocationSet Neighbors { get; } #if ENABLE_GRID_TOSTRING @@ -129,8 +129,9 @@ FlutePoint GetOrAddPoint(ILocationDictionary locationToPoint, Branch // Add in pumpjack information foreach ((var center, var terminals) in context.CenterToTerminals.EnumeratePairs()) { - foreach (var terminal in terminals) + for (var i = 0; i < terminals.Count; i++) { + var terminal = terminals[i]; var point = locationToPoint[terminal.Terminal]; point.Terminals.Add(terminal); point.Centers.Add(center); diff --git a/src/FactorioTools/OilField/Steps/CleanBlueprint.cs b/src/FactorioTools/OilField/Steps/CleanBlueprint.cs index bbd8848d..2c2d8f12 100644 --- a/src/FactorioTools/OilField/Steps/CleanBlueprint.cs +++ b/src/FactorioTools/OilField/Steps/CleanBlueprint.cs @@ -8,15 +8,17 @@ public static class CleanBlueprint { public static Blueprint Execute(Blueprint blueprint) { - var context = InitializeContext.Execute(new OilFieldOptions(), blueprint, Array.Empty()); + var context = InitializeContext.Execute(new OilFieldOptions(), blueprint, TableList.Empty()); - var entities = new List(); + var entities = TableList.New(); - foreach (var center in context.Centers) + for (var i = 0; i < context.Centers.Count; i++) { + var center = context.Centers[i]; + // Pumpjacks are given a direction that doesn't overlap with another pumpjack, preferring the direction // starting at the top then proceeding clockwise. - var terminal = context.CenterToTerminals[center].MinBy(x => x.Direction)!; + var terminal = context.CenterToTerminals[center].EnumerateItems().MinBy(x => x.Direction)!; entities.Add(new Entity { diff --git a/src/FactorioTools/OilField/Steps/InitializeContext.cs b/src/FactorioTools/OilField/Steps/InitializeContext.cs index 9a587a91..ad239ac0 100644 --- a/src/FactorioTools/OilField/Steps/InitializeContext.cs +++ b/src/FactorioTools/OilField/Steps/InitializeContext.cs @@ -7,7 +7,7 @@ namespace Knapcode.FactorioTools.OilField; public static class InitializeContext { - public static Context Execute(OilFieldOptions options, Blueprint blueprint, IReadOnlyList avoid) + public static Context Execute(OilFieldOptions options, Blueprint blueprint, IReadOnlyTableList avoid) { return Execute(options, blueprint, avoid, minWidth: 0, minHeight: 0); } @@ -33,16 +33,16 @@ public static Context GetEmpty(OilFieldOptions options, int width, int height) Version = 1, }; - return Execute(options, blueprint, Array.Empty(), width, height); + return Execute(options, blueprint, TableList.Empty(), width, height); } - public static Context Execute(OilFieldOptions options, Blueprint blueprint, IReadOnlyList avoid, int minWidth, int minHeight) + public static Context Execute(OilFieldOptions options, Blueprint blueprint, IReadOnlyTableList avoid, int minWidth, int minHeight) { var (centerAndOriginalDirections, avoidLocations, deltaX, deltaY, width, height) = TranslateLocations(options, blueprint, avoid, minWidth, minHeight); var grid = InitializeGrid(centerAndOriginalDirections, avoidLocations, width, height); - var centers = new List(centerAndOriginalDirections.Count); + var centers = TableList.New(centerAndOriginalDirections.Count); PopulateCenters(centerAndOriginalDirections, centers); centers.Sort((a, b) => { @@ -57,12 +57,12 @@ public static Context Execute(OilFieldOptions options, Blueprint blueprint, IRea #if USE_HASHSETS var centerToOriginalDirection = new LocationHashDictionary(centerAndOriginalDirections.Count); - var centerToTerminals = new LocationHashDictionary>(centerAndOriginalDirections.Count); - var locationToTerminals = new LocationHashDictionary>(); + var centerToTerminals = new LocationHashDictionary>(centerAndOriginalDirections.Count); + var locationToTerminals = new LocationHashDictionary>(); #else var centerToOriginalDirection = new LocationIntDictionary(grid.Width, centerAndOriginalDirections.Count); - var centerToTerminals = new LocationIntDictionary>(grid.Width, centerAndOriginalDirections.Count); - var locationToTerminals = new LocationIntDictionary>(grid.Width); + var centerToTerminals = new LocationIntDictionary>(grid.Width, centerAndOriginalDirections.Count); + var locationToTerminals = new LocationIntDictionary>(grid.Width); #endif PopulateCenterToOriginalDirection(centerAndOriginalDirections, centerToOriginalDirection); @@ -85,7 +85,7 @@ public static Context Execute(OilFieldOptions options, Blueprint blueprint, IRea }; } - private static void PopulateCenters(List> centerAndOriginalDirections, List centers) + private static void PopulateCenters(ITableList> centerAndOriginalDirections, ITableList centers) { for (int i = 0; i < centerAndOriginalDirections.Count; i++) { @@ -93,7 +93,7 @@ private static void PopulateCenters(List> centerAndOr } } - private static void PopulateCenterToOriginalDirection(List> centerAndOriginalDirections, ILocationDictionary centerToOriginalDirection) + private static void PopulateCenterToOriginalDirection(ITableList> centerAndOriginalDirections, ILocationDictionary centerToOriginalDirection) { for (int i = 0; i < centerAndOriginalDirections.Count; i++) { @@ -136,16 +136,16 @@ private static int[] GetLocationToAdjacentCount(SquareGrid grid) } private record TranslatedLocations( - List> CenterAndOriginalDirections, - List AvoidLocations, + ITableList> CenterAndOriginalDirections, + ITableList AvoidLocations, float DeltaX, float DeltaY, int Width, int Height); - private static TranslatedLocations TranslateLocations(OilFieldOptions options, Blueprint blueprint, IReadOnlyList avoid, int minWidth, int minHeight) + private static TranslatedLocations TranslateLocations(OilFieldOptions options, Blueprint blueprint, IReadOnlyTableList avoid, int minWidth, int minHeight) { - var pumpjacks = new List(); + var pumpjacks = TableList.New(blueprint.Entities.Length); for (var i = 0; i < blueprint.Entities.Length; i++) { var entity = blueprint.Entities[i]; @@ -161,8 +161,8 @@ private static TranslatedLocations TranslateLocations(OilFieldOptions options, B throw new FactorioToolsException($"Having more than {maxPumpjacks} pumpjacks is not supported. There are {pumpjacks.Count} pumpjacks provided."); } - var centerAndOriginalDirections = new List>(pumpjacks.Count); - var avoidLocations = new List(avoid.Count); + var centerAndOriginalDirections = TableList.New>(pumpjacks.Count); + var avoidLocations = TableList.New(avoid.Count); float deltaX = 0; float deltaY = 0; @@ -182,10 +182,10 @@ private static TranslatedLocations TranslateLocations(OilFieldOptions options, B var pumpjackOffsetX = PumpjackWidth / 2; var pumpjackOffsetY = PumpjackHeight / 2; - minX = pumpjacks.Min(p => p.Position.X) - pumpjackOffsetX; - minY = pumpjacks.Min(p => p.Position.Y) - pumpjackOffsetY; - maxX = pumpjacks.Max(p => p.Position.X) + pumpjackOffsetX; - maxY = pumpjacks.Max(p => p.Position.Y) + pumpjackOffsetY; + minX = pumpjacks.EnumerateItems().Min(p => p.Position.X) - pumpjackOffsetX; + minY = pumpjacks.EnumerateItems().Min(p => p.Position.Y) - pumpjackOffsetY; + maxX = pumpjacks.EnumerateItems().Max(p => p.Position.X) + pumpjackOffsetX; + maxY = pumpjacks.EnumerateItems().Max(p => p.Position.Y) + pumpjackOffsetY; if (options.AddBeacons) { @@ -201,10 +201,10 @@ private static TranslatedLocations TranslateLocations(OilFieldOptions options, B if (avoid.Count > 0) { - minX = Math.Min(minX, avoid.Min(a => a.X)); - minY = Math.Min(minY, avoid.Min(a => a.Y)); - maxX = Math.Max(maxX, avoid.Max(a => a.X)); - maxY = Math.Max(maxY, avoid.Max(a => a.Y)); + minX = Math.Min(minX, avoid.EnumerateItems().Min(a => a.X)); + minY = Math.Min(minY, avoid.EnumerateItems().Min(a => a.Y)); + maxX = Math.Max(maxX, avoid.EnumerateItems().Max(a => a.X)); + maxY = Math.Max(maxY, avoid.EnumerateItems().Max(a => a.Y)); } // Leave some space on all sides to cover: @@ -256,7 +256,7 @@ private static TranslatedLocations TranslateLocations(OilFieldOptions options, B return new TranslatedLocations(centerAndOriginalDirections, avoidLocations, deltaX, deltaY, width, height); } - private static SquareGrid InitializeGrid(List> centerAndOriginalDirections, List avoidLocations, int width, int height) + private static SquareGrid InitializeGrid(ITableList> centerAndOriginalDirections, ITableList avoidLocations, int width, int height) { const int maxWidth = 1000; const int maxHeight = 1000; diff --git a/src/FactorioTools/OilField/Steps/PlanBeacons.0.cs b/src/FactorioTools/OilField/Steps/PlanBeacons.0.cs index d5cec60a..179d2545 100644 --- a/src/FactorioTools/OilField/Steps/PlanBeacons.0.cs +++ b/src/FactorioTools/OilField/Steps/PlanBeacons.0.cs @@ -3,22 +3,24 @@ namespace Knapcode.FactorioTools.OilField; -public record BeaconPlannerResult(List Beacons, int Effects); +public record BeaconPlannerResult(ITableList Beacons, int Effects); public static class PlanBeacons { - public static List Execute(Context context, ILocationSet pipes) + public static ITableList Execute(Context context, ILocationSet pipes) { foreach (var pipe in pipes.EnumerateItems()) { context.Grid.AddEntity(pipe, new TemporaryEntity(context.Grid.GetId())); } - var solutions = new List(context.Options.BeaconStrategies.Count); + var solutions = TableList.New(context.Options.BeaconStrategies.Count); var completedStrategies = new CountedBitArray((int)BeaconStrategy.Snug + 1); // max value - foreach (var strategy in context.Options.BeaconStrategies) + for (var i = 0; i < context.Options.BeaconStrategies.Count; i++) { + var strategy = context.Options.BeaconStrategies[i]; + if (completedStrategies[(int)strategy]) { continue; diff --git a/src/FactorioTools/OilField/Steps/PlanBeacons.1.FBE.cs b/src/FactorioTools/OilField/Steps/PlanBeacons.1.FBE.cs index 5229a5f9..aa096a6c 100644 --- a/src/FactorioTools/OilField/Steps/PlanBeacons.1.FBE.cs +++ b/src/FactorioTools/OilField/Steps/PlanBeacons.1.FBE.cs @@ -50,7 +50,7 @@ public static BeaconPlannerResult Execute(Context context, BeaconStrategy strate if (strategy == BeaconStrategy.Fbe) { - possibleBeacons = SortPossibleBeacons(context, possibleBeacons); + SortPossibleBeacons(context, possibleBeacons); } else if (strategy == BeaconStrategy.FbeOriginal) { @@ -61,9 +61,9 @@ public static BeaconPlannerResult Execute(Context context, BeaconStrategy strate return GetBeacons(context, effectEntityAreas, possibleBeacons); } - private static BeaconPlannerResult GetBeacons(Context context, List effectEntityAreas, List possibleBeacons) + private static BeaconPlannerResult GetBeacons(Context context, ITableList effectEntityAreas, ITableList possibleBeacons) { - var beacons = new List(); + var beacons = TableList.New(); var effects = 0; var collisionArea = context.GetLocationSet(); var coveredEntityAreas = context.Options.OverlapBeacons ? null : new CountedBitArray(effectEntityAreas.Count); @@ -104,7 +104,7 @@ private static BeaconPlannerResult GetBeacons(Context context, List effect return new BeaconPlannerResult(beacons, effects); } - private static List SortPossibleBeacons(Context context, List possibleBeacons) + private static void SortPossibleBeacons(Context context, ITableList possibleBeacons) { var candidateToDistance = context.GetLocationDictionary(); @@ -144,11 +144,9 @@ private static List SortPossibleBeacons(Context context, List possibleBeacons) + private static void SortPossibleBeaconsOriginal(ITableList possibleBeacons) { /* possibleBeacons @@ -219,10 +217,10 @@ private static void SortPossibleBeaconsOriginal(List possibleBe // possibleBeacons.Reverse(); } - private static List GetPossibleBeacons( + private static ITableList GetPossibleBeacons( Context context, - List effectEntityAreas, - List possibleBeaconAreas, + ITableList effectEntityAreas, + ITableList possibleBeaconAreas, ILocationDictionary pointToBeaconCount, ILocationDictionary pointToEntityArea) { @@ -232,7 +230,7 @@ private static List GetPossibleBeacons( var centerY = (context.Options.BeaconHeight - 1) / 2; var centerIndex = centerY * context.Options.BeaconWidth + centerX; - var possibleBeacons = new List(possibleBeaconAreas.Count); + var possibleBeacons = TableList.New(possibleBeaconAreas.Count); var effectsGiven = new CountedBitArray(effectEntityAreas.Count); for (var i = 0; i < possibleBeaconAreas.Count; i++) { @@ -329,7 +327,7 @@ private static Bounds GetBounds(IReadOnlyCollection locations) return new Bounds(entityMinX, entityMinY, entityMaxX, entityMaxY); } - private static ILocationSet GetOccupiedPositions(Context context, List entityAreas) + private static ILocationSet GetOccupiedPositions(Context context, ITableList entityAreas) { var occupiedPositions = context.GetLocationSet(); for (int i = 0; i < entityAreas.Count; i++) @@ -344,9 +342,9 @@ private static ILocationSet GetOccupiedPositions(Context context, List ent return occupiedPositions; } - private static List GetEntityAreas(Context context) + private static ITableList GetEntityAreas(Context context) { - var areas = new List(context.Grid.EntityLocations.Count); + var areas = TableList.New(context.Grid.EntityLocations.Count); foreach (var location in context.Grid.EntityLocations.EnumerateItems()) { @@ -407,9 +405,9 @@ private static List GetEntityAreas(Context context) return areas; } - private static List GetEffectEntityAreas(List entityAreas) + private static ITableList GetEffectEntityAreas(ITableList entityAreas) { - var effectEntityArea = new List(); + var effectEntityArea = TableList.New(); for (var i = 0; i < entityAreas.Count; i++) { var area = entityAreas[i]; @@ -427,10 +425,10 @@ private static List GetEffectEntityAreas(List entityAreas) return effectEntityArea; } - private static List GetPossibleBeaconAreas(Context context, ILocationSet occupiedPositions) + private static ITableList GetPossibleBeaconAreas(Context context, ILocationSet occupiedPositions) { var validBeaconCenters = context.GetLocationSet(); - var possibleBeaconAreas = new List(); + var possibleBeaconAreas = TableList.New(); var gridMinX = (context.Options.BeaconWidth - 1) / 2; var gridMinY = (context.Options.BeaconHeight - 1) / 2; @@ -449,8 +447,9 @@ private static List GetPossibleBeaconAreas(Context context, ILocatio var area = new Location[context.Options.BeaconWidth * context.Options.BeaconHeight]; - foreach (var center in context.Centers) + for (var i = 0; i < context.Centers.Count; i++) { + var center = context.Centers[i]; var supplyMinX = Math.Max(gridMinX, center.X - supplyLeft); var supplyMinY = Math.Max(gridMinY, center.Y - supplyUp); var supplyMaxX = Math.Min(gridMaxX, center.X + supplyRight); @@ -472,7 +471,7 @@ private static List GetPossibleBeaconAreas(Context context, ILocatio var maxY = beaconCenter.Y + beaconDown; var fits = true; - var i = 0; + var j = 0; for (var y = minY; fits && y <= maxY; y++) { for (var x = minX; fits && x <= maxX; x++) @@ -484,7 +483,8 @@ private static List GetPossibleBeaconAreas(Context context, ILocatio } else { - area[i++] = location; + area[j] = location; + j++; } } } @@ -501,7 +501,7 @@ private static List GetPossibleBeaconAreas(Context context, ILocatio return possibleBeaconAreas; } - private static ILocationDictionary GetPointToBeaconCount(Context context, List possibleBeaconAreas) + private static ILocationDictionary GetPointToBeaconCount(Context context, ITableList possibleBeaconAreas) { var pointToBeaconCount = context.GetLocationDictionary(); for (var i = 0; i < possibleBeaconAreas.Count; i++) @@ -524,7 +524,7 @@ private static ILocationDictionary GetPointToBeaconCount(Context context, L return pointToBeaconCount; } - private static ILocationDictionary GetPointToEntityArea(Context context, List effectEntityAreas) + private static ILocationDictionary GetPointToEntityArea(Context context, ITableList effectEntityAreas) { var pointToEntityArea = context.GetLocationDictionary(); for (var i = 0; i < effectEntityAreas.Count; i++) diff --git a/src/FactorioTools/OilField/Steps/PlanBeacons.2.Snug.cs b/src/FactorioTools/OilField/Steps/PlanBeacons.2.Snug.cs index d65d6322..92091c4c 100644 --- a/src/FactorioTools/OilField/Steps/PlanBeacons.2.Snug.cs +++ b/src/FactorioTools/OilField/Steps/PlanBeacons.2.Snug.cs @@ -8,10 +8,10 @@ public static class PlanBeaconsSnug { public static BeaconPlannerResult Execute(Context context) { - var poweredEntities = new List(context.CenterToTerminals.Count); - foreach (var center in context.Centers) + var poweredEntities = TableList.New(context.Centers.Count); + for (var i = 0; i < context.Centers.Count; i++) { - poweredEntities.Add(new ProviderRecipient(center, PumpjackWidth, PumpjackHeight)); + poweredEntities.Add(new ProviderRecipient(context.Centers[i], PumpjackWidth, PumpjackHeight)); } // We don't try to remove unused beacons here because there should not be any existing beacons at this point. @@ -29,7 +29,7 @@ public static BeaconPlannerResult Execute(Context context) var sorter = new SnugCandidateSorter(); - var scopedCandidates = new List>(); + var scopedCandidates = TableList.New>(); var scopedCandidatesSet = context.GetLocationDictionary(); Dictionary>? coveredToCandidates = null; @@ -38,7 +38,7 @@ public static BeaconPlannerResult Execute(Context context) coveredToCandidates = GetCoveredToCandidates(context, candidateToInfo, coveredEntities); } - var beacons = new List(); + var beacons = TableList.New(); var effects = 0; while (candidateToInfo.Count > 0) @@ -48,7 +48,7 @@ public static BeaconPlannerResult Execute(Context context) .MinBy(pair => { return Tuple.Create( - beacons.Count > 0 && context.Options.OverlapBeacons ? beacons.Min(x => x.GetManhattanDistance(pair.Key)) : 0, + beacons.Count > 0 && context.Options.OverlapBeacons ? beacons.EnumerateItems().Min(x => x.GetManhattanDistance(pair.Key)) : 0, -pair.Value.Covered.TrueCount, -pair.Value.EntityDistance, pair.Value.MiddleDistance @@ -139,7 +139,7 @@ public BeaconCandidateInfo Create(CountedBitArray covered) private static void PopulateCandidateToInfo( Context context, ILocationDictionary candidateToInfo, - List poweredEntities) + ITableList poweredEntities) { foreach ((var candidate, var info) in candidateToInfo.EnumeratePairs()) { @@ -162,7 +162,7 @@ public BeaconCandidateInfo(CountedBitArray covered) : base(covered) private static void AddNeighborsAndSort( Context context, ILocationDictionary candidateToInfo, - List> scopedCandidates, + ITableList> scopedCandidates, ILocationDictionary scopedCandidatesSet, SnugCandidateSorter sorter, Location candidate) @@ -235,7 +235,7 @@ private static void AddNeighborsAndSort( if (initialCount < scopedCandidates.Count) { - scopedCandidates.Sort(initialCount, scopedCandidates.Count - initialCount, sorter); + scopedCandidates.SortRange(initialCount, scopedCandidates.Count - initialCount, sorter); } } diff --git a/src/FactorioTools/OilField/Steps/PlanUndergroundPipes.cs b/src/FactorioTools/OilField/Steps/PlanUndergroundPipes.cs index a56fe653..4afbc9c0 100644 --- a/src/FactorioTools/OilField/Steps/PlanUndergroundPipes.cs +++ b/src/FactorioTools/OilField/Steps/PlanUndergroundPipes.cs @@ -103,7 +103,7 @@ private static void ConvertInOneDirection( continue; } - var direction = terminals.Single().Direction; + var direction = terminals[0].Direction; if (direction != forwardDirection && direction != backwardDirection) { continue; @@ -119,10 +119,10 @@ private static void ConvertInOneDirection( return; } - var sorted = candidates.EnumerateItems().ToList(); + var sorted = candidates.EnumerateItems().ToTableList(); sorted.Sort(sort); - var currentRun = new List { sorted[0] }; + var currentRun = TableList.New(sorted[0]); for (var i = 1; i < sorted.Count; i++) { var previous = currentRun[currentRun.Count - 1]; @@ -156,7 +156,7 @@ private static void AddRunAndClear( ILocationDictionary locationToDirection, Direction forwardDirection, Direction backwardDirection, - List currentRun) + ITableList currentRun) { if (currentRun.Count >= MinUnderground) { diff --git a/src/FactorioTools/OilField/Steps/RotateOptimize.cs b/src/FactorioTools/OilField/Steps/RotateOptimize.cs index 1cbcd41f..ed82f152 100644 --- a/src/FactorioTools/OilField/Steps/RotateOptimize.cs +++ b/src/FactorioTools/OilField/Steps/RotateOptimize.cs @@ -29,7 +29,12 @@ public static void Execute(Context parentContext, ILocationSet pipes) var changedTerminal = false; foreach (var terminals in context.CenterToTerminals.Values) { - var currentTerminal = terminals.Single(); + if (terminals.Count != 1) + { + throw new FactorioToolsException("There should be a single terminal at this point."); + } + + var currentTerminal = terminals[0]; if (context.LocationToTerminals[currentTerminal.Terminal].Count > 1 || context.Intersections.Contains(currentTerminal.Terminal)) @@ -47,8 +52,10 @@ public static void Execute(Context parentContext, ILocationSet pipes) // VisualizeIntersections(context); var shortenedPath = false; - foreach (var intersection in context.Intersections.EnumerateItems().ToList()) + var intersections = context.Intersections.EnumerateItems().ToTableList(); + for (var i = 0; i < intersections.Count; i++) { + var intersection = intersections[i]; if (!context.Intersections.Contains(intersection)) { continue; @@ -58,9 +65,9 @@ public static void Execute(Context parentContext, ILocationSet pipes) var exploredPaths = ExplorePaths(context, intersection); context.Goals.Add(intersection); - foreach (var goal in exploredPaths.ReachedGoals) + for (var j = 0; j < exploredPaths.ReachedGoals.Count; j++) { - if (UseShortestPath(context, exploredPaths, intersection, goal)) + if (UseShortestPath(context, exploredPaths, intersection, exploredPaths.ReachedGoals[j])) { // VisualizeIntersections(context); shortenedPath = true; @@ -87,7 +94,7 @@ private static void VisualizeIntersections(ChildContext context) { var clone = new PipeGrid(context.ExistingPipeGrid); AddPipeEntities.Execute(context.ParentContext, clone, context.Pipes, allowMultipleTerminals: true); - Visualizer.Show(clone, context.Intersections.ToDelaunatorPoints(), Array.Empty()); + Visualizer.Show(clone, context.Intersections.ToDelaunatorPoints().EnumerateItems(), Array.Empty()); } #endif @@ -105,10 +112,15 @@ private static bool UseBestTerminal(ChildContext context, TerminalLocation origi } */ - var originalGoal = exploredPaths.ReachedGoals.Single(); + if (exploredPaths.ReachedGoals.Count != 1) + { + throw new FactorioToolsException("Only a single goal should have been reached."); + } + + var originalGoal = exploredPaths.ReachedGoals[0]; #if !USE_SHARED_INSTANCES - var originalPath = new List(); + var originalPath = TableList.New(); #else var originalPath = context.ParentContext.SharedInstances.LocationListA; try @@ -139,7 +151,7 @@ private static bool UseBestTerminal(ChildContext context, TerminalLocation origi #if USE_SHARED_INSTANCES var newPath = minPath == context.ParentContext.SharedInstances.LocationListA ? context.ParentContext.SharedInstances.LocationListB : context.ParentContext.SharedInstances.LocationListA; #else - var newPath = new List(); + var newPath = TableList.New(); #endif var result = AStar.GetShortestPath(context.ParentContext, context.Grid, terminalCandidate, context.Pipes, outputList: newPath); if (result.Success) @@ -168,7 +180,7 @@ private static bool UseBestTerminal(ChildContext context, TerminalLocation origi } } - context.Pipes.UnionWith(minPath); + context.Pipes.UnionWith(minPath.EnumerateItems()); if (changedPath) { @@ -178,7 +190,7 @@ private static bool UseBestTerminal(ChildContext context, TerminalLocation origi if (!context.LocationToTerminals.TryGetValue(minTerminal.Terminal, out var locationTerminals)) { - locationTerminals = new List { minTerminal }; + locationTerminals = TableList.New(minTerminal); context.LocationToTerminals.Add(minTerminal.Terminal, locationTerminals); } else @@ -223,7 +235,7 @@ private static bool UseShortestPath( Location originalGoal) { #if !USE_SHARED_INSTANCES - var originalPath = new List(); + var originalPath = TableList.New(); var connectionPoints = context.ParentContext.GetLocationSet(context.Pipes.Count, allowEnumerate: true); #else var originalPath = context.ParentContext.SharedInstances.LocationListA; @@ -269,12 +281,12 @@ private static bool UseShortestPath( if (result.Path.Count > originalPath.Count || (result.Path.Count == originalPath.Count && CountTurns(result.Path) >= CountTurns(originalPath))) { - context.Pipes.UnionWith(originalPath); + context.Pipes.UnionWith(originalPath.EnumerateItems()); return false; } - context.Pipes.UnionWith(result.Path); + context.Pipes.UnionWith(result.Path.EnumerateItems()); context.UpdateIntersectionsAndGoals(); /* @@ -373,7 +385,7 @@ private static ExploredPaths ExplorePaths(ChildContext context, Location start) #endif #endif - var reachedGoals = new List(); + var reachedGoals = TableList.New(); while (toExplore.Count > 0) { @@ -427,8 +439,8 @@ public ChildContext(Context parentContext, ILocationSet pipes) public Context ParentContext { get; } public SquareGrid Grid => ParentContext.Grid; - public ILocationDictionary> LocationToTerminals => ParentContext.LocationToTerminals; - public ILocationDictionary> CenterToTerminals => ParentContext.CenterToTerminals; + public ILocationDictionary> LocationToTerminals => ParentContext.LocationToTerminals; + public ILocationDictionary> CenterToTerminals => ParentContext.CenterToTerminals; public ILocationSet Pipes { get; } public ILocationSet Intersections { get; } public ILocationSet Goals { get; } @@ -476,7 +488,7 @@ public void UpdateIntersectionsAndGoals() private class ExploredPaths { - public ExploredPaths(Location start, ILocationDictionary cameFrom, List reachedGoals) + public ExploredPaths(Location start, ILocationDictionary cameFrom, ITableList reachedGoals) { Start = start; CameFrom = cameFrom; @@ -485,9 +497,9 @@ public ExploredPaths(Location start, ILocationDictionary cameFrom, Lis public Location Start { get; } public ILocationDictionary CameFrom { get; } - public List ReachedGoals { get; } + public ITableList ReachedGoals { get; } - public void AddPath(Location goal, List outputList) + public void AddPath(Location goal, ITableList outputList) { Helpers.AddPath(CameFrom, goal, outputList); } diff --git a/src/FactorioTools/OilField/Steps/Validate.cs b/src/FactorioTools/OilField/Steps/Validate.cs index 0d7dd6b2..9d27642e 100644 --- a/src/FactorioTools/OilField/Steps/Validate.cs +++ b/src/FactorioTools/OilField/Steps/Validate.cs @@ -71,11 +71,11 @@ public static void PipesDoNotMatch( } } - public static void BeaconsDoNotOverlap(Context context, List solutions) + public static void BeaconsDoNotOverlap(Context context, ITableList solutions) { if (context.Options.ValidateSolution && !context.Options.OverlapBeacons) { - foreach (var solution in solutions) + for (var i = 0; i < solutions.Count; i++) { var beaconCenterToCoveredCenters = GetProviderCenterToCoveredCenters( context, @@ -83,7 +83,7 @@ public static void BeaconsDoNotOverlap(Context context, List sol context.Options.BeaconHeight, context.Options.BeaconSupplyWidth, context.Options.BeaconSupplyHeight, - solution.Beacons, + solutions[i].Beacons.EnumerateItems(), includePumpjacks: true, includeBeacons: false); @@ -112,7 +112,7 @@ public static void NoOverlappingEntities( Context context, ILocationSet optimizedPipes, ILocationDictionary? undergroundPipes, - List? beaconSolutions) + ITableList? beaconSolutions) { if (context.Options.ValidateSolution) { @@ -123,11 +123,11 @@ public static void NoOverlappingEntities( } else { - foreach (var solution in beaconSolutions) + for (var i = 0; i < beaconSolutions.Count; i++) { var clone = new PipeGrid(context.Grid); AddPipeEntities.Execute(context, clone, optimizedPipes, undergroundPipes); - AddBeaconsToGrid(clone, context.Options, solution.Beacons); + AddBeaconsToGrid(clone, context.Options, beaconSolutions[i].Beacons); } } } @@ -135,7 +135,7 @@ public static void NoOverlappingEntities( public static void CandidateCoversMoreEntities( Context context, - List poweredEntities, + ITableList poweredEntities, CountedBitArray coveredEntities, Location candidate, ElectricPoleCandidateInfo candidateInfo) @@ -166,7 +166,7 @@ public static void AllEntitiesHavePower(Context context) { (var poweredEntities, _) = GetPoweredEntities(context); - var electricPoleCenters = new List(); + var electricPoleCenters = TableList.New(); foreach (var location in context.Grid.EntityLocations.EnumerateItems()) { var entity = context.Grid[location]; @@ -176,7 +176,7 @@ public static void AllEntitiesHavePower(Context context) } } - GetElectricPoleCoverage(context, poweredEntities, electricPoleCenters); + GetElectricPoleCoverage(context, poweredEntities, electricPoleCenters.EnumerateItems()); } } } diff --git a/src/Sandbox/Program.cs b/src/Sandbox/Program.cs index 54c052f6..8e3a0e20 100644 --- a/src/Sandbox/Program.cs +++ b/src/Sandbox/Program.cs @@ -42,78 +42,10 @@ private static string GetRepositoryRoot() private static void Sandbox() { - var blueprintStringsAll = ParseBlueprint.ReadBlueprintFile(BigListDataPath).ToArray(); - // var blueprintStrings = blueprintStringsAll; - var blueprintStrings = new[] { blueprintStringsAll[91] }; - // var blueprintStrings = blueprintStringsAll.Reverse().Take(11).Skip(11).ToArray(); - // var blueprintStrings = blueprintStringsAll.Take(5).ToArray(); - // var blueprintStrings = Enumerable.Repeat(blueprintStringsAll[1], 50).ToArray(); - - // var optionsAll = new[] { Options.ForSmallElectricPole, Options.ForMediumElectricPole, Options.ForSubstation, Options.ForBigElectricPole }; - // var optionsAll = new[] { Options.ForSmallElectricPole }; - var optionsAll = new[] { OilFieldOptions.ForMediumElectricPole }; - // var optionsAll = new[] { OilFieldOptions.ForBigElectricPole }; - - // var addBeaconsAll = new[] { true, false }; - var addBeaconsAll = new[] { true }; - // var addBeaconsAll = new[] { false }; - - foreach (var addBeacons in addBeaconsAll) + for (var i = 0; i < 100; i++) { - foreach (var options in optionsAll) - { - for (int i = 0; i < blueprintStrings.Length; i++) - { - Console.WriteLine("index " + i); - string? blueprintString = blueprintStrings[i]; - - if (blueprintStrings.Length == 1) - { - Console.WriteLine(blueprintString); - } - - /* - blueprintString = NormalizeBlueprints.Normalize(blueprintString, includeFbeOffset: true); - - if (blueprintStrings.Length == 1) - { - Console.WriteLine(blueprintString); - } - */ - - var inputBlueprint = ParseBlueprint.Execute(blueprintString); - - /* - var entityTypes = inputBlueprint - .Entities - .GroupBy(e => e.Name) - .ToDictionary(g => g.Key, g => g.Count()) - .OrderByDescending(p => p.Value); - */ - - // var options = Options.ForSubstation; - // options.ElectricPoleWidth = 3; - // options.ElectricPoleHeight = 3; - // options.ElectricPoleSupplyWidth = 9; - // options.ElectricPoleSupplyHeight = 9; - // options.AddBeacons = addBeacons; - // options.UseUndergroundPipes = options.AddBeacons; - // options.OptimizePipes = true; - // options.ValidateSolution = true; - // options.OverlapBeacons = true; - // options.BeaconStrategies.Remove(BeaconStrategy.Fbe); - options.PipeStrategies = OilFieldOptions.AllPipeStrategies.ToList(); - options.BeaconStrategies = OilFieldOptions.AllBeaconStrategies.ToList(); - - (var context, _) = Planner.Execute(options, inputBlueprint); - - if (blueprintStrings.Length == 1) - { - var newBlueprint = GridToBlueprintString.Execute(context, addFbeOffset: false, addAvoidEntities: true); - Console.WriteLine(newBlueprint); - } - } - } + Console.Write("."); + Planner.ExecuteSample(); } } } \ No newline at end of file diff --git a/src/Sandbox/Properties/launchSettings.json b/src/Sandbox/Properties/launchSettings.json index d242bf07..151d5ffd 100644 --- a/src/Sandbox/Properties/launchSettings.json +++ b/src/Sandbox/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Sandbox": { "commandName": "Project", - "commandLineArgs": "sample" + "commandLineArgs": "sandbox" } } } \ No newline at end of file diff --git a/src/WebApp/Models/GenericCollectionSchemaFilter.cs b/src/WebApp/Models/GenericCollectionSchemaFilter.cs new file mode 100644 index 00000000..033ff4fb --- /dev/null +++ b/src/WebApp/Models/GenericCollectionSchemaFilter.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; +using System.Reflection; +using Knapcode.FactorioTools.OilField; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Knapcode.FactorioTools.WebApp.Models; + +public class GenericCollectionSchemaFilter : ISchemaFilter +{ + public void Apply(OpenApiSchema model, SchemaFilterContext context) + { + var propertyToInfo = context + .Type + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); + + foreach (var (propertyKey, property) in model.Properties) + { + if (propertyToInfo.TryGetValue(propertyKey, out var info)) + { + if (info.PropertyType.IsGenericType + && info.PropertyType.GetGenericTypeDefinition() == typeof(ITableList<>)) + { + var listType = typeof(List<>).MakeGenericType(info.PropertyType.GenericTypeArguments[0]); + var generated = context.SchemaGenerator.GenerateSchema(listType, context.SchemaRepository); + property.AllOf = generated.AllOf; + property.Type = generated.Type; + property.Items = generated.Items; + } + } + } + } +} \ No newline at end of file diff --git a/src/WebApp/Models/OilFieldPlanRequestDefaultsSchemaFilter.cs b/src/WebApp/Models/OilFieldPlanRequestDefaultsSchemaFilter.cs index 1b2fcda3..57279dc3 100644 --- a/src/WebApp/Models/OilFieldPlanRequestDefaultsSchemaFilter.cs +++ b/src/WebApp/Models/OilFieldPlanRequestDefaultsSchemaFilter.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System.Diagnostics; +using System.Reflection; using Knapcode.FactorioTools.OilField; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; @@ -42,8 +43,8 @@ public void Apply(OpenApiSchema model, SchemaFilterContext context) int v => new OpenApiInteger(v), double v => new OpenApiDouble(v), bool v => new OpenApiBoolean(v), - IEnumerable v => ToStringArray(v), - IEnumerable v => ToStringArray(v), + ITableList v => ToStringArray(v), + ITableList v => ToStringArray(v), IEnumerable> v => ToObjectArray(v), _ => throw new NotImplementedException(), }; @@ -53,12 +54,12 @@ public void Apply(OpenApiSchema model, SchemaFilterContext context) } } - private OpenApiArray ToStringArray(IEnumerable values) + private OpenApiArray ToStringArray(ITableList values) { var array = new OpenApiArray(); - foreach (var value in values) + for (var i = 0; i < values.Count; i++) { - array.Add(new OpenApiString(value!.ToString())); + array.Add(new OpenApiString(values[i]!.ToString())); } return array; diff --git a/src/WebApp/Program.cs b/src/WebApp/Program.cs index 37e4dd62..92de4e62 100644 --- a/src/WebApp/Program.cs +++ b/src/WebApp/Program.cs @@ -1,3 +1,4 @@ +using System.IO.Pipelines; using System.Text.Json.Serialization; using System.Threading.RateLimiting; using Knapcode.FactorioTools.OilField; @@ -5,6 +6,7 @@ using Microsoft.ApplicationInsights.Extensibility; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.OpenApi.Models; namespace Knapcode.FactorioTools.WebApp; @@ -66,6 +68,7 @@ private static void Main(string[] args) builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { + options.SchemaFilter(); options.SchemaFilter(); options.SchemaFilter>(); options.SchemaFilter>(); @@ -73,6 +76,9 @@ private static void Main(string[] args) options.SchemaFilter>(); options.SchemaFilter>(); options.SchemaFilter>(); + options.MapType>(() => new OpenApiSchema()); + options.MapType>(() => new OpenApiSchema()); + options.MapType>(() => new OpenApiSchema()); options.SupportNonNullableReferenceTypes(); options.UseAllOfToExtendReferenceSchemas(); options.IncludeXmlComments(Path.ChangeExtension(typeof(OilFieldOptions).Assembly.Location, ".xml")); diff --git a/test/FactorioTools.Test/ExtensionMethods.cs b/test/FactorioTools.Test/ExtensionMethods.cs new file mode 100644 index 00000000..8dc42d1b --- /dev/null +++ b/test/FactorioTools.Test/ExtensionMethods.cs @@ -0,0 +1,19 @@ +using Knapcode.FactorioTools.OilField; + +namespace Knapcode.FactorioTools; + +public static class ExtensionMethods +{ + public static ITableList ToTableArray(this IEnumerable source) + { + return source.ToList().ToTableList(); + } + + public static IEnumerable GetEntities(this SquareGrid grid) + { + foreach (var location in grid.EntityLocations.EnumerateItems()) + { + yield return grid[location]!; + } + } +} \ No newline at end of file diff --git a/test/FactorioTools.Test/OilField/AllowsBlueprintWithNonBlockingIsolatedArea/Theory.cs b/test/FactorioTools.Test/OilField/AllowsBlueprintWithNonBlockingIsolatedArea/Theory.cs index 40279687..603b6bfe 100644 --- a/test/FactorioTools.Test/OilField/AllowsBlueprintWithNonBlockingIsolatedArea/Theory.cs +++ b/test/FactorioTools.Test/OilField/AllowsBlueprintWithNonBlockingIsolatedArea/Theory.cs @@ -8,7 +8,7 @@ public async Task Execute(PipeStrategy strategy) { // Arrange var options = OilFieldOptions.ForMediumElectricPole; - options.PipeStrategies = new List { strategy }; + options.PipeStrategies = TableList.New(strategy); var blueprintString = "0eJyU1ctugzAQBdB/mbUXZmwg8CtVFRFiVW6Dg3hURYh/r8Fe9IHEZQmYw2DN9cx0e4ym7awbqJzJ1k/XU/kyU2/fXPVY77mqMVRSOzbte1V/kKBhatc7djANLYKsu5svKpPlVZBxgx2sCcZ2MV3d2NxM5xeIHat99v6Fp1u/5BHWgia/VHn3bjtTh2dyEf84BjjFMKcQLoc5jXAycNlvjne49ER1AJcBXBK5/Phnc4DTWeAux9wFaZRYXXHMFcjeKZhLJLJ5wWMJeCdywQngQcGQuHciGcyAh0SDY31A0hIkG7FdWAMeEg6lcQ9Kh8Q9JB76RH1IPjj2M3AYMJIPVex6e2cVQ/lIN08B/cwnBgfkQfkIp59K/3p+Zm5ztPwxiAV9mq6PC5ZvAAAA//8DADIXgDE="; var blueprint = ParseBlueprint.Execute(blueprintString); diff --git a/test/FactorioTools.Test/OilField/BasePlannerTest.cs b/test/FactorioTools.Test/OilField/BasePlannerTest.cs index 10416c70..db40923b 100644 --- a/test/FactorioTools.Test/OilField/BasePlannerTest.cs +++ b/test/FactorioTools.Test/OilField/BasePlannerTest.cs @@ -27,8 +27,8 @@ public static PlannerResult ExecuteAllStrategies(string blueprintString) { var options = OilFieldOptions.ForMediumElectricPole; options.ValidateSolution = true; - options.PipeStrategies = OilFieldOptions.AllPipeStrategies.ToList(); - options.BeaconStrategies = OilFieldOptions.AllBeaconStrategies.ToList(); + options.PipeStrategies = OilFieldOptions.AllPipeStrategies.ToTableList(); + options.BeaconStrategies = OilFieldOptions.AllBeaconStrategies.ToTableList(); var blueprint = ParseBlueprint.Execute(blueprintString); return Planner.Execute(options, blueprint); @@ -40,9 +40,9 @@ public static string GetGridString(PlannerResult result) var plans = Enumerable .Empty<(char Prefix, OilFieldPlan Plan)>() - .Concat(result.Summary.SelectedPlans.Select(x => (Prefix: 'S', Plan: x))) - .Concat(result.Summary.AlternatePlans.Select(x => (Prefix: 'A', Plan: x))) - .Concat(result.Summary.UnusedPlans.Select(x => (Prefix: ' ', Plan: x))) + .Concat(result.Summary.SelectedPlans.EnumerateItems().Select(x => (Prefix: 'S', Plan: x))) + .Concat(result.Summary.AlternatePlans.EnumerateItems().Select(x => (Prefix: 'A', Plan: x))) + .Concat(result.Summary.UnusedPlans.EnumerateItems().Select(x => (Prefix: ' ', Plan: x))) .OrderBy(x => x.Plan.PipeStrategy) .ThenBy(x => x.Plan.OptimizePipes) .ThenBy(x => x.Plan.BeaconStrategy) diff --git a/test/FactorioTools.Test/OilField/BaseTest.cs b/test/FactorioTools.Test/OilField/BaseTest.cs index 669feeb5..58707075 100644 --- a/test/FactorioTools.Test/OilField/BaseTest.cs +++ b/test/FactorioTools.Test/OilField/BaseTest.cs @@ -1,4 +1,5 @@ -using Knapcode.FactorioTools.Data; +using System.Linq; +using Knapcode.FactorioTools.Data; using static Knapcode.FactorioTools.OilField.Helpers; namespace Knapcode.FactorioTools.OilField; @@ -41,13 +42,13 @@ public static PumpjackCenter AddPumpjack(Context context, Location center, Direc var previousCenterToTerminals = context.CenterToTerminals; - context.CenterToTerminals = GetCenterToTerminals(context, context.Grid, context.CenterToTerminals.Keys.Concat(new[] { center }.Distinct(context)).ToList()); + context.CenterToTerminals = GetCenterToTerminals(context, context.Grid, context.CenterToTerminals.Keys.Concat(new[] { center }).DistinctBy(l => (l.X, l.Y)).ToTableArray()); context.LocationToTerminals = GetLocationToTerminals(context, context.CenterToTerminals); foreach ((var otherCenter, var terminals) in context.CenterToTerminals.EnumeratePairs().ToList()) { - var selectedDirection = center == otherCenter ? direction.GetValueOrDefault() : previousCenterToTerminals[otherCenter].First().Direction; - var selectedTerminal = terminals.OrderByDescending(t => t.Direction == selectedDirection).First(); + var selectedDirection = center == otherCenter ? direction.GetValueOrDefault() : previousCenterToTerminals[otherCenter].EnumerateItems().First().Direction; + var selectedTerminal = terminals.EnumerateItems().OrderByDescending(t => t.Direction == selectedDirection).First(); EliminateOtherTerminals(context, selectedTerminal); if (context.Grid[selectedTerminal.Terminal] is Pipe) diff --git a/test/FactorioTools.Test/OilField/Collections/DictionaryTableArrayTest.cs b/test/FactorioTools.Test/OilField/Collections/DictionaryTableArrayTest.cs new file mode 100644 index 00000000..08703bad --- /dev/null +++ b/test/FactorioTools.Test/OilField/Collections/DictionaryTableArrayTest.cs @@ -0,0 +1,81 @@ +namespace Knapcode.FactorioTools.OilField; + +public class DictionaryTableArrayTest +{ + [Fact] + public void RemoveRange_Beginning() + { + var target = new DictionaryTableList(); + target.AddCollection(Enumerable.Range(0, 8).ToList()); + + target.RemoveRange(0, 5); + + Assert.Equal(new[] { 5, 6, 7 }, target.ToArray()); + } + + [Fact] + public void RemoveRange_Middle() + { + var target = new DictionaryTableList(); + target.AddCollection(Enumerable.Range(0, 8).ToList()); + + target.RemoveRange(2, 3); + + Assert.Equal(new[] { 0, 1, 5, 6, 7 }, target.ToArray()); + } + + [Fact] + public void RemoveRange_End() + { + var target = new DictionaryTableList(); + target.AddCollection(Enumerable.Range(0, 8).ToList()); + + target.RemoveRange(4, 4); + + Assert.Equal(new[] { 0, 1, 2, 3 }, target.ToArray()); + } + + [Fact] + public void RemoveRange_All() + { + var target = new DictionaryTableList(); + target.AddCollection(Enumerable.Range(0, 8).ToList()); + + target.RemoveRange(0, 8); + + Assert.Empty(target.ToArray()); + } + + [Fact] + public void RemoveAt_Beginning() + { + var target = new DictionaryTableList(); + target.AddCollection(Enumerable.Range(0, 8).ToList()); + + target.RemoveAt(0); + + Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7 }, target.ToArray()); + } + + [Fact] + public void RemoveAt_Middle() + { + var target = new DictionaryTableList(); + target.AddCollection(Enumerable.Range(0, 8).ToList()); + + target.RemoveAt(3); + + Assert.Equal(new[] { 0, 1, 2, 4, 5, 6, 7 }, target.ToArray()); + } + + [Fact] + public void RemoveAt_End() + { + var target = new DictionaryTableList(); + target.AddCollection(Enumerable.Range(0, 8).ToList()); + + target.RemoveAt(7); + + Assert.Equal(new[] { 0, 1, 2, 3, 4, 5, 6 }, target.ToArray()); + } +} diff --git a/test/FactorioTools.Test/OilField/NonStandardBeacon/Theory.cs b/test/FactorioTools.Test/OilField/NonStandardBeacon/Theory.cs index ce74ad04..a5b3ed75 100644 --- a/test/FactorioTools.Test/OilField/NonStandardBeacon/Theory.cs +++ b/test/FactorioTools.Test/OilField/NonStandardBeacon/Theory.cs @@ -16,7 +16,7 @@ public async Task Execute(int blueprintIndex) options.BeaconHeight = 4; options.BeaconSupplyWidth = 4; options.BeaconSupplyHeight = 6; - options.BeaconStrategies = OilFieldOptions.AllBeaconStrategies.ToList(); + options.BeaconStrategies = OilFieldOptions.AllBeaconStrategies.ToTableList(); var blueprintString = SmallListBlueprintStrings[blueprintIndex]; var blueprint = ParseBlueprint.Execute(blueprintString); diff --git a/test/FactorioTools.Test/OilField/PlannerIssueTest.cs b/test/FactorioTools.Test/OilField/PlannerIssueTest.cs index d680f6a7..8e289da5 100644 --- a/test/FactorioTools.Test/OilField/PlannerIssueTest.cs +++ b/test/FactorioTools.Test/OilField/PlannerIssueTest.cs @@ -82,7 +82,7 @@ public async Task AllowsManyTurnsAroundAvoidLocations() } }; - var avoid = new AvoidLocation[] + var avoid = new[] { new AvoidLocation(33.5f, -10.5f), new AvoidLocation(34.5f, -10.5f), @@ -362,7 +362,7 @@ public async Task AllowsManyTurnsAroundAvoidLocations() new AvoidLocation(54.5f, -19.5f), new AvoidLocation(54.5f, -18.5f), new AvoidLocation(54.5f, -17.5f), - }; + }.ToTableList(); // Act var result = Planner.Execute(options, blueprint, avoid); @@ -416,7 +416,7 @@ public async Task CanPlanSinglePumpjackSurrounded() new AvoidLocation(46.5f, -4.5f), new AvoidLocation(46.5f, -5.5f), new AvoidLocation(46.5f, -6.5f), - }; + }.ToTableList(); // Act var result = Planner.Execute(options, blueprint, avoid); @@ -618,7 +618,7 @@ public async Task CanPlanTwoPumpjacksWithLotsOfAvoids() new AvoidLocation(46.5f, -4.5f), new AvoidLocation(46.5f, -3.5f), new AvoidLocation(46.5f, -0.5f), - }; + }.ToTableList(); // Act var result = Planner.Execute(options, blueprint, avoid); @@ -660,7 +660,7 @@ public void FbeOriginalFallsBackToFbeWhenLeftoverPumpsCannotConnect() // Arrange var options = OilFieldOptions.ForMediumElectricPole; options.ValidateSolution = true; - options.PipeStrategies = new List { PipeStrategy.FbeOriginal }; + options.PipeStrategies = TableList.New(PipeStrategy.FbeOriginal); var blueprintString = "0eJyM1ctuhSAQBuB3mTULGby/StM0Hg9paI9ovDQ1xncviou2h4R/KeLH4PCHjW6PRQ+jsTPVG5m2txPVLxtN5t02j2PMNp2mmoalGz6a9pMEzetwjJhZd7QLMvauv6mW+6sgbWczG+2N82F9s0t306ObIALW0E/ug94eKzlEsaDVTVXOvZtRt/5dsosnjgFO5p7L45wCOC48V8a5FOFSz1VxLkM2W50cJ3EuR1rhq2MZ5wpks76zzH85DnAlwlVBLlRdhfy70nMqXp1MEC/zXhYvTyK5uI4K5CHB4MR7QDAkkgwlvVcAHhKNY9HDK4F+INngywOiJpFwcIl7SDqu86KAsEkoHn6/6l880pCH5IPzoBfqByP5uLz0Kb7uDjnvlfrXxSToS4/TNWH/AQAA//8DANANMhY="; var blueprint = ParseBlueprint.Execute(blueprintString); @@ -669,7 +669,7 @@ public void FbeOriginalFallsBackToFbeWhenLeftoverPumpsCannotConnect() var (_, summary) = Planner.Execute(options, blueprint); // Assert - var plan = Assert.Single(summary.SelectedPlans); + var plan = Assert.Single(summary.SelectedPlans.EnumerateItems()); Assert.Equal(PipeStrategy.Fbe, plan.PipeStrategy); } @@ -682,7 +682,7 @@ public void FbeOriginalFallsBackToFbeWhenAloneGroupRemains() // Arrange var options = OilFieldOptions.ForMediumElectricPole; options.ValidateSolution = true; - options.PipeStrategies = new List { PipeStrategy.FbeOriginal }; + options.PipeStrategies = TableList.New(PipeStrategy.FbeOriginal); var blueprintString = "0eJyUlttuwyAMht/F11wEDDm9yjRNPaCJraFRkk6rqrz70ppKU8eUv5dNyReD/dlcaHs4+X4IcaL2QmF3jCO1Lxcaw3vcHK7P4qbz1FJ/6vqPze6TFE3n/vokTL6jWVGIe/9NrZ5fFfk4hSl4Ydx+nN/iqdv6YVmgMqz+OC4vHOP1SwuEa0XnZSkv3H0Y/E7+K2b1B2cAnCsE59ZxjERXCq5ex1kAV6bomnWcA3BW33CmWMeVyNkZwel1XIXgKsGZdVyNpMIJDiiUBjm7CsbpAgmvEZ4FeIgXmoUHFLJGxDApvhLgIWZYl+WZHA9Rg9N+KyA+yI0C5z0jB8JD7OBUf0Bn0YgeNp1fA+QD8qPO8rJ9+Qk/GGguBpobUn8MdBeD+JH6wSMvd34GmhxSf4zMNcQPa3Ae4od7Ij7Ej3t+H/qfzfEQP+77LQEe5IfL8rL7RfxgKzygHzDkR8oH0A8Y8eNez4C/jPiR+rMF/GXID6kXC9QfQ/ND8muR/ULzw/7DW+68t3tw++sirejLD2NaMP8AAAD//wMA5MG5Zw=="; var blueprint = ParseBlueprint.Execute(blueprintString); @@ -691,7 +691,7 @@ public void FbeOriginalFallsBackToFbeWhenAloneGroupRemains() var (_, summary) = Planner.Execute(options, blueprint); // Assert - var plan = Assert.Single(summary.SelectedPlans); + var plan = Assert.Single(summary.SelectedPlans.EnumerateItems()); Assert.Equal(PipeStrategy.Fbe, plan.PipeStrategy); } } diff --git a/test/FactorioTools.Test/OilField/PlannerTest.cs b/test/FactorioTools.Test/OilField/PlannerTest.cs index aac4b8b5..a5d81f00 100644 --- a/test/FactorioTools.Test/OilField/PlannerTest.cs +++ b/test/FactorioTools.Test/OilField/PlannerTest.cs @@ -114,7 +114,7 @@ public async Task AllowsLocationsToBeAvoided() Item = ItemNames.Vanilla.Blueprint, Version = 0, }; - var avoid = Enumerable.Range(-7, 16).Select(x => new AvoidLocation(x, 0)).ToArray(); + var avoid = Enumerable.Range(-7, 16).Select(x => new AvoidLocation(x, 0)).ToTableArray(); // Act var result = Planner.Execute(options, blueprint, avoid); @@ -227,8 +227,8 @@ public void YieldsAlternateSolutions() var (_, summary) = Planner.Execute(options, blueprint); // Assert - Assert.Single(summary.SelectedPlans); - Assert.Single(summary.AlternatePlans); - Assert.NotEmpty(summary.UnusedPlans); + Assert.Single(summary.SelectedPlans.EnumerateItems()); + Assert.Single(summary.AlternatePlans.EnumerateItems()); + Assert.NotEmpty(summary.UnusedPlans.EnumerateItems()); } } diff --git a/test/FactorioTools.Test/OilField/Score.cs b/test/FactorioTools.Test/OilField/Score.cs index 5f0468b5..e15d7f62 100644 --- a/test/FactorioTools.Test/OilField/Score.cs +++ b/test/FactorioTools.Test/OilField/Score.cs @@ -35,8 +35,8 @@ public async Task HasExpectedScore() options.ValidateSolution = false; options.OverlapBeacons = input.OverlapBeacons; - options.PipeStrategies = OilFieldOptions.AllPipeStrategies.ToList(); - options.BeaconStrategies = OilFieldOptions.AllBeaconStrategies.ToList(); + options.PipeStrategies = OilFieldOptions.AllPipeStrategies.ToTableList(); + options.BeaconStrategies = OilFieldOptions.AllBeaconStrategies.ToTableList(); var (context, summary) = Planner.Execute(options, inputBlueprint); @@ -92,7 +92,7 @@ public async Task HasExpectedScore() output.AppendLine(new string('-', heading.Length)); var planToWins = addBeaconsGroup - .SelectMany(x => x.Summary.SelectedPlans.Concat(x.Summary.AlternatePlans)) + .SelectMany(x => x.Summary.SelectedPlans.EnumerateItems().Concat(x.Summary.AlternatePlans.EnumerateItems())) .GroupBy(x => x.ToString(includeCounts: false)) .ToDictionary(x => x.Key, x => x.Count()); diff --git a/test/FactorioTools.Test/OilField/Steps/HelpersTest.cs b/test/FactorioTools.Test/OilField/Steps/HelpersTest.cs index 2a31ef3a..e16df10c 100644 --- a/test/FactorioTools.Test/OilField/Steps/HelpersTest.cs +++ b/test/FactorioTools.Test/OilField/Steps/HelpersTest.cs @@ -238,8 +238,9 @@ public void SmallElectricPole_RemoveUnused() AddElectricPole(context, new Location(8, 3)); var pumpjacks = new[] { new Location(3, 2), new Location(13, 2) } .Select(c => new ProviderRecipient(c, PumpjackWidth, PumpjackHeight)) - .ToList(); - foreach (var pumpjack in pumpjacks) + .ToList() + .ToTableList(); + foreach (var pumpjack in pumpjacks.EnumerateItems()) { AddPumpjack(context, pumpjack.Center); } @@ -289,8 +290,9 @@ public void Substation_RemoveUnused() AddElectricPole(context, new Location(12, 12)); var pumpjacks = new[] { new Location(2, 2),} .Select(c => new ProviderRecipient(c, PumpjackWidth, PumpjackHeight)) - .ToList(); - foreach (var pumpjack in pumpjacks) + .ToList() + .ToTableList(); + foreach (var pumpjack in pumpjacks.EnumerateItems()) { AddPumpjack(context, pumpjack.Center); } diff --git a/test/FactorioTools.Test/OilField/Steps/InitializeContextTest.cs b/test/FactorioTools.Test/OilField/Steps/InitializeContextTest.cs index f905ef11..1eefbdfd 100644 --- a/test/FactorioTools.Test/OilField/Steps/InitializeContextTest.cs +++ b/test/FactorioTools.Test/OilField/Steps/InitializeContextTest.cs @@ -28,7 +28,7 @@ public void OnePumpjack_SmallPower_NoBeacon() Assert.Equal(7, context.Grid.Height); Assert.Equal(-22.5, context.DeltaX); Assert.Equal(17.5, context.DeltaY); - Assert.Equal(new Location(3, 3), Assert.Single(context.Centers)); + Assert.Equal(new Location(3, 3), Assert.Single(context.Centers.EnumerateItems())); } [Fact] @@ -40,7 +40,7 @@ public void OnePumpjack_BigPower_NoBeacon() Assert.Equal(9, context.Grid.Height); Assert.Equal(-21.5, context.DeltaX); Assert.Equal(18.5, context.DeltaY); - Assert.Equal(new Location(4, 4), Assert.Single(context.Centers)); + Assert.Equal(new Location(4, 4), Assert.Single(context.Centers.EnumerateItems())); } [Fact] @@ -52,7 +52,7 @@ public void OnePumpjack_SmallPower_WithBeacon() Assert.Equal(17, context.Grid.Height); Assert.Equal(-17.5, context.DeltaX); Assert.Equal(22.5, context.DeltaY); - Assert.Equal(new Location(8, 8), Assert.Single(context.Centers)); + Assert.Equal(new Location(8, 8), Assert.Single(context.Centers.EnumerateItems())); } [Fact] @@ -64,7 +64,7 @@ public void OnePumpjack_BigPower_WithBeacon() Assert.Equal(19, context.Grid.Height); Assert.Equal(-16.5, context.DeltaX); Assert.Equal(23.5, context.DeltaY); - Assert.Equal(new Location(9, 9), Assert.Single(context.Centers)); + Assert.Equal(new Location(9, 9), Assert.Single(context.Centers.EnumerateItems())); } [Fact] @@ -76,7 +76,7 @@ public void OnePumpjack_SmallPower_WithNonStandardBeacon() Assert.Equal(15, context.Grid.Height); Assert.Equal(-20.5, context.DeltaX); Assert.Equal(21.5, context.DeltaY); - Assert.Equal(new Location(5, 7), Assert.Single(context.Centers)); + Assert.Equal(new Location(5, 7), Assert.Single(context.Centers.EnumerateItems())); } [Fact] @@ -88,7 +88,7 @@ public void OnePumpjack_BigPower_WithNonStandardBeacon() Assert.Equal(17, context.Grid.Height); Assert.Equal(-19.5, context.DeltaX); Assert.Equal(22.5, context.DeltaY); - Assert.Equal(new Location(6, 8), Assert.Single(context.Centers)); + Assert.Equal(new Location(6, 8), Assert.Single(context.Centers.EnumerateItems())); } [Fact] @@ -123,7 +123,7 @@ public void TwoPumpjacks_BigPower_WithBeacon() public void AvoidAtMaxXMaxYBeaconBound() { var blueprint = BlueprintWithCenters((25.5f, -14.5f), (33.5f, -12.5f)); - var avoid = new[] { new AvoidLocation(39.5f, -6.5f) }; + var avoid = new[] { new AvoidLocation(39.5f, -6.5f) }.ToTableList(); Context context = InitializeContext.Execute(BigPowerWithBeacon, blueprint, avoid); Assert.Equal(27, context.Grid.Width); Assert.Equal(21, context.Grid.Height); @@ -138,7 +138,7 @@ public void AvoidAtMaxXMaxYBeaconBound() public void AvoidLessThanMinY() { var blueprint = BlueprintWithCenters((25.5f, -14.5f), (33.5f, -12.5f)); - var avoid = new[] { new AvoidLocation(29.5f, -21.5f) }; + var avoid = new[] { new AvoidLocation(29.5f, -21.5f) }.ToTableList(); Context context = InitializeContext.Execute(BigPowerWithBeacon, blueprint, avoid); var g = context.Grid.ToString(); Assert.Equal(27, context.Grid.Width); @@ -154,7 +154,7 @@ public void AvoidLessThanMinY() public void AvoidGreaterThanMaxY() { var blueprint = BlueprintWithCenters((25.5f, -14.5f), (33.5f, -12.5f)); - var avoid = new[] { new AvoidLocation(29.5f, -5.5f) }; + var avoid = new[] { new AvoidLocation(29.5f, -5.5f) }.ToTableList(); Context context = InitializeContext.Execute(BigPowerWithBeacon, blueprint, avoid); Assert.Equal(27, context.Grid.Width); Assert.Equal(22, context.Grid.Height); @@ -169,7 +169,7 @@ public void AvoidGreaterThanMaxY() public void AvoidLessThanMinX() { var blueprint = BlueprintWithCenters((25.5f, -14.5f), (33.5f, -12.5f)); - var avoid = new[] { new AvoidLocation(18.5f, -13.5f) }; + var avoid = new[] { new AvoidLocation(18.5f, -13.5f) }.ToTableList(); Context context = InitializeContext.Execute(BigPowerWithBeacon, blueprint, avoid); Assert.Equal(28, context.Grid.Width); Assert.Equal(21, context.Grid.Height); @@ -184,7 +184,7 @@ public void AvoidLessThanMinX() public void AvoidGreaterThanMaxX() { var blueprint = BlueprintWithCenters((25.5f, -14.5f), (33.5f, -12.5f)); - var avoid = new[] { new AvoidLocation(40.5f, -13.5f) }; + var avoid = new[] { new AvoidLocation(40.5f, -13.5f) }.ToTableList(); Context context = InitializeContext.Execute(BigPowerWithBeacon, blueprint, avoid); Assert.Equal(28, context.Grid.Width); Assert.Equal(21, context.Grid.Height); @@ -205,7 +205,7 @@ public void AvoidGreaterThanAllBeaconBounds() new AvoidLocation(43.5f, -13.5f), new AvoidLocation(29.5f, -2.5f), new AvoidLocation(15.5f, -13.5f), - }; + }.ToTableList(); Context context = InitializeContext.Execute(BigPowerWithBeacon, blueprint, avoid); Assert.Equal(35, context.Grid.Width); Assert.Equal(29, context.Grid.Height); @@ -240,7 +240,7 @@ public void LargerMinWidthAndMinHeightWithAvoid() new AvoidLocation(43.5f, -13.5f), new AvoidLocation(29.5f, -2.5f), new AvoidLocation(15.5f, -13.5f), - }; + }.ToTableList(); Context context = InitializeContext.Execute(BigPowerWithBeacon, blueprint, avoid, minWidth: 50, minHeight: 40); Assert.Equal(50, context.Grid.Width); Assert.Equal(40, context.Grid.Height); @@ -251,7 +251,7 @@ public void LargerMinWidthAndMinHeightWithAvoid() Assert.Equal(new Location(28, 20), context.Centers[1]); } - public AvoidLocation[] NoAvoid { get; } + public IReadOnlyTableList NoAvoid { get; } public OilFieldOptions SmallPowerNoBeacon { get; } public OilFieldOptions BigPowerNoBeacon { get; } public OilFieldOptions SmallPowerWithBeacon { get; } @@ -261,7 +261,7 @@ public void LargerMinWidthAndMinHeightWithAvoid() public InitializeContextTest() { - NoAvoid = Array.Empty(); + NoAvoid = TableList.Empty(); SmallPowerNoBeacon = OilFieldOptions.ForMediumElectricPole; SmallPowerNoBeacon.AddBeacons = false; BigPowerNoBeacon = OilFieldOptions.ForSubstation; diff --git a/test/FactorioTools.Test/SetVerifySettings.cs b/test/FactorioTools.Test/SetVerifySettings.cs index 5c197721..386eae4e 100644 --- a/test/FactorioTools.Test/SetVerifySettings.cs +++ b/test/FactorioTools.Test/SetVerifySettings.cs @@ -1,5 +1,4 @@ using System.Runtime.CompilerServices; -using Knapcode.FactorioTools.OilField; namespace Knapcode.FactorioTools; @@ -12,14 +11,3 @@ public static void Initialize() VerifierSettings.AutoVerify(includeBuildServer: false); } } - -public static class ExtensionMethods -{ - public static IEnumerable GetEntities(this SquareGrid grid) - { - foreach (var location in grid.EntityLocations.EnumerateItems()) - { - yield return grid[location]!; - } - } -} \ No newline at end of file