eval({ init: function(elevators, floors) { // Get elevator/building info const elevatorCount = elevators.length, floorCount = floors.length, bottomFloor = 0, topFloor = floorCount - 1; console.clear(); ("Starting (" + floorCount + " floors; " + elevatorCount + " elevator" + (floorCount == 1) ? "s" : "" + ")..."); // Direction constants const UP = 0, DOWN = 1, IDLE = 2; // Rest floor constants const LOW = 0, MED = 1, HIGH = 2; // Data structure for floor requests const requests = [new Set(), new Set()]; // ---- // Functions // ---- // getClosestFloor takes a floor, a list of floors, an optional // list of ignored floors, and an optional direction. It // returns the closest floor from that list in the given // direction that is not part of the ignored floors list. Ties // are broken by even/oddness of the current floor. // TODO: fix direction shadowing/usage function getClosestFloor(currentFloor, floorList, ignoredFloors = new Set(), direction = IDLE) { const isEven = (currentFloor % 2 == 0); let closestFloor = currentFloor, distance = floorCount, newDistance = distance; floorList.forEach(function(floorNum) { const direction = getDirection(currentFloor, floorNum); newDistance = Math.abs(currentFloor - floorNum); if (newDistance < distance && newDistance > 0 && !(ignoredFloors.has(floorNum))) { closestFloor = floorNum; distance = newDistance; } else if (newDistance == distance && !(ignoredFloors.has(floorNum))) { if ((isEven && direction == UP) || (!isEven && direction == DOWN) || (direction == IDLE && Math.random() < 0.5)) { closestFloor = floorNum; } } }); if (closestFloor != currentFloor) { return closestFloor; } else { throw new Error("No floor in selected direction"); } } // getNextFloor chooses the best destination for an elevator // when given the index of the elevator, along with all // destinations, requests, and rest floors. function getNextFloor(elevators, elevatorIndex, destinations, requests, restFloors) { let elevator = elevators[elevatorIndex], floorNum = restFloors[MED]; if (requests[UP].size == 0 && requests[DOWN].size == 0 && destinations.size == 0) { // No people; send to rest floor } else if (destinations.size == 0) { // No destinations; send to pick up passengers try { floorNum = getClosestFloor(elevator.currentFloor(), new Set([...requests[UP], ...requests[DOWN]])); } catch (e) { console.log("ERROR: Requests set was empty"); } } else { // Prioritize dropping off current passengers try { floorNum = getClosestFloor(elevator.currentFloor(), destinations); } catch (e) { console.log("ERROR: Destinations set was empty"); } } return floorNum; } // getClosestElevator takes a floor number and returns the // index of the closest elevator. function getClosestElevator(floorNum) { let closestElevator = 0, distance = floorCount, newDistance = distance; elevators.forEach(function(elevator, elevatorIndex) { newDistance = Math.abs(elevator.currentFloor() - floorNum); if (newDistance < distance) { distance = newDistance; closestElevator = elevatorIndex; } }); //console.log("Closest is elevator[" + closestElevator + "]") return closestElevator; } // getDirection get the direction (up or down) that an elevator // will need to travel to get to a specified floor function getDirection(currentFloor, floorNum) { const distance = currentFloor - floorNum; let direction = IDLE; if (distance > 0 || currentFloor == topFloor) { direction = DOWN; } else if (distance < 0 || currentFloor == bottomFloor) { direction = UP; } return direction; } // setDirection changes the indicator lights on a specified // elevator to match the given direction. function setDirection(elevator, direction) { switch (direction) { case UP: elevator.goingUpIndicator(true); elevator.goingDownIndicator(false); break; case DOWN: elevator.goingUpIndicator(false); elevator.goingDownIndicator(true); break; default: elevator.goingUpIndicator(true); elevator.goingDownIndicator(true); } } // ---- // Floors // ---- floors.forEach(function(floor) { floor.on("up_button_pressed", function() { //console.log("> Up request on floor[" + floor.level + "]"); requests[UP].add(floor.level); }); floor.on("down_button_pressed", function() { //console.log("> Down request on floor[" + floor.level + "]"); requests[DOWN].add(floor.level); }); }); // ---- // Elevators // ---- elevators.forEach(function(elevator, elevatorIndex) { // Elevator info and variables const restFloors = [ elevatorIndex * Math.floor(floorCount / (elevatorCount + 1)), (elevatorIndex + 1) * Math.floor(floorCount / (elevatorCount + 1)), (elevatorIndex + 2) * Math.floor(floorCount / (elevatorCount + 1)) ]; let destinations = new Set(), nextFloor = restFloors[LOW]; // Start setDirection(elevator, UP); // Starts on the ground floor, must be going up console.log("> Elevator[" + elevatorIndex + "] rest floors are " + restFloors[LOW] + ", " + restFloors[MED] + ", and " + restFloors[HIGH] + ". Starting at " + restFloors[LOW] + "."); elevator.goToFloor(nextFloor); // Nearest rest floor // Idle elevator.on("idle", function() { //console.log("> Elevator[" + elevatorIndex + "] is idle") nextFloor = getNextFloor(elevators, elevatorIndex, destinations, requests, restFloors); elevator.goToFloor(nextFloor); }); // Passing elevator.on("passing_floor", function(floorNum, direction) { if (destinations.has(floorNum) || (elevator.loadFactor() < 1 && (elevator.destinationDirection() == "up" && requests[UP].has(floorNum)) || (elevator.destinationDirection() == "down" && requests[DOWN].has(floorNum)))) { elevator.goToFloor(floorNum, true); } }); // Buttons elevator.on("floor_button_pressed", function(floorNum) { //console.log("> Pressed floor[" + floorNum + "] on elevator[" + elevatorIndex + "]"); destinations.add(floorNum); }); // Stopped elevator.on("stopped_at_floor", function(floorNum) { //console.log("> elevator[" + elevatorIndex + "] stopped at floor[" + floorNum + "]"); let isNext = (elevator.currentFloor() == nextFloor); destinations.delete(floorNum); if (isNext) { nextFloor = getNextFloor(elevators, elevatorIndex, destinations, requests, restFloors); setDirection(elevator, getDirection(elevator.currentFloor(), nextFloor)); } if (elevator.goingUpIndicator() == true) { requests[UP].delete(floorNum); } if (elevator.goingDownIndicator() == true) { requests[DOWN].delete(floorNum); } if (isNext) { elevator.goToFloor(nextFloor); } }); }); }, update: function(dt, elevators, floors) {} });