diff --git a/src/ReactorMaintenance.Simulation/Balancing.cs b/src/ReactorMaintenance.Simulation/Balancing.cs index a43c6b8..3ea6dac 100644 --- a/src/ReactorMaintenance.Simulation/Balancing.cs +++ b/src/ReactorMaintenance.Simulation/Balancing.cs @@ -43,11 +43,14 @@ public abstract class Balancing if (rowCarrier == ECarrierType.Fuel && colCarrier == ECarrierType.Electricity) return Ignite(rowBand, colBand); + if (rowCarrier == ECarrierType.Fuel && colCarrier == ECarrierType.Water) + return Dilute(rowBand, colBand); + if (rowCarrier == ECarrierType.Fuel && colCarrier is null) return rowBand == EBand.Critical || colBand == EBand.Critical ? Ignite(rowBand, colBand) : Warm(rowBand, colBand); if (rowCarrier == ECarrierType.Water && colCarrier == ECarrierType.Electricity) - return Short(rowBand, colBand); + return Conduct(rowBand, colBand); if (rowCarrier == ECarrierType.Water && colCarrier is null) 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; return new() { - Verb = ESurfaceInteractionVerb.Short, + Verb = ESurfaceInteractionVerb.Conduct, Quantity = ESurfaceQuantity.Electricity, - Amount = critical ? ShortCriticalHeat : ShortCautionHeat, - SecondaryAmount = critical ? ShortCriticalDischarge : ShortCautionDischarge + Amount = critical ? ConductCriticalHeat : ConductCautionHeat, + SecondaryAmount = critical ? ConductCriticalDischarge : ConductCautionDischarge }; } @@ -157,15 +169,21 @@ public abstract class Balancing public abstract float LeakIntensityScale { get; } public abstract float SprinklerWaterPerStep { 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 WetElectricityFlowMultiplier { get; } public abstract float WarmCautionAmount { get; } public abstract float WarmCriticalAmount { get; } + public abstract float DiluteCautionAmount { get; } + public abstract float DiluteCriticalAmount { get; } public abstract float QuenchCautionAmount { get; } public abstract float QuenchCriticalAmount { get; } - public abstract float ShortCautionHeat { get; } - public abstract float ShortCautionDischarge { get; } - public abstract float ShortCriticalHeat { get; } - public abstract float ShortCriticalDischarge { get; } + public abstract float ConductCautionHeat { get; } + public abstract float ConductCautionDischarge { get; } + public abstract float ConductCriticalHeat { get; } + public abstract float ConductCriticalDischarge { get; } public abstract float IgniteCautionHeat { get; } public abstract float IgniteCautionFuelConsumption { get; } public abstract float IgniteCriticalHeat { get; } diff --git a/src/ReactorMaintenance.Simulation/Difficulties/NormalBalancing.cs b/src/ReactorMaintenance.Simulation/Difficulties/NormalBalancing.cs index 47e7fc2..a50eae4 100644 --- a/src/ReactorMaintenance.Simulation/Difficulties/NormalBalancing.cs +++ b/src/ReactorMaintenance.Simulation/Difficulties/NormalBalancing.cs @@ -56,15 +56,21 @@ public class NormalBalancing : Balancing public override float LeakIntensityScale => 0.1f; public override float SprinklerWaterPerStep => 0.8f; 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 WetElectricityFlowMultiplier => 3.0f; public override float WarmCautionAmount => 0.5f; 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 QuenchCriticalAmount => 1.2f; - public override float ShortCautionHeat => 0.8f; - public override float ShortCautionDischarge => 0.8f; - public override float ShortCriticalHeat => 1.6f; - public override float ShortCriticalDischarge => 1.5f; + public override float ConductCautionHeat => 0.2f; + public override float ConductCautionDischarge => 0.4f; + public override float ConductCriticalHeat => 0.5f; + public override float ConductCriticalDischarge => 0.8f; public override float IgniteCautionHeat => 1.2f; public override float IgniteCautionFuelConsumption => 0.4f; public override float IgniteCriticalHeat => 2.4f; diff --git a/src/ReactorMaintenance.Simulation/LevelStateExtensions.cs b/src/ReactorMaintenance.Simulation/LevelStateExtensions.cs index 4ff5c07..c417a5c 100644 --- a/src/ReactorMaintenance.Simulation/LevelStateExtensions.cs +++ b/src/ReactorMaintenance.Simulation/LevelStateExtensions.cs @@ -45,6 +45,27 @@ public static class LevelStateExtensions 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) { var next = level.Terrain.ToArray(); diff --git a/src/ReactorMaintenance.Simulation/Models/ESurfaceInteractionVerb.cs b/src/ReactorMaintenance.Simulation/Models/ESurfaceInteractionVerb.cs index 8547c9c..40097f6 100644 --- a/src/ReactorMaintenance.Simulation/Models/ESurfaceInteractionVerb.cs +++ b/src/ReactorMaintenance.Simulation/Models/ESurfaceInteractionVerb.cs @@ -4,8 +4,10 @@ public enum ESurfaceInteractionVerb { Hold, Flow, + Dilute, Warm, Quench, - Short, + Evaporate, + Conduct, Ignite } \ No newline at end of file diff --git a/src/ReactorMaintenance.Simulation/Models/PropState.cs b/src/ReactorMaintenance.Simulation/Models/PropState.cs index 0a5305c..e560785 100644 --- a/src/ReactorMaintenance.Simulation/Models/PropState.cs +++ b/src/ReactorMaintenance.Simulation/Models/PropState.cs @@ -24,6 +24,7 @@ public sealed record PropState public bool Depleted { get; init; } public int ReactorId { get; init; } public EDoorState DoorState { get; init; } = EDoorState.Closed; + public bool Active { get; init; } public GridPosition? LinkedPosition { get; init; } public GridPosition? OutletPosition { get; init; } diff --git a/src/ReactorMaintenance.Simulation/SimulationEngine.cs b/src/ReactorMaintenance.Simulation/SimulationEngine.cs index 3f9e2fc..31f407a 100644 --- a/src/ReactorMaintenance.Simulation/SimulationEngine.cs +++ b/src/ReactorMaintenance.Simulation/SimulationEngine.cs @@ -51,6 +51,9 @@ public sealed class SimulationEngine public IReadOnlyList Forecast(LevelState level) { + if (!level.HasActivePoweredTerminalAccess()) + return Array.Empty(); + return ForecastSystem.Forecast(level, simulated => ResolveStep(simulated, false)); } diff --git a/src/ReactorMaintenance.Simulation/Systems/PlayerActionSystem.cs b/src/ReactorMaintenance.Simulation/Systems/PlayerActionSystem.cs index 8fd828d..056af55 100644 --- a/src/ReactorMaintenance.Simulation/Systems/PlayerActionSystem.cs +++ b/src/ReactorMaintenance.Simulation/Systems/PlayerActionSystem.cs @@ -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() }; } public static LevelState InteractProp(LevelState level, Func resolveLengthyAction) @@ -33,7 +34,7 @@ internal static class PlayerActionSystem EPropType.SprinklerControl => ToggleProp(level, position, prop), EPropType.Junction => CycleJunctionMode(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.ReactorControl => ReactorSystem.Activate(level), _ => Refuse(level, "PROP NOT INTERACTIVE") @@ -78,10 +79,21 @@ internal static class PlayerActionSystem 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; 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) { if (prop.Depleted || level.Robot.Count(prop.RemedyType) >= Balancing.Current.InventoryCapacityPerRemedy) diff --git a/src/ReactorMaintenance.Simulation/Systems/SurfaceInteractionSystem.cs b/src/ReactorMaintenance.Simulation/Systems/SurfaceInteractionSystem.cs index af40901..daede9b 100644 --- a/src/ReactorMaintenance.Simulation/Systems/SurfaceInteractionSystem.cs +++ b/src/ReactorMaintenance.Simulation/Systems/SurfaceInteractionSystem.cs @@ -23,6 +23,12 @@ internal static class SurfaceInteractionSystem public static LevelState Resolve(LevelState level) { 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)) 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), null, SimulationBands.Heat(surface.Heat), 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); } + 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) { var surfaceA = level.GetSurface(a); @@ -88,10 +112,13 @@ internal static class SurfaceInteractionSystem case ESurfaceInteractionVerb.Warm: deltas[index].Heat += effect.Amount; break; + case ESurfaceInteractionVerb.Dilute: + deltas[index].Fuel -= effect.Amount; + break; case ESurfaceInteractionVerb.Quench: deltas[index].Heat -= effect.Amount; break; - case ESurfaceInteractionVerb.Short: + case ESurfaceInteractionVerb.Conduct: deltas[index].Heat += effect.Amount; deltas[index].Electricity -= effect.SecondaryAmount; break; @@ -109,6 +136,8 @@ internal static class SurfaceInteractionSystem return; 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 indexB = level.Index(b); diff --git a/tests/ReactorMaintenance.Simulation.Tests/SimulationEngineTests.cs b/tests/ReactorMaintenance.Simulation.Tests/SimulationEngineTests.cs index fefba18..bbf58ab 100644 --- a/tests/ReactorMaintenance.Simulation.Tests/SimulationEngineTests.cs +++ b/tests/ReactorMaintenance.Simulation.Tests/SimulationEngineTests.cs @@ -7,14 +7,14 @@ public sealed class SimulationEngineTests { var level = BuildReadyLevel(); - var next = m_Engine.AdvanceTurn(level); + var next = m_Engine.AdvancePulseForDebug(level); var consumer = next.GetProp(new(3, 3)); Assert.Equal(EConsumerServiceState.Producing, consumer.FuelServiceState); Assert.Equal(EConsumerServiceState.Producing, consumer.WaterServiceState); Assert.Equal(EConsumerServiceState.Producing, consumer.ElectricityServiceState); Assert.Equal(ELevelState.Ready, next.Global.LevelState); - Assert.Contains(next.Forecasts, forecast => forecast.Kind == EForecastKind.ReactorReady); + Assert.Empty(next.Forecasts); } [Fact] @@ -23,7 +23,7 @@ public sealed class SimulationEngineTests var level = BuildReadyLevel(); 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); } @@ -31,7 +31,7 @@ public sealed class SimulationEngineTests [Fact] public void ReactorActivatesOnlyAtReadyControl() { - var level = m_Engine.AdvanceTurn(BuildReadyLevel()) with { + var level = m_Engine.AdvancePulseForDebug(BuildReadyLevel()) with { 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.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)); Assert.Equal(EConsumerServiceState.Disabled, consumer.FuelServiceState); @@ -83,6 +83,42 @@ public sealed class SimulationEngineTests 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] public void EveryAcceptedLengthyActionAdvancesOneFixedPulse() { @@ -138,7 +174,7 @@ public sealed class SimulationEngineTests var level = DoorLevel(); 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); } @@ -153,7 +189,7 @@ public sealed class SimulationEngineTests 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.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 }); - 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); } @@ -221,17 +257,107 @@ public sealed class SimulationEngineTests 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); } + [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] public void JunctionRatioSplitsFlowAcrossInferredOutgoingBranches() { 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.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 next = m_Engine.AdvanceTurn(level); + var next = m_Engine.AdvancePulseForDebug(level); Assert.Equal(0, next.GetUnderground(new(2, 2), ECarrierType.Fuel).Amount); 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); level = level.SetTerrain(new(3, 1), 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 }); } @@ -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(); } \ No newline at end of file