Skip to content

Commit

Permalink
Add test for scoring
Browse files Browse the repository at this point in the history
  • Loading branch information
joelverhagen committed Jan 10, 2024
1 parent 76d4f25 commit 99391d5
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 5 deletions.
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
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
8 changes: 7 additions & 1 deletion src/WebApp/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@
"beaconEffectCount",
"optimizePipes",
"pipeCount",
"pipeCountWithoutUnderground",
"pipeStrategy"
],
"type": "object",
Expand Down Expand Up @@ -239,7 +240,12 @@
},
"pipeCount": {
"type": "integer",
"description": "The number of pipes in the plan. For the same number of beacon effects and beacons, lower is better.",
"description": "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.",
"format": "int32"
},
"pipeCountWithoutUnderground": {
"type": "integer",
"description": "The number of pipes before beacons or underground pipes are placed.",
"format": "int32"
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
Wins with beacons
-----------------
FBE -> optimize -> snug : 112
CC-DT -> optimize -> snug : 108
CC-DT-MST -> optimize -> snug : 83
FBE -> snug : 79
FBE* -> optimize -> snug : 75
CC-FLUTE -> optimize -> snug : 62
FBE* -> snug : 59
CC-DT -> snug : 55
FBE* -> FBE* : 40
CC-FLUTE -> FBE* : 37
CC-FLUTE -> optimize -> FBE* : 37
FBE -> FBE* : 37
CC-DT-MST -> snug : 35
CC-FLUTE -> snug : 35
CC-DT -> FBE* : 34
CC-DT -> optimize -> FBE* : 34
FBE* -> optimize -> FBE : 33
FBE* -> optimize -> FBE* : 33
FBE -> optimize -> FBE* : 32
CC-DT -> optimize -> FBE : 29
CC-DT-MST -> optimize -> FBE* : 29
CC-FLUTE -> optimize -> FBE : 27
CC-DT -> FBE : 22
CC-DT-MST -> optimize -> FBE : 21
CC-FLUTE -> FBE : 20
FBE* -> FBE : 20
CC-DT-MST -> FBE* : 19
FBE -> optimize -> FBE : 19
CC-DT-MST -> FBE : 17
FBE -> FBE : 14

Wins without beacons
--------------------
FBE : 83
FBE -> optimize : 83
CC-DT -> optimize : 80
CC-DT-MST -> optimize : 77
FBE* -> optimize : 63
CC-FLUTE -> optimize : 56
FBE* : 55
CC-DT : 40
CC-DT-MST : 24
CC-FLUTE : 20

| Electric pole | Add beacons | Overlap beacons | Effects | Beacons | Pipes (UG) | Pipes (no UG) | Poles |
| -------------------- | ----------- | --------------- | ------------------ | ----------------- | ------------------ | ----------------- | ------------------ |
| small-electric-pole | yes | yes | 110.39344262295081 | 80.8688524590164 | 46.36065573770492 | 92.26229508196721 | 33.73770491803279 |
| medium-electric-pole | yes | yes | 110.39344262295081 | 80.8688524590164 | 46.36065573770492 | 92.26229508196721 | 26.278688524590162 |
| substation | yes | yes | 110.39344262295081 | 80.8688524590164 | 46.36065573770492 | 92.26229508196721 | 8.131147540983607 |
| big-electric-pole | yes | yes | 106.39344262295081 | 78.65573770491804 | 49.68852459016394 | 95.47540983606558 | 35.21311475409836 |
| small-electric-pole | yes | no | 11.80327868852459 | 6.180327868852459 | 44.557377049180324 | 91.01639344262296 | 14.245901639344263 |
| medium-electric-pole | yes | no | 11.80327868852459 | 6.180327868852459 | 44.557377049180324 | 91.01639344262296 | 11.59016393442623 |
| substation | yes | no | 11.80327868852459 | 6.180327868852459 | 44.557377049180324 | 91.01639344262296 | 4.377049180327869 |
| big-electric-pole | yes | no | 11.80327868852459 | 6.180327868852459 | 44.77049180327869 | 91.68852459016394 | 11.295081967213115 |
| small-electric-pole | no | N/A | 0 | 0 | 43.57377049180328 | 91.36065573770492 | 13.508196721311476 |
| medium-electric-pole | no | N/A | 0 | 0 | 43.57377049180328 | 91.36065573770492 | 10.557377049180328 |
| substation | no | N/A | 0 | 0 | 43.57377049180328 | 91.36065573770492 | 3.9344262295081966 |
| big-electric-pole | no | N/A | 0 | 0 | 43.77049180327869 | 91.52459016393442 | 9.540983606557377 |
205 changes: 205 additions & 0 deletions test/FactorioTools.Test/OilField/Score.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
using System.Text;

namespace Knapcode.FactorioTools.OilField;

public class Score : BasePlannerFacts
{
[Fact]
public async Task HasExpectedScore()
{
var getOptionsAll = new[]
{
() => OilFieldOptions.ForSmallElectricPole,
() => OilFieldOptions.ForMediumElectricPole,
() => OilFieldOptions.ForSubstation,
() => OilFieldOptions.ForBigElectricPole
};
var addBeaconsAll = new[] { true, false };
var overlapBeaconsAll = new[] { true, false };

var results = addBeaconsAll
.Select(x => new { AddBeacons = x })
.SelectMany(x => overlapBeaconsAll.Select(y => new { x.AddBeacons, OverlapBeacons = y }))
.SelectMany(x => getOptionsAll.Select((y, i) => new { x.AddBeacons, x.OverlapBeacons, GetOptions = y, OptionsIndex = i }))
.SelectMany(x => SmallListBlueprintStrings.Select(y => new { x.AddBeacons, x.OverlapBeacons, x.GetOptions, x.OptionsIndex, BlueprintString = y }))
.Where(x => x.AddBeacons || !x.OverlapBeacons)
.AsParallel()
.Select(input =>
{
var inputBlueprint = ParseBlueprint.Execute(input.BlueprintString);

var options = input.GetOptions();
options.AddBeacons = input.AddBeacons;
options.UseUndergroundPipes = true;
options.OptimizePipes = true;
options.ValidateSolution = false;
options.OverlapBeacons = input.OverlapBeacons;

options.PipeStrategies = OilFieldOptions.AllPipeStrategies.ToList();
options.BeaconStrategies = OilFieldOptions.AllBeaconStrategies.ToList();

var (context, summary) = Planner.Execute(options, inputBlueprint);

return new
{
Input = input,
Summary = summary,
PoleName = options.ElectricPoleEntityName,
summary.SelectedPlans[0].BeaconEffectCount,
summary.SelectedPlans[0].BeaconCount,
PipeCountWithUnderground = summary.SelectedPlans[0].PipeCount,
summary.SelectedPlans[0].PipeCountWithoutUnderground,
PoleCount = context.Grid.EntityToLocation.Keys.OfType<ElectricPoleCenter>().Count(),
};
});

var output = new StringBuilder();

var tableBuilder = new TableBuilder();
tableBuilder.AddColumns(
"Electric pole",
"Add beacons",
"Overlap beacons",
"Effects",
"Beacons",
"Pipes (UG)",
"Pipes (no UG)",
"Poles");

var addBeaconsGroups = results.GroupBy(x => x.Input.AddBeacons).OrderBy(x => x.Key);
foreach (var addBeaconsGroup in addBeaconsGroups.OrderByDescending(x => x.Key))
{
var overlapBeaconsGroups = addBeaconsGroup.GroupBy(x => x.Input.OverlapBeacons);
foreach (var overlapBeaconsGroup in overlapBeaconsGroups.OrderByDescending(x => x.Key))
{
var poleGroups = overlapBeaconsGroup.GroupBy(x => new { x.PoleName, x.Input.OptionsIndex });
foreach (var poleGroup in poleGroups.OrderBy(x => x.Key.OptionsIndex))
{
tableBuilder.AddRow(
poleGroup.Key.PoleName,
addBeaconsGroup.Key ? "yes" : "no",
addBeaconsGroup.Key ? (overlapBeaconsGroup.Key ? "yes" : "no") : "N/A",
poleGroup.Average(x => x.BeaconEffectCount),
poleGroup.Average(x => x.BeaconCount),
poleGroup.Average(x => x.PipeCountWithUnderground),
poleGroup.Average(x => x.PipeCountWithoutUnderground),
poleGroup.Average(x => x.PoleCount));
}
}

var heading = $"Wins {(addBeaconsGroup.Key ? "with" : "without")} beacons";
output.AppendLine(heading);
output.AppendLine(new string('-', heading.Length));

var planToWins = addBeaconsGroup
.SelectMany(x => x.Summary.SelectedPlans.Concat(x.Summary.AlternatePlans))
.GroupBy(x => x.ToString(includeCounts: false))
.ToDictionary(x => x.Key, x => x.Count());

var maxWidth = planToWins.Keys.Max(p => p.Length);
foreach ((var plan, var wins) in planToWins.OrderByDescending(p => p.Value).ThenBy(p => p.Key))
{
output.AppendLine($"{plan.PadLeft(maxWidth)} : {wins}");
}
output.AppendLine();
}

tableBuilder.Build(output);

await Verify(output.ToString());
}

private record MeasureRow(
string ElectricPoleEntityName,
bool AddBeacons,
string OverlapBeacons,
double PipeCount,
double PoleCount,
double BeaconCount,
double EffectCount);

public class TableBuilder
{
private readonly List<string> _columns = new();
private readonly List<IEnumerable<object>> _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<T>(IEnumerable<T> row)
{
_rows.Add(row.Cast<object>());
}

public void Build(StringBuilder builder)
{
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);
}

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);
}
}

public string Build()
{
var builder = new StringBuilder();
Build(builder);
return builder.ToString();
}

private static void AppendRow(StringBuilder builder, int columnCount, int[] maxWidths, Func<int, string> 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();
}
}
}

0 comments on commit 99391d5

Please sign in to comment.