namespace ReactorMaintenance.Simulation; public static class LevelEditor { public sealed record MoveOccupantResult(bool Success, LevelState Level, string Reason) { public static MoveOccupantResult Succeeded(LevelState level) { return new(true, level, string.Empty); } public static MoveOccupantResult Failed(LevelState level, string reason) { return new(false, level, reason); } } public static LevelState MoveOccupant(LevelState level, GridPosition source, GridPosition destination) { return TryMoveOccupant(level, source, destination).Level; } public static MoveOccupantResult TryMoveOccupant(LevelState level, GridPosition source, GridPosition destination) { if (!level.InBounds(source)) return MoveOccupantResult.Failed(level, "Drag start is outside the level."); if (!level.InBounds(destination)) return MoveOccupantResult.Failed(level, "Drop target is outside the level."); if (source == destination) return MoveOccupantResult.Failed(level, "Drop target is the same cell."); var prop = level.GetProp(source); if (prop.Type != EPropType.None) return TryMoveProp(level, source, destination, prop); var leak = level.Leaks.FirstOrDefault(leak => !leak.Repaired && (leak.AccessPosition == source || leak.UndergroundPosition == source)); if (leak is not null) return TryMoveLeak(level, leak, destination); return level.Robot.Position == source ? TryMoveRobot(level, destination) : MoveOccupantResult.Failed(level, "No movable robot, prop, source, or leak starts here."); } private static MoveOccupantResult TryMoveRobot(LevelState level, GridPosition destination) { if (!level.IsFloor(destination)) return MoveOccupantResult.Failed(level, "Robot destination must be a floor cell."); return MoveOccupantResult.Succeeded(level with { Robot = level.Robot with { Position = destination } }); } private static MoveOccupantResult TryMoveProp(LevelState level, GridPosition source, GridPosition destination, PropState prop) { if (!level.IsFloor(destination)) return MoveOccupantResult.Failed(level, "Prop destination must be a floor cell."); if (level.GetProp(destination).Type != EPropType.None) return MoveOccupantResult.Failed(level, "Prop destination is already occupied."); var next = level.SetProp(source, new()).SetProp(destination, prop); if (prop.Type != EPropType.ReactorControl) return MoveOccupantResult.Succeeded(next); return MoveOccupantResult.Succeeded(next with { Reactors = next.Reactors .Select(reactor => reactor.ControlPosition == source ? reactor with { ControlPosition = destination } : reactor) .ToArray() }); } private static MoveOccupantResult TryMoveLeak(LevelState level, LeakState leak, GridPosition destination) { if (leak.Carrier is ECarrierType.Fuel or ECarrierType.Water) { if (!level.IsFloor(destination)) return MoveOccupantResult.Failed(level, "Fuel and water leaks must move to a floor cell."); var next = ClearLeak(level, leak) .SetUnderground(leak.UndergroundPosition, leak.Carrier, new()); return MoveOccupantResult.Succeeded(SetLeak(next, destination, destination, leak.Carrier)); } if (leak.Carrier == ECarrierType.Electricity) { if (!level.IsFloor(destination)) return MoveOccupantResult.Failed(level, "Electric leak destination must be an adjacent floor access cell."); var undergroundPosition = leak.UndergroundPosition; if (undergroundPosition.ManhattanDistance(destination) != 1) return MoveOccupantResult.Failed(level, "Electric leak destination must stay adjacent to its underground wall cell."); return MoveOccupantResult.Succeeded(SetLeak(ClearLeak(level, leak), undergroundPosition, destination, leak.Carrier)); } return MoveOccupantResult.Failed(level, "Unsupported leak carrier."); } private static LevelState ClearLeak(LevelState level, LeakState leak) { return level with { Leaks = level.Leaks.Where(candidate => candidate != leak).ToArray() }; } public static LevelState CycleElectricityLeakAccess(LevelState level, GridPosition undergroundPosition) { if (!level.InBounds(undergroundPosition)) return level; if (!level.GetUnderground(undergroundPosition, ECarrierType.Electricity).IsPresent) return level; var accessPositions = undergroundPosition.Neighbors().Where(level.IsFloor).ToArray(); if (accessPositions.Length == 0) return level; var existingLeak = level.Leaks.FirstOrDefault(leak => leak.Carrier == ECarrierType.Electricity && leak.UndergroundPosition == undergroundPosition); var nextAccessPosition = accessPositions[0]; if (existingLeak is not null) { var index = Array.IndexOf(accessPositions, existingLeak.AccessPosition); nextAccessPosition = accessPositions[(index + 1) % accessPositions.Length]; } return SetLeak(level, undergroundPosition, nextAccessPosition, ECarrierType.Electricity); } public static LevelState Apply(LevelState level, GridPosition position, EditorToolCommand command) { if (!level.InBounds(position)) return level; return command.Tool switch { EEditorTool.Cursor => level, EEditorTool.Floor => level.SetTerrain(position, ECellTerrain.Floor), EEditorTool.Wall => level.SetTerrain(position, ECellTerrain.Wall), EEditorTool.Underground => SetUnderground(level, position, command.Carrier), EEditorTool.Flow => SetCarrierProp(level, position, EPropType.Flow, command.Carrier), EEditorTool.Consumer => SetFloorProp(level, position, new() { Type = EPropType.Consumer, SwitchState = EPropSwitchState.Enabled }), EEditorTool.Junction => SetFloorProp(level, position, new() { Type = EPropType.Junction }), EEditorTool.Door => ToggleOrSetDoor(level, position), EEditorTool.AllSeeingEyeTerminal => SetFloorProp(level, position, new() { Type = EPropType.AllSeeingEyeTerminal }), EEditorTool.RemedySupply => SetFloorProp(level, position, new() { Type = EPropType.RemedySupply, RemedyType = command.RemedyType }), EEditorTool.ReactorControl => SetReactorControl(level, position), EEditorTool.Leak => SetLeak(level, position, command.Carrier), EEditorTool.SurfaceHazard => AddSurfaceHazard(level, position, command.Carrier), EEditorTool.Heat => level.SetSurface(position, level.GetSurface(position) with { Heat = level.GetSurface(position).Heat + 1 }), EEditorTool.Robot => level.IsFloor(position) ? level with { Robot = level.Robot with { Position = position } } : level, _ => level }; } public static LevelState SetLeak(LevelState level, GridPosition undergroundPosition, GridPosition accessPosition, ECarrierType carrier) { if (!level.InBounds(undergroundPosition) || !level.IsFloor(accessPosition)) return level; if (carrier is ECarrierType.Fuel or ECarrierType.Water && undergroundPosition != accessPosition) return level; if (carrier == ECarrierType.Electricity && undergroundPosition.ManhattanDistance(accessPosition) != 1) return level; var next = level.SetUnderground(undergroundPosition, carrier, new() { State = EUndergroundState.Leaking, StructuralIntegrity = Balancing.Current.StructuralIntegrityLeakThreshold }); return next with { Leaks = [ .. next.Leaks.Where(leak => leak.UndergroundPosition != undergroundPosition || leak.Carrier != carrier), new() { Carrier = carrier, UndergroundPosition = undergroundPosition, AccessPosition = accessPosition } ] }; } private static LevelState SetUnderground(LevelState level, GridPosition position, ECarrierType carrier) { return level.SetUnderground(position, carrier, new() { State = EUndergroundState.Intact, StructuralIntegrity = Balancing.Current.MaxStructuralIntegrity }); } private static LevelState SetCarrierProp(LevelState level, GridPosition position, EPropType type, ECarrierType carrier) { return SetFloorProp(level, position, new() { Type = type, Carrier = carrier, SwitchState = EPropSwitchState.Enabled }); } private static LevelState AddSurfaceHazard(LevelState level, GridPosition position, ECarrierType carrier) { var surface = level.GetSurface(position); return carrier switch { ECarrierType.Fuel => level.SetSurface(position, surface with { Fuel = surface.Fuel + 1 }), ECarrierType.Water => level.SetSurface(position, surface with { Water = surface.Water + 1 }), ECarrierType.Electricity => level.SetSurface(position, surface with { Electricity = surface.Electricity + 1 }), _ => level }; } private static LevelState SetFloorProp(LevelState level, GridPosition position, PropState prop) { return level.IsFloor(position) ? level.SetProp(position, prop) : level; } private static LevelState ToggleOrSetDoor(LevelState level, GridPosition position) { if (!level.IsFloor(position)) return level; var prop = level.GetProp(position); if (prop.Type == EPropType.Door) { var nextState = prop.DoorState == EDoorState.Open ? EDoorState.Closed : EDoorState.Open; return level.SetProp(position, prop with { DoorState = nextState }); } return level.SetProp(position, new() { Type = EPropType.Door, DoorState = EDoorState.Closed }); } private static LevelState SetReactorControl(LevelState level, GridPosition position) { if (!level.IsFloor(position)) return level; var id = level.Reactors.Count == 0 ? 1 : level.Reactors.Max(reactor => reactor.ReactorId) + 1; var levelWithProp = level.SetProp(position, new() { Type = EPropType.ReactorControl, ReactorId = id }); return levelWithProp with { Reactors = [ .. level.Reactors, new() { ReactorId = id, ControlPosition = position } ] }; } private static LevelState SetLeak(LevelState level, GridPosition position, ECarrierType carrier) { if (!level.InBounds(position)) return level; return SetLeak(level, position, position, carrier); } }