diff --git a/elevator/.gitignore b/elevator/.gitignore new file mode 100644 index 0000000..c5b6d2e --- /dev/null +++ b/elevator/.gitignore @@ -0,0 +1,405 @@ +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# content below from: https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ \ No newline at end of file diff --git a/elevator/CoreElevator.sln b/elevator/CoreElevator.sln new file mode 100644 index 0000000..84a76cc --- /dev/null +++ b/elevator/CoreElevator.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34031.279 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreElevator", "CoreElevator\CoreElevator.csproj", "{59C7519B-5367-4017-9113-7E82808A8D4D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElevatorTests", "ElevatorTests\ElevatorTests.csproj", "{D325C5EA-35BA-4E11-AD3D-7453EA969E85}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {59C7519B-5367-4017-9113-7E82808A8D4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59C7519B-5367-4017-9113-7E82808A8D4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59C7519B-5367-4017-9113-7E82808A8D4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59C7519B-5367-4017-9113-7E82808A8D4D}.Release|Any CPU.Build.0 = Release|Any CPU + {D325C5EA-35BA-4E11-AD3D-7453EA969E85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D325C5EA-35BA-4E11-AD3D-7453EA969E85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D325C5EA-35BA-4E11-AD3D-7453EA969E85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D325C5EA-35BA-4E11-AD3D-7453EA969E85}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FDA64D25-38A8-499E-AA50-98F7A77398F7} + EndGlobalSection +EndGlobal diff --git a/elevator/CoreElevator/CoreElevator.csproj b/elevator/CoreElevator/CoreElevator.csproj new file mode 100644 index 0000000..ac62417 --- /dev/null +++ b/elevator/CoreElevator/CoreElevator.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/elevator/CoreElevator/ElevatorController.cs b/elevator/CoreElevator/ElevatorController.cs new file mode 100644 index 0000000..f50a93d --- /dev/null +++ b/elevator/CoreElevator/ElevatorController.cs @@ -0,0 +1,634 @@ +using System; +using System.Reflection.Metadata.Ecma335; +using System.Threading; +using Konsole; + +namespace CoreElevator +{ + public class ElevatorController + { + public FloorRequests requests; + public ElevatorState currentState; + public int totalFloors = 50; + private IConsole queueWin; + private ConcurrentWriter status; + public LogEntry logEntry; + + public ElevatorController(ConcurrentWriter? Status, IConsole? QueueWin) + { + currentState = new ElevatorState(); + if(QueueWin != null) + { + queueWin = QueueWin; + } + if(Status != null) + { + status = Status; + } + + requests = new FloorRequests(queueWin); + logEntry = new LogEntry(); + } + + async public Task addRequest(string request) + { + FloorRequest newRequest = new FloorRequest(request, totalFloors); + + //if we're already on the floor requested, no need to add it to the list + if (newRequest.floor != currentState.CurrentFloor && newRequest.floor > 0) + { + + requests.Add(newRequest, currentState, totalFloors); + + if(requests.downRequests.Count + requests.upRequests.Count + requests.insideRequests.Count <= 1) + { + while(requests.downRequests.Count + requests.upRequests.Count + requests.insideRequests.Count > 0) + { + await move(); + } + } + + } + + return true; + } + + async private Task move() + { + if (currentState.Status == StateType.Free) + { + if (requests.upRequests.Count > 0) + { + int requestedFloor = requests.upRequests[0]; + // make sure floor isn't zero or lower, should have already been checked, but double checking + if (requestedFloor > 0) + { + //change to floor + if (setFloor(requests.upRequests[0], StateType.Up) || hasInsideFloorRequest(true)) + { + await processFloor(EventAction.Wait); + } + else + { + await processFloor(EventAction.Pass); + } + + } + } + else if (requests.downRequests.Count > 0) + { + int requestedFloor = requests.downRequests[0]; + // make sure floor isn't zero or lower, should have already been checked, but double checking + if (requestedFloor > 0) + { + //change to floor + if (setFloor(requests.downRequests[0], StateType.Down) || hasInsideFloorRequest(true)) + { + await processFloor(EventAction.Wait); + } + else + { + await processFloor(EventAction.Pass); + } + + } + } + else if(requests.insideRequests.Count > 0) + { + int requestedFloor = requests.insideRequests[0]; + + if (requestedFloor > currentState.CurrentFloor) + { + //change to floor + if (setFloor(requestedFloor, StateType.Up, true)) + { + await processFloor(EventAction.Wait); + } + else + { + await processFloor(EventAction.Pass); + } + + } + else if (requestedFloor < currentState.CurrentFloor) + { + //change to floor + if (setFloor(requestedFloor, StateType.Down, true)) + { + await processFloor(EventAction.Wait); + } + else + { + await processFloor(EventAction.Pass); + } + } + + } + else + { + + } + } + else if (currentState.Status == StateType.Up) + { + if (requests.upRequests.Count > 0) + { + int requestedFloor = requests.upRequests[0]; + // make sure floor isn't zero or lower, should have already been checked, but double checking + if (requestedFloor > 0) + { + //change to floor + if (setFloor(requests.upRequests[0], StateType.Up) || hasInsideFloorRequest(true)) + { + await processFloor(EventAction.Wait); + } + else + { + await processFloor(EventAction.Pass); + } + + } + } + //check inside requests for higher floor as they take priority + else if(hasInsideRequestThisDirection(StateType.Up)) + { + int requestedFloorIndex = getInsideRequestThisDirection(StateType.Up); + int requestedFloor = requests.insideRequests[requestedFloorIndex]; + //change to floor + if (setFloor(requestedFloor, StateType.Up, true)) + { + await processFloor(EventAction.Wait); + } + else + { + await processFloor(EventAction.Pass); + } + } + else if (requests.downRequests.Count > 0) + { + // we emptied the previous direction, time to check the other + currentState.Status = StateType.Down; + } + //check inside requests for lower floor as they take priority + else if (hasInsideRequestThisDirection(StateType.Down)) + { + int requestedFloorIndex = getInsideRequestThisDirection(StateType.Down); + int requestedFloor = requests.insideRequests[requestedFloorIndex]; + //change to floor + if (setFloor(requestedFloor, StateType.Down, true)) + { + await processFloor(EventAction.Wait); + } + else + { + await processFloor(EventAction.Pass); + } + } + else + { + currentState.Status = StateType.Free; + } + } + else if (currentState.Status == StateType.Down) + { + if (requests.downRequests.Count > 0) + { + int requestedFloor = requests.downRequests[0]; + // make sure floor isn't zero or lower, should have already been checked, but double checking + if (requestedFloor > 0) + { + //change to floor + if (setFloor(requests.downRequests[0], StateType.Down) || hasInsideFloorRequest(true)) + { + await processFloor(EventAction.Wait); + } + else + { + await processFloor(EventAction.Pass); + } + + } + } + //check inside requests for lower floor as they take priority + else if (hasInsideRequestThisDirection(StateType.Down)) + { + int requestedFloorIndex = getInsideRequestThisDirection(StateType.Down); + int requestedFloor = requests.insideRequests[requestedFloorIndex]; + //change to floor + if (setFloor(requestedFloor, StateType.Down, true)) + { + await processFloor(EventAction.Wait); + } + else + { + await processFloor(EventAction.Pass); + } + } + else if (requests.upRequests.Count > 0) + { + // we emptied the previous direction, time to check the other + currentState.Status = StateType.Up; + } + //check inside requests for higher floor as they take priority + else if (hasInsideRequestThisDirection(StateType.Up)) + { + int requestedFloorIndex = getInsideRequestThisDirection(StateType.Up); + int requestedFloor = requests.insideRequests[requestedFloorIndex]; + //change to floor + if (setFloor(requestedFloor, StateType.Up, true)) + { + await processFloor(EventAction.Wait); + } + else + { + await processFloor(EventAction.Pass); + } + } + else + { + currentState.Status = StateType.Free; + } + + } + if(queueWin != null) + requests.outputLists(); + return true; + } + enum EventAction + { + Pass, + Wait + } + private async Task processFloor(EventAction eventAction) + { + if(eventAction == EventAction.Wait) + { + if(status != null) + status.WriteLine(ConsoleColor.Green, "Waiting at Floor " + currentState.CurrentFloor); + //Log the event + logEntry.WriteLog(currentState.CurrentFloor.ToString() + " - Waiting", LogEntry.logType.FloorEvent); + //wait one second + await Task.Delay(1000); + } + else if(eventAction == EventAction.Pass) + { + if (status != null) + status.WriteLine(ConsoleColor.Yellow, "Currently Passed Floor " + currentState.CurrentFloor); + //Log the event + logEntry.WriteLog(currentState.CurrentFloor.ToString() + " - Passing", LogEntry.logType.FloorEvent); + //wait 3 seconds + await Task.Delay(3000); + } + } + private bool hasInsideRequestThisDirection(StateType direction) + { + return getInsideRequestThisDirection(direction) >= 0 ? true : false; + } + private int getInsideRequestThisDirection(StateType direction) + { + if (direction == StateType.Up) + { + for (int i = 0; i <= requests.insideRequests.Count - 1; i++) + { + if (requests.insideRequests[i] > currentState.CurrentFloor) + { + return i; + } + + } + return -1; + } + else if (direction == StateType.Down) + { + for (int i = requests.insideRequests.Count - 1; i >= 0; i--) + { + if (requests.insideRequests[i] < currentState.CurrentFloor) + { + return i; + } + + } + return -1; + } + else + { + return -1; + } + + } + private bool hasInsideFloorRequest(bool alsoRemove) + { + if(requests.insideRequests.Contains(currentState.CurrentFloor)) + { + // since the current floor exists as an open request from a current rider we to stop + if(alsoRemove) + { + requests.insideRequests.RemoveAt(requests.insideRequests.IndexOf(currentState.CurrentFloor)); + } + return true; + } + else + { + return false; + } + + } + private bool setFloor(int nextFloor, StateType state, bool isInsideRequest = false) + { + currentState.Status = state; + + if(state == StateType.Up) + currentState.CurrentFloor = nextFloor >= currentState.CurrentFloor? + currentState.CurrentFloor + 1: currentState.CurrentFloor - 1; + else if(state == StateType.Down) + currentState.CurrentFloor = nextFloor <= currentState.CurrentFloor ? + currentState.CurrentFloor - 1 : currentState.CurrentFloor + 1; + + if (currentState.CurrentFloor == nextFloor) + { + //we reached the next targeted floor, remove it from the list + if(state == StateType.Up) + { + if(!isInsideRequest) + { + requests.upRequests.RemoveAt(0); + } + else + { + requests.insideRequests.RemoveAt(requests.insideRequests.IndexOf(currentState.CurrentFloor)); + } + + if (requests.upRequests.Count == 0 && requests.downRequests.Count == 0 && requests.insideRequests.Count == 0) + { + currentState.Status = StateType.Free; + + } + } + else if(state == StateType.Down) + { + if (!isInsideRequest) + { + requests.downRequests.RemoveAt(0); + } + else + { + requests.insideRequests.RemoveAt(requests.insideRequests.IndexOf(currentState.CurrentFloor)); + } + + if (requests.upRequests.Count == 0 && requests.downRequests.Count == 0 && requests.insideRequests.Count == 0) + { + currentState.Status = StateType.Free; + + } + } + return true; + } + else + { + //didn't reach the floor yet + return false; + } + } + + } + + public class ElevatorState + { + private StateType status = StateType.Free; + private int currentFloor = 1; + + public StateType Status + { + get { return status; } + set { status = value; } + } + public int CurrentFloor + { + get { return currentFloor; } + set { currentFloor = value; } + } + + } + public class FloorRequests + { + public List upRequests = new List(); + public List downRequests = new List(); + public List insideRequests = new List(); + private LogEntry logEntry = new LogEntry(); + + private IConsole queueWin; + + public FloorRequests(IConsole? QueueWin) + { + if(QueueWin != null) + queueWin = QueueWin; + } + public int TotalCurrentRequests() + { + return upRequests.Count + downRequests.Count + insideRequests.Count; + } + public void Add(FloorRequest request, ElevatorState elevatorState, int totalFloors) + { + //First check for a request that has direction regardless of floor + if(request.direction == Direction.None) + { + //since there isn't a direction, it's a choice made from INSIDE the elevator + insideRequests.Add(request.floor); + } + else if(request.direction == Direction.Down) + { + downRequests.Add(request.floor); + } + else if(request.direction == Direction.Up) + { + upRequests.Add(request.floor); + } + // all floor requests below should be of type None which means it was selected from INSIDE the elevator + else if (elevatorState.Status == StateType.Free || elevatorState.Status == StateType.None) + { + if (request.floor > elevatorState.CurrentFloor) + { + upRequests.Add(request.floor); + } + else if (request.floor < elevatorState.CurrentFloor) + { + downRequests.Add(request.floor); + } + } + else if(elevatorState.Status == StateType.Up) + { + if (request.floor > elevatorState.CurrentFloor) + { + upRequests.Add(request.floor); + } + else if (request.floor < elevatorState.CurrentFloor) + { + downRequests.Add(request.floor); + } + } + else if (elevatorState.Status == StateType.Down) + { + if (request.floor < elevatorState.CurrentFloor) + { + downRequests.Add(request.floor); + } + else if (request.floor > elevatorState.CurrentFloor) + { + upRequests.Add(request.floor); + } + } + //Log the request + string logFloorDirection = request.direction != Direction.None ? request.direction.ToString() : ""; + logEntry.WriteLog(request.floor.ToString() + logFloorDirection, LogEntry.logType.FloorRequest); + + //sort Lists accordingly + sortList(elevatorState); + if(queueWin != null) + outputLists(); + + } + public void outputLists() + { + if(queueWin != null) + { + queueWin.Clear(); + queueWin.WriteLine(ConsoleColor.Blue, "Inside Rider Requests:"); + insideRequests.ForEach(r => queueWin.WriteLine("\t" + r)); + queueWin.WriteLine(ConsoleColor.Blue, "Pending Up Requests:"); + upRequests.ForEach(r => queueWin.WriteLine("\t" + r)); + queueWin.WriteLine(ConsoleColor.Blue, "Pending Down Requests:"); + downRequests.ForEach(r => queueWin.WriteLine("\t" + r)); + } + + + } + public void sortList(ElevatorState elevatorState) + { + upRequests.Sort(); + int upRequestsLength = upRequests.Count; + //List newUpList = new List(); + for (int i=0; i <= upRequestsLength - 1; i++) + { + if (upRequests[0] <= elevatorState.CurrentFloor) + { + int item = upRequests[0]; + upRequests.RemoveAt(0); + upRequests.Add(item); + + } + else + { + // we reached a number that is in the right order, let's bail out + break; + } + } + + downRequests.Sort(); + downRequests.Reverse(); + int downRequestsLength = downRequests.Count; + //int counter = 0; + for (int i=0; i <= downRequestsLength - 1; i++) + { + if (downRequests[0] >= elevatorState.CurrentFloor) + { + int item = downRequests[0]; + downRequests.RemoveAt(0); + downRequests.Add(item); + + } + else + { + // we reached a number that is in the right order, let's bail out + break; + } + } + // sort normal first + insideRequests.Sort(); + int insideRequestsLength = insideRequests.Count; + + //remove dups + upRequests = upRequests.Distinct().ToList(); + downRequests = downRequests.Distinct().ToList(); + insideRequests = insideRequests.Distinct().ToList(); + + } + } + public class FloorRequest + { + public int floor + { + get; set; + } + public Direction direction + { + get; set; + } + + public FloorRequest(string request, int totalFloors) + { + // parse the request to floor and possible direction + if(request.Length > 0) + { + //check if we have a letter as the last char for our direction + // if not, then the button press is from inside the elevator + char lastLetter = request[request.Length - 1]; + if (Char.IsLetter(lastLetter)) + { + // make sure we have a floor number as a minimum requirement + direction = checkDirection(lastLetter); + + floor = checkFloor(request.Substring(0, request.Length - 1), totalFloors); + } + else + { + // make sure we have a floor number as a minimum requirement + floor = checkFloor(request, totalFloors); + } + + } + } + private bool isValidFloor(int floorNum, int totalFloors) + { + return floorNum > 0 && floorNum <= totalFloors; + } + private int checkFloor( string request, int totalFloors) + { + // make sure we have a floor number as a minimum requirement + int floorNum = -1; + bool isNum = int.TryParse(request, out floorNum); + if ( isNum && isValidFloor(floorNum, totalFloors)) + { + return floorNum; + } + //raise error at some point + return 0; + } + private Direction checkDirection(char lastLetter) + { + switch (lastLetter.ToString().ToUpper()) + { + case "U": + return Direction.Up; + + case "D": + return Direction.Down; + + } + return Direction.None; + } + } + public enum StateType + { + None = 0, + Up = 1, + Down = 2, + Free = 3, + //BusyWaiting = 4 + } + public enum Direction + { + None = 0, + Up = 1, + Down = 2 + } +} \ No newline at end of file diff --git a/elevator/CoreElevator/LogEntryClass.cs b/elevator/CoreElevator/LogEntryClass.cs new file mode 100644 index 0000000..7997fa1 --- /dev/null +++ b/elevator/CoreElevator/LogEntryClass.cs @@ -0,0 +1,45 @@ +using System; + +public class LogEntry +{ + private string logFormat = string.Empty; + private string logPath = string.Empty; + + public LogEntry() + { + //WriteLog(logMessage); + } + + /// + /// Writes a log entry to a log file with hardcoded name. It is comma separated to make ingesting it easier + /// + /// + // + // + public void WriteLog(string logMessage, logType type = logType.FloorRequest) + { + logPath = System.AppContext.BaseDirectory; + logFormat = DateTime.Now.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss") + ", " + type + ", "; + + try + { + using (StreamWriter writer = File.AppendText(logPath + + "\\" + "logs.text")) + { + // Writes a string followed by a line terminator asynchronously to the stream. + //writer.WriteLineAsync(logFormat + logMessage ); + + writer.WriteLine(logFormat + logMessage); + } + } + catch (Exception ex) + { + //Do not do anything + } + } + public enum logType + { + FloorRequest = 0, + FloorEvent = 1 + } +} diff --git a/elevator/CoreElevator/Program.cs b/elevator/CoreElevator/Program.cs new file mode 100644 index 0000000..1fc563a --- /dev/null +++ b/elevator/CoreElevator/Program.cs @@ -0,0 +1,71 @@ +using CoreElevator; +using Konsole; +using System; +using System.Threading; + +// create an 80 by 20 inline window +var window = new Window().Concurrent(); +var w = Window.OpenBox("Elevator 3000"); + +//var top = window.SplitTop("Elevator 3000"); +//var bottom = window.SplitBottom("Commands"); +// split that window into boxes +var queueWin = window.SplitLeft("Pending Floor Requests"); +var status = window.SplitRight("Elevator Status"); +var tsConsole = new ConcurrentWriter(status); +var commands = window.OpenBox("Commands", 0, window.WindowHeight-1, window.WindowWidth, 10); + +// right and left are threadsafe, window is not. + +//create elevator +ElevatorController elevator = new CoreElevator.ElevatorController(tsConsole, queueWin); + +StartAsync(elevator, commands, tsConsole, queueWin); +//set elevator state floor + +//accept new floor request async +//FloorRequest floorRequest = new FloorRequest("6U"); +//FloorRequests floorRequests = new FloorRequests(); +//elevator.addRequest("6U"); + +//floorRequests.Add(floorRequest, elevator.currentState); + + +//start moving elevator to requested floor +//stop at requested floor and wait specified time +//continue listening for additional floor requests(internal or external) + +static async void StartAsync(ElevatorController elevator, IConsole bottom, ConcurrentWriter status, IConsole queueWin) +{ + status.WriteLine("Floor: " + elevator.currentState.CurrentFloor + " : " + elevator.currentState.Status); + + string? userInput; + var cmdWindow = Window.Open(bottom); + cmdWindow.Write("Enter Floor/Direction (i.e. 12, 12U):"); + userInput = Console.ReadLine(); + + while (true) + { + if (userInput?.ToUpper() == "Q") + { + //make sure all previous requests are fullfilled first. + while (elevator.requests.TotalCurrentRequests() > 0) + { + //sleep for one second on each check so we don't check too often.. for performance. + Thread.Sleep(1000); + } + Environment.Exit(0); + } + else + { + if (!String.IsNullOrEmpty(userInput)) + { + elevator.addRequest(userInput); + } + cmdWindow = Window.Open(bottom); + cmdWindow.Write("Enter Floor/Direction (i.e. 12, 12U):"); + userInput = Console.ReadLine(); + } + + } +} \ No newline at end of file diff --git a/elevator/ElevatorTests/CoreElevatorTest.cs b/elevator/ElevatorTests/CoreElevatorTest.cs new file mode 100644 index 0000000..e7d36ed --- /dev/null +++ b/elevator/ElevatorTests/CoreElevatorTest.cs @@ -0,0 +1,57 @@ +using CoreElevator; +using Konsole; + +namespace ElevatorTests +{ + [TestClass] + public class CoreElevatorTest + { + [TestMethod] + public void TestElevatorController() + { + ElevatorController coreElevator = new ElevatorController(null, null); + Assert.IsNotNull(coreElevator); + Assert.AreEqual(StateType.Free, coreElevator.currentState.Status); + Assert.AreEqual(1, coreElevator.currentState.CurrentFloor); + Assert.AreEqual(0, coreElevator.requests.TotalCurrentRequests()); + } + [TestMethod] + public void TestElevatorState() + { + ElevatorState elevatorState = new ElevatorState(); + Assert.IsNotNull(elevatorState); + Assert.AreEqual(elevatorState.Status, StateType.Free); + Assert.AreEqual(1, elevatorState.CurrentFloor); + + } + + + [TestMethod] + public void TestFloorRequests() + { + FloorRequests floorRequests = new FloorRequests(null); + Assert.IsNotNull(floorRequests); + Assert.AreEqual(0, floorRequests.TotalCurrentRequests()); + + } + [TestMethod] + public async Task TestElevatorAddRequest() + { + ElevatorController coreElevator = new ElevatorController(null, null); + Assert.IsNotNull(coreElevator); + //Assert.AreEqual(coreElevator.currentState.Status, StateType.Free); + Assert.AreEqual(1, coreElevator.currentState.CurrentFloor); + Assert.AreEqual(0, coreElevator.requests.TotalCurrentRequests()); + + //Thread.Sleep(1000); + //add a request now + coreElevator.addRequest("10U"); //not awaiting, so we know it's currently processing for tests below. + Thread.Sleep(2000); + Assert.AreEqual(StateType.Up, coreElevator.currentState.Status); + Assert.AreNotEqual(1, coreElevator.currentState.CurrentFloor); + Assert.AreEqual(1, coreElevator.requests.TotalCurrentRequests()); + + + } + } +} \ No newline at end of file diff --git a/elevator/ElevatorTests/ElevatorTests.csproj b/elevator/ElevatorTests/ElevatorTests.csproj new file mode 100644 index 0000000..11eebef --- /dev/null +++ b/elevator/ElevatorTests/ElevatorTests.csproj @@ -0,0 +1,23 @@ + + + + net7.0-windows + enable + enable + + false + true + + + + + + + + + + + + + + diff --git a/elevator/ElevatorTests/GlobalUsings.cs b/elevator/ElevatorTests/GlobalUsings.cs new file mode 100644 index 0000000..ab67c7e --- /dev/null +++ b/elevator/ElevatorTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/elevator/README.md b/elevator/README.md index e161796..9acde1c 100644 --- a/elevator/README.md +++ b/elevator/README.md @@ -1,23 +1,6 @@ -# Build an Elevator Coding Challenge! - -## The Challenge -Create an application that simulates the operation of a simple elevator. - -## Requirements - - The elevator must travel in one direction at a time until it needs to go no further (**e.g.** keep going until the elevator has reached the top/bottom of the building, or no stop is requested on any floor ahead). - - Elevator floor request buttons can be pressed **asynchronously** from inside or outside the elevator while it is running. - - Elevator will stop at the closest floor first, in the direction of motion, then the next closest and so on. Any floors requested while the elevator is moving should be taken into account. - - Elevator will stop at all asynchronously requested floors, only if the request is made while the elevator is at least one floor away (**e.g.** if elevator is **between** 4th and 5th floor, going up, and the 5th floor is requested at that moment, elevator will not stop at the 5th floor while going up; it will stop there while going down). - - When elevator arrives at a requested floor, it waits for 1 second. It takes 3 seconds to travel between consecutive floors. - - A sensor tells the elevator its direction, next/current floor, state (stopped, moving) and if the elevator has reached its max weight limit. - - Use the sensor data plus the asynchronous floor request button data to work the elevator. - - Write meaningful **unit tests** that show the elevator works correctly, even if the application is not run. - - Log the following to a file, to verify elevator works well: - - Timestamp and asynchronous floor request, every time one occurs. - - Timestamp and floor, every time elevator **passes** a floor. - - Timestamp and floor, every time elevator **stops** at a floor. - -**Bonus Enhancement:** - - Enhance the application as follows: If the elevator has reached its weight limit, it should stop only at floors that were selected from inside the elevator (to let passengers out), until it is no longer at the max weight limit. - -**Note:** For simplicity, the asynchronous request buttons can be entered by the application user via the console, by entering **"5U"** (request from 5th floor wanting to go Up) or **"8D"** (request from 8th floor wanting to go Down) or **"2"** (request from inside elevator wanting to stop at 2nd floor). When the user enters **"Q"** on the console, the application must end after visiting all floors entered before **"Q"**. \ No newline at end of file +# CoreElevator +- Run CoreElevator and you'll be presented with a GUI in the console. The commands window at the bottom let's you put in floor requests. +- Enter floors like "12" or "12U" etc. The pending requests will show in the upper left, and the status of the elevator is on the top right. +- Logs are kept in the root of the folder of the EXE. + +![image](https://github.com/travisryan/CoreElevator/assets/3151669/f187f504-66a8-4c12-aafd-38a32fbfaae2)