Implement surface safety and powered props
This commit is contained in:
@@ -43,11 +43,14 @@ public abstract class Balancing
|
|||||||
if (rowCarrier == ECarrierType.Fuel && colCarrier == ECarrierType.Electricity)
|
if (rowCarrier == ECarrierType.Fuel && colCarrier == ECarrierType.Electricity)
|
||||||
return Ignite(rowBand, colBand);
|
return Ignite(rowBand, colBand);
|
||||||
|
|
||||||
|
if (rowCarrier == ECarrierType.Fuel && colCarrier == ECarrierType.Water)
|
||||||
|
return Dilute(rowBand, colBand);
|
||||||
|
|
||||||
if (rowCarrier == ECarrierType.Fuel && colCarrier is null)
|
if (rowCarrier == ECarrierType.Fuel && colCarrier is null)
|
||||||
return rowBand == EBand.Critical || colBand == EBand.Critical ? Ignite(rowBand, colBand) : Warm(rowBand, colBand);
|
return rowBand == EBand.Critical || colBand == EBand.Critical ? Ignite(rowBand, colBand) : Warm(rowBand, colBand);
|
||||||
|
|
||||||
if (rowCarrier == ECarrierType.Water && colCarrier == ECarrierType.Electricity)
|
if (rowCarrier == ECarrierType.Water && colCarrier == ECarrierType.Electricity)
|
||||||
return Short(rowBand, colBand);
|
return Conduct(rowBand, colBand);
|
||||||
|
|
||||||
if (rowCarrier == ECarrierType.Water && colCarrier is null)
|
if (rowCarrier == ECarrierType.Water && colCarrier is null)
|
||||||
return Quench(rowBand, colBand);
|
return Quench(rowBand, colBand);
|
||||||
@@ -88,14 +91,23 @@ public abstract class Balancing
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private SurfaceInteractionEffect Short(EBand rowBand, EBand colBand)
|
private SurfaceInteractionEffect Dilute(EBand rowBand, EBand colBand)
|
||||||
|
{
|
||||||
|
return new() {
|
||||||
|
Verb = ESurfaceInteractionVerb.Dilute,
|
||||||
|
Quantity = ESurfaceQuantity.Fuel,
|
||||||
|
Amount = Strongest(rowBand, colBand) == EBand.Critical ? DiluteCriticalAmount : DiluteCautionAmount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private SurfaceInteractionEffect Conduct(EBand rowBand, EBand colBand)
|
||||||
{
|
{
|
||||||
var critical = Strongest(rowBand, colBand) == EBand.Critical;
|
var critical = Strongest(rowBand, colBand) == EBand.Critical;
|
||||||
return new() {
|
return new() {
|
||||||
Verb = ESurfaceInteractionVerb.Short,
|
Verb = ESurfaceInteractionVerb.Conduct,
|
||||||
Quantity = ESurfaceQuantity.Electricity,
|
Quantity = ESurfaceQuantity.Electricity,
|
||||||
Amount = critical ? ShortCriticalHeat : ShortCautionHeat,
|
Amount = critical ? ConductCriticalHeat : ConductCautionHeat,
|
||||||
SecondaryAmount = critical ? ShortCriticalDischarge : ShortCautionDischarge
|
SecondaryAmount = critical ? ConductCriticalDischarge : ConductCautionDischarge
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,15 +169,21 @@ public abstract class Balancing
|
|||||||
public abstract float LeakIntensityScale { get; }
|
public abstract float LeakIntensityScale { get; }
|
||||||
public abstract float SprinklerWaterPerStep { get; }
|
public abstract float SprinklerWaterPerStep { get; }
|
||||||
public abstract float SprinklerPressureDebt { get; }
|
public abstract float SprinklerPressureDebt { get; }
|
||||||
|
public abstract float AmbientEvaporationPerStep { get; }
|
||||||
|
public abstract float HeatEvaporationScale { get; }
|
||||||
|
public abstract float EvaporationCoolingScale { get; }
|
||||||
public abstract float FlowTransferRatio { get; }
|
public abstract float FlowTransferRatio { get; }
|
||||||
|
public abstract float WetElectricityFlowMultiplier { get; }
|
||||||
public abstract float WarmCautionAmount { get; }
|
public abstract float WarmCautionAmount { get; }
|
||||||
public abstract float WarmCriticalAmount { get; }
|
public abstract float WarmCriticalAmount { get; }
|
||||||
|
public abstract float DiluteCautionAmount { get; }
|
||||||
|
public abstract float DiluteCriticalAmount { get; }
|
||||||
public abstract float QuenchCautionAmount { get; }
|
public abstract float QuenchCautionAmount { get; }
|
||||||
public abstract float QuenchCriticalAmount { get; }
|
public abstract float QuenchCriticalAmount { get; }
|
||||||
public abstract float ShortCautionHeat { get; }
|
public abstract float ConductCautionHeat { get; }
|
||||||
public abstract float ShortCautionDischarge { get; }
|
public abstract float ConductCautionDischarge { get; }
|
||||||
public abstract float ShortCriticalHeat { get; }
|
public abstract float ConductCriticalHeat { get; }
|
||||||
public abstract float ShortCriticalDischarge { get; }
|
public abstract float ConductCriticalDischarge { get; }
|
||||||
public abstract float IgniteCautionHeat { get; }
|
public abstract float IgniteCautionHeat { get; }
|
||||||
public abstract float IgniteCautionFuelConsumption { get; }
|
public abstract float IgniteCautionFuelConsumption { get; }
|
||||||
public abstract float IgniteCriticalHeat { get; }
|
public abstract float IgniteCriticalHeat { get; }
|
||||||
|
|||||||
@@ -56,15 +56,21 @@ public class NormalBalancing : Balancing
|
|||||||
public override float LeakIntensityScale => 0.1f;
|
public override float LeakIntensityScale => 0.1f;
|
||||||
public override float SprinklerWaterPerStep => 0.8f;
|
public override float SprinklerWaterPerStep => 0.8f;
|
||||||
public override float SprinklerPressureDebt => 3.0f;
|
public override float SprinklerPressureDebt => 3.0f;
|
||||||
|
public override float AmbientEvaporationPerStep => 0.1f;
|
||||||
|
public override float HeatEvaporationScale => 0.08f;
|
||||||
|
public override float EvaporationCoolingScale => 0.25f;
|
||||||
public override float FlowTransferRatio => 0.05f;
|
public override float FlowTransferRatio => 0.05f;
|
||||||
|
public override float WetElectricityFlowMultiplier => 3.0f;
|
||||||
public override float WarmCautionAmount => 0.5f;
|
public override float WarmCautionAmount => 0.5f;
|
||||||
public override float WarmCriticalAmount => 1.0f;
|
public override float WarmCriticalAmount => 1.0f;
|
||||||
|
public override float DiluteCautionAmount => 0.5f;
|
||||||
|
public override float DiluteCriticalAmount => 1.0f;
|
||||||
public override float QuenchCautionAmount => 0.6f;
|
public override float QuenchCautionAmount => 0.6f;
|
||||||
public override float QuenchCriticalAmount => 1.2f;
|
public override float QuenchCriticalAmount => 1.2f;
|
||||||
public override float ShortCautionHeat => 0.8f;
|
public override float ConductCautionHeat => 0.2f;
|
||||||
public override float ShortCautionDischarge => 0.8f;
|
public override float ConductCautionDischarge => 0.4f;
|
||||||
public override float ShortCriticalHeat => 1.6f;
|
public override float ConductCriticalHeat => 0.5f;
|
||||||
public override float ShortCriticalDischarge => 1.5f;
|
public override float ConductCriticalDischarge => 0.8f;
|
||||||
public override float IgniteCautionHeat => 1.2f;
|
public override float IgniteCautionHeat => 1.2f;
|
||||||
public override float IgniteCautionFuelConsumption => 0.4f;
|
public override float IgniteCautionFuelConsumption => 0.4f;
|
||||||
public override float IgniteCriticalHeat => 2.4f;
|
public override float IgniteCriticalHeat => 2.4f;
|
||||||
|
|||||||
@@ -45,6 +45,27 @@ public static class LevelStateExtensions
|
|||||||
return DoorBlocksEdge(level, a, b) || DoorBlocksEdge(level, b, a);
|
return DoorBlocksEdge(level, a, b) || DoorBlocksEdge(level, b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool HasActivePoweredTerminalAccess(this LevelState level)
|
||||||
|
{
|
||||||
|
var position = level.Robot.Position;
|
||||||
|
return level.InBounds(position)
|
||||||
|
&& level.GetProp(position) is { Type: EPropType.AllSeeingEyeTerminal, Active: true }
|
||||||
|
&& level.IsPowered(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsPowered(this LevelState level, GridPosition position)
|
||||||
|
{
|
||||||
|
if (!level.InBounds(position))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var current = level.GetUnderground(position, ECarrierType.Electricity);
|
||||||
|
if (current is { Amount: > 0, Intensity: > 0 })
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var propagated = NetworkPropagationSystem.Propagate(level);
|
||||||
|
return propagated.GetUnderground(position, ECarrierType.Electricity) is { Amount: > 0, Intensity: > 0 };
|
||||||
|
}
|
||||||
|
|
||||||
public static LevelState SetTerrain(this LevelState level, GridPosition position, ECellTerrain terrain)
|
public static LevelState SetTerrain(this LevelState level, GridPosition position, ECellTerrain terrain)
|
||||||
{
|
{
|
||||||
var next = level.Terrain.ToArray();
|
var next = level.Terrain.ToArray();
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ public enum ESurfaceInteractionVerb
|
|||||||
{
|
{
|
||||||
Hold,
|
Hold,
|
||||||
Flow,
|
Flow,
|
||||||
|
Dilute,
|
||||||
Warm,
|
Warm,
|
||||||
Quench,
|
Quench,
|
||||||
Short,
|
Evaporate,
|
||||||
|
Conduct,
|
||||||
Ignite
|
Ignite
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,7 @@ public sealed record PropState
|
|||||||
public bool Depleted { get; init; }
|
public bool Depleted { get; init; }
|
||||||
public int ReactorId { get; init; }
|
public int ReactorId { get; init; }
|
||||||
public EDoorState DoorState { get; init; } = EDoorState.Closed;
|
public EDoorState DoorState { get; init; } = EDoorState.Closed;
|
||||||
|
public bool Active { get; init; }
|
||||||
public GridPosition? LinkedPosition { get; init; }
|
public GridPosition? LinkedPosition { get; init; }
|
||||||
public GridPosition? OutletPosition { get; init; }
|
public GridPosition? OutletPosition { get; init; }
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ public sealed class SimulationEngine
|
|||||||
|
|
||||||
public IReadOnlyList<Forecast> Forecast(LevelState level)
|
public IReadOnlyList<Forecast> Forecast(LevelState level)
|
||||||
{
|
{
|
||||||
|
if (!level.HasActivePoweredTerminalAccess())
|
||||||
|
return Array.Empty<Forecast>();
|
||||||
|
|
||||||
return ForecastSystem.Forecast(level, simulated => ResolveStep(simulated, false));
|
return ForecastSystem.Forecast(level, simulated => ResolveStep(simulated, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ internal static class PlayerActionSystem
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return RobotSafetySystem.ResolveEntry(next);
|
next = RobotSafetySystem.ResolveEntry(next);
|
||||||
|
return next.HasActivePoweredTerminalAccess() ? next : next with { Forecasts = Array.Empty<Forecast>() };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LevelState InteractProp(LevelState level, Func<LevelState, LevelState> resolveLengthyAction)
|
public static LevelState InteractProp(LevelState level, Func<LevelState, LevelState> resolveLengthyAction)
|
||||||
@@ -33,7 +34,7 @@ internal static class PlayerActionSystem
|
|||||||
EPropType.SprinklerControl => ToggleProp(level, position, prop),
|
EPropType.SprinklerControl => ToggleProp(level, position, prop),
|
||||||
EPropType.Junction => CycleJunctionMode(level, position, prop),
|
EPropType.Junction => CycleJunctionMode(level, position, prop),
|
||||||
EPropType.Door => ToggleDoor(level, position, prop),
|
EPropType.Door => ToggleDoor(level, position, prop),
|
||||||
EPropType.AllSeeingEyeTerminal => level with { Global = level.Global with { Status = "ALL-SEEING-EYE AVAILABLE" } },
|
EPropType.AllSeeingEyeTerminal => ActivateTerminal(level, position, prop),
|
||||||
EPropType.RemedySupply => PickUpRemedy(level, position, prop),
|
EPropType.RemedySupply => PickUpRemedy(level, position, prop),
|
||||||
EPropType.ReactorControl => ReactorSystem.Activate(level),
|
EPropType.ReactorControl => ReactorSystem.Activate(level),
|
||||||
_ => Refuse(level, "PROP NOT INTERACTIVE")
|
_ => Refuse(level, "PROP NOT INTERACTIVE")
|
||||||
@@ -78,10 +79,21 @@ internal static class PlayerActionSystem
|
|||||||
|
|
||||||
private static LevelState ToggleDoor(LevelState level, GridPosition position, PropState prop)
|
private static LevelState ToggleDoor(LevelState level, GridPosition position, PropState prop)
|
||||||
{
|
{
|
||||||
|
if (!level.IsPowered(position))
|
||||||
|
return level with { Global = level.Global with { Status = "DOOR UNPOWERED" } };
|
||||||
|
|
||||||
var doorState = prop.DoorState == EDoorState.Open ? EDoorState.Closed : EDoorState.Open;
|
var doorState = prop.DoorState == EDoorState.Open ? EDoorState.Closed : EDoorState.Open;
|
||||||
return level.SetProp(position, prop with { DoorState = doorState });
|
return level.SetProp(position, prop with { DoorState = doorState });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static LevelState ActivateTerminal(LevelState level, GridPosition position, PropState prop)
|
||||||
|
{
|
||||||
|
if (!level.IsPowered(position))
|
||||||
|
return level.SetProp(position, prop with { Active = false }) with { Global = level.Global with { Status = "TERMINAL UNPOWERED" } };
|
||||||
|
|
||||||
|
return level.SetProp(position, prop with { Active = true }) with { Global = level.Global with { Status = "ALL-SEEING-EYE AVAILABLE" } };
|
||||||
|
}
|
||||||
|
|
||||||
private static LevelState PickUpRemedy(LevelState level, GridPosition position, PropState prop)
|
private static LevelState PickUpRemedy(LevelState level, GridPosition position, PropState prop)
|
||||||
{
|
{
|
||||||
if (prop.Depleted || level.Robot.Count(prop.RemedyType) >= Balancing.Current.InventoryCapacityPerRemedy)
|
if (prop.Depleted || level.Robot.Count(prop.RemedyType) >= Balancing.Current.InventoryCapacityPerRemedy)
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ internal static class SurfaceInteractionSystem
|
|||||||
public static LevelState Resolve(LevelState level)
|
public static LevelState Resolve(LevelState level)
|
||||||
{
|
{
|
||||||
var deltas = Enumerable.Range(0, level.Width * level.Height).Select(_ => new SurfaceDelta()).ToArray();
|
var deltas = Enumerable.Range(0, level.Width * level.Height).Select(_ => new SurfaceDelta()).ToArray();
|
||||||
|
foreach (var position in LevelTraversal.AllPositions(level).Where(level.IsFloor))
|
||||||
|
ApplyWaterMitigation(level, position, deltas);
|
||||||
|
|
||||||
|
foreach (var position in LevelTraversal.AllPositions(level).Where(level.IsFloor))
|
||||||
|
ApplyEvaporation(level, position, deltas);
|
||||||
|
|
||||||
foreach (var position in LevelTraversal.AllPositions(level).Where(level.IsFloor))
|
foreach (var position in LevelTraversal.AllPositions(level).Where(level.IsFloor))
|
||||||
ApplySameCellInteractions(level, position, deltas);
|
ApplySameCellInteractions(level, position, deltas);
|
||||||
|
|
||||||
@@ -62,9 +68,27 @@ internal static class SurfaceInteractionSystem
|
|||||||
ApplyPair(level, position, ECarrierType.Fuel, SimulationBands.Fuel(surface.Fuel), ECarrierType.Electricity, SimulationBands.Electricity(surface.Electricity), deltas);
|
ApplyPair(level, position, ECarrierType.Fuel, SimulationBands.Fuel(surface.Fuel), ECarrierType.Electricity, SimulationBands.Electricity(surface.Electricity), deltas);
|
||||||
ApplyPair(level, position, ECarrierType.Fuel, SimulationBands.Fuel(surface.Fuel), null, SimulationBands.Heat(surface.Heat), deltas);
|
ApplyPair(level, position, ECarrierType.Fuel, SimulationBands.Fuel(surface.Fuel), null, SimulationBands.Heat(surface.Heat), deltas);
|
||||||
ApplyPair(level, position, ECarrierType.Water, SimulationBands.Water(surface.Water), ECarrierType.Electricity, SimulationBands.Electricity(surface.Electricity), deltas);
|
ApplyPair(level, position, ECarrierType.Water, SimulationBands.Water(surface.Water), ECarrierType.Electricity, SimulationBands.Electricity(surface.Electricity), deltas);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyWaterMitigation(LevelState level, GridPosition position, SurfaceDelta[] deltas)
|
||||||
|
{
|
||||||
|
var surface = level.GetSurface(position);
|
||||||
|
ApplyPair(level, position, ECarrierType.Fuel, SimulationBands.Fuel(surface.Fuel), ECarrierType.Water, SimulationBands.Water(surface.Water), deltas);
|
||||||
ApplyPair(level, position, ECarrierType.Water, SimulationBands.Water(surface.Water), null, SimulationBands.Heat(surface.Heat), deltas);
|
ApplyPair(level, position, ECarrierType.Water, SimulationBands.Water(surface.Water), null, SimulationBands.Heat(surface.Heat), deltas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ApplyEvaporation(LevelState level, GridPosition position, SurfaceDelta[] deltas)
|
||||||
|
{
|
||||||
|
var surface = level.GetSurface(position);
|
||||||
|
if (surface.Water <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var evaporated = Math.Min(surface.Water, Balancing.Current.AmbientEvaporationPerStep + (surface.Heat * Balancing.Current.HeatEvaporationScale));
|
||||||
|
var index = level.Index(position);
|
||||||
|
deltas[index].Water -= evaporated;
|
||||||
|
deltas[index].Heat -= evaporated * Balancing.Current.EvaporationCoolingScale;
|
||||||
|
}
|
||||||
|
|
||||||
private static void ApplyAdjacentInteractions(LevelState level, GridPosition a, GridPosition b, SurfaceDelta[] deltas)
|
private static void ApplyAdjacentInteractions(LevelState level, GridPosition a, GridPosition b, SurfaceDelta[] deltas)
|
||||||
{
|
{
|
||||||
var surfaceA = level.GetSurface(a);
|
var surfaceA = level.GetSurface(a);
|
||||||
@@ -88,10 +112,13 @@ internal static class SurfaceInteractionSystem
|
|||||||
case ESurfaceInteractionVerb.Warm:
|
case ESurfaceInteractionVerb.Warm:
|
||||||
deltas[index].Heat += effect.Amount;
|
deltas[index].Heat += effect.Amount;
|
||||||
break;
|
break;
|
||||||
|
case ESurfaceInteractionVerb.Dilute:
|
||||||
|
deltas[index].Fuel -= effect.Amount;
|
||||||
|
break;
|
||||||
case ESurfaceInteractionVerb.Quench:
|
case ESurfaceInteractionVerb.Quench:
|
||||||
deltas[index].Heat -= effect.Amount;
|
deltas[index].Heat -= effect.Amount;
|
||||||
break;
|
break;
|
||||||
case ESurfaceInteractionVerb.Short:
|
case ESurfaceInteractionVerb.Conduct:
|
||||||
deltas[index].Heat += effect.Amount;
|
deltas[index].Heat += effect.Amount;
|
||||||
deltas[index].Electricity -= effect.SecondaryAmount;
|
deltas[index].Electricity -= effect.SecondaryAmount;
|
||||||
break;
|
break;
|
||||||
@@ -109,6 +136,8 @@ internal static class SurfaceInteractionSystem
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var amount = difference * effect.Amount;
|
var amount = difference * effect.Amount;
|
||||||
|
if (effect.Quantity == ESurfaceQuantity.Electricity && (level.GetSurface(a).Water > Balancing.Current.WaterSafe || level.GetSurface(b).Water > Balancing.Current.WaterSafe))
|
||||||
|
amount *= Balancing.Current.WetElectricityFlowMultiplier;
|
||||||
var indexA = level.Index(a);
|
var indexA = level.Index(a);
|
||||||
var indexB = level.Index(b);
|
var indexB = level.Index(b);
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ public sealed class SimulationEngineTests
|
|||||||
{
|
{
|
||||||
var level = BuildReadyLevel();
|
var level = BuildReadyLevel();
|
||||||
|
|
||||||
var next = m_Engine.AdvanceTurn(level);
|
var next = m_Engine.AdvancePulseForDebug(level);
|
||||||
var consumer = next.GetProp(new(3, 3));
|
var consumer = next.GetProp(new(3, 3));
|
||||||
|
|
||||||
Assert.Equal(EConsumerServiceState.Producing, consumer.FuelServiceState);
|
Assert.Equal(EConsumerServiceState.Producing, consumer.FuelServiceState);
|
||||||
Assert.Equal(EConsumerServiceState.Producing, consumer.WaterServiceState);
|
Assert.Equal(EConsumerServiceState.Producing, consumer.WaterServiceState);
|
||||||
Assert.Equal(EConsumerServiceState.Producing, consumer.ElectricityServiceState);
|
Assert.Equal(EConsumerServiceState.Producing, consumer.ElectricityServiceState);
|
||||||
Assert.Equal(ELevelState.Ready, next.Global.LevelState);
|
Assert.Equal(ELevelState.Ready, next.Global.LevelState);
|
||||||
Assert.Contains(next.Forecasts, forecast => forecast.Kind == EForecastKind.ReactorReady);
|
Assert.Empty(next.Forecasts);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -23,7 +23,7 @@ public sealed class SimulationEngineTests
|
|||||||
var level = BuildReadyLevel();
|
var level = BuildReadyLevel();
|
||||||
level = level.SetUnderground(new(5, 3), ECarrierType.Fuel, new() { State = EUndergroundState.Intact });
|
level = level.SetUnderground(new(5, 3), ECarrierType.Fuel, new() { State = EUndergroundState.Intact });
|
||||||
|
|
||||||
var next = m_Engine.AdvanceTurn(level);
|
var next = m_Engine.AdvancePulseForDebug(level);
|
||||||
|
|
||||||
Assert.NotEqual(ELevelState.Ready, next.Global.LevelState);
|
Assert.NotEqual(ELevelState.Ready, next.Global.LevelState);
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ public sealed class SimulationEngineTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void ReactorActivatesOnlyAtReadyControl()
|
public void ReactorActivatesOnlyAtReadyControl()
|
||||||
{
|
{
|
||||||
var level = m_Engine.AdvanceTurn(BuildReadyLevel()) with {
|
var level = m_Engine.AdvancePulseForDebug(BuildReadyLevel()) with {
|
||||||
Robot = new() { Position = new(5, 3) }
|
Robot = new() { Position = new(5, 3) }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ public sealed class SimulationEngineTests
|
|||||||
level = level.SetUnderground(new(2, 2), ECarrierType.Fuel, new() { State = EUndergroundState.Intact });
|
level = level.SetUnderground(new(2, 2), ECarrierType.Fuel, new() { State = EUndergroundState.Intact });
|
||||||
level = level.SetProp(new(2, 2), new() { Type = EPropType.Consumer, SwitchState = EPropSwitchState.Disabled });
|
level = level.SetProp(new(2, 2), new() { Type = EPropType.Consumer, SwitchState = EPropSwitchState.Disabled });
|
||||||
|
|
||||||
var next = m_Engine.AdvanceTurn(level);
|
var next = m_Engine.AdvancePulseForDebug(level);
|
||||||
var consumer = next.GetProp(new(2, 2));
|
var consumer = next.GetProp(new(2, 2));
|
||||||
|
|
||||||
Assert.Equal(EConsumerServiceState.Disabled, consumer.FuelServiceState);
|
Assert.Equal(EConsumerServiceState.Disabled, consumer.FuelServiceState);
|
||||||
@@ -83,6 +83,42 @@ public sealed class SimulationEngineTests
|
|||||||
Assert.Equal(Balancing.Current.StepsPerPulse, next.Global.Step);
|
Assert.Equal(Balancing.Current.StepsPerPulse, next.Global.Step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UnpoweredDoorInteractionChangesNoDoorStateButStillPulses()
|
||||||
|
{
|
||||||
|
var level = DoorLevel(powered: false) with { Robot = new() { Position = new(3, 2) } };
|
||||||
|
|
||||||
|
var next = m_Engine.InteractProp(level);
|
||||||
|
|
||||||
|
Assert.Equal(EDoorState.Closed, next.GetProp(new(3, 2)).DoorState);
|
||||||
|
Assert.Equal(1, next.Global.Pulse);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PoweredTerminalInteractionEnablesLocalForecastsUntilRobotLeaves()
|
||||||
|
{
|
||||||
|
var level = TerminalLevel();
|
||||||
|
|
||||||
|
var activated = m_Engine.InteractProp(level);
|
||||||
|
var moved = m_Engine.MoveRobot(activated, new(2, 1));
|
||||||
|
|
||||||
|
Assert.True(activated.GetProp(new(1, 1)).Active);
|
||||||
|
Assert.NotEmpty(activated.Forecasts);
|
||||||
|
Assert.Empty(moved.Forecasts);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UnpoweredTerminalInteractionRevealsNothingButStillPulses()
|
||||||
|
{
|
||||||
|
var level = TerminalLevel(false);
|
||||||
|
|
||||||
|
var next = m_Engine.InteractProp(level);
|
||||||
|
|
||||||
|
Assert.False(next.GetProp(new(1, 1)).Active);
|
||||||
|
Assert.Empty(next.Forecasts);
|
||||||
|
Assert.Equal(1, next.Global.Pulse);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void EveryAcceptedLengthyActionAdvancesOneFixedPulse()
|
public void EveryAcceptedLengthyActionAdvancesOneFixedPulse()
|
||||||
{
|
{
|
||||||
@@ -138,7 +174,7 @@ public sealed class SimulationEngineTests
|
|||||||
var level = DoorLevel();
|
var level = DoorLevel();
|
||||||
level = level.SetSurface(new(3, 2), new() { Heat = 8 });
|
level = level.SetSurface(new(3, 2), new() { Heat = 8 });
|
||||||
|
|
||||||
var next = m_Engine.AdvanceTurn(level);
|
var next = m_Engine.AdvancePulseForDebug(level);
|
||||||
|
|
||||||
Assert.Equal(0, next.GetSurface(new(4, 2)).Heat);
|
Assert.Equal(0, next.GetSurface(new(4, 2)).Heat);
|
||||||
}
|
}
|
||||||
@@ -153,7 +189,7 @@ public sealed class SimulationEngineTests
|
|||||||
StructuralIntegrity = Balancing.Current.StructuralIntegrityLeakThreshold
|
StructuralIntegrity = Balancing.Current.StructuralIntegrityLeakThreshold
|
||||||
});
|
});
|
||||||
|
|
||||||
var next = m_Engine.AdvanceTurn(level);
|
var next = m_Engine.AdvancePulseForDebug(level);
|
||||||
|
|
||||||
Assert.Equal(EUndergroundState.Leaking, next.GetUnderground(new(2, 2), ECarrierType.Fuel).State);
|
Assert.Equal(EUndergroundState.Leaking, next.GetUnderground(new(2, 2), ECarrierType.Fuel).State);
|
||||||
Assert.Contains(next.Leaks, leak => leak.Carrier == ECarrierType.Fuel && leak.UndergroundPosition == new GridPosition(2, 2));
|
Assert.Contains(next.Leaks, leak => leak.Carrier == ECarrierType.Fuel && leak.UndergroundPosition == new GridPosition(2, 2));
|
||||||
@@ -170,7 +206,7 @@ public sealed class SimulationEngineTests
|
|||||||
StructuralIntegrity = Balancing.Current.MaxStructuralIntegrity - 1
|
StructuralIntegrity = Balancing.Current.MaxStructuralIntegrity - 1
|
||||||
});
|
});
|
||||||
|
|
||||||
var next = m_Engine.AdvanceTurn(level);
|
var next = m_Engine.AdvancePulseForDebug(level);
|
||||||
|
|
||||||
Assert.True(next.GetUnderground(new(2, 2), ECarrierType.Fuel).StructuralIntegrity < Balancing.Current.MaxStructuralIntegrity - 1);
|
Assert.True(next.GetUnderground(new(2, 2), ECarrierType.Fuel).StructuralIntegrity < Balancing.Current.MaxStructuralIntegrity - 1);
|
||||||
}
|
}
|
||||||
@@ -221,17 +257,107 @@ public sealed class SimulationEngineTests
|
|||||||
Robot = new() { Position = new(2, 2), HeatImmunitySteps = 1 }
|
Robot = new() { Position = new(2, 2), HeatImmunitySteps = 1 }
|
||||||
};
|
};
|
||||||
|
|
||||||
var next = m_Engine.AdvanceTurn(level);
|
var next = m_Engine.AdvancePulseForDebug(level);
|
||||||
|
|
||||||
Assert.NotEqual(ELevelState.Lost, next.Global.LevelState);
|
Assert.NotEqual(ELevelState.Lost, next.Global.LevelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UnsafeEntryLossOnlyHappensWhenMovingIntoUnsafeDestination()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Unsafe", 5, 5) with { Robot = new() { Position = new(1, 1) } };
|
||||||
|
level = level.SetSurface(new(2, 1), new() { Heat = Balancing.Current.RobotHeatSafetyThreshold });
|
||||||
|
|
||||||
|
var next = m_Engine.MoveRobot(level, new(2, 1));
|
||||||
|
|
||||||
|
Assert.Equal(ELevelState.Lost, next.Global.LevelState);
|
||||||
|
Assert.Equal("UNSAFE ENTRY LOSS", next.Global.Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FuelOnlyAndWaterOnlyCellsAreSafeToEnter()
|
||||||
|
{
|
||||||
|
var fuel = LevelState.Create("Fuel safe", 5, 5) with { Robot = new() { Position = new(1, 1) } };
|
||||||
|
fuel = fuel.SetSurface(new(2, 1), new() { Fuel = Balancing.Current.MaxValue });
|
||||||
|
var water = LevelState.Create("Water safe", 5, 5) with { Robot = new() { Position = new(1, 1) } };
|
||||||
|
water = water.SetSurface(new(2, 1), new() { Water = Balancing.Current.MaxValue });
|
||||||
|
|
||||||
|
Assert.NotEqual(ELevelState.Lost, m_Engine.MoveRobot(fuel, new(2, 1)).Global.LevelState);
|
||||||
|
Assert.NotEqual(ELevelState.Lost, m_Engine.MoveRobot(water, new(2, 1)).Global.LevelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PulseDoesNotLoseStationaryRobotWhenCellBecomesUnsafe()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Stationary", 5, 5) with { Robot = new() { Position = new(2, 2) } };
|
||||||
|
level = level.SetSurface(new(2, 2), new() { Heat = Balancing.Current.RobotHeatSafetyThreshold });
|
||||||
|
|
||||||
|
var next = m_Engine.AdvancePulseForDebug(level);
|
||||||
|
|
||||||
|
Assert.NotEqual(ELevelState.Lost, next.Global.LevelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WaterDilutesFuelAndQuenchesHeatBeforeIgnition()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Mitigation", 5, 5);
|
||||||
|
level = level.SetSurface(new(2, 2), new() {
|
||||||
|
Fuel = Balancing.Current.FuelCaution,
|
||||||
|
Water = Balancing.Current.WaterCritical,
|
||||||
|
Heat = Balancing.Current.HeatCaution
|
||||||
|
});
|
||||||
|
|
||||||
|
var next = m_Engine.AdvanceStepForDebug(level);
|
||||||
|
|
||||||
|
Assert.True(next.GetSurface(new(2, 2)).Fuel < level.GetSurface(new(2, 2)).Fuel);
|
||||||
|
Assert.True(next.GetSurface(new(2, 2)).Heat < level.GetSurface(new(2, 2)).Heat);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HotCellsEvaporateWaterFasterThanColdCells()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Evaporation", 5, 5);
|
||||||
|
level = level.SetSurface(new(1, 1), new() { Water = 5 });
|
||||||
|
level = level.SetSurface(new(2, 1), new() { Water = 5, Heat = 6 });
|
||||||
|
|
||||||
|
var next = m_Engine.AdvanceStepForDebug(level);
|
||||||
|
|
||||||
|
Assert.True(next.GetSurface(new(2, 1)).Water < next.GetSurface(new(1, 1)).Water);
|
||||||
|
Assert.True(next.GetSurface(new(2, 1)).Heat < 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WetCellsConductElectricityFasterThanDryCells()
|
||||||
|
{
|
||||||
|
var wet = LevelState.Create("Wet conduct", 5, 5);
|
||||||
|
wet = wet.SetSurface(new(1, 1), new() { Electricity = 8, Water = Balancing.Current.WaterCaution });
|
||||||
|
var dry = LevelState.Create("Dry conduct", 5, 5);
|
||||||
|
dry = dry.SetSurface(new(1, 1), new() { Electricity = 8 });
|
||||||
|
|
||||||
|
var wetNext = m_Engine.AdvanceStepForDebug(wet);
|
||||||
|
var dryNext = m_Engine.AdvanceStepForDebug(dry);
|
||||||
|
|
||||||
|
Assert.True(wetNext.GetSurface(new(2, 1)).Electricity > dryNext.GetSurface(new(2, 1)).Electricity);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FuelAndElectricityCanIgniteIntoHeat()
|
||||||
|
{
|
||||||
|
var level = LevelState.Create("Ignite", 5, 5);
|
||||||
|
level = level.SetSurface(new(2, 2), new() { Fuel = Balancing.Current.FuelCritical, Electricity = Balancing.Current.ElectricityCritical });
|
||||||
|
|
||||||
|
var next = m_Engine.AdvanceStepForDebug(level);
|
||||||
|
|
||||||
|
Assert.True(next.GetSurface(new(2, 2)).Heat > 0);
|
||||||
|
Assert.True(next.GetSurface(new(2, 2)).Fuel < level.GetSurface(new(2, 2)).Fuel);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void JunctionRatioSplitsFlowAcrossInferredOutgoingBranches()
|
public void JunctionRatioSplitsFlowAcrossInferredOutgoingBranches()
|
||||||
{
|
{
|
||||||
var level = BuildJunctionLevel(2);
|
var level = BuildJunctionLevel(2);
|
||||||
|
|
||||||
var next = m_Engine.AdvanceTurn(level);
|
var next = m_Engine.AdvancePulseForDebug(level);
|
||||||
|
|
||||||
Assert.True(next.GetUnderground(new(2, 2), ECarrierType.Fuel).Amount > 0);
|
Assert.True(next.GetUnderground(new(2, 2), ECarrierType.Fuel).Amount > 0);
|
||||||
Assert.Equal(next.GetUnderground(new(2, 2), ECarrierType.Fuel).Amount, next.GetUnderground(new(3, 3), ECarrierType.Fuel).Amount);
|
Assert.Equal(next.GetUnderground(new(2, 2), ECarrierType.Fuel).Amount, next.GetUnderground(new(3, 3), ECarrierType.Fuel).Amount);
|
||||||
@@ -243,7 +369,7 @@ public sealed class SimulationEngineTests
|
|||||||
{
|
{
|
||||||
var level = BuildJunctionLevel(0);
|
var level = BuildJunctionLevel(0);
|
||||||
|
|
||||||
var next = m_Engine.AdvanceTurn(level);
|
var next = m_Engine.AdvancePulseForDebug(level);
|
||||||
|
|
||||||
Assert.Equal(0, next.GetUnderground(new(2, 2), ECarrierType.Fuel).Amount);
|
Assert.Equal(0, next.GetUnderground(new(2, 2), ECarrierType.Fuel).Amount);
|
||||||
Assert.True(next.GetUnderground(new(3, 3), ECarrierType.Fuel).Amount > 0);
|
Assert.True(next.GetUnderground(new(3, 3), ECarrierType.Fuel).Amount > 0);
|
||||||
@@ -393,11 +519,17 @@ public sealed class SimulationEngineTests
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LevelState DoorLevel(LevelState? seed = null)
|
private static LevelState DoorLevel(LevelState? seed = null, bool powered = true)
|
||||||
{
|
{
|
||||||
var level = seed ?? LevelState.Create("Door", 6, 6);
|
var level = seed ?? LevelState.Create("Door", 6, 6);
|
||||||
level = level.SetTerrain(new(3, 1), ECellTerrain.Wall);
|
level = level.SetTerrain(new(3, 1), ECellTerrain.Wall);
|
||||||
level = level.SetTerrain(new(3, 3), ECellTerrain.Wall);
|
level = level.SetTerrain(new(3, 3), ECellTerrain.Wall);
|
||||||
|
if (powered)
|
||||||
|
{
|
||||||
|
level = AddLine(level, ECarrierType.Electricity, new(2, 2), new(3, 2));
|
||||||
|
level = level.SetProp(new(2, 2), new() { Type = EPropType.Flow, Carrier = ECarrierType.Electricity });
|
||||||
|
}
|
||||||
|
|
||||||
return level.SetProp(new(3, 2), new() { Type = EPropType.Door, DoorState = EDoorState.Closed });
|
return level.SetProp(new(3, 2), new() { Type = EPropType.Door, DoorState = EDoorState.Closed });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,5 +585,19 @@ public sealed class SimulationEngineTests
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static LevelState TerminalLevel(bool powered = true)
|
||||||
|
{
|
||||||
|
var level = BuildReadyLevel();
|
||||||
|
level = level.SetProp(new(1, 1), new() { Type = EPropType.AllSeeingEyeTerminal });
|
||||||
|
level = level.SetUnderground(new(1, 1), ECarrierType.Electricity, new() { State = EUndergroundState.Intact });
|
||||||
|
if (powered)
|
||||||
|
{
|
||||||
|
level = level.SetUnderground(new(1, 2), ECarrierType.Electricity, new() { State = EUndergroundState.Intact });
|
||||||
|
level = level.SetProp(new(1, 2), new() { Type = EPropType.Flow, Carrier = ECarrierType.Electricity });
|
||||||
|
}
|
||||||
|
|
||||||
|
return level with { Robot = new() { Position = new(1, 1) } };
|
||||||
|
}
|
||||||
|
|
||||||
private readonly SimulationEngine m_Engine = new();
|
private readonly SimulationEngine m_Engine = new();
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user