Skip to content

Commit

Permalink
Allow specific locations to be avoided
Browse files Browse the repository at this point in the history
  • Loading branch information
joelverhagen committed Jan 16, 2024
1 parent a892aa0 commit f12436c
Show file tree
Hide file tree
Showing 14 changed files with 243 additions and 56 deletions.
2 changes: 1 addition & 1 deletion src/BlazorWebApp/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
var options = OilFieldOptions.ForMediumElectricPole;
var bp = ParseBlueprint.Execute(InputBlueprint);
(var context, _)= Planner.Execute(options, bp);
OutputBlueprint = GridToBlueprintString.Execute(context, addFbeOffset: false);
OutputBlueprint = GridToBlueprintString.Execute(context, addFbeOffset: false, addAvoidEntities: false);
Status = "Done: " + sw.Elapsed;
}
catch (Exception ex)
Expand Down
2 changes: 1 addition & 1 deletion src/BrowserWasm/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static string Greeting()
var blueprintString = "0eJyM1MFqhDAQBuB3mXMOTkzcNa9SyuK6oaRdo2gsFfHdGxMPhS34n8QYPx0m/6x0f852GJ0PZFZybe8nMm8rTe7DN899zTedJUPD3A2fTftFgsIy7Csu2I42Qc4/7A8Z3t4FWR9ccDYb6Wa5+bm72zFuEP9YQz/FF3q/fyki5UXQEi+RfbjRtvlRsYkXTQKa5qSpc60ENKWSps81hVSasPoc0wimUK0CNM5N4OKcuyA/l7vAfM5dAU5WmQO6WiPF5kYw0FYukGo17iGJuGauAjgkEqrGPSQUUmYPOHqMxEJnTwKHhTVerwQmCiPZkDlpEqkXCYc8Bp4EPCQdR73lSzricE4D2/yZ+IK+7TgdG7ZfAAAA//8DABy5/JQ=";
var blueprint = ParseBlueprint.Execute(blueprintString);
(var context, _) = Planner.Execute(options, blueprint);
var outputBlueprintString = GridToBlueprintString.Execute(context, addFbeOffset: false);
var outputBlueprintString = GridToBlueprintString.Execute(context, addFbeOffset: false, addAvoidEntities: true);
return $"Elapsed: {sw.Elapsed}, blueprint: {outputBlueprintString}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System;
using System.Linq;
using System.IO;
using System.Runtime.CompilerServices;

namespace Knapcode.FactorioTools.OilField;

Expand All @@ -26,7 +27,7 @@ public static class GridToBlueprintString
{ EntityNames.AaiIndustry.SmallIronElectricPole, (1, 1) },
};

public static string Execute(Context context, bool addFbeOffset)
public static string Execute(Context context, bool addFbeOffset, bool addAvoidEntities)
{
var entities = new List<Entity>();
var nextEntityNumber = 1;
Expand Down Expand Up @@ -126,9 +127,19 @@ int GetEntityNumber(ElectricPoleCenter pole)
Items = context.Options.BeaconModules,
});
break;
case AvoidEntity:
if (addAvoidEntities)
{
entities.Add(new Entity
{
EntityNumber = nextEntityNumber++,
Name = EntityNames.Vanilla.Wall,
Position = position,
});
}
break;
case BeaconSide:
case TemporaryEntity:
// Ignore
break;
default:
throw new NotImplementedException();
Expand All @@ -137,9 +148,20 @@ int GetEntityNumber(ElectricPoleCenter pole)

var blueprint = new Blueprint
{
Icons = context.InputBlueprint.Icons,
Icons = context.InputBlueprint.Icons ?? new[]
{
new Icon
{
Index = 1,
Signal = new SignalID
{
Name = EntityNames.Vanilla.Pumpjack,
Type = SignalTypes.Vanilla.Item,
}
}
},
Version = context.InputBlueprint.Version,
Item = context.InputBlueprint.Item,
Item = context.InputBlueprint.Item ?? ItemNames.Vanilla.Blueprint,
Entities = entities.ToArray(),
};

Expand Down
12 changes: 12 additions & 0 deletions src/FactorioTools/OilField/Grid/AvoidEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Knapcode.FactorioTools.OilField;

