diff --git a/src/ReactorMaintenance.Simulation/GridPositionExtensions.cs b/src/ReactorMaintenance.Simulation/GridPositionExtensions.cs new file mode 100644 index 0000000..d53b654 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/GridPositionExtensions.cs @@ -0,0 +1,17 @@ +namespace ReactorMaintenance.Simulation; + +public static class GridPositionExtensions +{ + public static IEnumerable Neighbors(this GridPosition position) + { + yield return new(position.X, position.Y - 1); + yield return new(position.X + 1, position.Y); + yield return new(position.X, position.Y + 1); + yield return new(position.X - 1, position.Y); + } + + public static int ManhattanDistance(this GridPosition position, GridPosition other) + { + return Math.Abs(position.X - other.X) + Math.Abs(position.Y - other.Y); + } +} diff --git a/src/ReactorMaintenance.Simulation/LevelStateExtensions.cs b/src/ReactorMaintenance.Simulation/LevelStateExtensions.cs new file mode 100644 index 0000000..b34655c --- /dev/null +++ b/src/ReactorMaintenance.Simulation/LevelStateExtensions.cs @@ -0,0 +1,116 @@ +namespace ReactorMaintenance.Simulation; + +public static class LevelStateExtensions +{ + public static bool InBounds(this LevelState level, GridPosition position) + { + return position.X >= 0 && position.Y >= 0 && position.X < level.Width && position.Y < level.Height; + } + + public static int Index(this LevelState level, GridPosition position) + { + if (!level.InBounds(position)) + throw new ArgumentOutOfRangeException(nameof(position), $"Position {position.X},{position.Y} is outside {level.Width}x{level.Height}."); + + return position.Y * level.Width + position.X; + } + + public static ECellTerrain GetTerrain(this LevelState level, GridPosition position) + { + return level.Terrain[level.Index(position)]; + } + + public static UndergroundCell GetUnderground(this LevelState level, GridPosition position, ECarrierType carrier) + { + return level.Layer(carrier)[level.Index(position)]; + } + + public static SurfaceState GetSurface(this LevelState level, GridPosition position) + { + return level.Surface[level.Index(position)]; + } + + public static PropState GetProp(this LevelState level, GridPosition position) + { + return level.Props[level.Index(position)]; + } + + public static bool IsFloor(this LevelState level, GridPosition position) + { + return level.InBounds(position) && level.GetTerrain(position) == ECellTerrain.Floor; + } + + public static bool IsClosedDoorEdge(this LevelState level, GridPosition a, GridPosition b) + { + return level.Doors.Any(door => door.State == EDoorState.Closed && SameEdge(door.A, door.B, a, b)); + } + + public static LevelState SetTerrain(this LevelState level, GridPosition position, ECellTerrain terrain) + { + var next = level.Terrain.ToArray(); + next[level.Index(position)] = terrain; + var updated = level with { Terrain = next }; + return terrain == ECellTerrain.Wall ? updated.ClearFloorOnlyState(position) : updated; + } + + public static LevelState SetUnderground(this LevelState level, GridPosition position, ECarrierType carrier, UndergroundCell cell) + { + var next = level.Layer(carrier).ToArray(); + next[level.Index(position)] = cell; + return carrier switch { + ECarrierType.Fuel => level with { Fuel = next }, + ECarrierType.Coolant => level with { Coolant = next }, + ECarrierType.Electricity => level with { Electricity = next }, + _ => throw new ArgumentOutOfRangeException(nameof(carrier), carrier, "Unsupported carrier.") + }; + } + + public static LevelState SetSurface(this LevelState level, GridPosition position, SurfaceState surface) + { + var next = level.Surface.ToArray(); + next[level.Index(position)] = surface.Clamp(); + return level with { Surface = next }; + } + + public static LevelState SetProp(this LevelState level, GridPosition position, PropState prop) + { + var next = level.Props.ToArray(); + next[level.Index(position)] = prop; + return level with { Props = next }; + } + + public static LevelState WithRuntimeArrays(this LevelState level, UndergroundCell[] fuel, UndergroundCell[] coolant, UndergroundCell[] electricity, SurfaceState[] surface, PropState[] props) + { + return level with { + Fuel = fuel, + Coolant = coolant, + Electricity = electricity, + Surface = surface, + Props = props + }; + } + + public static IReadOnlyList Layer(this LevelState level, ECarrierType carrier) + { + return carrier switch { + ECarrierType.Fuel => level.Fuel, + ECarrierType.Coolant => level.Coolant, + ECarrierType.Electricity => level.Electricity, + _ => throw new ArgumentOutOfRangeException(nameof(carrier), carrier, "Unsupported carrier.") + }; + } + + private static LevelState ClearFloorOnlyState(this LevelState level, GridPosition position) + { + return level.SetSurface(position, new()) + .SetProp(position, new()) + .SetUnderground(position, ECarrierType.Fuel, new()) + .SetUnderground(position, ECarrierType.Coolant, new()) + .SetUnderground(position, ECarrierType.Electricity, new()); + } + + private static bool SameEdge(GridPosition edgeA, GridPosition edgeB, GridPosition a, GridPosition b) + { + return edgeA == a && edgeB == b || edgeA == b && edgeB == a; + } +} diff --git a/src/ReactorMaintenance.Simulation/LevelStateFactory.cs b/src/ReactorMaintenance.Simulation/LevelStateFactory.cs new file mode 100644 index 0000000..ee80b73 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/LevelStateFactory.cs @@ -0,0 +1,54 @@ +namespace ReactorMaintenance.Simulation; + +public static class LevelStateFactory +{ + public static LevelState Create(string name, int width, int height) + { + if (width < Balancing.Current.MinimumLevelSize || height < Balancing.Current.MinimumLevelSize) + throw new ArgumentOutOfRangeException(nameof(width), $"Levels must be at least {Balancing.Current.MinimumLevelSize}x{Balancing.Current.MinimumLevelSize}."); + + return new() { + Name = name, + Width = width, + Height = height, + Terrain = CreateTerrain(width, height), + Fuel = CreateUnderground(width, height), + Coolant = CreateUnderground(width, height), + Electricity = CreateUnderground(width, height), + Surface = CreateSurface(width, height), + Props = CreateProps(width, height), + Robot = new() { Position = new(1, 1) }, + Forecasts = Array.Empty() + }; + } + + public static ECellTerrain[] CreateTerrain(int width, int height) + { + var terrain = Enumerable.Repeat(ECellTerrain.Floor, width * height).ToArray(); + for (var y = 0; y < height; y++) + { + for (var x = 0; x < width; x++) + { + if (x == 0 || y == 0 || x == width - 1 || y == height - 1) + terrain[y * width + x] = ECellTerrain.Wall; + } + } + + return terrain; + } + + public static UndergroundCell[] CreateUnderground(int width, int height) + { + return Enumerable.Range(0, width * height).Select(_ => new UndergroundCell()).ToArray(); + } + + public static SurfaceState[] CreateSurface(int width, int height) + { + return Enumerable.Range(0, width * height).Select(_ => new SurfaceState()).ToArray(); + } + + public static PropState[] CreateProps(int width, int height) + { + return Enumerable.Range(0, width * height).Select(_ => new PropState()).ToArray(); + } +} diff --git a/src/ReactorMaintenance.Simulation/Models.cs b/src/ReactorMaintenance.Simulation/Models.cs deleted file mode 100644 index 0716a26..0000000 --- a/src/ReactorMaintenance.Simulation/Models.cs +++ /dev/null @@ -1,558 +0,0 @@ -namespace ReactorMaintenance.Simulation; - -public enum ECellTerrain -{ - Floor, - Wall -} - -public enum ECarrierType -{ - Fuel, - Coolant, - Electricity -} - -public enum EUndergroundState -{ - Absent, - Intact, - Leaking -} - -public enum EPropType -{ - None, - Flow, - Consumer, - TJunction, - CrossJunction, - Door, - AllSeeingEyeTerminal, - RemedySupply, - ReactorControl -} - -public enum EPropSwitchState -{ - Disabled, - Enabled -} - -public enum EConsumerServiceState -{ - Unknown, - Disabled, - Starved, - Supplied, - Producing -} - -public enum ETJunctionMode -{ - ZeroFour, - OneThree, - TwoTwo, - ThreeOne, - FourZero -} - -public enum ECrossJunctionMode -{ - ZeroThreeThree, - ThreeZeroThree, - ThreeThreeZero, - TwoTwoTwo -} - -public enum EDoorState -{ - Open, - Closed -} - -public enum ERemedyType -{ - FuelNeutralizer, - CoolantNeutralizer, - ElectricityNeutralizer, - HeatShield -} - -public enum ELevelState -{ - Stable, - Caution, - Critical, - Ready, - Lost, - Won -} - -public enum EForecastKind -{ - TerminalLoss, - ReactorReady, - ConsumerStarved, - HazardGrowth, - RuleEvent -} - -public enum ERuleEventPhase -{ - StartOfSimulation, - EndOfTurn -} - -public enum ERulePredicateKind -{ - TurnAtLeast, - LevelStateIs, - ReactorReadyIs, - ReactorLostIs, - ReactorWonIs, - PropStateAt, - ConsumerStateAt, - NetworkBandAt, - SurfaceBandAt, - RobotAt, - RobotInventoryAtLeast, - AllSeeingEyeUnlocked -} - -public enum ERuleEffectKind -{ - StartLeak, - WorsenLeak, - RepairNetworkCell, - DisableNetworkCell, - SetPropEnabled, - AddSurfaceHazard, - RemoveSurfaceHazard, - AddHeat, - RemoveHeat, - AddInventory, - RemoveInventory, - MarkTerminalLoss, - EmitWarning -} - -public enum ENetworkValueKind -{ - Amount, - Intensity -} - -public enum EBand -{ - Safe, - Caution, - Critical -} - -public enum EPairEffect -{ - Hold, - FuelFlow, - CoolFlow, - ChargeFlow, - HeatFlow, - HeatFlow2, - Warm1, - Warm2, - Quench1, - Quench2, - Short1, - Short2, - Ignite1, - Ignite2 -} - -public sealed record GridPosition(int X, int Y) -{ - public IEnumerable Neighbors() - { - yield return new(X, Y - 1); - yield return new(X + 1, Y); - yield return new(X, Y + 1); - yield return new(X - 1, Y); - } - - public int ManhattanDistance(GridPosition other) - { - return Math.Abs(X - other.X) + Math.Abs(Y - other.Y); - } -} - -public sealed record UndergroundCell -{ - public EUndergroundState State { get; init; } - public float Amount { get; init; } - public float Intensity { get; init; } - - public bool IsPresent => State != EUndergroundState.Absent; - public bool CarriesFlow => State is EUndergroundState.Intact or EUndergroundState.Leaking; -} - -public sealed record SurfaceState -{ - public float Fuel { get; init; } - public float Coolant { get; init; } - public float Electricity { get; init; } - public float Heat { get; init; } - public int FuelBlockTurns { get; init; } - public int CoolantBlockTurns { get; init; } - public int ElectricityBlockTurns { get; init; } - - public SurfaceState Clamp() - { - var balancing = Balancing.Current; - return this with { - Fuel = balancing.ClampValue(Fuel), - Coolant = balancing.ClampValue(Coolant), - Electricity = balancing.ClampValue(Electricity), - Heat = balancing.ClampValue(Heat), - FuelBlockTurns = Math.Max(0, FuelBlockTurns), - CoolantBlockTurns = Math.Max(0, CoolantBlockTurns), - ElectricityBlockTurns = Math.Max(0, ElectricityBlockTurns) - }; - } - - public bool Blocks(ECarrierType carrier) - { - return carrier switch { - ECarrierType.Fuel => FuelBlockTurns > 0, - ECarrierType.Coolant => CoolantBlockTurns > 0, - ECarrierType.Electricity => ElectricityBlockTurns > 0, - _ => throw new ArgumentOutOfRangeException(nameof(carrier), carrier, "Unsupported carrier.") - }; - } -} - -public sealed record PropState -{ - public EPropType Type { get; init; } - 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 ERemedyType RemedyType { get; init; } - public bool Depleted { get; init; } - public int ReactorId { get; init; } - - public bool IsEnabled => SwitchState == EPropSwitchState.Enabled; -} - -public sealed record DoorState -{ - public GridPosition A { get; init; } = new(0, 0); - public GridPosition B { get; init; } = new(0, 0); - public EDoorState State { get; init; } = EDoorState.Closed; -} - -public sealed record LeakState -{ - public ECarrierType Carrier { get; init; } - public GridPosition UndergroundPosition { get; init; } = new(0, 0); - public GridPosition AccessPosition { get; init; } = new(0, 0); - public bool Repaired { get; init; } -} - -public sealed record ReactorBinding -{ - public int ReactorId { get; init; } - public GridPosition ControlPosition { get; init; } = new(0, 0); - public GridPosition FuelConsumerPosition { get; init; } = new(0, 0); - public GridPosition CoolantConsumerPosition { get; init; } = new(0, 0); - public GridPosition ElectricityConsumerPosition { get; init; } = new(0, 0); - public bool Ready { get; init; } - public bool Activated { get; init; } -} - -public sealed record RobotState -{ - public GridPosition Position { get; init; } = new(1, 1); - public int FuelNeutralizers { get; init; } - public int CoolantNeutralizers { get; init; } - public int ElectricityNeutralizers { get; init; } - public int HeatShields { get; init; } - public int HeatImmunitySteps { get; init; } - - public int Count(ERemedyType remedy) - { - return remedy switch { - ERemedyType.FuelNeutralizer => FuelNeutralizers, - ERemedyType.CoolantNeutralizer => CoolantNeutralizers, - ERemedyType.ElectricityNeutralizer => ElectricityNeutralizers, - ERemedyType.HeatShield => HeatShields, - _ => throw new ArgumentOutOfRangeException(nameof(remedy), remedy, "Unsupported remedy.") - }; - } - - public RobotState Add(ERemedyType remedy, int amount) - { - return remedy switch { - ERemedyType.FuelNeutralizer => this with { FuelNeutralizers = ClampInventory(FuelNeutralizers + amount) }, - ERemedyType.CoolantNeutralizer => this with { CoolantNeutralizers = ClampInventory(CoolantNeutralizers + amount) }, - ERemedyType.ElectricityNeutralizer => this with { ElectricityNeutralizers = ClampInventory(ElectricityNeutralizers + amount) }, - ERemedyType.HeatShield => this with { HeatShields = ClampInventory(HeatShields + amount) }, - _ => throw new ArgumentOutOfRangeException(nameof(remedy), remedy, "Unsupported remedy.") - }; - } - - public RobotState Spend(ERemedyType remedy) - { - return Count(remedy) <= 0 ? this : Add(remedy, -1); - } - - private static int ClampInventory(int value) - { - return Math.Clamp(value, 0, Balancing.Current.InventoryCapacityPerRemedy); - } -} - -public sealed record RulePredicate -{ - public ERulePredicateKind Kind { get; init; } - public GridPosition Position { get; init; } = new(0, 0); - public int ReactorId { get; init; } - public int Turn { get; init; } - public ELevelState LevelState { get; init; } - public EPropSwitchState PropSwitchState { get; init; } - public EConsumerServiceState ConsumerServiceState { get; init; } - public ECarrierType Carrier { get; init; } - public ENetworkValueKind NetworkValue { get; init; } - public ERemedyType Remedy { get; init; } - public EBand Band { get; init; } - public int InventoryCount { get; init; } - public bool BoolValue { get; init; } -} - -public sealed record RuleEffect -{ - public ERuleEffectKind Kind { get; init; } - public GridPosition Position { get; init; } = new(0, 0); - public GridPosition? AccessPosition { get; init; } - public ECarrierType Carrier { get; init; } - public ERemedyType Remedy { get; init; } - public float Amount { get; init; } - public EPropSwitchState PropSwitchState { get; init; } - public string Message { get; init; } = string.Empty; -} - -public sealed record RuleEventState -{ - public string Id { get; init; } = string.Empty; - public bool Enabled { get; init; } = true; - public bool Repeat { get; init; } - public bool Triggered { get; init; } - public int Priority { get; init; } - public ERuleEventPhase Phase { get; init; } - public IReadOnlyList Predicates { get; init; } = Array.Empty(); - public IReadOnlyList Effects { get; init; } = Array.Empty(); - public string ForecastText { get; init; } = string.Empty; -} - -public sealed record Forecast(EForecastKind Kind, GridPosition? Position, int Turns, string Message); -public sealed record ValidationIssue(string Message, GridPosition? Position = null); - -public sealed record ValidationReport -{ - public IReadOnlyList Errors { get; init; } = Array.Empty(); - public IReadOnlyList Warnings { get; init; } = Array.Empty(); - public bool IsValid => Errors.Count == 0; -} - -public sealed record GlobalState -{ - public int Turn { get; init; } - public int ActionsRemaining { get; init; } = Balancing.Current.ActionsPerTurn; - public ELevelState LevelState { get; init; } = ELevelState.Stable; - public string Status { get; init; } = "STABLE"; - public bool AllSeeingEyeUnlocked { get; init; } - public bool TerminalLoss { get; init; } - public IReadOnlyList Warnings { get; init; } = Array.Empty(); -} - -public sealed record LevelState -{ - public static LevelState Create(string name, int width, int height) - { - if (width < Balancing.Current.MinimumLevelSize || height < Balancing.Current.MinimumLevelSize) - throw new ArgumentOutOfRangeException(nameof(width), $"Levels must be at least {Balancing.Current.MinimumLevelSize}x{Balancing.Current.MinimumLevelSize}."); - - var terrain = Enumerable.Repeat(ECellTerrain.Floor, width * height).ToArray(); - for (var y = 0; y < height; y++) - { - for (var x = 0; x < width; x++) - { - if (x == 0 || y == 0 || x == width - 1 || y == height - 1) - terrain[y * width + x] = ECellTerrain.Wall; - } - } - - var level = new LevelState { - Name = name, - Width = width, - Height = height, - Terrain = terrain, - Fuel = CreateUnderground(width, height), - Coolant = CreateUnderground(width, height), - Electricity = CreateUnderground(width, height), - Surface = CreateSurface(width, height), - Props = CreateProps(width, height), - Robot = new() { Position = new(1, 1) } - }; - - return level with { Forecasts = Array.Empty() }; - } - - public bool InBounds(GridPosition position) - { - return position.X >= 0 && position.Y >= 0 && position.X < Width && position.Y < Height; - } - - public int Index(GridPosition position) - { - EnsureInBounds(position); - return position.Y * Width + position.X; - } - - public ECellTerrain GetTerrain(GridPosition position) - { - return Terrain[Index(position)]; - } - - public UndergroundCell GetUnderground(GridPosition position, ECarrierType carrier) - { - return Layer(carrier)[Index(position)]; - } - - public SurfaceState GetSurface(GridPosition position) - { - return Surface[Index(position)]; - } - - public PropState GetProp(GridPosition position) - { - return Props[Index(position)]; - } - - public bool IsFloor(GridPosition position) - { - return InBounds(position) && GetTerrain(position) == ECellTerrain.Floor; - } - - public bool IsClosedDoorEdge(GridPosition a, GridPosition b) - { - return Doors.Any(door => door.State == EDoorState.Closed && SameEdge(door.A, door.B, a, b)); - } - - public LevelState SetTerrain(GridPosition position, ECellTerrain terrain) - { - var next = Terrain.ToArray(); - next[Index(position)] = terrain; - var level = this with { Terrain = next }; - return terrain == ECellTerrain.Wall ? level.ClearFloorOnlyState(position) : level; - } - - public LevelState SetUnderground(GridPosition position, ECarrierType carrier, UndergroundCell cell) - { - var next = Layer(carrier).ToArray(); - next[Index(position)] = cell; - return carrier switch { - ECarrierType.Fuel => this with { Fuel = next }, - ECarrierType.Coolant => this with { Coolant = next }, - ECarrierType.Electricity => this with { Electricity = next }, - _ => throw new ArgumentOutOfRangeException(nameof(carrier), carrier, "Unsupported carrier.") - }; - } - - public LevelState SetSurface(GridPosition position, SurfaceState surface) - { - var next = Surface.ToArray(); - next[Index(position)] = surface.Clamp(); - return this with { Surface = next }; - } - - public LevelState SetProp(GridPosition position, PropState prop) - { - var next = Props.ToArray(); - next[Index(position)] = prop; - return this with { Props = next }; - } - - public LevelState WithRuntimeArrays(UndergroundCell[] fuel, UndergroundCell[] coolant, UndergroundCell[] electricity, SurfaceState[] surface, PropState[] props) - { - return this with { - Fuel = fuel, - Coolant = coolant, - Electricity = electricity, - Surface = surface, - Props = props - }; - } - - public IReadOnlyList Layer(ECarrierType carrier) - { - return carrier switch { - ECarrierType.Fuel => Fuel, - ECarrierType.Coolant => Coolant, - ECarrierType.Electricity => Electricity, - _ => throw new ArgumentOutOfRangeException(nameof(carrier), carrier, "Unsupported carrier.") - }; - } - - private LevelState ClearFloorOnlyState(GridPosition position) - { - return SetSurface(position, new()) - .SetProp(position, new()) - .SetUnderground(position, ECarrierType.Fuel, new()) - .SetUnderground(position, ECarrierType.Coolant, new()) - .SetUnderground(position, ECarrierType.Electricity, new()); - } - - private void EnsureInBounds(GridPosition position) - { - if (!InBounds(position)) - throw new ArgumentOutOfRangeException(nameof(position), $"Position {position.X},{position.Y} is outside {Width}x{Height}."); - } - - private static bool SameEdge(GridPosition edgeA, GridPosition edgeB, GridPosition a, GridPosition b) - { - return edgeA == a && edgeB == b || edgeA == b && edgeB == a; - } - - private static UndergroundCell[] CreateUnderground(int width, int height) - { - return Enumerable.Range(0, width * height).Select(_ => new UndergroundCell()).ToArray(); - } - - private static SurfaceState[] CreateSurface(int width, int height) - { - return Enumerable.Range(0, width * height).Select(_ => new SurfaceState()).ToArray(); - } - - private static PropState[] CreateProps(int width, int height) - { - return Enumerable.Range(0, width * height).Select(_ => new PropState()).ToArray(); - } - - public string Name { get; init; } = "New Reactor"; - public int Width { get; init; } = Balancing.Current.DefaultLevelWidth; - public int Height { get; init; } = Balancing.Current.DefaultLevelHeight; - public ECellTerrain[] Terrain { get; init; } = Enumerable.Repeat(ECellTerrain.Floor, Balancing.Current.DefaultLevelWidth * Balancing.Current.DefaultLevelHeight).ToArray(); - public UndergroundCell[] Fuel { get; init; } = CreateUnderground(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight); - public UndergroundCell[] Coolant { get; init; } = CreateUnderground(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight); - public UndergroundCell[] Electricity { get; init; } = CreateUnderground(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight); - public SurfaceState[] Surface { get; init; } = CreateSurface(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight); - public PropState[] Props { get; init; } = CreateProps(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight); - public IReadOnlyList Doors { get; init; } = Array.Empty(); - public IReadOnlyList Leaks { get; init; } = Array.Empty(); - public IReadOnlyList Reactors { get; init; } = Array.Empty(); - public IReadOnlyList RuleEvents { get; init; } = Array.Empty(); - public RobotState Robot { get; init; } = new(); - public GlobalState Global { get; init; } = new(); - public IReadOnlyList Forecasts { get; init; } = Array.Empty(); -} \ No newline at end of file diff --git a/src/ReactorMaintenance.Simulation/Models/DoorState.cs b/src/ReactorMaintenance.Simulation/Models/DoorState.cs new file mode 100644 index 0000000..09e2262 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/DoorState.cs @@ -0,0 +1,8 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record DoorState +{ + public GridPosition A { get; init; } = new(0, 0); + public GridPosition B { get; init; } = new(0, 0); + public EDoorState State { get; init; } = EDoorState.Closed; +} diff --git a/src/ReactorMaintenance.Simulation/Models/EBand.cs b/src/ReactorMaintenance.Simulation/Models/EBand.cs new file mode 100644 index 0000000..86338b7 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/EBand.cs @@ -0,0 +1,8 @@ +namespace ReactorMaintenance.Simulation; + +public enum EBand +{ + Safe, + Caution, + Critical +} diff --git a/src/ReactorMaintenance.Simulation/Models/ECarrierType.cs b/src/ReactorMaintenance.Simulation/Models/ECarrierType.cs new file mode 100644 index 0000000..3bffdbc --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/ECarrierType.cs @@ -0,0 +1,8 @@ +namespace ReactorMaintenance.Simulation; + +public enum ECarrierType +{ + Fuel, + Coolant, + Electricity +} diff --git a/src/ReactorMaintenance.Simulation/Models/ECellTerrain.cs b/src/ReactorMaintenance.Simulation/Models/ECellTerrain.cs new file mode 100644 index 0000000..ce31c59 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/ECellTerrain.cs @@ -0,0 +1,7 @@ +namespace ReactorMaintenance.Simulation; + +public enum ECellTerrain +{ + Floor, + Wall +} diff --git a/src/ReactorMaintenance.Simulation/Models/EConsumerServiceState.cs b/src/ReactorMaintenance.Simulation/Models/EConsumerServiceState.cs new file mode 100644 index 0000000..4c13250 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/EConsumerServiceState.cs @@ -0,0 +1,10 @@ +namespace ReactorMaintenance.Simulation; + +public enum EConsumerServiceState +{ + Unknown, + Disabled, + Starved, + Supplied, + Producing +} diff --git a/src/ReactorMaintenance.Simulation/Models/ECrossJunctionMode.cs b/src/ReactorMaintenance.Simulation/Models/ECrossJunctionMode.cs new file mode 100644 index 0000000..b195df6 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/ECrossJunctionMode.cs @@ -0,0 +1,9 @@ +namespace ReactorMaintenance.Simulation; + +public enum ECrossJunctionMode +{ + ZeroThreeThree, + ThreeZeroThree, + ThreeThreeZero, + TwoTwoTwo +} diff --git a/src/ReactorMaintenance.Simulation/Models/EDoorState.cs b/src/ReactorMaintenance.Simulation/Models/EDoorState.cs new file mode 100644 index 0000000..88c8595 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/EDoorState.cs @@ -0,0 +1,7 @@ +namespace ReactorMaintenance.Simulation; + +public enum EDoorState +{ + Open, + Closed +} diff --git a/src/ReactorMaintenance.Simulation/Models/EForecastKind.cs b/src/ReactorMaintenance.Simulation/Models/EForecastKind.cs new file mode 100644 index 0000000..e8ebf09 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/EForecastKind.cs @@ -0,0 +1,10 @@ +namespace ReactorMaintenance.Simulation; + +public enum EForecastKind +{ + TerminalLoss, + ReactorReady, + ConsumerStarved, + HazardGrowth, + RuleEvent +} diff --git a/src/ReactorMaintenance.Simulation/Models/ELevelState.cs b/src/ReactorMaintenance.Simulation/Models/ELevelState.cs new file mode 100644 index 0000000..f576fef --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/ELevelState.cs @@ -0,0 +1,11 @@ +namespace ReactorMaintenance.Simulation; + +public enum ELevelState +{ + Stable, + Caution, + Critical, + Ready, + Lost, + Won +} diff --git a/src/ReactorMaintenance.Simulation/Models/ENetworkValueKind.cs b/src/ReactorMaintenance.Simulation/Models/ENetworkValueKind.cs new file mode 100644 index 0000000..fae6efb --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/ENetworkValueKind.cs @@ -0,0 +1,7 @@ +namespace ReactorMaintenance.Simulation; + +public enum ENetworkValueKind +{ + Amount, + Intensity +} diff --git a/src/ReactorMaintenance.Simulation/Models/EPairEffect.cs b/src/ReactorMaintenance.Simulation/Models/EPairEffect.cs new file mode 100644 index 0000000..dcf896f --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/EPairEffect.cs @@ -0,0 +1,19 @@ +namespace ReactorMaintenance.Simulation; + +public enum EPairEffect +{ + Hold, + FuelFlow, + CoolFlow, + ChargeFlow, + HeatFlow, + HeatFlow2, + Warm1, + Warm2, + Quench1, + Quench2, + Short1, + Short2, + Ignite1, + Ignite2 +} diff --git a/src/ReactorMaintenance.Simulation/Models/EPropSwitchState.cs b/src/ReactorMaintenance.Simulation/Models/EPropSwitchState.cs new file mode 100644 index 0000000..a13f785 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/EPropSwitchState.cs @@ -0,0 +1,7 @@ +namespace ReactorMaintenance.Simulation; + +public enum EPropSwitchState +{ + Disabled, + Enabled +} diff --git a/src/ReactorMaintenance.Simulation/Models/EPropType.cs b/src/ReactorMaintenance.Simulation/Models/EPropType.cs new file mode 100644 index 0000000..8f33369 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/EPropType.cs @@ -0,0 +1,14 @@ +namespace ReactorMaintenance.Simulation; + +public enum EPropType +{ + None, + Flow, + Consumer, + TJunction, + CrossJunction, + Door, + AllSeeingEyeTerminal, + RemedySupply, + ReactorControl +} diff --git a/src/ReactorMaintenance.Simulation/Models/ERemedyType.cs b/src/ReactorMaintenance.Simulation/Models/ERemedyType.cs new file mode 100644 index 0000000..ea9e064 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/ERemedyType.cs @@ -0,0 +1,9 @@ +namespace ReactorMaintenance.Simulation; + +public enum ERemedyType +{ + FuelNeutralizer, + CoolantNeutralizer, + ElectricityNeutralizer, + HeatShield +} diff --git a/src/ReactorMaintenance.Simulation/Models/ERuleEffectKind.cs b/src/ReactorMaintenance.Simulation/Models/ERuleEffectKind.cs new file mode 100644 index 0000000..8274ca1 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/ERuleEffectKind.cs @@ -0,0 +1,18 @@ +namespace ReactorMaintenance.Simulation; + +public enum ERuleEffectKind +{ + StartLeak, + WorsenLeak, + RepairNetworkCell, + DisableNetworkCell, + SetPropEnabled, + AddSurfaceHazard, + RemoveSurfaceHazard, + AddHeat, + RemoveHeat, + AddInventory, + RemoveInventory, + MarkTerminalLoss, + EmitWarning +} diff --git a/src/ReactorMaintenance.Simulation/Models/ERuleEventPhase.cs b/src/ReactorMaintenance.Simulation/Models/ERuleEventPhase.cs new file mode 100644 index 0000000..e9f61d8 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/ERuleEventPhase.cs @@ -0,0 +1,7 @@ +namespace ReactorMaintenance.Simulation; + +public enum ERuleEventPhase +{ + StartOfSimulation, + EndOfTurn +} diff --git a/src/ReactorMaintenance.Simulation/Models/ERulePredicateKind.cs b/src/ReactorMaintenance.Simulation/Models/ERulePredicateKind.cs new file mode 100644 index 0000000..0cd1ab2 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/ERulePredicateKind.cs @@ -0,0 +1,17 @@ +namespace ReactorMaintenance.Simulation; + +public enum ERulePredicateKind +{ + TurnAtLeast, + LevelStateIs, + ReactorReadyIs, + ReactorLostIs, + ReactorWonIs, + PropStateAt, + ConsumerStateAt, + NetworkBandAt, + SurfaceBandAt, + RobotAt, + RobotInventoryAtLeast, + AllSeeingEyeUnlocked +} diff --git a/src/ReactorMaintenance.Simulation/Models/ETJunctionMode.cs b/src/ReactorMaintenance.Simulation/Models/ETJunctionMode.cs new file mode 100644 index 0000000..5dbe047 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/ETJunctionMode.cs @@ -0,0 +1,10 @@ +namespace ReactorMaintenance.Simulation; + +public enum ETJunctionMode +{ + ZeroFour, + OneThree, + TwoTwo, + ThreeOne, + FourZero +} diff --git a/src/ReactorMaintenance.Simulation/Models/EUndergroundState.cs b/src/ReactorMaintenance.Simulation/Models/EUndergroundState.cs new file mode 100644 index 0000000..4773554 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/EUndergroundState.cs @@ -0,0 +1,8 @@ +namespace ReactorMaintenance.Simulation; + +public enum EUndergroundState +{ + Absent, + Intact, + Leaking +} diff --git a/src/ReactorMaintenance.Simulation/Models/Forecast.cs b/src/ReactorMaintenance.Simulation/Models/Forecast.cs new file mode 100644 index 0000000..64d85cb --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/Forecast.cs @@ -0,0 +1,3 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record Forecast(EForecastKind Kind, GridPosition? Position, int Turns, string Message); diff --git a/src/ReactorMaintenance.Simulation/Models/GlobalState.cs b/src/ReactorMaintenance.Simulation/Models/GlobalState.cs new file mode 100644 index 0000000..df05106 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/GlobalState.cs @@ -0,0 +1,12 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record GlobalState +{ + public int Turn { get; init; } + public int ActionsRemaining { get; init; } = Balancing.Current.ActionsPerTurn; + public ELevelState LevelState { get; init; } = ELevelState.Stable; + public string Status { get; init; } = "STABLE"; + public bool AllSeeingEyeUnlocked { get; init; } + public bool TerminalLoss { get; init; } + public IReadOnlyList Warnings { get; init; } = Array.Empty(); +} diff --git a/src/ReactorMaintenance.Simulation/Models/GridPosition.cs b/src/ReactorMaintenance.Simulation/Models/GridPosition.cs new file mode 100644 index 0000000..fbb44c1 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/GridPosition.cs @@ -0,0 +1,3 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record GridPosition(int X, int Y); diff --git a/src/ReactorMaintenance.Simulation/Models/LeakState.cs b/src/ReactorMaintenance.Simulation/Models/LeakState.cs new file mode 100644 index 0000000..3c2e65c --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/LeakState.cs @@ -0,0 +1,9 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record LeakState +{ + public ECarrierType Carrier { get; init; } + public GridPosition UndergroundPosition { get; init; } = new(0, 0); + public GridPosition AccessPosition { get; init; } = new(0, 0); + public bool Repaired { get; init; } +} diff --git a/src/ReactorMaintenance.Simulation/Models/LevelState.cs b/src/ReactorMaintenance.Simulation/Models/LevelState.cs new file mode 100644 index 0000000..c5b5825 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/LevelState.cs @@ -0,0 +1,26 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record LevelState +{ + public static LevelState Create(string name, int width, int height) + { + return LevelStateFactory.Create(name, width, height); + } + + public string Name { get; init; } = "New Reactor"; + public int Width { get; init; } = Balancing.Current.DefaultLevelWidth; + public int Height { get; init; } = Balancing.Current.DefaultLevelHeight; + public ECellTerrain[] Terrain { get; init; } = LevelStateFactory.CreateTerrain(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight); + public UndergroundCell[] Fuel { get; init; } = LevelStateFactory.CreateUnderground(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight); + public UndergroundCell[] Coolant { get; init; } = LevelStateFactory.CreateUnderground(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight); + public UndergroundCell[] Electricity { get; init; } = LevelStateFactory.CreateUnderground(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight); + public SurfaceState[] Surface { get; init; } = LevelStateFactory.CreateSurface(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight); + public PropState[] Props { get; init; } = LevelStateFactory.CreateProps(Balancing.Current.DefaultLevelWidth, Balancing.Current.DefaultLevelHeight); + public IReadOnlyList Doors { get; init; } = Array.Empty(); + public IReadOnlyList Leaks { get; init; } = Array.Empty(); + public IReadOnlyList Reactors { get; init; } = Array.Empty(); + public IReadOnlyList RuleEvents { get; init; } = Array.Empty(); + public RobotState Robot { get; init; } = new(); + public GlobalState Global { get; init; } = new(); + public IReadOnlyList Forecasts { get; init; } = Array.Empty(); +} diff --git a/src/ReactorMaintenance.Simulation/Models/PropState.cs b/src/ReactorMaintenance.Simulation/Models/PropState.cs new file mode 100644 index 0000000..c5f3d42 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/PropState.cs @@ -0,0 +1,16 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record PropState +{ + public EPropType Type { get; init; } + 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 ERemedyType RemedyType { get; init; } + public bool Depleted { get; init; } + public int ReactorId { get; init; } + + public bool IsEnabled => SwitchState == EPropSwitchState.Enabled; +} diff --git a/src/ReactorMaintenance.Simulation/Models/ReactorBinding.cs b/src/ReactorMaintenance.Simulation/Models/ReactorBinding.cs new file mode 100644 index 0000000..976562c --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/ReactorBinding.cs @@ -0,0 +1,12 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record ReactorBinding +{ + public int ReactorId { get; init; } + public GridPosition ControlPosition { get; init; } = new(0, 0); + public GridPosition FuelConsumerPosition { get; init; } = new(0, 0); + public GridPosition CoolantConsumerPosition { get; init; } = new(0, 0); + public GridPosition ElectricityConsumerPosition { get; init; } = new(0, 0); + public bool Ready { get; init; } + public bool Activated { get; init; } +} diff --git a/src/ReactorMaintenance.Simulation/Models/RobotState.cs b/src/ReactorMaintenance.Simulation/Models/RobotState.cs new file mode 100644 index 0000000..01589cc --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/RobotState.cs @@ -0,0 +1,11 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record RobotState +{ + public GridPosition Position { get; init; } = new(1, 1); + public int FuelNeutralizers { get; init; } + public int CoolantNeutralizers { get; init; } + public int ElectricityNeutralizers { get; init; } + public int HeatShields { get; init; } + public int HeatImmunitySteps { get; init; } +} diff --git a/src/ReactorMaintenance.Simulation/Models/RuleEffect.cs b/src/ReactorMaintenance.Simulation/Models/RuleEffect.cs new file mode 100644 index 0000000..22712a8 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/RuleEffect.cs @@ -0,0 +1,13 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record RuleEffect +{ + public ERuleEffectKind Kind { get; init; } + public GridPosition Position { get; init; } = new(0, 0); + public GridPosition? AccessPosition { get; init; } + public ECarrierType Carrier { get; init; } + public ERemedyType Remedy { get; init; } + public float Amount { get; init; } + public EPropSwitchState PropSwitchState { get; init; } + public string Message { get; init; } = string.Empty; +} diff --git a/src/ReactorMaintenance.Simulation/Models/RuleEventState.cs b/src/ReactorMaintenance.Simulation/Models/RuleEventState.cs new file mode 100644 index 0000000..e71f7f9 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/RuleEventState.cs @@ -0,0 +1,14 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record RuleEventState +{ + public string Id { get; init; } = string.Empty; + public bool Enabled { get; init; } = true; + public bool Repeat { get; init; } + public bool Triggered { get; init; } + public int Priority { get; init; } + public ERuleEventPhase Phase { get; init; } + public IReadOnlyList Predicates { get; init; } = Array.Empty(); + public IReadOnlyList Effects { get; init; } = Array.Empty(); + public string ForecastText { get; init; } = string.Empty; +} diff --git a/src/ReactorMaintenance.Simulation/Models/RulePredicate.cs b/src/ReactorMaintenance.Simulation/Models/RulePredicate.cs new file mode 100644 index 0000000..9e84475 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/RulePredicate.cs @@ -0,0 +1,18 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record RulePredicate +{ + public ERulePredicateKind Kind { get; init; } + public GridPosition Position { get; init; } = new(0, 0); + public int ReactorId { get; init; } + public int Turn { get; init; } + public ELevelState LevelState { get; init; } + public EPropSwitchState PropSwitchState { get; init; } + public EConsumerServiceState ConsumerServiceState { get; init; } + public ECarrierType Carrier { get; init; } + public ENetworkValueKind NetworkValue { get; init; } + public ERemedyType Remedy { get; init; } + public EBand Band { get; init; } + public int InventoryCount { get; init; } + public bool BoolValue { get; init; } +} diff --git a/src/ReactorMaintenance.Simulation/Models/SurfaceState.cs b/src/ReactorMaintenance.Simulation/Models/SurfaceState.cs new file mode 100644 index 0000000..933baa5 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/SurfaceState.cs @@ -0,0 +1,12 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record SurfaceState +{ + public float Fuel { get; init; } + public float Coolant { get; init; } + public float Electricity { get; init; } + public float Heat { get; init; } + public int FuelBlockTurns { get; init; } + public int CoolantBlockTurns { get; init; } + public int ElectricityBlockTurns { get; init; } +} diff --git a/src/ReactorMaintenance.Simulation/Models/UndergroundCell.cs b/src/ReactorMaintenance.Simulation/Models/UndergroundCell.cs new file mode 100644 index 0000000..6195b57 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/UndergroundCell.cs @@ -0,0 +1,11 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record UndergroundCell +{ + public EUndergroundState State { get; init; } + public float Amount { get; init; } + public float Intensity { get; init; } + + public bool IsPresent => State != EUndergroundState.Absent; + public bool CarriesFlow => State is EUndergroundState.Intact or EUndergroundState.Leaking; +} diff --git a/src/ReactorMaintenance.Simulation/Models/ValidationIssue.cs b/src/ReactorMaintenance.Simulation/Models/ValidationIssue.cs new file mode 100644 index 0000000..fd012c5 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/ValidationIssue.cs @@ -0,0 +1,3 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record ValidationIssue(string Message, GridPosition? Position = null); diff --git a/src/ReactorMaintenance.Simulation/Models/ValidationReport.cs b/src/ReactorMaintenance.Simulation/Models/ValidationReport.cs new file mode 100644 index 0000000..edc11a7 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/Models/ValidationReport.cs @@ -0,0 +1,8 @@ +namespace ReactorMaintenance.Simulation; + +public sealed record ValidationReport +{ + public IReadOnlyList Errors { get; init; } = Array.Empty(); + public IReadOnlyList Warnings { get; init; } = Array.Empty(); + public bool IsValid => Errors.Count == 0; +} diff --git a/src/ReactorMaintenance.Simulation/RobotStateExtensions.cs b/src/ReactorMaintenance.Simulation/RobotStateExtensions.cs new file mode 100644 index 0000000..b1b8f56 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/RobotStateExtensions.cs @@ -0,0 +1,36 @@ +namespace ReactorMaintenance.Simulation; + +public static class RobotStateExtensions +{ + public static int Count(this RobotState robot, ERemedyType remedy) + { + return remedy switch { + ERemedyType.FuelNeutralizer => robot.FuelNeutralizers, + ERemedyType.CoolantNeutralizer => robot.CoolantNeutralizers, + ERemedyType.ElectricityNeutralizer => robot.ElectricityNeutralizers, + ERemedyType.HeatShield => robot.HeatShields, + _ => throw new ArgumentOutOfRangeException(nameof(remedy), remedy, "Unsupported remedy.") + }; + } + + public static RobotState Add(this RobotState robot, ERemedyType remedy, int amount) + { + return remedy switch { + ERemedyType.FuelNeutralizer => robot with { FuelNeutralizers = ClampInventory(robot.FuelNeutralizers + amount) }, + ERemedyType.CoolantNeutralizer => robot with { CoolantNeutralizers = ClampInventory(robot.CoolantNeutralizers + amount) }, + ERemedyType.ElectricityNeutralizer => robot with { ElectricityNeutralizers = ClampInventory(robot.ElectricityNeutralizers + amount) }, + ERemedyType.HeatShield => robot with { HeatShields = ClampInventory(robot.HeatShields + amount) }, + _ => throw new ArgumentOutOfRangeException(nameof(remedy), remedy, "Unsupported remedy.") + }; + } + + public static RobotState Spend(this RobotState robot, ERemedyType remedy) + { + return robot.Count(remedy) <= 0 ? robot : robot.Add(remedy, -1); + } + + private static int ClampInventory(int value) + { + return Math.Clamp(value, 0, Balancing.Current.InventoryCapacityPerRemedy); + } +} diff --git a/src/ReactorMaintenance.Simulation/SurfaceStateExtensions.cs b/src/ReactorMaintenance.Simulation/SurfaceStateExtensions.cs new file mode 100644 index 0000000..4554c09 --- /dev/null +++ b/src/ReactorMaintenance.Simulation/SurfaceStateExtensions.cs @@ -0,0 +1,28 @@ +namespace ReactorMaintenance.Simulation; + +public static class SurfaceStateExtensions +{ + public static SurfaceState Clamp(this SurfaceState surface) + { + var balancing = Balancing.Current; + return surface with { + Fuel = balancing.ClampValue(surface.Fuel), + Coolant = balancing.ClampValue(surface.Coolant), + Electricity = balancing.ClampValue(surface.Electricity), + Heat = balancing.ClampValue(surface.Heat), + FuelBlockTurns = Math.Max(0, surface.FuelBlockTurns), + CoolantBlockTurns = Math.Max(0, surface.CoolantBlockTurns), + ElectricityBlockTurns = Math.Max(0, surface.ElectricityBlockTurns) + }; + } + + public static bool Blocks(this SurfaceState surface, ECarrierType carrier) + { + return carrier switch { + ECarrierType.Fuel => surface.FuelBlockTurns > 0, + ECarrierType.Coolant => surface.CoolantBlockTurns > 0, + ECarrierType.Electricity => surface.ElectricityBlockTurns > 0, + _ => throw new ArgumentOutOfRangeException(nameof(carrier), carrier, "Unsupported carrier.") + }; + } +}