diff --git a/api/src/main/scala/system/schedulers/PriorityScheduler.scala b/api/src/main/scala/system/schedulers/PriorityScheduler.scala new file mode 100644 index 0000000..331b4ae --- /dev/null +++ b/api/src/main/scala/system/schedulers/PriorityScheduler.scala @@ -0,0 +1,40 @@ +package system.schedulers + +import server.Schemas.PickupRequest +import system.Direction +import system.ElevatorCab + +object PriorityScheduler { + + def findBestCab(elevatorCabs: List[ElevatorCab], request: PickupRequest, floorQuantity: Int) + : ElevatorCab = { + val scoredCabs = elevatorCabs.map { cab => + val distanceScore = Math.abs(cab.currentFloor - request.fromFloor) + val directionScore = + if ( + cab.direction == Direction.Idle || + cab.direction == Direction.MovingUp && request.direction > 0 && cab.currentFloor <= request.fromFloor || + cab.direction == Direction.MovingDown && request.direction < 0 && cab.currentFloor >= request.fromFloor + ) + 0 + else + Int.MaxValue + val requestsSizeScore = cab.dropRequests.size * .5 + (cab, distanceScore + directionScore + requestsSizeScore) + } + + val bestCab = scoredCabs.minBy(_._2)._1 + bestCab + } + +} + +// val requestThatCanChangeDirection = cab +// .dropRequests +// .minBy(r => +// if (request.direction > 0) +// r.targetFloor +// else +// -r.targetFloor +// ) +// Math.abs(request.fromFloor - requestThatCanChangeDirection.targetFloor) diff --git a/api/src/test/scala/AppSuite.scala b/api/src/test/scala/AppSuite.scala deleted file mode 100644 index f5701b0..0000000 --- a/api/src/test/scala/AppSuite.scala +++ /dev/null @@ -1,7 +0,0 @@ -import org.scalatest.funsuite.AnyFunSuite - -class AppSuite extends AnyFunSuite { - test("Adding 2 and 2 should produce 4") { - assert(2 + 2 == 4) - } -} diff --git a/api/src/test/scala/system/BuildingSuite.scala b/api/src/test/scala/system/BuildingSuite.scala new file mode 100644 index 0000000..ce6128b --- /dev/null +++ b/api/src/test/scala/system/BuildingSuite.scala @@ -0,0 +1,83 @@ +import org.scalatest.funsuite.AnyFunSuite +import server.Schemas.DropRequest +import server.Schemas.PickupRequest +import system.Building +import system.ElevatorCab + +import scala.collection.immutable.HashMap +import scala.collection.immutable.SortedSet + +class BuildingTest extends AnyFunSuite { + implicit val dropRequestOrdering: Ordering[DropRequest] = Ordering.by(_.targetFloor) + + test("Add person waiting on a specific floor") { + val initialFloors = HashMap(1 -> 0, 2 -> 0, 3 -> 0) + val building = Building(initialFloors, List(), List()) + + val updatedBuilding = building.addPersonWaitingOnFloor(2) + + assert(updatedBuilding.floors(1) == 0) + assert(updatedBuilding.floors(2) == 1) + assert(updatedBuilding.floors(3) == 0) + } + + test("Add multiple pickup requests") { + val initialFloors = HashMap(1 -> 0, 2 -> 0, 3 -> 0, 4 -> 0) + val pickupRequests = List( + PickupRequest(2, 1), + PickupRequest(3, -1), + PickupRequest(2, 1), + ) + val building = Building(initialFloors, List(), List()) + + val updatedBuilding = building.addPickupRequests(pickupRequests) + + assert(updatedBuilding.floors(1) == 0) + assert(updatedBuilding.floors(2) == 2) + assert(updatedBuilding.floors(3) == 1) + assert(updatedBuilding.floors(4) == 0) + assert(updatedBuilding.outsideRequests == pickupRequests) + } + + test("Add person waiting on an already populated floor") { + val initialFloors = HashMap(1 -> 1, 2 -> 3, 3 -> 0) + val building = Building(initialFloors, List(), List()) + + val updatedBuilding = building.addPersonWaitingOnFloor(2) + + assert(updatedBuilding.floors(1) == 1) + assert(updatedBuilding.floors(2) == 4) + assert(updatedBuilding.floors(3) == 0) + } + + test("Add pickup requests with existing requests") { + val initialFloors = HashMap(1 -> 1, 2 -> 2, 3 -> 0) + val existingRequests = List(PickupRequest(1, 1)) + val newRequests = List( + PickupRequest(2, 1), + PickupRequest(3, -1), + ) + val building = Building(initialFloors, List(), existingRequests) + + val updatedBuilding = building.addPickupRequests(newRequests) + + assert(updatedBuilding.floors(1) == 1) + assert(updatedBuilding.floors(2) == 3) + assert(updatedBuilding.floors(3) == 1) + assert(updatedBuilding.outsideRequests == existingRequests ++ newRequests) + } + + test("Add pickup request and check elevators unchanged") { + val initialFloors = HashMap(1 -> 0, 2 -> 0, 3 -> 0) + val initialCabs = List(ElevatorCab(1, 1, SortedSet(DropRequest(2)))) + val pickupRequests = List(PickupRequest(2, 1)) + val building = Building(initialFloors, initialCabs, List()) + + val updatedBuilding = building.addPickupRequests(pickupRequests) + + assert(updatedBuilding.floors(1) == 0) + assert(updatedBuilding.floors(2) == 1) + assert(updatedBuilding.floors(3) == 0) + assert(updatedBuilding.elevatorCabs == initialCabs) + } +} diff --git a/api/src/test/scala/system/ElevatorCabSuite.scala b/api/src/test/scala/system/ElevatorCabSuite.scala new file mode 100644 index 0000000..d33b3d4 --- /dev/null +++ b/api/src/test/scala/system/ElevatorCabSuite.scala @@ -0,0 +1,109 @@ +package system + +import org.scalatest.funsuite.AnyFunSuite +import server.Schemas.DropRequest +import server.Schemas.PickupRequest +import system.Building +import system.ElevatorCab + +import scala.collection.immutable.HashMap +import scala.collection.immutable.SortedSet + +class ElevatorCabSuite extends AnyFunSuite { + implicit val dropRequestOrdering: Ordering[DropRequest] = Ordering.by(_.targetFloor) + + test("Get target floor when moving up") { + val dropRequests = SortedSet(DropRequest(5), DropRequest(10))(dropRequestOrdering) + val elevator = ElevatorCab(1, 3, dropRequests, Direction.MovingUp) + assert(elevator.targetFloor.contains(5)) + } + + test("Get target floor when moving down") { + val dropRequests = + SortedSet(DropRequest(1), DropRequest(2), DropRequest(5))(dropRequestOrdering) + val elevator = ElevatorCab(1, 4, dropRequests, Direction.MovingDown) + assert(elevator.targetFloor.contains(2)) + } + + test("Get target floor when idle") { + val dropRequests = SortedSet(DropRequest(1), DropRequest(2))(dropRequestOrdering) + val elevator = ElevatorCab(1, 4, dropRequests, Direction.Idle) + assert(elevator.targetFloor.isEmpty) + } + + test("Step when moving up") { + val dropRequests = SortedSet(DropRequest(5))(dropRequestOrdering) + val elevator = ElevatorCab(1, 3, dropRequests, Direction.MovingUp) + val updatedElevator = elevator.step + assert(updatedElevator.currentFloor == 4) + } + + test("Step when moving down") { + val dropRequests = SortedSet(DropRequest(1))(dropRequestOrdering) + val elevator = ElevatorCab(1, 3, dropRequests, Direction.MovingDown) + val updatedElevator = elevator.step + assert(updatedElevator.currentFloor == 2) + } + + test("Step when idle and on target floor") { + val dropRequests = SortedSet(DropRequest(3))(dropRequestOrdering) + val elevator = ElevatorCab(1, 3, dropRequests, Direction.Idle) + val updatedElevator = elevator.step + assert(updatedElevator.dropRequests.isEmpty) + } + + test("Add new request when idle") { + val dropRequests = SortedSet(DropRequest(5))(dropRequestOrdering) + val elevator = ElevatorCab(1, 3, SortedSet.empty, Direction.Idle) + val updatedElevator = elevator.addNewRequest(DropRequest(5)) + assert(updatedElevator.dropRequests.contains(DropRequest(5))) + assert(updatedElevator.direction == Direction.MovingUp) + } + + test("Add new request when moving up") { + val dropRequests = SortedSet(DropRequest(7))(dropRequestOrdering) + val elevator = ElevatorCab(1, 5, dropRequests, Direction.MovingUp) + val updatedElevator = elevator.addNewRequest(DropRequest(9)) + assert(updatedElevator.dropRequests.contains(DropRequest(9))) + assert(updatedElevator.direction == Direction.MovingUp) + } + + test("Add new request when moving down") { + val dropRequests = SortedSet(DropRequest(3))(dropRequestOrdering) + val elevator = ElevatorCab(1, 5, dropRequests, Direction.MovingDown) + val updatedElevator = elevator.addNewRequest(DropRequest(1)) + assert(updatedElevator.dropRequests.contains(DropRequest(1))) + assert(updatedElevator.direction == Direction.MovingDown) + } + + test("Add multiple requests when idle") { + val dropRequests = Seq(DropRequest(5), DropRequest(2)) + val expectedDropRequests = SortedSet(DropRequest(2), DropRequest(5))(dropRequestOrdering) + val elevator = ElevatorCab(1, 3, SortedSet.empty, Direction.Idle) + val updatedElevator = elevator.addMultipleRequests(dropRequests) + assert(updatedElevator.dropRequests == expectedDropRequests) + assert(updatedElevator.direction == Direction.MovingDown) + } + + test("Add multiple requests when moving up") { + val initialRequests = SortedSet(DropRequest(7))(dropRequestOrdering) + val newRequests = Seq(DropRequest(9), DropRequest(10)) + val elevator = ElevatorCab(1, 5, initialRequests, Direction.MovingUp) + val updatedElevator = elevator.addMultipleRequests(newRequests) + assert( + updatedElevator.dropRequests == SortedSet(DropRequest(7), DropRequest(9), DropRequest(10)) + ) + assert(updatedElevator.direction == Direction.MovingUp) + } + + test("Add multiple requests when moving down") { + val initialRequests = SortedSet(DropRequest(3))(dropRequestOrdering) + val newRequests = Seq(DropRequest(1), DropRequest(2)) + val elevator = ElevatorCab(1, 5, initialRequests, Direction.MovingDown) + val updatedElevator = elevator.addMultipleRequests(newRequests) + assert( + updatedElevator.dropRequests == SortedSet(DropRequest(1), DropRequest(2), DropRequest(3)) + ) + assert(updatedElevator.direction == Direction.MovingDown) + } +} diff --git a/api/src/test/scala/system/SimulationSuite.scala b/api/src/test/scala/system/SimulationSuite.scala new file mode 100644 index 0000000..a0f3aad --- /dev/null +++ b/api/src/test/scala/system/SimulationSuite.scala @@ -0,0 +1,44 @@ +package system + +import org.scalatest.funsuite.AnyFunSuite +import server.Schemas.{DropRequest, PickupRequest} + +import scala.collection.immutable.HashMap +import scala.util.Random + +class SimulationSuite extends AnyFunSuite { + implicit val dropRequestOrdering: Ordering[DropRequest] = Ordering.by(_.targetFloor) + private final val RNG_SEED = 42 + private final val RANDOM = new Random(RNG_SEED) + private final val CAB_QUANTITY = 16 + private final val FLOORS = 10 + private final val MAX_PICKUP_REQUESTS_PER_STEP = 10 + + class SimulationTest extends AnyFunSuite { + + test("Generate Building") { + val building = Simulation.generateBuilding() + assert(building.floors.size == FLOORS) + assert(building.floors.forall(_._2 == 0)) + assert(building.elevatorCabs.size == CAB_QUANTITY) + assert(building.outsideRequests.isEmpty) + } + + test("Run Simulation Step") { + val initialBuilding = Simulation.generateBuilding() + val updatedBuilding = Simulation.run(initialBuilding) + + assert(updatedBuilding.floors.size == FLOORS) + assert(updatedBuilding.elevatorCabs.size == CAB_QUANTITY) + } + + test("Simulation handles new requests correctly") { + val initialBuilding = Simulation.generateBuilding() + val newRequests = List(PickupRequest(1, 1), PickupRequest(2, -1)) + val buildingWithNewRequests = initialBuilding.addPickupRequests(newRequests) + assert(buildingWithNewRequests.outsideRequests == newRequests) + assert(newRequests.forall(req => buildingWithNewRequests.floors(req.fromFloor) > 0)) + } + } + +} diff --git a/api/src/test/scala/system/schedulers/FIFOSchedulerSuite.scala b/api/src/test/scala/system/schedulers/FIFOSchedulerSuite.scala new file mode 100644 index 0000000..7667427 --- /dev/null +++ b/api/src/test/scala/system/schedulers/FIFOSchedulerSuite.scala @@ -0,0 +1,37 @@ +package system.schedulers + +import org.scalatest.funsuite.AnyFunSuite +import server.Schemas.DropRequest +import server.Schemas.PickupRequest +import system.ElevatorCab +import system.schedulers.FIFOScheduler.findBestCab + +import scala.collection.immutable.SortedSet + +class FIFOSchedulerSuite extends AnyFunSuite { + implicit val dropRequestOrdering: Ordering[DropRequest] = Ordering.by(_.targetFloor) + + test("Single Elevator") { + val elevator = ElevatorCab(1, 1, SortedSet(DropRequest(3))) + val request = PickupRequest(5, 1) + val result = findBestCab(List(elevator), request, 10) + assert(result == elevator) + } + + test("Second elevator is closer") { + val elevator1 = ElevatorCab(1, 1, SortedSet(DropRequest(3))) + val elevator2 = ElevatorCab(2, 4, SortedSet(DropRequest(4))) + val request = PickupRequest(5, 1) + val result = findBestCab(List(elevator1), request, 10) + assert(result == elevator1) + } + + test("Second elevator is already on the pickup floor") { + val elevator1 = ElevatorCab(1, 1, SortedSet(DropRequest(3))) + val elevator2 = ElevatorCab(2, 4, SortedSet(DropRequest(2))) + val request = PickupRequest(4, 1) + val result = findBestCab(List(elevator1, elevator2), request, 10) + assert(result == elevator1) + } + +} diff --git a/api/src/test/scala/system/schedulers/PrioritySchedulerSuite.scala b/api/src/test/scala/system/schedulers/PrioritySchedulerSuite.scala new file mode 100644 index 0000000..7d6a721 --- /dev/null +++ b/api/src/test/scala/system/schedulers/PrioritySchedulerSuite.scala @@ -0,0 +1,104 @@ +package system.schedulers + +import org.scalatest.funsuite.AnyFunSuite +import server.Schemas.DropRequest +import server.Schemas.PickupRequest +import system.Direction +import system.ElevatorCab +import system.schedulers.FIFOScheduler.findBestCab + +import scala.collection.immutable.SortedSet + +class PrioritySchedulerSuite extends AnyFunSuite { + implicit val dropRequestOrdering: Ordering[DropRequest] = Ordering.by(_.targetFloor) + + class ElevatorSystemTest extends AnyFunSuite { + + test("Single Elevator") { + val elevator = ElevatorCab(1, 1, SortedSet(DropRequest(3))) + val request = PickupRequest(5, 1) + val result = findBestCab(List(elevator), request, 10) + assert(result == elevator) + } + + test("Multiple Elevators, Same Floor Requests") { + val elevator1 = ElevatorCab(1, 1, SortedSet(DropRequest(3)), Direction.MovingUp) + val elevator2 = ElevatorCab(2, 5, SortedSet(DropRequest(4)), Direction.MovingUp) + val request = PickupRequest(5, 1) + val result = findBestCab(List(elevator1, elevator2), request, 10) + assert(result == elevator2) + } + + test("Multiple Elevators, Different Floor Requests") { + val elevator1 = ElevatorCab(1, 1, SortedSet(DropRequest(3)), Direction.MovingUp) + val elevator2 = ElevatorCab( + 2, + 2, + SortedSet(DropRequest(4), DropRequest(5)), + Direction.MovingUp, + ) + val request = PickupRequest(6, 1) + val result = findBestCab(List(elevator1, elevator2), request, 10) + assert(result == elevator2) + } + + test("Elevators with Different Directions") { + val elevator1 = ElevatorCab(1, 1, SortedSet(DropRequest(3)), Direction.MovingUp) + val elevator2 = ElevatorCab(2, 5, SortedSet(DropRequest(2)), Direction.MovingDown) + val request = PickupRequest(4, 1) + val result = findBestCab(List(elevator1, elevator2), request, 10) + assert(result == elevator1) + } + + test("Elevators in Idle State") { + val elevator1 = ElevatorCab(1, 1, SortedSet(), Direction.Idle) + val elevator2 = ElevatorCab(2, 5, SortedSet(DropRequest(2)), Direction.MovingDown) + val request = PickupRequest(4, 1) + val result = findBestCab(List(elevator1, elevator2), request, 10) + assert(result == elevator1) + } + + test("Elevator Already on the Pickup Floor") { + val elevator1 = ElevatorCab(1, 4, SortedSet(DropRequest(3))) + val elevator2 = ElevatorCab(2, 5, SortedSet(DropRequest(2))) + val request = PickupRequest(4, 1) + val result = findBestCab(List(elevator1, elevator2), request, 10) + assert(result == elevator1) + } + + test("No Elevators Available") { + val request = PickupRequest(4, 1) + val result = findBestCab(List(), request, 10) + assert(result == null) + } + + test("Elevators with Mixed Requests and Directions") { + val elevator1 = ElevatorCab(1, 1, SortedSet(DropRequest(3)), Direction.MovingUp) + val elevator2 = ElevatorCab( + 2, + 4, + SortedSet(DropRequest(6), DropRequest(7)), + Direction.MovingUp, + ) + val elevator3 = ElevatorCab(3, 2, SortedSet(DropRequest(5)), Direction.MovingDown) + val request = PickupRequest(3, 1) + val result = findBestCab(List(elevator1, elevator2, elevator3), request, 10) + assert(result == elevator1) + } + + test("Elevators with Idle and Busy States") { + val elevator1 = ElevatorCab(1, 3, SortedSet(), Direction.Idle) + val elevator2 = ElevatorCab( + 2, + 1, + SortedSet(DropRequest(4), DropRequest(6)), + Direction.MovingUp, + ) + val elevator3 = ElevatorCab(3, 5, SortedSet(DropRequest(7)), Direction.MovingDown) + val request = PickupRequest(2, 1) + val result = findBestCab(List(elevator1, elevator2, elevator3), request, 10) + assert(result == elevator1) + } + } + +}