Skip to content

Commit

Permalink
Use consistent Centers list instead of arbitrary hash map ordering
Browse files Browse the repository at this point in the history
Use stable sort for FBE beacon sort
Eliminate need for Reverse
Remove more LINQ
Add consistent ordering to planner results
Add test for scoring
  • Loading branch information
joelverhagen committed Jan 10, 2024
1 parent 7763d69 commit 3dc591e
Show file tree
Hide file tree
Showing 2,313 changed files with 50,340 additions and 50,028 deletions.
2 changes: 1 addition & 1 deletion src/FactorioTools/OilField/Containers/EmptyLocationSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void CopyTo(Location[] array)
{
}

public IEnumerable<Location> EnumerateItems()
public IReadOnlyCollection<Location> EnumerateItems()
{
return Array.Empty<Location>();
}
Expand Down
2 changes: 1 addition & 1 deletion src/FactorioTools/OilField/Containers/ILocationSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public interface ILocationSet
void Clear();
bool Contains(Location location);
void CopyTo(Location[] array);
IEnumerable<Location> EnumerateItems();
IReadOnlyCollection<Location> EnumerateItems();
void ExceptWith(ILocationSet other);
bool Overlaps(IEnumerable<Location> other);
bool Remove(Location location);
Expand Down
2 changes: 1 addition & 1 deletion src/FactorioTools/OilField/Containers/LocationBitSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public void CopyTo(Location[] array)
throw new NotSupportedException();
}

public IEnumerable<Location> EnumerateItems()
public IReadOnlyCollection<Location> EnumerateItems()
{
throw new NotSupportedException();
}
Expand Down
2 changes: 1 addition & 1 deletion src/FactorioTools/OilField/Containers/LocationHashSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void CopyTo(Location[] array)
_set.CopyTo(array);
}

public IEnumerable<Location> EnumerateItems()
public IReadOnlyCollection<Location> EnumerateItems()
{
return _set;
}
Expand Down
2 changes: 1 addition & 1 deletion src/FactorioTools/OilField/Containers/LocationIntSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public void CopyTo(Location[] array)
}
}

public IEnumerable<Location> EnumerateItems()
public IReadOnlyCollection<Location> EnumerateItems()
{
var items = new List<Location>(_set.Count);
foreach (var item in _set)
Expand Down
2 changes: 1 addition & 1 deletion src/FactorioTools/OilField/Containers/SingleLocationSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void CopyTo(Location[] array)
array[0] = new Location(_x, _y);
}

public IEnumerable<Location> EnumerateItems()
public IReadOnlyCollection<Location> EnumerateItems()
{
return _hasItem ? new[] { new Location(_x, _y) } : Array.Empty<Location>();
}
Expand Down
36 changes: 28 additions & 8 deletions src/FactorioTools/OilField/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,7 @@ public static void AddPath(ILocationDictionary<Location> cameFrom, Location reac
}
}

public static bool AreLocationsCollinear(List<Location> locations)
public static bool AreLocationsCollinear(IReadOnlyList<Location> locations)
{
double lastSlope = 0;
for (var i = 0; i < locations.Count; i++)
Expand Down Expand Up @@ -1080,16 +1080,36 @@ public static List<Location> MakeStraightLine(Location a, Location b)
throw new ArgumentException("The two points must be one the same line either horizontally or vertically.");
}

public static List<Endpoints> PointsToLines(IReadOnlyCollection<Location> nodes)
{
return PointsToLines(nodes.ToList(), sort: true);
}

