Unify junction props
This commit is contained in:
@@ -19,6 +19,24 @@ public abstract class Balancing
|
||||
return value >= caution ? EBand.Caution : EBand.Safe;
|
||||
}
|
||||
|
||||
public IReadOnlyList<JunctionRatioPreset> JunctionRatios(int outflowCount)
|
||||
{
|
||||
return outflowCount switch {
|
||||
2 => TwoOutflowJunctionRatios,
|
||||
3 => ThreeOutflowJunctionRatios,
|
||||
_ => Array.Empty<JunctionRatioPreset>()
|
||||
};
|
||||
}
|
||||
|
||||
public float[] JunctionWeights(int outflowCount, int mode)
|
||||
{
|
||||
var ratios = JunctionRatios(outflowCount);
|
||||
if (ratios.Count == 0)
|
||||
return Array.Empty<float>();
|
||||
|
||||
return ratios[Math.Clamp(mode, 0, ratios.Count - 1)].Weights;
|
||||
}
|
||||
|
||||
public abstract int DefaultLevelWidth { get; }
|
||||
public abstract int DefaultLevelHeight { get; }
|
||||
public abstract int MinimumLevelSize { get; }
|
||||
@@ -47,6 +65,8 @@ public abstract class Balancing
|
||||
public abstract float SourceIntensity { get; }
|
||||
public abstract float DistanceAmountFalloff { get; }
|
||||
public abstract float DistanceIntensityFalloff { get; }
|
||||
public abstract IReadOnlyList<JunctionRatioPreset> TwoOutflowJunctionRatios { get; }
|
||||
public abstract IReadOnlyList<JunctionRatioPreset> ThreeOutflowJunctionRatios { get; }
|
||||
public abstract float ConsumerRequiredAmount { get; }
|
||||
public abstract float ConsumerRequiredIntensity { get; }
|
||||
public abstract float LeakBaseAmount { get; }
|
||||
@@ -69,4 +89,4 @@ public abstract class Balancing
|
||||
public abstract int RemedyBlockTurns { get; }
|
||||
public abstract int HeatShieldSteps { get; }
|
||||
public abstract int InventoryCapacityPerRemedy { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,19 @@ public class NormalBalancing : Balancing
|
||||
public override float SourceIntensity => 8;
|
||||
public override float DistanceAmountFalloff => 0.5f;
|
||||
public override float DistanceIntensityFalloff => 0.4f;
|
||||
public override IReadOnlyList<JunctionRatioPreset> TwoOutflowJunctionRatios { get; } = [
|
||||
new("0/4", [0, 1]),
|
||||
new("1/3", [0.25f, 0.75f]),
|
||||
new("2/2", [0.5f, 0.5f]),
|
||||
new("3/1", [0.75f, 0.25f]),
|
||||
new("4/0", [1, 0])
|
||||
];
|
||||
public override IReadOnlyList<JunctionRatioPreset> ThreeOutflowJunctionRatios { get; } = [
|
||||
new("0/3/3", [0, 0.5f, 0.5f]),
|
||||
new("3/0/3", [0.5f, 0, 0.5f]),
|
||||
new("3/3/0", [0.5f, 0.5f, 0]),
|
||||
new("2/2/2", [1f / 3f, 1f / 3f, 1f / 3f])
|
||||
];
|
||||
public override float ConsumerRequiredAmount => 2.5f;
|
||||
public override float ConsumerRequiredIntensity => 2.5f;
|
||||
public override float LeakBaseAmount => 0.5f;
|
||||
@@ -52,4 +65,4 @@ public class NormalBalancing : Balancing
|
||||
public override int RemedyBlockTurns => 2;
|
||||
public override int HeatShieldSteps => 3;
|
||||
public override int InventoryCapacityPerRemedy => 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,7 @@ public sealed record JunctionFlow
|
||||
if (index < 0)
|
||||
return 0;
|
||||
|
||||
var weights = Prop.Type == EPropType.TJunction
|
||||
? TJunctionWeights(Prop.TJunctionMode)
|
||||
: CrossJunctionWeights(Prop.CrossJunctionMode);
|
||||
var weights = Balancing.Current.JunctionWeights(OutgoingBranches.Count, Prop.JunctionMode);
|
||||
return index < weights.Length ? weights[index] : 0;
|
||||
}
|
||||
|
||||
@@ -33,27 +31,4 @@ public sealed record JunctionFlow
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static float[] TJunctionWeights(ETJunctionMode mode)
|
||||
{
|
||||
return mode switch {
|
||||
ETJunctionMode.ZeroFour => [0, 1],
|
||||
ETJunctionMode.OneThree => [0.25f, 0.75f],
|
||||
ETJunctionMode.TwoTwo => [0.5f, 0.5f],
|
||||
ETJunctionMode.ThreeOne => [0.75f, 0.25f],
|
||||
ETJunctionMode.FourZero => [1, 0],
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported T-junction mode.")
|
||||
};
|
||||
}
|
||||
|
||||
private static float[] CrossJunctionWeights(ECrossJunctionMode mode)
|
||||
{
|
||||
return mode switch {
|
||||
ECrossJunctionMode.ZeroThreeThree => [0, 0.5f, 0.5f],
|
||||
ECrossJunctionMode.ThreeZeroThree => [0.5f, 0, 0.5f],
|
||||
ECrossJunctionMode.ThreeThreeZero => [0.5f, 0.5f, 0],
|
||||
ECrossJunctionMode.TwoTwoTwo => [1f / 3f, 1f / 3f, 1f / 3f],
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported cross-junction mode.")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ public static class JunctionFlowAnalyzer
|
||||
{
|
||||
var position = new GridPosition(x, y);
|
||||
var prop = level.GetProp(position);
|
||||
if (prop.Type is not (EPropType.TJunction or EPropType.CrossJunction))
|
||||
if (prop.Type != EPropType.Junction)
|
||||
continue;
|
||||
|
||||
flows.Add(AnalyzeJunction(level, position, prop));
|
||||
@@ -34,9 +34,8 @@ public static class JunctionFlowAnalyzer
|
||||
? position.Neighbors().Where(level.InBounds).Where(neighbor => level.GetUnderground(neighbor, carrier).CarriesFlow).ToArray()
|
||||
: Array.Empty<GridPosition>();
|
||||
|
||||
var expectedBranches = prop.Type == EPropType.TJunction ? 3 : 4;
|
||||
if (carriers.Length == 1 && branches.Length != expectedBranches)
|
||||
errors.Add($"{prop.Type} must have exactly {expectedBranches} connected underground branches.");
|
||||
if (carriers.Length == 1 && branches.Length is not 3 and not 4)
|
||||
errors.Add("Junction must have one incoming branch and two or three outgoing branches.");
|
||||
|
||||
var sourceBranches = carriers.Length == 1
|
||||
? branches.Select(branch => new SourceBranch(branch, ShortestDistanceToSource(level, branch, position, carrier)))
|
||||
@@ -95,4 +94,4 @@ public static class JunctionFlowAnalyzer
|
||||
}
|
||||
|
||||
private sealed record SourceBranch(GridPosition Position, int? Distance);
|
||||
}
|
||||
}
|
||||
|
||||
3
src/ReactorMaintenance.Simulation/JunctionRatioPreset.cs
Normal file
3
src/ReactorMaintenance.Simulation/JunctionRatioPreset.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace ReactorMaintenance.Simulation;
|
||||
|
||||
public sealed record JunctionRatioPreset(string Label, float[] Weights);
|
||||
@@ -14,8 +14,7 @@ public enum EEditorTool
|
||||
FuelConsumer,
|
||||
CoolantConsumer,
|
||||
ElectricityConsumer,
|
||||
TJunction,
|
||||
CrossJunction,
|
||||
Junction,
|
||||
Door,
|
||||
AllSeeingEyeTerminal,
|
||||
FuelRemedySupply,
|
||||
@@ -53,8 +52,7 @@ public static class LevelEditor
|
||||
EEditorTool.FuelConsumer => SetCarrierProp(level, position, EPropType.Consumer, ECarrierType.Fuel),
|
||||
EEditorTool.CoolantConsumer => SetCarrierProp(level, position, EPropType.Consumer, ECarrierType.Coolant),
|
||||
EEditorTool.ElectricityConsumer => SetCarrierProp(level, position, EPropType.Consumer, ECarrierType.Electricity),
|
||||
EEditorTool.TJunction => SetFloorProp(level, position, new() { Type = EPropType.TJunction }),
|
||||
EEditorTool.CrossJunction => SetFloorProp(level, position, new() { Type = EPropType.CrossJunction }),
|
||||
EEditorTool.Junction => SetFloorProp(level, position, new() { Type = EPropType.Junction }),
|
||||
EEditorTool.Door => SetDoor(level, position),
|
||||
EEditorTool.AllSeeingEyeTerminal => SetFloorProp(level, position, new() { Type = EPropType.AllSeeingEyeTerminal }),
|
||||
EEditorTool.FuelRemedySupply => SetFloorProp(level, position, new() { Type = EPropType.RemedySupply, RemedyType = ERemedyType.FuelNeutralizer }),
|
||||
@@ -162,4 +160,4 @@ public static class LevelEditor
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace ReactorMaintenance.Simulation;
|
||||
|
||||
public enum ECrossJunctionMode
|
||||
{
|
||||
ZeroThreeThree,
|
||||
ThreeZeroThree,
|
||||
ThreeThreeZero,
|
||||
TwoTwoTwo
|
||||
}
|
||||
@@ -5,8 +5,7 @@ public enum EPropType
|
||||
None,
|
||||
Flow,
|
||||
Consumer,
|
||||
TJunction,
|
||||
CrossJunction,
|
||||
Junction,
|
||||
Door,
|
||||
AllSeeingEyeTerminal,
|
||||
RemedySupply,
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace ReactorMaintenance.Simulation;
|
||||
|
||||
public enum ETJunctionMode
|
||||
{
|
||||
ZeroFour,
|
||||
OneThree,
|
||||
TwoTwo,
|
||||
ThreeOne,
|
||||
FourZero
|
||||
}
|
||||
@@ -6,8 +6,7 @@ public sealed record PropState
|
||||
public ECarrierType Carrier { get; init; }
|
||||
public EPropSwitchState SwitchState { get; init; } = EPropSwitchState.Enabled;
|
||||
public EConsumerServiceState ServiceState { get; init; } = EConsumerServiceState.Unknown;
|
||||
public ETJunctionMode TJunctionMode { get; init; } = ETJunctionMode.TwoTwo;
|
||||
public ECrossJunctionMode CrossJunctionMode { get; init; } = ECrossJunctionMode.TwoTwoTwo;
|
||||
public int JunctionMode { get; init; }
|
||||
public ERemedyType RemedyType { get; init; }
|
||||
public bool Depleted { get; init; }
|
||||
public int ReactorId { get; init; }
|
||||
|
||||
@@ -27,8 +27,7 @@ public sealed class SimulationEngine
|
||||
|
||||
var next = prop.Type switch {
|
||||
EPropType.Flow or EPropType.Consumer => ToggleProp(level, position, prop),
|
||||
EPropType.TJunction => level.SetProp(position, prop with { TJunctionMode = NextTJunctionMode(prop.TJunctionMode) }),
|
||||
EPropType.CrossJunction => level.SetProp(position, prop with { CrossJunctionMode = NextCrossJunctionMode(prop.CrossJunctionMode) }),
|
||||
EPropType.Junction => CycleJunctionMode(level, position, prop),
|
||||
EPropType.Door => ToggleDoor(level, position),
|
||||
EPropType.AllSeeingEyeTerminal => level with { Global = level.Global with { AllSeeingEyeUnlocked = true, Status = "ALL-SEEING-EYE ONLINE" } },
|
||||
EPropType.RemedySupply => PickUpRemedy(level, position, prop),
|
||||
@@ -631,14 +630,15 @@ public sealed class SimulationEngine
|
||||
return level with { Global = level.Global with { Status = message } };
|
||||
}
|
||||
|
||||
private static ETJunctionMode NextTJunctionMode(ETJunctionMode mode)
|
||||
private static LevelState CycleJunctionMode(LevelState level, GridPosition position, PropState prop)
|
||||
{
|
||||
return mode == ETJunctionMode.FourZero ? ETJunctionMode.ZeroFour : mode + 1;
|
||||
}
|
||||
var flow = JunctionFlowAnalyzer.Analyze(level).FirstOrDefault(junction => junction.Position == position);
|
||||
var outflowCount = flow?.OutgoingBranches.Count ?? 2;
|
||||
var ratios = Balancing.Current.JunctionRatios(outflowCount);
|
||||
if (ratios.Count == 0)
|
||||
return level;
|
||||
|
||||
private static ECrossJunctionMode NextCrossJunctionMode(ECrossJunctionMode mode)
|
||||
{
|
||||
return mode == ECrossJunctionMode.TwoTwoTwo ? ECrossJunctionMode.ZeroThreeThree : mode + 1;
|
||||
return level.SetProp(position, prop with { JunctionMode = (prop.JunctionMode + 1) % ratios.Count });
|
||||
}
|
||||
|
||||
private static EBand SurfaceBand(SurfaceState surface, ECarrierType carrier)
|
||||
@@ -746,4 +746,4 @@ public sealed class SimulationEngine
|
||||
}
|
||||
|
||||
private readonly LevelValidator m_Validator = new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,7 +613,7 @@ public sealed partial class MainWindow
|
||||
return prop.Type switch {
|
||||
EPropType.Flow => CarrierColor(prop.Carrier),
|
||||
EPropType.Consumer => ColorHelper.FromArgb(255, 93, 123, 170),
|
||||
EPropType.TJunction or EPropType.CrossJunction => ColorHelper.FromArgb(255, 143, 111, 178),
|
||||
EPropType.Junction => ColorHelper.FromArgb(255, 143, 111, 178),
|
||||
EPropType.Door => ColorHelper.FromArgb(255, 187, 119, 55),
|
||||
EPropType.AllSeeingEyeTerminal => ColorHelper.FromArgb(255, 85, 151, 156),
|
||||
EPropType.RemedySupply => ColorHelper.FromArgb(255, 76, 145, 86),
|
||||
@@ -637,8 +637,7 @@ public sealed partial class MainWindow
|
||||
return prop.Type switch {
|
||||
EPropType.Flow => $"{CarrierShort(prop.Carrier)} SRC",
|
||||
EPropType.Consumer => $"{CarrierShort(prop.Carrier)} CON",
|
||||
EPropType.TJunction => $"T {prop.TJunctionMode}",
|
||||
EPropType.CrossJunction => $"X {prop.CrossJunctionMode}",
|
||||
EPropType.Junction => $"J {prop.JunctionMode}",
|
||||
EPropType.Door => "DOOR",
|
||||
EPropType.AllSeeingEyeTerminal => "EYE",
|
||||
EPropType.RemedySupply => RemedyShort(prop.RemedyType),
|
||||
@@ -718,4 +717,4 @@ public sealed partial class MainWindow
|
||||
private CanvasBitmap? m_RobotSprite;
|
||||
private CanvasBitmap? m_LeakSprite;
|
||||
private CanvasBitmap? m_HeatSprite;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,9 +87,9 @@ public sealed class SimulationEngineTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TJunctionRatioSplitsFlowAcrossInferredOutgoingBranches()
|
||||
public void JunctionRatioSplitsFlowAcrossInferredOutgoingBranches()
|
||||
{
|
||||
var level = BuildTJunctionLevel(ETJunctionMode.TwoTwo);
|
||||
var level = BuildJunctionLevel(2);
|
||||
|
||||
var next = m_Engine.AdvanceTurn(level);
|
||||
|
||||
@@ -99,9 +99,9 @@ public sealed class SimulationEngineTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TJunctionZeroWeightBranchReceivesNoIntentionalOutflow()
|
||||
public void JunctionZeroWeightBranchReceivesNoIntentionalOutflow()
|
||||
{
|
||||
var level = BuildTJunctionLevel(ETJunctionMode.ZeroFour);
|
||||
var level = BuildJunctionLevel(0);
|
||||
|
||||
var next = m_Engine.AdvanceTurn(level);
|
||||
|
||||
@@ -112,7 +112,7 @@ public sealed class SimulationEngineTests
|
||||
[Fact]
|
||||
public void ValidatorRejectsAmbiguousJunctionSourceBranches()
|
||||
{
|
||||
var level = BuildTJunctionLevel(ETJunctionMode.TwoTwo);
|
||||
var level = BuildJunctionLevel(2);
|
||||
level = level.SetProp(new(3, 3), new() { Type = EPropType.Flow, Carrier = ECarrierType.Fuel });
|
||||
|
||||
var report = new LevelValidator().Validate(level);
|
||||
@@ -385,15 +385,15 @@ public sealed class SimulationEngineTests
|
||||
return level;
|
||||
}
|
||||
|
||||
private static LevelState BuildTJunctionLevel(ETJunctionMode mode)
|
||||
private static LevelState BuildJunctionLevel(int mode)
|
||||
{
|
||||
var level = LevelState.Create("T junction", 6, 6);
|
||||
var level = LevelState.Create("Junction", 6, 6);
|
||||
level = level.SetUnderground(new(1, 3), ECarrierType.Fuel, new() { State = EUndergroundState.Intact });
|
||||
level = level.SetUnderground(new(2, 3), ECarrierType.Fuel, new() { State = EUndergroundState.Intact });
|
||||
level = level.SetUnderground(new(2, 2), ECarrierType.Fuel, new() { State = EUndergroundState.Intact });
|
||||
level = level.SetUnderground(new(3, 3), ECarrierType.Fuel, new() { State = EUndergroundState.Intact });
|
||||
level = level.SetProp(new(1, 3), new() { Type = EPropType.Flow, Carrier = ECarrierType.Fuel });
|
||||
return level.SetProp(new(2, 3), new() { Type = EPropType.TJunction, Carrier = ECarrierType.Fuel, TJunctionMode = mode });
|
||||
return level.SetProp(new(2, 3), new() { Type = EPropType.Junction, Carrier = ECarrierType.Fuel, JunctionMode = mode });
|
||||
}
|
||||
|
||||
private static LevelState AddHorizontalUnderground(LevelState level, ECarrierType carrier, int startX, int endX, int y)
|
||||
@@ -405,4 +405,4 @@ public sealed class SimulationEngineTests
|
||||
}
|
||||
|
||||
private readonly SimulationEngine m_Engine = new();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user