diff --git a/src/FactorioTools.Serialization/OilField/Steps/GridToBlueprintString.cs b/src/FactorioTools.Serialization/OilField/Steps/GridToBlueprintString.cs index 1468d229..838e41ff 100644 --- a/src/FactorioTools.Serialization/OilField/Steps/GridToBlueprintString.cs +++ b/src/FactorioTools.Serialization/OilField/Steps/GridToBlueprintString.cs @@ -44,8 +44,9 @@ int GetEntityNumber(ElectricPoleCenter pole) return entityNumber; } - foreach ((var gridEntity, var location) in context.Grid.EntityToLocation) + foreach (var location in context.Grid.EntityLocations.EnumerateItems()) { + var gridEntity = context.Grid[location]; var position = new Position { X = location.X, diff --git a/src/FactorioTools/OilField/Grid/BeaconCenter.cs b/src/FactorioTools/OilField/Grid/BeaconCenter.cs index f093df8a..60ee8960 100644 --- a/src/FactorioTools/OilField/Grid/BeaconCenter.cs +++ b/src/FactorioTools/OilField/Grid/BeaconCenter.cs @@ -2,6 +2,10 @@ public class BeaconCenter : GridEntity { + public BeaconCenter(int id) : base(id) + { + } + #if ENABLE_GRID_TOSTRING public override string Label => "B"; #endif diff --git a/src/FactorioTools/OilField/Grid/BeaconSide.cs b/src/FactorioTools/OilField/Grid/BeaconSide.cs index f6b3843f..e798dae5 100644 --- a/src/FactorioTools/OilField/Grid/BeaconSide.cs +++ b/src/FactorioTools/OilField/Grid/BeaconSide.cs @@ -2,7 +2,7 @@ public class BeaconSide : GridEntity { - public BeaconSide(BeaconCenter center) + public BeaconSide(int id, BeaconCenter center) : base(id) { Center = center; } diff --git a/src/FactorioTools/OilField/Grid/ElectricPoleCenter.cs b/src/FactorioTools/OilField/Grid/ElectricPoleCenter.cs index f28322f3..6cb704ca 100644 --- a/src/FactorioTools/OilField/Grid/ElectricPoleCenter.cs +++ b/src/FactorioTools/OilField/Grid/ElectricPoleCenter.cs @@ -6,6 +6,10 @@ public class ElectricPoleCenter : GridEntity { private readonly HashSet _neighbors = new(); + public ElectricPoleCenter(int id) : base(id) + { + } + #if ENABLE_GRID_TOSTRING public override string Label => "E"; #endif diff --git a/src/FactorioTools/OilField/Grid/ElectricPoleSide.cs b/src/FactorioTools/OilField/Grid/ElectricPoleSide.cs index 2fe55442..b6bbd326 100644 --- a/src/FactorioTools/OilField/Grid/ElectricPoleSide.cs +++ b/src/FactorioTools/OilField/Grid/ElectricPoleSide.cs @@ -2,7 +2,7 @@ public class ElectricPoleSide : GridEntity { - public ElectricPoleSide(ElectricPoleCenter center) + public ElectricPoleSide(int id, ElectricPoleCenter center) : base(id) { Center = center; } diff --git a/src/FactorioTools/OilField/Grid/GridEntity.cs b/src/FactorioTools/OilField/Grid/GridEntity.cs index 808b9168..e0153839 100644 --- a/src/FactorioTools/OilField/Grid/GridEntity.cs +++ b/src/FactorioTools/OilField/Grid/GridEntity.cs @@ -2,10 +2,17 @@ public abstract class GridEntity { + protected GridEntity(int id) + { + Id = id; + } + #if ENABLE_GRID_TOSTRING public abstract string Label { get; } #endif + public int Id { get; } + public virtual void Unlink() { } diff --git a/src/FactorioTools/OilField/Grid/Pipe.cs b/src/FactorioTools/OilField/Grid/Pipe.cs index e66d607d..109eb4c2 100644 --- a/src/FactorioTools/OilField/Grid/Pipe.cs +++ b/src/FactorioTools/OilField/Grid/Pipe.cs @@ -2,6 +2,10 @@ public class Pipe : GridEntity { + public Pipe(int id) : base(id) + { + } + #if ENABLE_GRID_TOSTRING public override string Label => "o"; #endif diff --git a/src/FactorioTools/OilField/Grid/PumpjackCenter.cs b/src/FactorioTools/OilField/Grid/PumpjackCenter.cs index 731abb07..52fc6ddc 100644 --- a/src/FactorioTools/OilField/Grid/PumpjackCenter.cs +++ b/src/FactorioTools/OilField/Grid/PumpjackCenter.cs @@ -2,6 +2,10 @@ public class PumpjackCenter : GridEntity { + public PumpjackCenter(int id) : base(id) + { + } + #if ENABLE_GRID_TOSTRING public override string Label => "J"; #endif diff --git a/src/FactorioTools/OilField/Grid/PumpjackSide.cs b/src/FactorioTools/OilField/Grid/PumpjackSide.cs index b0dc82d1..fc27a723 100644 --- a/src/FactorioTools/OilField/Grid/PumpjackSide.cs +++ b/src/FactorioTools/OilField/Grid/PumpjackSide.cs @@ -2,7 +2,7 @@ public class PumpjackSide : GridEntity { - public PumpjackSide(PumpjackCenter center) + public PumpjackSide(int id, PumpjackCenter center) : base(id) { Center = center; } diff --git a/src/FactorioTools/OilField/Grid/SquareGrid.cs b/src/FactorioTools/OilField/Grid/SquareGrid.cs index 1daf6a65..b3379664 100644 --- a/src/FactorioTools/OilField/Grid/SquareGrid.cs +++ b/src/FactorioTools/OilField/Grid/SquareGrid.cs @@ -13,23 +13,36 @@ public abstract class SquareGrid private const string EmptyLabel = "."; #endif - private readonly Dictionary _entityToLocation; + private readonly Dictionary _entityIdToLocation; + private readonly ILocationSet _entityLocations; private readonly GridEntity?[] _grid; + private int _nextId; public SquareGrid(SquareGrid existing, bool clone) { Width = existing.Width; Height = existing.Height; - Middle = new Location(Width / 2, Height / 2); - _entityToLocation = clone ? new Dictionary(existing._entityToLocation) : existing._entityToLocation; + Middle = existing.Middle; if (clone) { + _entityIdToLocation = new Dictionary(existing._entityIdToLocation); +#if USE_HASHSETS + _entityLocations = new LocationHashSet(existing._entityLocations.Count); +#else + _entityLocations = new LocationIntSet(existing.Width, existing._entityLocations.Count); +#endif + _entityLocations.UnionWith(existing._entityLocations); + _grid = (GridEntity?[])existing._grid.Clone(); + _nextId = existing._nextId; } else { + _entityIdToLocation = existing._entityIdToLocation; + _entityLocations = existing._entityLocations; _grid = existing._grid; + _nextId = existing._nextId; } } @@ -38,78 +51,94 @@ public SquareGrid(int width, int height) Width = width; Height = height; Middle = new Location(Width / 2, Height / 2); - _entityToLocation = new Dictionary(); + _entityIdToLocation = new Dictionary(); +#if USE_HASHSETS + _entityLocations = new LocationHashSet(); +#else + _entityLocations = new LocationIntSet(width); +#endif _grid = new GridEntity?[width * height]; + _nextId = 1; } public int Width { get; } public int Height { get; } public Location Middle { get; } - public GridEntity? this[Location id] + public GridEntity? this[Location location] { get { - return _grid[GetIndex(id)]; + return _grid[GetIndex(location)]; } } - public IReadOnlyDictionary EntityToLocation => _entityToLocation; + public int GetId() + { + var id = _nextId; + _nextId++; + return id; + } + + public IReadOnlyDictionary EntityIdToLocation => _entityIdToLocation; + public ILocationSet EntityLocations => _entityLocations; - public bool IsEmpty(Location id) + public bool IsEmpty(Location location) { - return _grid[GetIndex(id)] is null; + return _grid[GetIndex(location)] is null; } - public void AddEntity(Location id, GridEntity entity) + public void AddEntity(Location location, GridEntity entity) { - var index = GetIndex(id); + var index = GetIndex(location); if (_grid[index] is not null) { - throw new FactorioToolsException($"There is already an entity at {id}."); + throw new FactorioToolsException($"There is already an entity at {location}."); } _grid[index] = entity; - _entityToLocation.Add(entity, id); + _entityLocations.Add(location); + _entityIdToLocation.Add(entity.Id, location); } - public void RemoveEntity(Location id) + public void RemoveEntity(Location location) { - var index = GetIndex(id); + var index = GetIndex(location); var entity = _grid[index]; if (entity is not null) { _grid[index] = null; - _entityToLocation.Remove(entity); + _entityLocations.Remove(location); + _entityIdToLocation.Remove(entity.Id); entity.Unlink(); } } - public bool IsEntityType(Location id) where T : GridEntity + public bool IsEntityType(Location location) where T : GridEntity { - return _grid[GetIndex(id)] is T; + return _grid[GetIndex(location)] is T; } - public bool IsInBounds(Location id) + public bool IsInBounds(Location location) { - return id.X >= 0 && id.X < Width && id.Y >= 0 && id.Y < Height; + return location.X >= 0 && location.X < Width && location.Y >= 0 && location.Y < Height; } - public abstract void GetNeighbors(Span neighbors, Location id); + public abstract void GetNeighbors(Span neighbors, Location location); - public void GetAdjacent(Span adjacent, Location id) + public void GetAdjacent(Span adjacent, Location location) { - var a = id.Translate(1, 0); + var a = location.Translate(1, 0); adjacent[0] = IsInBounds(a) ? a : Location.Invalid; - var b = id.Translate(0, -1); + var b = location.Translate(0, -1); adjacent[1] = IsInBounds(b) ? b : Location.Invalid; - var c = id.Translate(-1, 0); + var c = location.Translate(-1, 0); adjacent[2] = IsInBounds(c) ? c : Location.Invalid; - var d = id.Translate(0, 1); + var d = location.Translate(0, 1); adjacent[3] = IsInBounds(d) ? d : Location.Invalid; } @@ -129,7 +158,7 @@ public override string ToString() public void ToString(StringBuilder builder, int spacing) { - var maxLabelLength = _entityToLocation.Keys.Max(x => x.Label.Length) + spacing; + var maxLabelLength = _entityLocations.EnumerateItems().Max(x => this[x]!.Label.Length) + spacing; for (var y = 0; y < Height; y++) { @@ -158,7 +187,11 @@ public void ToString(StringBuilder builder, int spacing) private class Empty : GridEntity { - public static Empty Instance { get; } = new Empty(); + public static Empty Instance { get; } = new Empty(0); + + public Empty(int id) : base(id) + { + } #if ENABLE_GRID_TOSTRING public override string Label => EmptyLabel; diff --git a/src/FactorioTools/OilField/Grid/TemporaryEntity.cs b/src/FactorioTools/OilField/Grid/TemporaryEntity.cs index 0281404d..a840431a 100644 --- a/src/FactorioTools/OilField/Grid/TemporaryEntity.cs +++ b/src/FactorioTools/OilField/Grid/TemporaryEntity.cs @@ -2,6 +2,10 @@ public class TemporaryEntity : GridEntity { + public TemporaryEntity(int id) : base(id) + { + } + #if ENABLE_GRID_TOSTRING public override string Label => "?"; #endif diff --git a/src/FactorioTools/OilField/Grid/Terminal.cs b/src/FactorioTools/OilField/Grid/Terminal.cs index 19c52890..bb436756 100644 --- a/src/FactorioTools/OilField/Grid/Terminal.cs +++ b/src/FactorioTools/OilField/Grid/Terminal.cs @@ -2,6 +2,10 @@ public class Terminal : Pipe { + public Terminal(int id) : base(id) + { + } + #if ENABLE_GRID_TOSTRING public override string Label => "+"; #endif diff --git a/src/FactorioTools/OilField/Grid/UndergroundPipe.cs b/src/FactorioTools/OilField/Grid/UndergroundPipe.cs index 67c03df6..84c1c549 100644 --- a/src/FactorioTools/OilField/Grid/UndergroundPipe.cs +++ b/src/FactorioTools/OilField/Grid/UndergroundPipe.cs @@ -5,7 +5,7 @@ namespace Knapcode.FactorioTools.OilField; public class UndergroundPipe : Pipe { - public UndergroundPipe(Direction direction) + public UndergroundPipe(int id, Direction direction) : base(id) { Direction = direction; } diff --git a/src/FactorioTools/OilField/Helpers.cs b/src/FactorioTools/OilField/Helpers.cs index a098b483..ad07e0b2 100644 --- a/src/FactorioTools/OilField/Helpers.cs +++ b/src/FactorioTools/OilField/Helpers.cs @@ -27,12 +27,12 @@ public static class Helpers public static PumpjackCenter AddPumpjack(SquareGrid grid, Location center) { - var centerEntity = new PumpjackCenter(); + var centerEntity = new PumpjackCenter(grid.GetId()); for (var x = -1; x <= 1; x++) { for (var y = -1; y <= 1; y++) { - GridEntity entity = x != 0 || y != 0 ? new PumpjackSide(centerEntity) : centerEntity; + GridEntity entity = x != 0 || y != 0 ? new PumpjackSide(grid.GetId(), centerEntity) : centerEntity; grid.AddEntity(new Location(center.X + x, center.Y + y), entity); } } @@ -154,9 +154,9 @@ private static (ILocationDictionary CandidateToInfo, CountedBitArray Cove var coveredEntities = new CountedBitArray(recipients.Count); var providers = context.GetLocationDictionary(); - foreach (var (entity, location) in context.Grid.EntityToLocation) + foreach (var location in context.Grid.EntityLocations.EnumerateItems()) { - var provider = entity as TProvider; + var provider = context.Grid[location] as TProvider; if (provider is null) { continue; @@ -401,7 +401,7 @@ private static void AddCoveredCenters( } else if (includePumpjacks && entity is PumpjackSide pumpjackSide) { - coveredCenters.Add(grid.EntityToLocation[pumpjackSide.Center]); + coveredCenters.Add(grid.EntityIdToLocation[pumpjackSide.Center.Id]); } else if (includeBeacons && entity is BeaconCenter) { @@ -409,7 +409,7 @@ private static void AddCoveredCenters( } else if (includeBeacons && entity is BeaconSide beaconSide) { - coveredCenters.Add(grid.EntityToLocation[beaconSide.Center]); + coveredCenters.Add(grid.EntityIdToLocation[beaconSide.Center.Id]); } } } @@ -700,8 +700,9 @@ public static (List PoweredEntities, bool HasBeacons) GetPowe var poweredEntities = new List(); var hasBeacons = false; - foreach ((var entity, var location) in context.Grid.EntityToLocation) + foreach (var location in context.Grid.EntityLocations.EnumerateItems()) { + var entity = context.Grid[location]; if (entity is PumpjackCenter) { poweredEntities.Add(new ProviderRecipient(location, PumpjackWidth, PumpjackHeight)); @@ -935,8 +936,8 @@ public static void AddBeaconsToGrid(SquareGrid grid, OilFieldOptions options, IE AddProviderToGrid( grid, center, - new BeaconCenter(), - c => new BeaconSide(c), + new BeaconCenter(grid.GetId()), + c => new BeaconSide(grid.GetId(), c), options.BeaconWidth, options.BeaconHeight); } diff --git a/src/FactorioTools/OilField/Steps/AddElectricPoles.cs b/src/FactorioTools/OilField/Steps/AddElectricPoles.cs index 265e37ac..80685e00 100644 --- a/src/FactorioTools/OilField/Steps/AddElectricPoles.cs +++ b/src/FactorioTools/OilField/Steps/AddElectricPoles.cs @@ -26,7 +26,7 @@ private enum RetryStrategy { if (avoidEntities.Add(location)) { - context.Grid.AddEntity(location, new TemporaryEntity()); + context.Grid.AddEntity(location, new TemporaryEntity(context.Grid.GetId())); } } } @@ -150,7 +150,7 @@ private static bool ArePolesConnectedWithout(ILocationDictionary(); + var discovered = new HashSet(); while (queue.Count > 0) { @@ -161,7 +161,7 @@ private static bool ArePolesConnectedWithout(ILocationDictionary electricPoles, Location center) { - var centerEntity = new ElectricPoleCenter(); + var centerEntity = new ElectricPoleCenter(context.Grid.GetId()); AddProviderToGrid( context.Grid, center, centerEntity, - c => new ElectricPoleSide(c), + c => new ElectricPoleSide(context.Grid.GetId(), c), context.Options.ElectricPoleWidth, context.Options.ElectricPoleHeight); @@ -739,7 +739,7 @@ private static void AddSinglePoleForConnection(Context context, ILocationDiction var match = false; foreach (var neighbor in center.Neighbors) { - var location = context.Grid.EntityToLocation[neighbor]; + var location = context.Grid.EntityIdToLocation[neighbor.Id]; if (group.Contains(location)) { match = true; @@ -777,14 +777,14 @@ private static List GetElectricPoleGroups(Context context, ILocati var current = remaining.EnumerateItems().First(); remaining.Remove(current); - var entities = new HashSet(); + var entityIds = new HashSet(); var explore = new Queue(); explore.Enqueue(electricPoles[current]); while (explore.Count > 0) { var entity = explore.Dequeue(); - if (entities.Add(entity)) + if (entityIds.Add(entity.Id)) { foreach (var neighbor in entity.Neighbors) { @@ -794,9 +794,9 @@ private static List GetElectricPoleGroups(Context context, ILocati } var group = context.GetLocationSet(allowEnumerate: true); - foreach (var entity in entities) + foreach (var entityId in entityIds) { - var location = context.Grid.EntityToLocation[entity]; + var location = context.Grid.EntityIdToLocation[entityId]; group.Add(location); } diff --git a/src/FactorioTools/OilField/Steps/AddPipeEntities.cs b/src/FactorioTools/OilField/Steps/AddPipeEntities.cs index 3d80ed70..dafa5b0f 100644 --- a/src/FactorioTools/OilField/Steps/AddPipeEntities.cs +++ b/src/FactorioTools/OilField/Steps/AddPipeEntities.cs @@ -31,7 +31,7 @@ public static void Execute( foreach ((var location, var direction) in undergroundPipes.EnumeratePairs()) { addedPipes.Add(location); - grid.AddEntity(location, new UndergroundPipe(direction)); + grid.AddEntity(location, new UndergroundPipe(grid.GetId(), direction)); } } @@ -46,7 +46,7 @@ public static void Execute( { if (addedPipes.Add(terminals[i].Terminal)) { - grid.AddEntity(terminals[i].Terminal, new Terminal()); + grid.AddEntity(terminals[i].Terminal, new Terminal(grid.GetId())); } } } @@ -55,7 +55,7 @@ public static void Execute( { if (addedPipes.Add(pipe)) { - grid.AddEntity(pipe, new Pipe()); + grid.AddEntity(pipe, new Pipe(grid.GetId())); } } diff --git a/src/FactorioTools/OilField/Steps/InitializeContext.cs b/src/FactorioTools/OilField/Steps/InitializeContext.cs index 8ef4a895..3ca2e496 100644 --- a/src/FactorioTools/OilField/Steps/InitializeContext.cs +++ b/src/FactorioTools/OilField/Steps/InitializeContext.cs @@ -123,8 +123,9 @@ private static int[] GetLocationToAdjacentCount(SquareGrid grid) Span neighbors = new Location[4]; #endif - foreach ((var entity, var location) in grid.EntityToLocation) + foreach (var location in grid.EntityLocations.EnumerateItems()) { + var entity = grid[location]; if (entity is not PumpjackSide) { continue; diff --git a/src/FactorioTools/OilField/Steps/PlanBeacons.0.cs b/src/FactorioTools/OilField/Steps/PlanBeacons.0.cs index 995a6bc8..66026ed5 100644 --- a/src/FactorioTools/OilField/Steps/PlanBeacons.0.cs +++ b/src/FactorioTools/OilField/Steps/PlanBeacons.0.cs @@ -10,7 +10,7 @@ public static List Execute(Context context, ILocationSet pipes) { foreach (var pipe in pipes.EnumerateItems()) { - context.Grid.AddEntity(pipe, new TemporaryEntity()); + context.Grid.AddEntity(pipe, new TemporaryEntity(context.Grid.GetId())); } var solutions = new List(context.Options.BeaconStrategies.Count); diff --git a/src/FactorioTools/OilField/Steps/PlanBeacons.1.FBE.cs b/src/FactorioTools/OilField/Steps/PlanBeacons.1.FBE.cs index e6d42e30..04158cbe 100644 --- a/src/FactorioTools/OilField/Steps/PlanBeacons.1.FBE.cs +++ b/src/FactorioTools/OilField/Steps/PlanBeacons.1.FBE.cs @@ -344,12 +344,11 @@ private static ILocationSet GetOccupiedPositions(Context context, List ent private static List GetEntityAreas(Context context) { - GridEntity pipe = new Pipe(); + var areas = new List(context.Grid.EntityLocations.Count); - var areas = new List(context.Grid.EntityToLocation.Count); - - foreach (var (entity, location) in context.Grid.EntityToLocation) + foreach (var location in context.Grid.EntityLocations.EnumerateItems()) { + var entity = context.Grid[location]; int width; int height; bool effect; diff --git a/src/FactorioTools/OilField/Steps/Validate.cs b/src/FactorioTools/OilField/Steps/Validate.cs index 053758fd..b62ae18f 100644 --- a/src/FactorioTools/OilField/Steps/Validate.cs +++ b/src/FactorioTools/OilField/Steps/Validate.cs @@ -169,8 +169,9 @@ public static void AllEntitiesHavePower(Context context) (var poweredEntities, _) = GetPoweredEntities(context); var electricPoleCenters = new List(); - foreach ((var entity, var location) in context.Grid.EntityToLocation) + foreach (var location in context.Grid.EntityLocations.EnumerateItems()) { + var entity = context.Grid[location]; if (entity is ElectricPoleCenter) { electricPoleCenters.Add(location); diff --git a/src/Sandbox/Program.cs b/src/Sandbox/Program.cs index 29db8cc0..42612576 100644 --- a/src/Sandbox/Program.cs +++ b/src/Sandbox/Program.cs @@ -12,10 +12,6 @@ private static void Main(string[] args) { NormalizeBlueprints.Execute(BigListDataPath, SmallListDataPath); } - else if (args.Length > 0 && args[0] == "measure") - { - Measure(); - } else if (args.Length > 0 && args[0] == "sample") { var (context, summary) = Planner.ExecuteSample(); @@ -119,208 +115,4 @@ private static void Sandbox() } } } - - private static void Measure() - { - var blueprintStringsAll = ParseBlueprint.ReadBlueprintFile(SmallListDataPath).ToArray(); - var blueprintStrings = blueprintStringsAll; - var optionsAll = new[] { OilFieldOptions.ForSmallElectricPole, OilFieldOptions.ForMediumElectricPole, OilFieldOptions.ForSubstation, OilFieldOptions.ForBigElectricPole }; - - var outputs = new List(); - - var addBeaconsAll = new[] { true, false }; - var overlapBeaconsAll = new[] { true, false }; - - foreach (var addBeacons in addBeaconsAll) - { - var planToWins = new Dictionary(); - - foreach (var overlapBeacons in overlapBeaconsAll) - { - if (!addBeacons && !overlapBeacons) - { - continue; - } - - foreach (var options in optionsAll) - { - var pipeSum = 0; - var poleSum = 0; - var beaconSum = 0; - var effectSum = 0; - var blueprintCount = 0; - for (int i = 0; i < blueprintStrings.Length; i++) - { - Console.WriteLine("Index " + i); - string? blueprintString = blueprintStrings[i]; - var inputBlueprint = ParseBlueprint.Execute(blueprintString); - - options.AddBeacons = addBeacons; - options.UseUndergroundPipes = options.AddBeacons; - options.OptimizePipes = true; - options.ValidateSolution = false; - options.OverlapBeacons = overlapBeacons; - - var (context, summary) = Planner.Execute(options, inputBlueprint); - - var pipeCount = context.Grid.EntityToLocation.Keys.OfType().Count(); - var poleCount = context.Grid.EntityToLocation.Keys.OfType().Count(); - var beaconCount = context.Grid.EntityToLocation.Keys.OfType().Count(); - var effectCount = summary.SelectedPlans[0].BeaconEffectCount; - - foreach (var plan in summary.SelectedPlans.Concat(summary.AlternatePlans).Select(p => p.ToString())) - { - if (!planToWins.TryGetValue(plan, out var count)) - { - planToWins.Add(plan, 1); - } - else - { - planToWins[plan] = count + 1; - } - } - - Console.WriteLine($"{pipeCount},{poleCount},{beaconCount},{effectCount}"); - - pipeSum += pipeCount; - poleSum += poleCount; - beaconSum += beaconCount; - effectSum += effectCount; - blueprintCount++; - } - - outputs.Add(new MeasureResult( - options.ElectricPoleEntityName, - options.AddBeacons, - options.AddBeacons ? (options.OverlapBeacons ? "yes" : "no") : "N/A", - pipeSum * 1.0 / blueprintCount, - poleSum * 1.0 / blueprintCount, - beaconSum * 1.0 / blueprintCount, - effectSum * 1.0 / blueprintCount)); - } - } - - Console.WriteLine(); - var maxWidth = planToWins.Keys.Max(p => p.Length); - foreach ((var plan, var wins) in planToWins.OrderBy(p => p.Value)) - { - Console.WriteLine($"{plan.PadLeft(maxWidth)} : {wins}"); - } - Console.WriteLine(); - } - - var tableBuilder = new TableBuilder(); - tableBuilder.AddColumns( - "Electric pole", - "Add beacons", - "Overlap beacons", - "Pipe count", - "Pole count", - "Beacon count", - "Effect count"); - - foreach (var output in outputs) - { - tableBuilder.AddRow( - output.ElectricPoleEntityName, - output.AddBeacons ? "yes" : "no", - output.OverlapBeacons, - output.PipeCount, - output.PoleCount, - output.BeaconCount, - output.EffectCount); - } - - Console.WriteLine(tableBuilder.Build()); - } - - public class TableBuilder - { - private readonly List _columns = new(); - private readonly List> _rows = new(); - - public void AddColumn(string label) - { - _columns.Add(label); - } - - public void AddColumns(params string[] labels) - { - for (var i = 0; i < labels.Length; i++) - { - AddColumn(labels[i]); - } - } - - public void AddRow(params object[] row) - { - _rows.Add(row); - } - - public void AddRow(IEnumerable row) - { - _rows.Add(row.Cast()); - } - - public string Build() - { - var rows = _rows.Select(x => x.ToList()).ToList(); - var columnCount = Math.Max(_columns.Count, rows.Select(x => x.Count).DefaultIfEmpty(0).Max()); - - var maxWidths = new int[columnCount]; - var columnHeadings = new string[columnCount]; - - for (var i = 0; i < columnCount; i++) - { - columnHeadings[i] = _columns.ElementAtOrDefault(i) ?? $"(column {i + 1})"; - maxWidths[i] = _rows - .Select(x => x.ElementAtOrDefault(i)?.ToString() ?? string.Empty) - .Append(columnHeadings[i]) - .Max(x => x.Length); - } - - var builder = new StringBuilder(); - AppendRow(builder, columnCount, maxWidths, i => columnHeadings[i]); - AppendRow(builder, columnCount, maxWidths, i => new string('-', maxWidths[i])); - foreach (var row in rows) - { - AppendRow(builder, columnCount, maxWidths, i => row[i]?.ToString() ?? string.Empty); - } - - return builder.ToString(); - } - - private static void AppendRow(StringBuilder builder, int columnCount, int[] maxWidths, Func getValue) - { - for (var i = 0; i < columnCount; i++) - { - if (i == 0) - { - builder.Append("| "); - } - else - { - builder.Append(" | "); - } - - builder.Append(getValue(i).PadRight(maxWidths[i])); - - if (i == columnCount - 1) - { - builder.Append(" |"); - } - } - - builder.AppendLine(); - } - } - - private record MeasureResult( - string ElectricPoleEntityName, - bool AddBeacons, - string OverlapBeacons, - double PipeCount, - double PoleCount, - double BeaconCount, - double EffectCount); } \ No newline at end of file diff --git a/test/FactorioTools.Test/OilField/BaseFacts.cs b/test/FactorioTools.Test/OilField/BaseFacts.cs index f5ff2d6b..fe9b7392 100644 --- a/test/FactorioTools.Test/OilField/BaseFacts.cs +++ b/test/FactorioTools.Test/OilField/BaseFacts.cs @@ -7,13 +7,13 @@ public abstract class BaseFacts { public static ElectricPoleCenter AddElectricPole(Context context, Location center) { - var entity = new ElectricPoleCenter(); + var entity = new ElectricPoleCenter(context.Grid.GetId()); AddProviderToGrid( context.Grid, center, entity, - c => new ElectricPoleSide(c), + c => new ElectricPoleSide(context.Grid.GetId(), c), providerWidth: context.Options.ElectricPoleWidth, providerHeight: context.Options.ElectricPoleHeight); @@ -22,13 +22,13 @@ public static ElectricPoleCenter AddElectricPole(Context context, Location cente public static BeaconCenter AddBeacon(Context context, Location center) { - var entity = new BeaconCenter(); + var entity = new BeaconCenter(context.Grid.GetId()); AddProviderToGrid( context.Grid, center, entity, - c => new BeaconSide(c), + c => new BeaconSide(context.Grid.GetId(),c), providerWidth: context.Options.BeaconWidth, providerHeight: context.Options.BeaconHeight); @@ -53,7 +53,7 @@ public static PumpjackCenter AddPumpjack(Context context, Location center, Direc if (context.Grid[selectedTerminal.Terminal] is Pipe) { context.Grid.RemoveEntity(selectedTerminal.Terminal); - context.Grid.AddEntity(selectedTerminal.Terminal, new Terminal()); + context.Grid.AddEntity(selectedTerminal.Terminal, new Terminal(context.Grid.GetId())); } } diff --git a/test/FactorioTools.Test/OilField/Score.cs b/test/FactorioTools.Test/OilField/Score.cs index 6b5ac707..a1e854c2 100644 --- a/test/FactorioTools.Test/OilField/Score.cs +++ b/test/FactorioTools.Test/OilField/Score.cs @@ -49,7 +49,7 @@ public async Task HasExpectedScore() summary.SelectedPlans[0].BeaconCount, PipeCountWithUnderground = summary.SelectedPlans[0].PipeCount, summary.SelectedPlans[0].PipeCountWithoutUnderground, - PoleCount = context.Grid.EntityToLocation.Keys.OfType().Count(), + PoleCount = context.Grid.GetEntities().OfType().Count(), }; }); diff --git a/test/FactorioTools.Test/OilField/Steps/HelpersTest.cs b/test/FactorioTools.Test/OilField/Steps/HelpersTest.cs index 5e9d4dfc..91ef7745 100644 --- a/test/FactorioTools.Test/OilField/Steps/HelpersTest.cs +++ b/test/FactorioTools.Test/OilField/Steps/HelpersTest.cs @@ -303,7 +303,7 @@ public void Substation_RemoveUnused() // Visualizer.Show(context.Grid, candidateToCovered.Keys.Select(c => (DelaunatorSharp.IPoint)new DelaunatorSharp.Point(c.X, c.Y)), Array.Empty()); - Assert.All(context.Grid.EntityToLocation.Keys, e => Assert.True(e is PumpjackSide || e is PumpjackCenter)); + Assert.All(context.Grid.GetEntities(), e => Assert.True(e is PumpjackSide || e is PumpjackCenter)); Assert.Equal(0, providers.Count); // These are positions that should become candidates after unused substations are removed. diff --git a/test/FactorioTools.Test/OilField/Steps/PlanUndergroundPipesTest.cs b/test/FactorioTools.Test/OilField/Steps/PlanUndergroundPipesTest.cs index fb0f894d..ba64e136 100644 --- a/test/FactorioTools.Test/OilField/Steps/PlanUndergroundPipesTest.cs +++ b/test/FactorioTools.Test/OilField/Steps/PlanUndergroundPipesTest.cs @@ -40,8 +40,8 @@ public void Horizontal_OneRun(int length) Run(context, pipes); - Assert.Equal(2, context.Grid.EntityToLocation.Count); - Assert.All(context.Grid.EntityToLocation.Keys, e => Assert.IsType(e)); + Assert.Equal(2, context.Grid.EntityLocations.Count); + Assert.All(context.Grid.GetEntities(), e => Assert.IsType(e)); var left = Assert.IsType(context.Grid[new Location(1, 1)]); Assert.Equal(Direction.Left, left.Direction); @@ -61,10 +61,10 @@ public void Horizontal_OneRun_WithExtra(int length) Run(context, pipes); - Assert.Equal(2 + (length - 11), context.Grid.EntityToLocation.Count); - Assert.All(context.Grid.EntityToLocation.Keys, e => Assert.IsAssignableFrom(e)); - Assert.Equal(2, context.Grid.EntityToLocation.Keys.Count(e => e is UndergroundPipe)); - Assert.Equal(2 + (length - 11), context.Grid.EntityToLocation.Keys.Count(e => e is Pipe)); + Assert.Equal(2 + (length - 11), context.Grid.EntityLocations.Count); + Assert.All(context.Grid.GetEntities(), e => Assert.IsAssignableFrom(e)); + Assert.Equal(2, context.Grid.GetEntities().Count(e => e is UndergroundPipe)); + Assert.Equal(2 + (length - 11), context.Grid.GetEntities().Count(e => e is Pipe)); var left = Assert.IsType(context.Grid[new Location(1, 1)]); Assert.Equal(Direction.Left, left.Direction); @@ -92,9 +92,9 @@ public void Horizontal_TwoRuns(int length) Run(context, pipes); - Assert.Equal(4, context.Grid.EntityToLocation.Count); - Assert.All(context.Grid.EntityToLocation.Keys, e => Assert.IsAssignableFrom(e)); - Assert.Equal(4, context.Grid.EntityToLocation.Keys.Count(e => e is UndergroundPipe)); + Assert.Equal(4, context.Grid.EntityLocations.Count); + Assert.All(context.Grid.GetEntities(), e => Assert.IsAssignableFrom(e)); + Assert.Equal(4, context.Grid.GetEntities().Count(e => e is UndergroundPipe)); var leftA = Assert.IsType(context.Grid[new Location(1, 1)]); Assert.Equal(Direction.Left, leftA.Direction); @@ -119,10 +119,10 @@ public void Horizontal_TwoRuns_WithExtra(int length) Run(context, pipes); - Assert.Equal(4 + (length - 22), context.Grid.EntityToLocation.Count); - Assert.All(context.Grid.EntityToLocation.Keys, e => Assert.IsAssignableFrom(e)); - Assert.Equal(4, context.Grid.EntityToLocation.Keys.Count(e => e is UndergroundPipe)); - Assert.Equal(4 + (length - 22), context.Grid.EntityToLocation.Keys.Count(e => e is Pipe)); + Assert.Equal(4 + (length - 22), context.Grid.EntityLocations.Count); + Assert.All(context.Grid.GetEntities(), e => Assert.IsAssignableFrom(e)); + Assert.Equal(4, context.Grid.GetEntities().Count(e => e is UndergroundPipe)); + Assert.Equal(4 + (length - 22), context.Grid.GetEntities().Count(e => e is Pipe)); var leftA = Assert.IsType(context.Grid[new Location(1, 1)]); Assert.Equal(Direction.Left, leftA.Direction); @@ -171,8 +171,8 @@ public void Vertical_OneRun(int length) Run(context, pipes); - Assert.Equal(2, context.Grid.EntityToLocation.Count); - Assert.All(context.Grid.EntityToLocation.Keys, e => Assert.IsType(e)); + Assert.Equal(2, context.Grid.EntityLocations.Count); + Assert.All(context.Grid.GetEntities(), e => Assert.IsType(e)); var up = Assert.IsType(context.Grid[new Location(1, 1)]); Assert.Equal(Direction.Up, up.Direction); @@ -192,10 +192,10 @@ public void Vertical_OneRun_WithExtra(int length) Run(context, pipes); - Assert.Equal(2 + (length - 11), context.Grid.EntityToLocation.Count); - Assert.All(context.Grid.EntityToLocation.Keys, e => Assert.IsAssignableFrom(e)); - Assert.Equal(2, context.Grid.EntityToLocation.Keys.Count(e => e is UndergroundPipe)); - Assert.Equal(2 + (length - 11), context.Grid.EntityToLocation.Keys.Count(e => e is Pipe)); + Assert.Equal(2 + (length - 11), context.Grid.EntityLocations.Count); + Assert.All(context.Grid.GetEntities(), e => Assert.IsAssignableFrom(e)); + Assert.Equal(2, context.Grid.GetEntities().Count(e => e is UndergroundPipe)); + Assert.Equal(2 + (length - 11), context.Grid.GetEntities().Count(e => e is Pipe)); var up = Assert.IsType(context.Grid[new Location(1, 1)]); Assert.Equal(Direction.Up, up.Direction); @@ -223,9 +223,9 @@ public void Vertical_TwoRuns(int length) Run(context, pipes); - Assert.Equal(4, context.Grid.EntityToLocation.Count); - Assert.All(context.Grid.EntityToLocation.Keys, e => Assert.IsAssignableFrom(e)); - Assert.Equal(4, context.Grid.EntityToLocation.Keys.Count(e => e is UndergroundPipe)); + Assert.Equal(4, context.Grid.EntityLocations.Count); + Assert.All(context.Grid.GetEntities(), e => Assert.IsAssignableFrom(e)); + Assert.Equal(4, context.Grid.GetEntities().Count(e => e is UndergroundPipe)); var upA = Assert.IsType(context.Grid[new Location(1, 1)]); Assert.Equal(Direction.Up, upA.Direction); @@ -249,10 +249,10 @@ public void Vertical_TwoRuns_WithExtra(int length) Run(context, pipes); - Assert.Equal(4 + (length - 22), context.Grid.EntityToLocation.Count); - Assert.All(context.Grid.EntityToLocation.Keys, e => Assert.IsAssignableFrom(e)); - Assert.Equal(4, context.Grid.EntityToLocation.Keys.Count(e => e is UndergroundPipe)); - Assert.Equal(4 + (length - 22), context.Grid.EntityToLocation.Keys.Count(e => e is Pipe)); + Assert.Equal(4 + (length - 22), context.Grid.EntityLocations.Count); + Assert.All(context.Grid.GetEntities(), e => Assert.IsAssignableFrom(e)); + Assert.Equal(4, context.Grid.GetEntities().Count(e => e is UndergroundPipe)); + Assert.Equal(4 + (length - 22), context.Grid.GetEntities().Count(e => e is Pipe)); var upA = Assert.IsType(context.Grid[new Location(1, 1)]); Assert.Equal(Direction.Up, upA.Direction); @@ -277,8 +277,8 @@ public void Intersection_Plus() Run(context, pipes); - Assert.Equal(9, context.Grid.EntityToLocation.Count); - Assert.All(context.Grid.EntityToLocation.Keys, e => Assert.IsAssignableFrom(e)); + Assert.Equal(9, context.Grid.EntityLocations.Count); + Assert.All(context.Grid.GetEntities(), e => Assert.IsAssignableFrom(e)); Assert.Equal(Direction.Left, Assert.IsType(context.Grid[new Location(1, 6)]).Direction); Assert.Equal(Direction.Right, Assert.IsType(context.Grid[new Location(5, 6)]).Direction); @@ -305,8 +305,8 @@ public void Intersection_T() Run(context, pipes); - Assert.Equal(7, context.Grid.EntityToLocation.Count); - Assert.All(context.Grid.EntityToLocation.Keys, e => Assert.IsAssignableFrom(e)); + Assert.Equal(7, context.Grid.EntityLocations.Count); + Assert.All(context.Grid.GetEntities(), e => Assert.IsAssignableFrom(e)); Assert.Equal(Direction.Left, Assert.IsType(context.Grid[new Location(1, 6)]).Direction); Assert.Equal(Direction.Right, Assert.IsType(context.Grid[new Location(10, 6)]).Direction); @@ -330,7 +330,7 @@ public void RunIsInterruptedByTerminal() Run(context, pipes); - Assert.Equal(5, context.Grid.EntityToLocation.Keys.Count(e => e is Pipe)); + Assert.Equal(5, context.Grid.GetEntities().Count(e => e is Pipe)); Assert.Equal(Direction.Left, Assert.IsType(context.Grid[new Location(1, 4)]).Direction); Assert.Equal(Direction.Right, Assert.IsType(context.Grid[new Location(5, 4)]).Direction); @@ -352,7 +352,7 @@ public void RunIsAdjacentToTerminal() Run(context, pipes); - Assert.Equal(6, context.Grid.EntityToLocation.Keys.Count(e => e is Pipe)); + Assert.Equal(6, context.Grid.GetEntities().Count(e => e is Pipe)); Assert.Equal(Direction.Left, Assert.IsType(context.Grid[new Location(1, 4)]).Direction); Assert.Equal(Direction.Right, Assert.IsType(context.Grid[new Location(4, 4)]).Direction); @@ -374,7 +374,7 @@ public void RunEndsWithTerminal() Run(context, pipes); - Assert.Equal(2, context.Grid.EntityToLocation.Keys.Count(e => e is Pipe)); + Assert.Equal(2, context.Grid.GetEntities().Count(e => e is Pipe)); Assert.Equal(Direction.Left, Assert.IsType(context.Grid[new Location(1, 3)]).Direction); Assert.Equal(Direction.Right, Assert.IsType(context.Grid[new Location(5, 3)]).Direction); @@ -390,7 +390,7 @@ public void RunStartsWithTerminal() Run(context, pipes); - Assert.Equal(2, context.Grid.EntityToLocation.Keys.Count(e => e is Pipe)); + Assert.Equal(2, context.Grid.GetEntities().Count(e => e is Pipe)); Assert.Equal(Direction.Left, Assert.IsType(context.Grid[new Location(4, 1)]).Direction); Assert.Equal(Direction.Right, Assert.IsType(context.Grid[new Location(8, 1)]).Direction); @@ -407,7 +407,7 @@ public void RunStartsAndEndsWithTerminal() Run(context, pipes); - Assert.Equal(2, context.Grid.EntityToLocation.Keys.Count(e => e is Pipe)); + Assert.Equal(2, context.Grid.GetEntities().Count(e => e is Pipe)); Assert.Equal(Direction.Left, Assert.IsType(context.Grid[new Location(4, 3)]).Direction); Assert.Equal(Direction.Right, Assert.IsType(context.Grid[new Location(8, 3)]).Direction); @@ -418,7 +418,7 @@ private static void Run(Context context, List pipes) var locationSet = pipes.ToReadOnlySet(context, allowEnumerate: true); var undergroundPipes = PlanUndergroundPipes.Execute(context, locationSet); - Assert.Equal(0, context.Grid.EntityToLocation.Keys.Count(e => e is Pipe)); + Assert.Equal(0, context.Grid.GetEntities().Count(e => e is Pipe)); Assert.All(undergroundPipes.Keys, p => Assert.Contains(p, pipes)); AddPipeEntities.Execute(context, locationSet, undergroundPipes); diff --git a/test/FactorioTools.Test/SetVerifySettings.cs b/test/FactorioTools.Test/SetVerifySettings.cs index 386eae4e..5c197721 100644 --- a/test/FactorioTools.Test/SetVerifySettings.cs +++ b/test/FactorioTools.Test/SetVerifySettings.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using Knapcode.FactorioTools.OilField; namespace Knapcode.FactorioTools; @@ -11,3 +12,14 @@ 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