Split simulation models

This commit is contained in:
2026-05-10 18:03:46 +02:00
parent a0b10423ac
commit 1aa9734e08
40 changed files with 616 additions and 558 deletions

View File

@@ -0,0 +1,17 @@
namespace ReactorMaintenance.Simulation;
public static class GridPositionExtensions
{
public static IEnumerable<GridPosition> 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);
}
}

View File

@@ -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<UndergroundCell> 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;
}
}

View File

@@ -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<Forecast>()
};
}
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();
}
}

View File

@@ -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<GridPosition> 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<RulePredicate> Predicates { get; init; } = Array.Empty<RulePredicate>();
public IReadOnlyList<RuleEffect> Effects { get; init; } = Array.Empty<RuleEffect>();
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<ValidationIssue> Errors { get; init; } = Array.Empty<ValidationIssue>();
public IReadOnlyList<ValidationIssue> Warnings { get; init; } = Array.Empty<ValidationIssue>();
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<string> Warnings { get; init; } = Array.Empty<string>();
}
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<Forecast>() };
}
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<UndergroundCell> 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<DoorState> Doors { get; init; } = Array.Empty<DoorState>();
public IReadOnlyList<LeakState> Leaks { get; init; } = Array.Empty<LeakState>();
public IReadOnlyList<ReactorBinding> Reactors { get; init; } = Array.Empty<ReactorBinding>();
public IReadOnlyList<RuleEventState> RuleEvents { get; init; } = Array.Empty<RuleEventState>();
public RobotState Robot { get; init; } = new();
public GlobalState Global { get; init; } = new();
public IReadOnlyList<Forecast> Forecasts { get; init; } = Array.Empty<Forecast>();
}

View File

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

View File

@@ -0,0 +1,8 @@
namespace ReactorMaintenance.Simulation;
public enum EBand
{
Safe,
Caution,
Critical
}

View File

@@ -0,0 +1,8 @@
namespace ReactorMaintenance.Simulation;
public enum ECarrierType
{
Fuel,
Coolant,
Electricity
}

View File

@@ -0,0 +1,7 @@
namespace ReactorMaintenance.Simulation;
public enum ECellTerrain
{
Floor,
Wall
}

View File

@@ -0,0 +1,10 @@
namespace ReactorMaintenance.Simulation;
public enum EConsumerServiceState
{
Unknown,
Disabled,
Starved,
Supplied,
Producing
}

View File

@@ -0,0 +1,9 @@
namespace ReactorMaintenance.Simulation;
public enum ECrossJunctionMode
{
ZeroThreeThree,
ThreeZeroThree,
ThreeThreeZero,
TwoTwoTwo
}

View File

@@ -0,0 +1,7 @@
namespace ReactorMaintenance.Simulation;
public enum EDoorState
{
Open,
Closed
}

View File

@@ -0,0 +1,10 @@
namespace ReactorMaintenance.Simulation;
public enum EForecastKind
{
TerminalLoss,
ReactorReady,
ConsumerStarved,
HazardGrowth,
RuleEvent
}

View File

@@ -0,0 +1,11 @@
namespace ReactorMaintenance.Simulation;
public enum ELevelState
{
Stable,
Caution,
Critical,
Ready,
Lost,
Won
}

View File

@@ -0,0 +1,7 @@
namespace ReactorMaintenance.Simulation;
public enum ENetworkValueKind
{
Amount,
Intensity
}

View File

@@ -0,0 +1,19 @@
namespace ReactorMaintenance.Simulation;
public enum EPairEffect
{
Hold,
FuelFlow,
CoolFlow,
ChargeFlow,
HeatFlow,
HeatFlow2,
Warm1,
Warm2,
Quench1,
Quench2,
Short1,
Short2,
Ignite1,
Ignite2
}

View File

@@ -0,0 +1,7 @@
namespace ReactorMaintenance.Simulation;
public enum EPropSwitchState
{
Disabled,
Enabled
}