public class AvoidEntity : GridEntity
{
public AvoidEntity(int id) : base(id)
{
}

#if ENABLE_GRID_TOSTRING
public override string Label => "X";
#endif
}
10 changes: 10 additions & 0 deletions src/FactorioTools/OilField/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1197,4 +1197,14 @@ public static List<Endpoints> PointsToLines(IReadOnlyList<Location> nodes, bool

return lines;
}

public static int ToInt(float x, string error)
{
if (Math.Abs(x % 1) > float.Epsilon * 100)
{
throw new FactorioToolsException(error);
}

return (int)Math.Round(x, 0);
}
}
13 changes: 13 additions & 0 deletions src/FactorioTools/OilField/Models/AvoidLocation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Knapcode.FactorioTools.OilField;

public class AvoidLocation
{
public AvoidLocation(float x, float y)
{
X = x;
Y = y;
}

public float X { get; }
public float Y { get; }
}
26 changes: 21 additions & 5 deletions src/FactorioTools/OilField/Planner.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Knapcode.FactorioTools.Data;
using System;
using System.Collections.Generic;
using Knapcode.FactorioTools.Data;

namespace Knapcode.FactorioTools.OilField;

Expand Down Expand Up @@ -81,16 +83,30 @@ public static (Context Context, OilFieldPlanSummary Summary) ExecuteSample()

public static (Context Context, OilFieldPlanSummary Summary) Execute(OilFieldOptions options, Blueprint inputBlueprint)
{
return Execute(options, inputBlueprint, electricPolesAvoid: EmptyLocationSet.Instance, EletricPolesMode.AddLast);
return Execute(
options,
inputBlueprint,
avoid: Array.Empty<AvoidLocation>());
}

public static (Context Context, OilFieldPlanSummary Summary) Execute(OilFieldOptions options, Blueprint inputBlueprint, IReadOnlyList<AvoidLocation> avoid)
{
return Execute(
options,
inputBlueprint,
avoid,
electricPolesAvoid: EmptyLocationSet.Instance,
EletricPolesMode.AddLast);
}

private static (Context Context, OilFieldPlanSummary Summary) Execute(
OilFieldOptions options,
Blueprint blueprint,
IReadOnlyList<AvoidLocation> avoid,
ILocationSet electricPolesAvoid,
EletricPolesMode electricPolesMode)
{
var context = InitializeContext.Execute(options, blueprint);
var context = InitializeContext.Execute(options, blueprint, avoid);
var initialPumpjackCount = context.CenterToTerminals.Count;
var addElectricPolesFirst = electricPolesMode != EletricPolesMode.AddLast;

Expand Down Expand Up @@ -119,7 +135,7 @@ private static (Context Context, OilFieldPlanSummary Summary) Execute(
badInput: true);
}

