diff --git a/elevator/Elevator/Elevator.sln b/elevator/Elevator/Elevator.sln new file mode 100644 index 0000000..b70c9a9 --- /dev/null +++ b/elevator/Elevator/Elevator.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33829.357 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ElevatorTests", "ElevatorTests\ElevatorTests.csproj", "{E275736A-8470-4443-81CC-CE4CD316250C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elevator", "Elevator\Elevator.csproj", "{FBC375F6-0EA9-4D43-A0BC-83786A5D3711}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E275736A-8470-4443-81CC-CE4CD316250C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E275736A-8470-4443-81CC-CE4CD316250C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E275736A-8470-4443-81CC-CE4CD316250C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E275736A-8470-4443-81CC-CE4CD316250C}.Release|Any CPU.Build.0 = Release|Any CPU + {FBC375F6-0EA9-4D43-A0BC-83786A5D3711}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBC375F6-0EA9-4D43-A0BC-83786A5D3711}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBC375F6-0EA9-4D43-A0BC-83786A5D3711}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBC375F6-0EA9-4D43-A0BC-83786A5D3711}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E4BCEE56-C28E-432D-933C-2A103AF9947C} + EndGlobalSection +EndGlobal diff --git a/elevator/Elevator/Elevator/CommandProcessor.cs b/elevator/Elevator/Elevator/CommandProcessor.cs new file mode 100644 index 0000000..24ac0c1 --- /dev/null +++ b/elevator/Elevator/Elevator/CommandProcessor.cs @@ -0,0 +1,139 @@ +using NLog; + +namespace Elevator +{ + public interface ICommandProcessor + { + public Task RunInputLoopAsync(ElevatorSystem elevatorSystem, int numFloors); + + public Task ProcessCommandAsync(string command, ElevatorSystem elevatorSystem); + } + + public class CommandProcessor : ICommandProcessor + { + private readonly ILogger Logger; + + public CommandProcessor(ILogger logger) + { + Logger = logger; + } + + public async Task RunInputLoopAsync(ElevatorSystem elevatorSystem, int numFloors) + { + ShowHelp(numFloors); + + while (elevatorSystem.Status == ElevatorSystem.ElevatorSystemStatus.Running) + { + string command = Console.ReadLine(); + + var valid = await ProcessCommandAsync(command, elevatorSystem); + + if (!valid) + { + Logger.Info($"Invalid command: \"{command}\""); + + ShowHelp(numFloors); + } + else + { + Logger.Info($"Valid command: \"{command}\""); + } + } + } + + private void ShowHelp(int numFloors) + { + Console.WriteLine("Invalid command. Valid commands are:"); + Console.WriteLine("'#U' - Up button pressed on floor #. ex '5U'"); + Console.WriteLine("'#D' - Down button pressed on floor #. ex '5D'"); + Console.WriteLine("'#' - # button pressed on elevator. ex '5'"); + Console.WriteLine($"Valid floor numbers are 1 to {numFloors}"); + Console.WriteLine("'W+' - set the overweight state to true."); + Console.WriteLine("'W-' - set the overweight state to false"); + Console.WriteLine("'Q' - Stop recieving input and quit the application after doing all scheduled stops."); + } + + public async Task ProcessCommandAsync(string command, ElevatorSystem elevatorSystem) + { + if (command == null) + { + return false; + } + + command = command.Trim().ToLower(); + if (command == "q") + { + elevatorSystem.Status = ElevatorSystem.ElevatorSystemStatus.ShuttingDown; + return true; + } + + if (command.StartsWith('w')) + { + command = command.Substring(1).Trim(); + + if (command == "+") + { + elevatorSystem.Elevators[0].SensorOverWeight = true; + return true; + } + else if (command == "-") + { + elevatorSystem.Elevators[0].SensorOverWeight = false; + return true; + } + return false; + } + + + int floor; + + //up button pressed: ex "5U", "15u", "25 u" + if (command.EndsWith('u')) + { + command = command.Substring(0, command.Length - 1).Trim(); + + if (int.TryParse(command, out floor)) + { + if ((0 < floor) && (floor <= elevatorSystem.NumFloors)) + { + elevatorSystem.FloorStates[floor].Up = true; + return true; + } + } + + return false; + } + + //Down button pressed: ex "5D", "15D", "25 D" + if (command.EndsWith('d')) + { + command = command.Substring(0, command.Length - 1).Trim(); + + if (int.TryParse(command, out floor)) + { + if ((0 < floor) && (floor <= elevatorSystem.NumFloors)) + { + elevatorSystem.FloorStates[floor].Down = true; + return true; + } + } + + return false; + } + + //Elevator button pressed: ex "5", "15", "25" + if (int.TryParse(command, out floor)) + { + if ((0 < floor) && (floor <= elevatorSystem.NumFloors)) + { + elevatorSystem.Elevators[0].Stops[floor] = true; + return true; + } + + return false; + } + + return false; + } + } +} diff --git a/elevator/Elevator/Elevator/Elevator.cs b/elevator/Elevator/Elevator/Elevator.cs new file mode 100644 index 0000000..5adc340 --- /dev/null +++ b/elevator/Elevator/Elevator/Elevator.cs @@ -0,0 +1,48 @@ +namespace Elevator +{ + public class Elevator + { + public enum ElevatorAction + { + Idle, + Moving, + Stopping, + Open + } + + public enum MovingDirection + { + Up, + Down, + None + } + + public ElevatorAction CurrentAction { get; set; } + + public MovingDirection DesiredMovingDirection { get; set; } + public MovingDirection SensorMovingDirection { get; set; } + + public int SensorCurrentPosition { get; set; } + public int SensorNextPosition { get; set; } + + public bool SensorOverWeight { get; set; } + + public Dictionary Stops { get; set; } = new Dictionary(); + + public Elevator (int numFloors) + { + + for (int i = 0; i < numFloors; i++) + { + Stops.Add(i + 1, false); + } + + SensorOverWeight = false; + SensorCurrentPosition = 1; + SensorNextPosition = 1; + CurrentAction = ElevatorAction.Idle; + SensorMovingDirection = MovingDirection.None; + DesiredMovingDirection = MovingDirection.None; + } + } +} \ No newline at end of file diff --git a/elevator/Elevator/Elevator/Elevator.csproj b/elevator/Elevator/Elevator/Elevator.csproj new file mode 100644 index 0000000..f65e6aa --- /dev/null +++ b/elevator/Elevator/Elevator/Elevator.csproj @@ -0,0 +1,16 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + diff --git a/elevator/Elevator/Elevator/ElevatorControl.cs b/elevator/Elevator/Elevator/ElevatorControl.cs new file mode 100644 index 0000000..a85ba77 --- /dev/null +++ b/elevator/Elevator/Elevator/ElevatorControl.cs @@ -0,0 +1,269 @@ +//using Microsoft.Extensions.Logging; +using NLog; +using static Elevator.Elevator; + +namespace Elevator +{ + public interface IElevatorControl + { + public Task RunElevatorSystemControlLoop(ElevatorSystem elevatorSystem, int elevatorNumber); + } + + public class ElevatorControl : IElevatorControl + { + private readonly ILogger Logger; + + public ElevatorControl(ILogger logger) + { + Logger = logger; + } + + public async Task RunElevatorSystemControlLoop(ElevatorSystem elevatorSystem, int elevatorNumber) + { + Elevator elevator = elevatorSystem.Elevators[elevatorNumber]; + + while (elevatorSystem.Status != ElevatorSystem.ElevatorSystemStatus.ShutDown) + { + int operationTime = NextMove(elevatorSystem, elevatorNumber); + + if (operationTime != -1) + { + Thread.Sleep(operationTime * 1000); + if ((elevator.CurrentAction == ElevatorAction.Moving) || + (elevator.CurrentAction == ElevatorAction.Stopping)) + { + //simulate updates to the sensor values + elevator.SensorMovingDirection = elevator.DesiredMovingDirection; + elevator.SensorCurrentPosition = elevator.SensorCurrentPosition + (elevator.SensorMovingDirection == MovingDirection.Up ? 1 : -1); + if (elevator.DesiredMovingDirection == MovingDirection.Up) + { + elevator.SensorNextPosition++; + } + else + { + elevator.SensorNextPosition--; + } + + if (elevator.CurrentAction == ElevatorAction.Moving) + { + Logger.Info($"Passing floor {elevator.SensorCurrentPosition}"); + } + else + { + Logger.Info($"Stopping at floor {elevator.SensorCurrentPosition}"); + } + } + else if (elevator.CurrentAction == ElevatorAction.Open) + { + Logger.Info($"Opening at floor {elevator.SensorCurrentPosition}"); + } + } + else + { + //brief wait if no commands + Thread.Sleep(10); + } + } + } + + public int NextMove(ElevatorSystem elevatorSystem, int elevatorNumber) + { + var elevator = elevatorSystem.Elevators[elevatorNumber]; + int timeToNextAction = -1; + + switch (elevator.CurrentAction) + { + case ElevatorAction.Idle: + bool buttonAbovePressed = ButtonAbovePressed(elevatorSystem, elevator); + bool buttonBelowPressed = ButtonBelowPressed(elevatorSystem, elevator); + + if (buttonAbovePressed) + { + MoveUp(elevatorSystem, elevator); + } + else if (buttonBelowPressed) + { + MoveDown(elevatorSystem, elevator); + } + else if (elevatorSystem.Status == ElevatorSystem.ElevatorSystemStatus.ShuttingDown) + { + elevatorSystem.Status = ElevatorSystem.ElevatorSystemStatus.ShutDown; + } + break; + case ElevatorAction.Moving: + if (elevator.SensorMovingDirection == MovingDirection.Up) + { + MoveUp(elevatorSystem, elevator); + } + else + { + MoveDown(elevatorSystem, elevator); + } + break; + case ElevatorAction.Stopping: + //always clear the internal button + elevator.Stops[elevator.SensorCurrentPosition] = false; + + if (elevator.SensorMovingDirection == MovingDirection.Up) + { + if (elevatorSystem.FloorStates[elevator.SensorCurrentPosition].Up || ButtonAbovePressed(elevatorSystem, elevator)) + { + //normal case, keep going in the same direction to serve next stop + elevatorSystem.FloorStates[elevator.SensorCurrentPosition].Up = false; + } + else if (elevatorSystem.FloorStates[elevator.SensorCurrentPosition].Down) + { + //stop was requested going the opposite direction + elevatorSystem.FloorStates[elevator.SensorCurrentPosition].Down = false; + elevator.DesiredMovingDirection = MovingDirection.Down; + } + } + else + { + if (elevatorSystem.FloorStates[elevator.SensorCurrentPosition].Down || ButtonBelowPressed(elevatorSystem, elevator)) + { + elevatorSystem.FloorStates[elevator.SensorCurrentPosition].Down = false; + } + else if (elevatorSystem.FloorStates[elevator.SensorCurrentPosition].Up) + { + elevatorSystem.FloorStates[elevator.SensorCurrentPosition].Up = false; + elevator.DesiredMovingDirection = MovingDirection.Up; + } + } + + elevator.CurrentAction = ElevatorAction.Open; + break; + case ElevatorAction.Open: + buttonAbovePressed = ButtonAbovePressed(elevatorSystem, elevator); + buttonBelowPressed = ButtonBelowPressed(elevatorSystem, elevator); + + //first two cases keep moving the way it is moving as long as there's another stop in the same direction. + if ((elevator.DesiredMovingDirection == MovingDirection.Up) && buttonAbovePressed) + { + MoveUp(elevatorSystem, elevator); + } + else if ((elevator.DesiredMovingDirection == MovingDirection.Down) && buttonBelowPressed) + { + MoveDown(elevatorSystem, elevator); + } + else if (buttonAbovePressed) + { + MoveUp(elevatorSystem, elevator); + } + else if (buttonBelowPressed) + { + MoveDown(elevatorSystem, elevator); + } + else + { + elevator.CurrentAction = ElevatorAction.Idle; + elevator.DesiredMovingDirection = MovingDirection.None; + } + + break; + } + + switch (elevator.CurrentAction) + { + case ElevatorAction.Idle: + timeToNextAction = -1; + break; + case ElevatorAction.Moving: + timeToNextAction = 3; + break; + case ElevatorAction.Stopping: + timeToNextAction = 3; + break; + case ElevatorAction.Open: + timeToNextAction = 1; + break; + } + + return timeToNextAction; + } + + /// + /// True if any button requesting a stop above the current location has been pressed, unless the elevator is overweight in which case only internal buttons are counted. + /// + /// + /// + /// + /// + private static bool ButtonAbovePressed(ElevatorSystem elevatorSystem, Elevator elevator) + { + //first half checks if any buttons outside the elevator for locations above the elevator + //were pressed unless the elevator is overweight in which case they're ignored + //second half checks if a button above the current location in the elevator was pressed. + return (!elevator.SensorOverWeight && elevatorSystem.FloorStates.Any(f => f.Key > elevator.SensorCurrentPosition && (f.Value.Up || f.Value.Down))) || + elevator.Stops.Any(s => s.Key > elevator.SensorCurrentPosition && s.Value == true); + } + + private static bool ButtonBelowPressed(ElevatorSystem elevatorSystem, Elevator elevator) + { + return (!elevator.SensorOverWeight && elevatorSystem.FloorStates.Any(f => f.Key < elevator.SensorCurrentPosition && (f.Value.Up || f.Value.Down))) || + elevator.Stops.Any(s => s.Key < elevator.SensorCurrentPosition && s.Value == true); + } + + private static void MoveDown(ElevatorSystem elevatorSystem, Elevator elevator) + { + elevator.DesiredMovingDirection = MovingDirection.Down; + + if (NextFloorDownIsAStop(elevatorSystem, elevator)) + elevator.CurrentAction = ElevatorAction.Stopping; + else elevator.CurrentAction = ElevatorAction.Moving; + } + + private static bool NextFloorDownIsAStop(ElevatorSystem elevatorSystem, Elevator elevator) + { + if (elevator.SensorOverWeight) + { + bool nextFloorIsOnboardStopRequest = elevator.Stops[elevator.SensorCurrentPosition - 1]; + int lastStopRequest = elevator.Stops.Where(s => s.Key < elevator.SensorCurrentPosition && s.Value).Select(s => s.Key).Min(); + bool nextButtonPressedIsLastStop = lastStopRequest == (elevator.SensorCurrentPosition - 1); + return nextFloorIsOnboardStopRequest || nextButtonPressedIsLastStop; + } + else + { + bool nextFloorIsExernalStopRequest = elevatorSystem.FloorStates[elevator.SensorCurrentPosition - 1].Down; + bool nextFloorIsOnboardStopRequest = elevator.Stops[elevator.SensorCurrentPosition - 1]; + int lastButtonBelowPressed = elevatorSystem.FloorStates.Where(f => f.Key < elevator.SensorCurrentPosition && (f.Value.Up || f.Value.Down)).Select(f => f.Key) + .Concat(elevator.Stops.Where(s => s.Key < elevator.SensorCurrentPosition && s.Value).Select(s => s.Key)).Min(); + bool nextButtonPressedIsLastStop = lastButtonBelowPressed == (elevator.SensorCurrentPosition - 1); + return nextFloorIsExernalStopRequest || nextFloorIsOnboardStopRequest || nextButtonPressedIsLastStop; + } + } + + private static void MoveUp(ElevatorSystem elevatorSystem, Elevator elevator) + { + elevator.DesiredMovingDirection = MovingDirection.Up; + + + if (NextFloorUpIsAStop(elevatorSystem, elevator)) + { + elevator.CurrentAction = ElevatorAction.Stopping; + } + + else elevator.CurrentAction = ElevatorAction.Moving; + } + + private static bool NextFloorUpIsAStop(ElevatorSystem elevatorSystem, Elevator elevator) + { + if (elevator.SensorOverWeight) + { + bool nextFloorIsOnboardStopRequest = elevator.Stops[elevator.SensorCurrentPosition + 1]; + int lastButtonAbovePressed = elevator.Stops.Where(s => s.Key > elevator.SensorCurrentPosition && s.Value).Select(s => s.Key).Max(); + bool nextButtonPressedIsLastStop = lastButtonAbovePressed == (elevator.SensorCurrentPosition + 1); + return nextFloorIsOnboardStopRequest || nextButtonPressedIsLastStop; + } + else + { + bool nextFloorIsExernalStopRequest = elevatorSystem.FloorStates[elevator.SensorCurrentPosition + 1].Up; + bool nextFloorIsOnboardStopRequest = elevator.Stops[elevator.SensorCurrentPosition + 1]; + int lastButtonAbovePressed = elevatorSystem.FloorStates.Where(f => f.Key > elevator.SensorCurrentPosition && (f.Value.Up || f.Value.Down)).Select(f => f.Key) + .Concat(elevator.Stops.Where(s => s.Key > elevator.SensorCurrentPosition && s.Value).Select(s => s.Key)).Max(); + bool nextButtonPressedIsLastStop = lastButtonAbovePressed == (elevator.SensorCurrentPosition + 1); + return nextFloorIsExernalStopRequest || nextFloorIsOnboardStopRequest || nextButtonPressedIsLastStop; + } + } + } +} diff --git a/elevator/Elevator/Elevator/ElevatorSystem.cs b/elevator/Elevator/Elevator/ElevatorSystem.cs new file mode 100644 index 0000000..e457304 --- /dev/null +++ b/elevator/Elevator/Elevator/ElevatorSystem.cs @@ -0,0 +1,55 @@ +namespace Elevator +{ + public class FloorState + { + public bool Up { get; set; } = false; + public bool Down { get; set; } = false; + } + + public class ElevatorSystem + { + public enum ElevatorSystemStatus + { + Running, + ShuttingDown, + ShutDown, + } + + public ICommandProcessor CommandProcessor { get; private set; } + + public IElevatorControl ElevatorControl { get; private set; } + + public ElevatorSystemStatus Status { get; set; } = ElevatorSystemStatus.Running; + + public int NumFloors { get; set; } + + public Dictionary FloorStates { get; set; } = new Dictionary(); + + //Storing elevators as a list even though everything only uses one as defensive coding + //Changing a datamodel from 1:1 to many:1 has been painful enough in the past that when + //it's an obvious future requirement I prefer to build it in from the start. + public List Elevators { get; set; } = new List(); + + public ElevatorSystem(int numFloors, ElevatorSystemStatus status, ICommandProcessor commandProcessor, IElevatorControl elevatorControl) + { + CommandProcessor = commandProcessor; + ElevatorControl = elevatorControl; + + NumFloors = numFloors; + Status = status; + Elevators.Add(new Elevator(numFloors)); + for (int i = 0; i < numFloors; i++) + { + FloorStates.Add(i + 1, new FloorState()); + } + } + + public async Task RunElevatorSystem() + { + //block on the elevator operation not the input loop. Both need to run concurrently, but we want the input processing + //to stop immediately, but the app to continue until all stops are made. + Task.Run(() => CommandProcessor.RunInputLoopAsync(this, NumFloors)); + await ElevatorControl.RunElevatorSystemControlLoop(this, 0); + } + } +} \ No newline at end of file diff --git a/elevator/Elevator/Elevator/Program.cs b/elevator/Elevator/Elevator/Program.cs new file mode 100644 index 0000000..374834f --- /dev/null +++ b/elevator/Elevator/Elevator/Program.cs @@ -0,0 +1,33 @@ +using NLog; + +namespace Elevator +{ + internal class Program + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private static readonly int numFloors = 10; + + static void Main(string[] args) + { + LogManager.Setup().LoadConfiguration(builder => { + //builder.ForLogger().FilterMinLevel(LogLevel.Info).WriteToConsole(); + builder.ForLogger().FilterMinLevel(LogLevel.Info).WriteToFile(fileName: "elevator.log"); + }); + Logger.Info("\r\n"); + Logger.Info("Starting up."); + + Run(); + Logger.Info("Shutting down."); + } + + static async void Run() + { + ElevatorSystem elevatorSystem = new ElevatorSystem(numFloors, ElevatorSystem.ElevatorSystemStatus.Running, + new CommandProcessor(LogManager.GetLogger(typeof(CommandProcessor).FullName)), + new ElevatorControl(LogManager.GetLogger(typeof(ElevatorControl).FullName))); + + await elevatorSystem.RunElevatorSystem(); + } + } +} \ No newline at end of file diff --git a/elevator/Elevator/Elevator/Readme.txt b/elevator/Elevator/Elevator/Readme.txt new file mode 100644 index 0000000..11274b3 --- /dev/null +++ b/elevator/Elevator/Elevator/Readme.txt @@ -0,0 +1,9 @@ +Daniel Neely + +dan.neely@outlook.com + +The number of floors is specified by the numFloors value in Program.cs, it's currently set to 10. + +Logging is to elevator.log in the same location as the application. +Logging can also be output to the console by uncommenting the setup to do so on line 14 in Program.cs +//builder.ForLogger().FilterMinLevel(LogLevel.Info).WriteToConsole(); diff --git a/elevator/Elevator/ElevatorTests/ElevatorTests.csproj b/elevator/Elevator/ElevatorTests/ElevatorTests.csproj new file mode 100644 index 0000000..28d2709 --- /dev/null +++ b/elevator/Elevator/ElevatorTests/ElevatorTests.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/elevator/Elevator/ElevatorTests/Tests.cs b/elevator/Elevator/ElevatorTests/Tests.cs new file mode 100644 index 0000000..69d95f9 --- /dev/null +++ b/elevator/Elevator/ElevatorTests/Tests.cs @@ -0,0 +1,273 @@ +using Elevator; +using NLog; +using static Elevator.Elevator; +using static Elevator.ElevatorSystem; + +namespace ElevatorTests +{ + public class Tests + { + private ElevatorSystem elevatorSystem; + private CommandProcessor commandProcessor; + private ElevatorControl elevatorControl; + + public Tests() + { + commandProcessor = new CommandProcessor(LogManager.GetLogger(typeof(CommandProcessor).FullName)); + elevatorControl = new ElevatorControl(LogManager.GetLogger(typeof(ElevatorControl).FullName)); + elevatorSystem = new ElevatorSystem(10, ElevatorSystemStatus.Running, commandProcessor, elevatorControl); + } + + [Theory] + [InlineData(new object[] { null, false })] + [InlineData(new object[] { "", false })] + [InlineData(new object[] { " ", false})] + [InlineData(new object[] { "adsf", false })] + [InlineData(new object[] { "11", false })] + [InlineData(new object[] { "0", false })] + [InlineData(new object[] { "-1", false })] + [InlineData(new object[] { "Q", true })] + [InlineData(new object[] { "5", true })] + [InlineData(new object[] { " 5", true })] + [InlineData(new object[] { "5 ", true })] + [InlineData(new object[] { " 5 ", true })] + [InlineData(new object[] { "5U", true })] + [InlineData(new object[] { "5 u", true })] + [InlineData(new object[] { "5u", true })] + [InlineData(new object[] { " 5 u", true })] + [InlineData(new object[] { "5u ", true })] + [InlineData(new object[] { "5D", true })] + [InlineData(new object[] { "5 d", true })] + [InlineData(new object[] { "5d", true })] + [InlineData(new object[] { " 5 d", true })] + [InlineData(new object[] { "5d ", true })] + [InlineData(new object[] { "W+", true })] + [InlineData(new object[] { "w-", true })] + [InlineData(new object[] { "ww-", false })] + [InlineData(new object[] { "W++", false })] + public void TestCommandValidation(string command, bool valid) + { + var success = commandProcessor.ProcessCommandAsync(command, elevatorSystem); + Assert.Equal(valid, success.Result); + } + + [Theory] + [InlineData(new object[] { new string[] { "5U" }, new int[] { 5 }, new int[] { }, new int[] { }, ElevatorSystemStatus.Running })] + [InlineData(new object[] { new string[] { "Q" }, new int[] { }, new int[] { }, new int[] { }, ElevatorSystemStatus.ShuttingDown })] + [InlineData(new object[] { new string[] { "5U", "Q" }, new int[] { 5 }, new int[] { }, new int[] { }, ElevatorSystemStatus.ShuttingDown })] + [InlineData(new object[] { new string[] { "5U", "5U" }, new int[] { 5 }, new int[] { }, new int[] { }, ElevatorSystemStatus.Running })] + [InlineData(new object[] { new string[] { "5U", "6U" }, new int[] { 5, 6 }, new int[] { }, new int[] { }, ElevatorSystemStatus.Running })] + [InlineData(new object[] { new string[] { "5U", "5D" }, new int[] { 5 }, new int[] { 5 }, new int[] { }, ElevatorSystemStatus.Running })] + [InlineData(new object[] { new string[] { "5U", "5" }, new int[] { 5 }, new int[] { }, new int[] { 5 }, ElevatorSystemStatus.Running })] + [InlineData(new object[] { new string[] { "5U", "5", "8", "5U", "2D" }, new int[] { 5 }, new int[] { 2 }, new int[] { 5, 8 }, ElevatorSystemStatus.Running })] + [InlineData(new object[] { new string[] { "5U", "ASDF", "ASDFU", "ASDFD", "5", "-1U", "11U", "-1D", "11D", "-5", "8", "5U", "2D" }, new int[] { 5 }, new int[] { 2 }, new int[] { 5, 8 }, ElevatorSystemStatus.Running })] + public async void TestStateAfterCommands(string[] commands, int[] upStops, int[] downStops, int[] elevatorStops, ElevatorSystemStatus status) + { + await LoadCommands(commandProcessor, commands); + + for (int floor = 1; floor <= elevatorSystem.NumFloors; floor++) + { + Assert.Equal(upStops.Contains(floor), elevatorSystem.FloorStates[floor].Up); + Assert.Equal(downStops.Contains(floor), elevatorSystem.FloorStates[floor].Down); + + foreach(var elevatorState in elevatorSystem.Elevators) + { + Assert.Equal(elevatorStops.Contains(floor), elevatorState.Stops[floor]); + } + } + + Assert.Equal(status, elevatorSystem.Status); + } + + [Theory] + [InlineData(new object[] { new string[] { "5U" }, 1, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2U" }, 1, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "5D" }, 1, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2D" }, 1, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "5D" }, 6, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2D" }, 6, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "5U" }, 6, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2U" }, 6, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2D", "5D" }, 1, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2U", "5D" }, 1, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2D", "5D" }, 6, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2D", "5U" }, 6, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "5" }, 1, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2" }, 1, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "5" }, 6, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2" }, 6, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2D", "5" }, 1, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2", "5" }, 1, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2", "5" }, 6, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2", "5U" }, 6, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2U", "5", "W+" }, 1, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2", "5D", "W+" }, 6, 3, MovingDirection.Down, ElevatorAction.Moving })] + public async void TestNextMoveFromIdle(string[] commands, int elevatorPosition, int expectedOperationTime, + MovingDirection expectedMovingDirection, ElevatorAction expectedElevatorAction) + { + await LoadCommands(commandProcessor, commands); + elevatorSystem.Elevators[0].SensorCurrentPosition = elevatorPosition; + elevatorSystem.Elevators[0].CurrentAction = ElevatorAction.Idle; + + int time = elevatorControl.NextMove(elevatorSystem, 0); + + Assert.Equal(expectedOperationTime, time); + Assert.Equal(expectedMovingDirection, elevatorSystem.Elevators[0].DesiredMovingDirection); + Assert.Equal(expectedElevatorAction, elevatorSystem.Elevators[0].CurrentAction); + } + + + [Theory] + [InlineData(new object[] { new string[] { "5U" }, 1, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2U" }, 1, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "5D" }, 1, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2D" }, 1, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "5D" }, 6, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2D" }, 6, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "5U" }, 6, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2U" }, 6, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2D", "5D" }, 1, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2U", "5D" }, 1, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2D", "5D" }, 6, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2D", "5U" }, 6, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "1D", "5U" }, 3, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "1D", "5U" }, 3, 3, MovingDirection.Down, ElevatorAction.Moving })] + public async void TestNextMoveWhileMoving(string[] commands, int elevatorPosition, int expectedOperationTime, + MovingDirection movingDirection, ElevatorAction expectedElevatorAction) + { + await LoadCommands(commandProcessor, commands); + elevatorSystem.Elevators[0].SensorCurrentPosition = elevatorPosition; + elevatorSystem.Elevators[0].SensorNextPosition = elevatorPosition + (movingDirection == MovingDirection.Up ? 1 : -1); + elevatorSystem.Elevators[0].CurrentAction = ElevatorAction.Moving; + elevatorSystem.Elevators[0].DesiredMovingDirection = movingDirection; + elevatorSystem.Elevators[0].SensorMovingDirection = movingDirection; + + int time = elevatorControl.NextMove(elevatorSystem, 0); + + Assert.Equal(expectedOperationTime, time); + Assert.Equal(movingDirection, elevatorSystem.Elevators[0].DesiredMovingDirection); + Assert.Equal(expectedElevatorAction, elevatorSystem.Elevators[0].CurrentAction); + } + + [Theory] + [InlineData(new object[] { new string[] { "4U" }, 4, MovingDirection.Up, 1, MovingDirection.Up, ElevatorAction.Open })] + [InlineData(new object[] { new string[] { "4D" }, 4, MovingDirection.Up, 1, MovingDirection.Down, ElevatorAction.Open })] + [InlineData(new object[] { new string[] { "4U" }, 4, MovingDirection.Down, 1, MovingDirection.Up, ElevatorAction.Open })] + [InlineData(new object[] { new string[] { "4D" }, 4, MovingDirection.Down, 1, MovingDirection.Down, ElevatorAction.Open })] + //NOTE: These two scenarios are an edge case where my interpretation of the spec isn't how I'd expect a real elevator to behave. + //The elevator would only be coming to a stop here if when it passed the previous floor the change direction request at floor 4 was the only button pressed, + //with the second stop added while in transit. + //In this case, because the elevator decided to stop because of the request for a stop the other direction, I would expect it to have committed to reversal + //when it decided to make the stop, instead of making a stop and then going in the opposite direction of the people whose button press called for the stop. + [InlineData(new object[] { new string[] { "4D", "5U" }, 4, MovingDirection.Up, 1, MovingDirection.Up, ElevatorAction.Open })] + [InlineData(new object[] { new string[] { "4U", "3D" }, 4, MovingDirection.Down, 1, MovingDirection.Down, ElevatorAction.Open })] + public async void TestNextMoveWhileStopping(string[] commands, int elevatorPosition, MovingDirection previousMovingDirection, int expectedOperationTime, + MovingDirection expectedMovingDirection, ElevatorAction expectedElevatorAction) + { + await LoadCommands(commandProcessor, commands); + elevatorSystem.Elevators[0].SensorCurrentPosition = elevatorPosition; + elevatorSystem.Elevators[0].SensorNextPosition = elevatorPosition + (previousMovingDirection == MovingDirection.Up ? 1 : -1); + elevatorSystem.Elevators[0].CurrentAction = ElevatorAction.Stopping; + elevatorSystem.Elevators[0].DesiredMovingDirection = previousMovingDirection; + elevatorSystem.Elevators[0].SensorMovingDirection = previousMovingDirection; + + int time = elevatorControl.NextMove(elevatorSystem, 0); + + Assert.Equal(expectedOperationTime, time); + Assert.Equal(expectedMovingDirection, elevatorSystem.Elevators[0].DesiredMovingDirection); + Assert.Equal(expectedElevatorAction, elevatorSystem.Elevators[0].CurrentAction); + } + + [Theory] + [InlineData(new object[] { new string[] { "5U" }, 4, MovingDirection.Up, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "5" }, 4, MovingDirection.Up, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "6U" }, 4, MovingDirection.Up, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "6" }, 4, MovingDirection.Up, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "6", "3" }, 4, MovingDirection.Up, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "6U", "3" }, 4, MovingDirection.Up, 3, MovingDirection.Up, ElevatorAction.Moving })] + + [InlineData(new object[] { new string[] { "3U" }, 4, MovingDirection.Down, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "3" }, 4, MovingDirection.Down, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2U" }, 4, MovingDirection.Down, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2" }, 4, MovingDirection.Down, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2", "6" }, 4, MovingDirection.Down, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2U", "6" }, 4, MovingDirection.Down, 3, MovingDirection.Down, ElevatorAction.Moving })] + + [InlineData(new object[] { new string[] { "3D" }, 4, MovingDirection.Up, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "3" }, 4, MovingDirection.Up, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2U" }, 4, MovingDirection.Up, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2" }, 4, MovingDirection.Up, 3, MovingDirection.Down, ElevatorAction.Moving })] + + [InlineData(new object[] { new string[] { "5D" }, 4, MovingDirection.Down, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "5" }, 4, MovingDirection.Down, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "6U" }, 4, MovingDirection.Down, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "6" }, 4, MovingDirection.Down, 3, MovingDirection.Up, ElevatorAction.Moving })] + + [InlineData(new object[] { new string[] { }, 4, MovingDirection.Up, -1, MovingDirection.None, ElevatorAction.Idle })] + [InlineData(new object[] { new string[] { }, 4, MovingDirection.Down, -1, MovingDirection.None, ElevatorAction.Idle })] + public async void TestNextMoveAfterOpen(string[] commands, int elevatorPosition, MovingDirection previousMovingDirection, int expectedOperationTime, + MovingDirection expectedMovingDirection, ElevatorAction expectedElevatorAction) + { + await LoadCommands(commandProcessor, commands); + elevatorSystem.Elevators[0].SensorCurrentPosition = elevatorPosition; + elevatorSystem.Elevators[0].CurrentAction = ElevatorAction.Open; + elevatorSystem.Elevators[0].DesiredMovingDirection = previousMovingDirection; + + int time = elevatorControl.NextMove(elevatorSystem, 0); + + Assert.Equal(expectedOperationTime, time); + Assert.Equal(expectedMovingDirection, elevatorSystem.Elevators[0].DesiredMovingDirection); + Assert.Equal(expectedElevatorAction, elevatorSystem.Elevators[0].CurrentAction); + } + + [Fact] + public void TestShutdownFromIdle() + { + elevatorSystem.Status = ElevatorSystemStatus.ShuttingDown; + + int time = elevatorControl.NextMove(elevatorSystem, 0); + + Assert.Equal(ElevatorSystemStatus.ShutDown, elevatorSystem.Status); + } + + [Theory] + [InlineData(new object[] { new string[] { "5U" }, 1, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2U" }, 1, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "5D" }, 1, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2D" }, 1, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "5D" }, 6, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2D" }, 6, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "5U" }, 6, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2U" }, 6, 3, MovingDirection.Down, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "5" }, 1, 3, MovingDirection.Up, ElevatorAction.Moving })] + [InlineData(new object[] { new string[] { "2" }, 1, 3, MovingDirection.Up, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "5" }, 6, 3, MovingDirection.Down, ElevatorAction.Stopping })] + [InlineData(new object[] { new string[] { "2" }, 6, 3, MovingDirection.Down, ElevatorAction.Moving })] + public async void TestDontShutDownWhileNotFinished(string[] commands, int elevatorPosition, int expectedOperationTime, + MovingDirection movingDirection, ElevatorAction expectedElevatorAction) + { + await LoadCommands(commandProcessor, commands); + elevatorSystem.Status = ElevatorSystemStatus.ShuttingDown; + elevatorSystem.Elevators[0].SensorCurrentPosition = elevatorPosition; + elevatorSystem.Elevators[0].SensorNextPosition = elevatorPosition + (movingDirection == MovingDirection.Up ? 1 : -1); + elevatorSystem.Elevators[0].CurrentAction = ElevatorAction.Moving; + elevatorSystem.Elevators[0].DesiredMovingDirection = movingDirection; + elevatorSystem.Elevators[0].SensorMovingDirection = movingDirection; + + int time = elevatorControl.NextMove(elevatorSystem, 0); + + Assert.Equal(expectedOperationTime, time); + Assert.Equal(movingDirection, elevatorSystem.Elevators[0].DesiredMovingDirection); + Assert.Equal(expectedElevatorAction, elevatorSystem.Elevators[0].CurrentAction); + Assert.Equal(ElevatorSystemStatus.ShuttingDown, elevatorSystem.Status); + } + + private async Task LoadCommands(CommandProcessor commandProcessor, IEnumerable commands) + { + foreach (string command in commands) + { + await commandProcessor.ProcessCommandAsync(command, elevatorSystem); + } + } + } +} \ No newline at end of file diff --git a/elevator/Elevator/ElevatorTests/Usings.cs b/elevator/Elevator/ElevatorTests/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/elevator/Elevator/ElevatorTests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file