View File

@@ -0,0 +1,14 @@
namespace ReactorMaintenance.Simulation;
public enum EPropType
{
None,
Flow,
Consumer,
TJunction,
CrossJunction,
Door,
AllSeeingEyeTerminal,
RemedySupply,
ReactorControl
}

View File

@@ -0,0 +1,9 @@
namespace ReactorMaintenance.Simulation;
public enum ERemedyType
{
FuelNeutralizer,
CoolantNeutralizer,
ElectricityNeutralizer,
HeatShield
}

View File

@@ -0,0 +1,18 @@
namespace ReactorMaintenance.Simulation;
public enum ERuleEffectKind
{
StartLeak,
WorsenLeak,
RepairNetworkCell,
DisableNetworkCell,
SetPropEnabled,
AddSurfaceHazard,
RemoveSurfaceHazard,
AddHeat,
RemoveHeat,
AddInventory,
RemoveInventory,
MarkTerminalLoss,
EmitWarning
}

View File

@@ -0,0 +1,7 @@
namespace ReactorMaintenance.Simulation;
public enum ERuleEventPhase
{
StartOfSimulation,
EndOfTurn
}

View File

@@ -0,0 +1,17 @@
namespace ReactorMaintenance.Simulation;
public enum ERulePredicateKind
{
TurnAtLeast,
LevelStateIs,
ReactorReadyIs,
ReactorLostIs,
ReactorWonIs,
PropStateAt,
ConsumerStateAt,
NetworkBandAt,
SurfaceBandAt,
RobotAt,
RobotInventoryAtLeast,
AllSeeingEyeUnlocked
}

View File

@@ -0,0 +1,10 @@
namespace ReactorMaintenance.Simulation;
public enum ETJunctionMode
{
ZeroFour,
OneThree,
TwoTwo,
ThreeOne,
FourZero
}

View File

@@ -0,0 +1,8 @@
namespace ReactorMaintenance.Simulation;
public enum EUndergroundState
{
Absent,
Intact,
Leaking
}

View File

@@ -0,0 +1,3 @@
namespace ReactorMaintenance.Simulation;
public sealed record Forecast(EForecastKind Kind, GridPosition? Position, int Turns, string Message);

View File

@@ -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<string> Warnings { get; init; } = Array.Empty<string>();
}

View File

@@ -0,0 +1,3 @@
namespace ReactorMaintenance.Simulation;
public sealed record GridPosition(int X, int Y);

View File

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

View File

@@ -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<DoorState> Doors { get; init; } = Array.Empty<DoorState>();
public IReadOnlyList<LeakState> Leaks { get; init; } = Array.Empty<LeakState>();
public IReadOnlyList<ReactorBinding> Reactors { get; init; } = Array.Empty<ReactorBinding>();
public IReadOnlyList<RuleEventState> RuleEvents { get; init; } = Array.Empty<RuleEventState>();
public RobotState Robot { get; init; } = new();
public GlobalState Global { get; init; } = new();
public IReadOnlyList<Forecast> Forecasts { get; init; } = Array.Empty<Forecast>();
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<RulePredicate> Predicates { get; init; } = Array.Empty<RulePredicate>();
public IReadOnlyList<RuleEffect> Effects { get; init; } = Array.Empty<RuleEffect>();
public string ForecastText { get; init; } = string.Empty;
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
namespace ReactorMaintenance.Simulation;
public sealed record ValidationIssue(string Message, GridPosition? Position = null);

View File

@@ -0,0 +1,8 @@
namespace ReactorMaintenance.Simulation;
public sealed record ValidationReport
{
public IReadOnlyList<ValidationIssue> Errors { get; init; } = Array.Empty<ValidationIssue>();
public IReadOnlyList<ValidationIssue> Warnings { get; init; } = Array.Empty<ValidationIssue>();
public bool IsValid => Errors.Count == 0;
}

View File

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

View File

@@ -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.")
};
}
}