return Execute(options, blueprint, EmptyLocationSet.Instance, EletricPolesMode.AddFirstAndAvoidAllTerminals);
return Execute(options, blueprint, avoid, EmptyLocationSet.Instance, EletricPolesMode.AddFirstAndAvoidAllTerminals);
}
else
{
Expand Down Expand Up @@ -164,7 +180,7 @@ private static (Context Context, OilFieldPlanSummary Summary) Execute(
else
{
electricPolesAvoid = GetElectricPolesAvoid(context);
return Execute(options, blueprint, electricPolesAvoid, EletricPolesMode.AddFirstAndAvoidSpecificTerminals);
return Execute(options, blueprint, avoid, electricPolesAvoid, EletricPolesMode.AddFirstAndAvoidSpecificTerminals);
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/FactorioTools/OilField/Steps/CleanBlueprint.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Knapcode.FactorioTools.Data;

namespace Knapcode.FactorioTools.OilField;
Expand All @@ -7,7 +8,7 @@ public static class CleanBlueprint
{
public static Blueprint Execute(Blueprint blueprint)
{
var context = InitializeContext.Execute(new OilFieldOptions(), blueprint);
var context = InitializeContext.Execute(new OilFieldOptions(), blueprint, Array.Empty<AvoidLocation>());

var entities = new List<Entity>();

Expand Down
97 changes: 57 additions & 40 deletions src/FactorioTools/OilField/Steps/InitializeContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Knapcode.FactorioTools.OilField;

public static class InitializeContext
{
public static Context Execute(OilFieldOptions options, Blueprint blueprint)
public static Context Execute(OilFieldOptions options, Blueprint blueprint, IReadOnlyList<AvoidLocation> avoid)
{
// Translate the blueprint by the minimum X and Y. Leave three spaces on both lesser (left for X, top for Y) sides to cover:
// - The side of the pumpjack. It is a 3x3 entity and the position of the entity is the center.
Expand All @@ -22,7 +22,7 @@ public static Context Execute(OilFieldOptions options, Blueprint blueprint)
marginY += options.BeaconSupplyHeight + (options.BeaconHeight / 2);
}

return Execute(options, blueprint, marginX, marginY);
return Execute(options, blueprint, avoid, marginX, marginY);
}

public static Context GetEmpty(OilFieldOptions options, int width, int height)
Expand All @@ -46,14 +46,14 @@ public static Context GetEmpty(OilFieldOptions options, int width, int height)
Version = 1,
};

return Execute(options, blueprint, width, height);
return Execute(options, blueprint, Array.Empty<AvoidLocation>(), width, height);
}

private static Context Execute(OilFieldOptions options, Blueprint blueprint, int marginX, int marginY)
private static Context Execute(OilFieldOptions options, Blueprint blueprint, IReadOnlyList<AvoidLocation> avoid, int marginX, int marginY)
{
var (centerAndOriginalDirections, deltaX, deltaY) = GetCenterOriginalDirectionsAndDelta(blueprint, marginX, marginY);
var (centerAndOriginalDirections, avoidLocations, deltaX, deltaY) = TranslateLocations(blueprint, avoid, marginX, marginY);

var grid = InitializeGrid(centerAndOriginalDirections, marginX, marginY);
var grid = InitializeGrid(centerAndOriginalDirections, avoidLocations, marginX, marginY);

var centers = new List<Location>(centerAndOriginalDirections.Count);
PopulateCenters(centerAndOriginalDirections, centers);
Expand Down Expand Up @@ -148,7 +148,7 @@ private static int[] GetLocationToAdjacentCount(SquareGrid grid)
return locationToHasAdjacentPumpjack;
}

private static Tuple<List<Tuple<Location, Direction>>, float, float> GetCenterOriginalDirectionsAndDelta(Blueprint blueprint, int marginX, int marginY)
private static Tuple<List<Tuple<Location, Direction>>, List<Location>, float, float> TranslateLocations(Blueprint blueprint, IReadOnlyList<AvoidLocation> avoid, int marginX, int marginY)
{
var pumpjacks = new List<Entity>();
for (var i = 0; i < blueprint.Entities.Length; i++)
Expand All @@ -166,50 +166,56 @@ private static Tuple<List<Tuple<Location, Direction>>, float, float> GetCenterOr
throw new FactorioToolsException($"Having more than {maxPumpjacks} pumpjacks is not supported. There are {pumpjacks.Count} pumpjacks provided.");
}

var centerAndOriginalDirections = new List<Tuple<Location, Direction>>();
var centerAndOriginalDirections = new List<Tuple<Location, Direction>>(pumpjacks.Count);
var avoidLocations = new List<Location>(avoid.Count);

float deltaX = 0;
float deltaY = 0;

if (pumpjacks.Count > 0)
if (pumpjacks.Count > 0 || avoid.Count > 0)
{
deltaX = 0 - pumpjacks.Min(x => x.Position.X) + marginX;
deltaY = 0 - pumpjacks.Min(x => x.Position.Y) + marginY;
foreach (var entity in pumpjacks)
float minX = float.MaxValue;
float minY = float.MaxValue;

if (pumpjacks.Count > 0)
{
var x = entity.Position.X + deltaX;
var y = entity.Position.Y + deltaY;
minX = pumpjacks.Min(p => p.Position.X);
minY = pumpjacks.Min(p => p.Position.Y);
}

if (IsInteger(x))
{
throw new FactorioToolsException($"Entity {entity.EntityNumber} (a '{entity.Name}') does not have an integer X value after translation.");
}
if (avoid.Count > 0)
{
minX = Math.Min(minX, avoid.Min(a => a.X));
minY = Math.Min(minY, avoid.Min(a => a.Y));
}

if (IsInteger(y))
{
throw new FactorioToolsException($"Entity {entity.EntityNumber} (a '{entity.Name}') does not have an integer Y value after translation.");
}
deltaX = 0 - minX + marginX;
deltaY = 0 - minY + marginY;

var center = new Location(ToInt(x), ToInt(y));
var originalDireciton = entity.Direction.GetValueOrDefault(Direction.Up);
centerAndOriginalDirections.Add(Tuple.Create(center, originalDireciton));
for (int i = 0; i < pumpjacks.Count; i++)
{
var p = pumpjacks[i];
var x = ToInt(p.Position.X + deltaX, $"Entity {p.EntityNumber} (a '{p.Name}') does not have an integer X value after translation.");
var y = ToInt(p.Position.Y + deltaY, $"Entity {p.EntityNumber} (a '{p.Name}') does not have an integer Y value after translation.");
var center = new Location(x, y);
var originalDirection = p.Direction.GetValueOrDefault(Direction.Up);
centerAndOriginalDirections.Add(Tuple.Create(center, originalDirection));
}
}

return Tuple.Create(centerAndOriginalDirections, deltaX, deltaY);
}

private static int ToInt(float x)
{
return (int)Math.Round(x, 0);
}
for (var i = 0; i < avoid.Count; i++)
{
var a = avoid[i];
var x = ToInt(a.X + deltaX, $"Avoided location {i} does not have an integer X value after translation.");
var y = ToInt(a.Y + deltaY, $"Avoided location {i} does not have an integer Y value after translation.");
var avoidLocation = new Location(x, y);
avoidLocations.Add(avoidLocation);
}
}

private static bool IsInteger(float value)
{
return Math.Abs(value % 1) > float.Epsilon * 100;
return Tuple.Create(centerAndOriginalDirections, avoidLocations, deltaX, deltaY);
}

private static SquareGrid InitializeGrid(List<Tuple<Location, Direction>> centerAndOriginalDirections, int marginX, int marginY)
private static SquareGrid InitializeGrid(List<Tuple<Location, Direction>> centerAndOriginalDirections, List<Location> avoidLocations, int marginX, int marginY)
{
// Make a grid to contain game state. Similar to the above, we add extra spots for the pumpjacks, pipes, and
// electric poles.
Expand All @@ -234,10 +240,21 @@ private static SquareGrid InitializeGrid(List<Tuple<Location, Direction>> center

SquareGrid grid = new PipeGrid(width, height);

// Fill the grid with the pumpjacks
foreach (var pair in centerAndOriginalDirections)
for (int i = 0; i < centerAndOriginalDirections.Count; i++)
{
AddPumpjack(grid, centerAndOriginalDirections[i].Item1);
}

for (var i = 0; i < avoidLocations.Count; i++)
{
AddPumpjack(grid, pair.Item1);
var avoidLocation = avoidLocations[i];
var entity = grid[avoidLocation];
if (entity is not null && entity is not AvoidEntity)
{
throw new FactorioToolsException($"Avoided location {i} has another entity already placed there (perhaps it's part of a pumpjack spot).");
}

grid.AddEntity(avoidLocation, new AvoidEntity(grid.GetId()));
}

return grid;
Expand Down
2 changes: 1 addition & 1 deletion src/FactorioTools/OilField/Steps/PlanBeacons.1.FBE.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ private static List<Area> GetEntityAreas(Context context)
int height;
bool effect;

if (entity is TemporaryEntity)
if (entity is TemporaryEntity || entity is AvoidEntity)
{
width = 1;
height = 1;
Expand Down
2 changes: 1 addition & 1 deletion src/Sandbox/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ private static void Sandbox()

if (blueprintStrings.Length == 1)
{
var newBlueprint = GridToBlueprintString.Execute(context, addFbeOffset: false);
var newBlueprint = GridToBlueprintString.Execute(context, addFbeOffset: false, addAvoidEntities: true);
Console.WriteLine(newBlueprint);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/WebApp/Controllers/OilFieldController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public OilFieldPlanResponse GetPlan([FromBody] OilFieldPlanRequest request)
var parsedBlueprint = ParseBlueprint.Execute(request.Blueprint);
_logger.LogInformation("Planning oil field for blueprint {Blueprint}", request.Blueprint);
(var context, var summary) = Planner.Execute(request, parsedBlueprint);
var outputBlueprint = GridToBlueprintString.Execute(context, request.AddFbeOffset);
var outputBlueprint = GridToBlueprintString.Execute(context, request.AddFbeOffset, addAvoidEntities: false);
return new OilFieldPlanResponse(request, outputBlueprint, summary);
}
}
Loading

0 comments on commit f12436c

Please sign in to comment.