/// <summary>
/// Source: https://github.com/teoxoy/factorio-blueprint-editor/blob/21ab873d8316a41b9a05c719697d461d3ede095d/packages/editor/src/core/generators/util.ts#L62
/// </summary>
public static List<Endpoints> PointsToLines(Context context, IReadOnlyCollection<Location> nodes)
public static List<Endpoints> PointsToLines(IReadOnlyList<Location> nodes, bool sort)
{
var filteredNodes = nodes
.Distinct(context)
.OrderBy(x => x.X)
.ThenBy(x => x.Y)
.ToList();
IReadOnlyList<Location> filteredNodes;
if (sort)
{
var sortedXY = nodes.ToList();
sortedXY.Sort((a, b) =>
{
var c = a.X.CompareTo(b.X);
if (c != 0)
{
return c;
}

return a.Y.CompareTo(b.Y);
});
filteredNodes = sortedXY;
}
else
{
filteredNodes = nodes;
}

if (filteredNodes.Count == 1)
{
Expand All @@ -1104,7 +1124,7 @@ public static List<Endpoints> PointsToLines(Context context, IReadOnlyCollection
if (AreLocationsCollinear(filteredNodes))
{
return Enumerable
.Range(1, filteredNodes.Count - 1)
.Range(1, filteredNodes.Count - 1)
.Select(i => new Endpoints(filteredNodes[i - 1], filteredNodes[i]))
.ToList();
}
Expand Down
1 change: 1 addition & 0 deletions src/FactorioTools/OilField/Models/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class Context
public required OilFieldOptions Options { get; set; }
public required Blueprint InputBlueprint { get; set; }
public required SquareGrid Grid { get; set; }
public required List<Location> Centers { get; set; }
public required ILocationDictionary<List<TerminalLocation>> CenterToTerminals { get; set; }
public required ILocationDictionary<Direction> CenterToOriginalDirection { get; set; }
public required ILocationDictionary<List<TerminalLocation>> LocationToTerminals { get; set; }
Expand Down
6 changes: 4 additions & 2 deletions src/FactorioTools/OilField/Models/OilFieldPlan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ namespace Knapcode.FactorioTools.OilField;
/// <param name="BeaconStrategy">Which beacon strategy, if any, was used.</param>
/// <param name="BeaconEffectCount">The number of effects the beacons provided to pumpjacks. Higher is better.</param>
/// <param name="BeaconCount">The number of beacons in the plan. For the same number of beacon effects, lower is better.</param>
/// <param name="PipeCount">The number of pipes in the plan. For the same number of beacon effects and beacons, lower is better.</param>
/// <param name="PipeCount">The number of pipes in the plan. For the same number of beacon effects and beacons, lower is better. If underground pipes are used, this only counts the upwards and downwards connections for the underground stretches of pipes.</param>
/// <param name="PipeCountWithoutUnderground">The number of pipes before beacons or underground pipes are placed.</param>
public record OilFieldPlan(
PipeStrategy PipeStrategy,
bool OptimizePipes,
BeaconStrategy? BeaconStrategy,
int BeaconEffectCount,
int BeaconCount,
int PipeCount)
int PipeCount,
int PipeCountWithoutUnderground)
{
public bool IsEquivalent(OilFieldPlan other)
{
Expand Down
2 changes: 1 addition & 1 deletion src/FactorioTools/OilField/Steps/AddElectricPoles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ private static void ConnectElectricPoles(Context context, ILocationDictionary<El

while (groups.Count > 1)
{
var closest = PointsToLines(context, electricPoles.Keys)
var closest = PointsToLines(electricPoles.Keys)
.Select(e => new
{
Endpoints = e,
Expand Down
10 changes: 8 additions & 2 deletions src/FactorioTools/OilField/Steps/AddPipes.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ private static List<PlanInfo> GetAllPlans(Context context)
BeaconStrategy: null,
BeaconEffectCount: 0,
BeaconCount: 0,
solution.Pipes.Count);
solution.Pipes.Count,
solution.PipeCountWithoutUnderground);

plans.Add(new PlanInfo(groupNumber, solutionGroup.Count, plan, solution, null));
}
Expand All @@ -148,7 +149,8 @@ private static List<PlanInfo> GetAllPlans(Context context)
beacons.Strategy,
beacons.Effects,
beacons.Beacons.Count,
solution.Pipes.Count);
solution.Pipes.Count,
solution.PipeCountWithoutUnderground);

plans.Add(new PlanInfo(groupNumber, solutionGroup.Count, plan, solution, beacons));
}
Expand Down Expand Up @@ -320,6 +322,8 @@ private static Solution GetSolution(
{
Validate.PipesAreConnected(context, optimizedPipes);

var pipeCountBeforeUnderground = optimizedPipes.Count;

ILocationDictionary<Direction>? undergroundPipes = null;
if (context.Options.UseUndergroundPipes)
{
Expand All @@ -343,6 +347,7 @@ private static Solution GetSolution(
CenterToConnectedCenters = centerToConnectedCenters,
CenterToTerminals = context.CenterToTerminals,
LocationToTerminals = context.LocationToTerminals,
PipeCountWithoutUnderground = pipeCountBeforeUnderground,
Pipes = optimizedPipes,
UndergroundPipes = undergroundPipes,
BeaconSolutions = beaconSolutions,
Expand Down Expand Up @@ -420,6 +425,7 @@ private class Solution
public required ILocationDictionary<ILocationSet>? CenterToConnectedCenters { get; set; }
public required ILocationDictionary<List<TerminalLocation>> CenterToTerminals { get; set; }
public required ILocationDictionary<List<TerminalLocation>> LocationToTerminals { get; set; }
public required int PipeCountWithoutUnderground { get; set; }
public required ILocationSet Pipes { get; set; }
public required ILocationDictionary<Direction>? UndergroundPipes { get; set; }
public required List<BeaconSolution>? BeaconSolutions { get; set; }
Expand Down
22 changes: 16 additions & 6 deletions src/FactorioTools/OilField/Steps/AddPipes.1.FBE.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static (ILocationSet Pipes, PipeStrategy FinalStrategy) ExecuteWithFbe(Co
private static (List<TerminalLocation> Terminals, ILocationSet Pipes, PipeStrategy FinalStrategy) DelaunayTriangulation(Context context, Location middle, PipeStrategy strategy)
{
// GENERATE LINES
var lines = PointsToLines(context, context.CenterToTerminals.Keys)
var lines = PointsToLines(context.Centers, sort: false)
.Select(line =>
{
var connections = context.CenterToTerminals[line.A]
Expand Down Expand Up @@ -107,7 +107,7 @@ private static (List<TerminalLocation> Terminals, ILocationSet Pipes, PipeStrate
// this will only happen when only a few pumpjacks need to be connected
if (groups.Count == 0)
{
var connection = PointsToLines(context, context.CenterToTerminals.Keys)
var connection = PointsToLines(context.Centers, sort: false)
.Select(line =>
{
return context.CenterToTerminals[line.A]
Expand Down Expand Up @@ -156,7 +156,7 @@ private static (List<TerminalLocation> Terminals, ILocationSet Pipes, PipeStrate
var locationToGroup = groups.ToDictionary(context, x => x.Location, x => x);
locationToGroup.Add(group.Location, group);

var par = PointsToLines(context, locationToGroup.Keys)
var par = PointsToLines(locationToGroup.Keys)
.Where(l => l.A == group.Location || l.B == group.Location)
.Select(l => l.A == group.Location ? l.B : l.A)
.Select(l => locationToGroup[l])
Expand Down Expand Up @@ -201,8 +201,7 @@ private static (List<TerminalLocation> Terminals, ILocationSet Pipes, PipeStrate
}

var leftoverPumpjacks = context
.CenterToTerminals
.Keys
.Centers
.Except(addedPumpjacks.Select(x => x.Center), context)
.OrderBy(l => l.GetManhattanDistance(true ? middle : context.Grid.Middle));

Expand Down Expand Up @@ -300,7 +299,18 @@ private static TwoConnectedGroups ConnectTwoGroups(Context context, Group a, Gro
bLocationsOptimized = bLocations;
}

lines.AddRange(PointsToLines(context, aLocations.Concat(bLocationsOptimized).ToList())
var aPlusB = context.GetLocationSet(aLocations.Count + bLocationsOptimized.Count, allowEnumerate: true);
for (var i = 0; i < aLocations.Count; i++)
{
aPlusB.Add(aLocations[i]);
}

for (var i = 0; i < bLocationsOptimized.Count; i++)
{
aPlusB.Add(bLocationsOptimized[i]);
}

lines.AddRange(PointsToLines(aPlusB.EnumerateItems())
.Where(p => !((aLocations.Contains(p.A) && aLocations.Contains(p.B)) || (bLocations.Contains(p.A) && bLocations.Contains(p.B))))
.OrderBy(p => p.A.GetManhattanDistance(p.B))
.Take(5)
Expand Down
33 changes: 12 additions & 21 deletions src/FactorioTools/OilField/Steps/AddPipes.2.ConnectedCenters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,7 @@ public static partial class AddPipes

private static ILocationDictionary<ILocationSet> GetConnectedPumpjacks(Context context, PipeStrategy strategy)
{
var centers = context.CenterToTerminals.Keys.ToList();
centers.Sort((a, b) =>
{
var c = a.X.CompareTo(b.X);
if (c != 0)
{
return c;
}

return a.Y.CompareTo(b.Y);
});
var centers = context.Centers;

if (centers.Count == 2)
{
Expand Down Expand Up @@ -379,7 +369,7 @@ private static List<Trunk> FindTrunks(Context context, ILocationDictionary<ILoca
foreach (var trunk in trunkCandidates)
{
var path = MakeStraightLine(trunk.Start, trunk.End);
if (!path.Any(includedPipes.Contains) && !includedCenters.Overlaps(trunk.Centers.EnumerateItems()))
if (!includedPipes.Overlaps(path) && !includedCenters.Overlaps(trunk.Centers.EnumerateItems()))
{
selectedTrunks.Add(trunk);
includedPipes.UnionWith(path);
Expand Down Expand Up @@ -416,8 +406,8 @@ private static List<Trunk> FindTrunks(Context context, ILocationDictionary<ILoca

private static PumpjackGroup ConnectTwoClosestPumpjacks(Context context, ILocationDictionary<ILocationSet> centerToConnectedCenters, ILocationSet allIncludedCenters)
{
var bestConnection = centerToConnectedCenters
.Keys
var bestConnection = context
.Centers
.Select(center =>
{
return context
Expand Down Expand Up @@ -505,19 +495,17 @@ private static ILocationSet GetChildCenters(
private static List<Trunk> GetTrunkCandidates(Context context, ILocationDictionary<ILocationSet> centerToConnectedCenters)
{
var centerToMaxX = context
.CenterToTerminals
.Keys
.Centers
.ToDictionary(context, c => c, c => centerToConnectedCenters[c].EnumerateItems().Max(c => context.CenterToTerminals[c].Max(t => t.Terminal.X)));
var centerToMaxY = context
.CenterToTerminals
.Keys
.Centers
.ToDictionary(context, c => c, c => centerToConnectedCenters[c].EnumerateItems().Max(c => context.CenterToTerminals[c].Max(t => t.Terminal.Y)));

// Find paths that connect the most terminals of neighboring pumpjacks.
var trunkCandidates = new List<Trunk>();
foreach (var translation in Translations)
{
foreach (var startingCenter in context.CenterToTerminals.Keys.OrderBy(c => c.Y).ThenBy(c => c.X))
foreach (var startingCenter in context.Centers)
{
foreach (var terminal in context.CenterToTerminals[startingCenter])
{
Expand Down Expand Up @@ -579,7 +567,10 @@ private static List<Trunk> GetTrunkCandidates(Context context, ILocationDictiona
}

trunk.Terminals.AddRange(terminals);
trunk.TerminalLocations.UnionWith(terminals.Select(t => t.Terminal));
foreach (var other in terminals)
{
trunk.TerminalLocations.Add(other.Terminal);
}
foreach (var nextTerminal in terminals)
{
trunk.Centers.Add(nextTerminal.Center);
Expand Down Expand Up @@ -616,7 +607,7 @@ public Trunk(Context context, TerminalLocation startingTerminal, Location center
public ILocationSet Centers { get; }
public int Length => Start.GetManhattanDistance(End) + 1;
public Location Start => Terminals[0].Terminal;
public Location End => Terminals.Last().Terminal;
public Location End => Terminals[Terminals.Count - 1].Terminal;

#if ENABLE_GRID_TOSTRING
public override string ToString()
Expand Down
15 changes: 1 addition & 14 deletions src/FactorioTools/OilField/Steps/CleanBlueprint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,7 @@ public static Blueprint Execute(Blueprint blueprint)

var entities = new List<Entity>();

// Pumpjacks are sorted by their Y coordinate, then their X coordinate.
var sortedCenters = context.CenterToTerminals.Keys.ToList();
sortedCenters.Sort((a, b) =>
{
var c = a.Y.CompareTo(b.Y);
if (c != 0)
{
return c;
}

return a.X.CompareTo(b.X);
});

foreach (var center in sortedCenters)
foreach (var center in context.Centers)
{
// Pumpjacks are given a direction that doesn't overlap with another pumpjack, preferring the direction
// starting at the top then proceeding clockwise.
Expand Down
Loading

0 comments on commit 3dc591e

Please sign in to comment.