Last active
November 28, 2024 21:52
-
-
Save srpiroliro/fdbf0afdf02172786773b81c89006975 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const SPEED = 0.1; | |
| const DELAY_BETWEEN_PASSENGERS = 2000; | |
| const log = (category, message) => { | |
| const timestamp = new Date().toISOString().split("T")[1].slice(0, -1); | |
| console.log(`[${timestamp}] [${category}] ${message}`); | |
| }; | |
| const logState = (elevator) => { | |
| const state = { | |
| floor: elevator.getCurrentFloor(), | |
| direction: elevator.getCurrentDirection(), | |
| doorsOpen: elevator.doorsOpen, | |
| isMoving: elevator.isMoving, | |
| passengers: elevator.currentPassengers.size, | |
| waitingUp: [...elevator.pickUpQueue.up].join(","), | |
| waitingDown: [...elevator.pickUpQueue.down].join(","), | |
| destinations: [...elevator.destinationQueue].join(","), | |
| requestOrder: elevator.requestOrder | |
| .map((r) => `Passenger #${r.id} from ${r.startFloor} to ${r.destinationFloor}`) | |
| .join(","), | |
| }; | |
| log( | |
| "STATE", | |
| `Floor: ${state.floor}, Direction: ${state.direction || "idle"}, ` + | |
| `Doors: ${state.doorsOpen ? "open" : "closed"}, Moving: ${state.isMoving}, ` + | |
| `Passengers: ${state.passengers}/${HardwareElevator.MAX_CAPACITY}\n` + | |
| ` Waiting Up: [${state.waitingUp}], Waiting Down: [${state.waitingDown}]\n` + | |
| ` Destinations: [${state.destinations}]\n` + | |
| ` Request Order: [${state.requestOrder}]`, | |
| ); | |
| }; | |
| class HardwareElevator { | |
| static MIN_FLOOR = 1; | |
| static MAX_FLOOR = 10; | |
| static FLOOR_TRAVEL_TIME = SPEED * 2000; | |
| static DOOR_OPERATION_TIME = SPEED * 3000; | |
| static MAX_CAPACITY = 4; | |
| constructor() { | |
| this.currentFloor = HardwareElevator.MIN_FLOOR; | |
| this.currentDirection = null; | |
| this.isMoving = false; | |
| this.doorsOpen = false; | |
| this.isOperating = false; | |
| } | |
| async moveUp() { | |
| this.isMoving = true; | |
| this.currentDirection = "up"; | |
| log("MOVEMENT", `Moving up from floor ${this.currentFloor}`); | |
| await new Promise((resolve) => | |
| setTimeout(resolve, HardwareElevator.FLOOR_TRAVEL_TIME), | |
| ); | |
| this.currentFloor++; | |
| this.isMoving = false; | |
| log("MOVEMENT", `Arrived at floor ${this.currentFloor}`); | |
| } | |
| async moveDown() { | |
| this.isMoving = true; | |
| this.currentDirection = "down"; | |
| log("MOVEMENT", `Moving down from floor ${this.currentFloor}`); | |
| await new Promise((resolve) => | |
| setTimeout(resolve, HardwareElevator.FLOOR_TRAVEL_TIME), | |
| ); | |
| this.currentFloor--; | |
| this.isMoving = false; | |
| log("MOVEMENT", `Arrived at floor ${this.currentFloor}`); | |
| } | |
| async stopAndOpenDoors() { | |
| if (this.isOperating) { | |
| log("DOORS", "Cannot operate doors - elevator is busy"); | |
| return; | |
| } | |
| this.isOperating = true; | |
| log("DOORS", `Opening doors at floor ${this.currentFloor}`); | |
| await new Promise((resolve) => | |
| setTimeout(resolve, HardwareElevator.DOOR_OPERATION_TIME / 2), | |
| ); | |
| this.doorsOpen = true; | |
| log("DOORS", "Doors fully open - waiting"); | |
| await new Promise((resolve) => | |
| setTimeout(resolve, HardwareElevator.DOOR_OPERATION_TIME), | |
| ); | |
| log("DOORS", "Closing doors"); | |
| this.doorsOpen = false; | |
| await new Promise((resolve) => | |
| setTimeout(resolve, HardwareElevator.DOOR_OPERATION_TIME / 2), | |
| ); | |
| log("DOORS", "Doors fully closed"); | |
| this.isOperating = false; | |
| } | |
| getCurrentFloor() { | |
| return this.currentFloor; | |
| } | |
| getCurrentDirection() { | |
| return this.currentDirection; | |
| } | |
| hasCapacity() { | |
| return this.currentPassengers.size < HardwareElevator.MAX_CAPACITY; | |
| } | |
| } | |
| class Passenger { | |
| constructor(id, startFloor, destinationFloor) { | |
| if ( | |
| startFloor < HardwareElevator.MIN_FLOOR || | |
| startFloor > HardwareElevator.MAX_FLOOR || | |
| destinationFloor < HardwareElevator.MIN_FLOOR || | |
| destinationFloor > HardwareElevator.MAX_FLOOR | |
| ) { | |
| throw new Error("Invalid floor number"); | |
| } | |
| this.id = id; | |
| this.startFloor = startFloor; | |
| this.destinationFloor = destinationFloor; | |
| this.state = "waiting"; | |
| this.direction = startFloor < destinationFloor ? "up" : "down"; | |
| } | |
| } | |
| class Elevator extends HardwareElevator { | |
| constructor() { | |
| super(); | |
| this.pickUpQueue = { | |
| up: new Set(), | |
| down: new Set(), | |
| }; | |
| this.destinationQueue = []; | |
| this.waitingPassengers = new Map(); | |
| this.currentPassengers = new Set(); | |
| this.requestOrder = []; | |
| log("INIT", "Elevator system initialized"); | |
| } | |
| async run() { | |
| log("INIT", "Elevator system running"); | |
| while(!this._shouldWeMove()) { | |
| await new Promise(resolve => setTimeout(resolve, 100)); | |
| } | |
| while (this._shouldWeMove()) { | |
| await this._move(); | |
| } | |
| log("FINISHED", "Elevator finished"); | |
| } | |
| async onDoorsClosed() { | |
| // ... | |
| } | |
| async onBeforeFloor() { | |
| const floor = this.getCurrentFloor(); | |
| logState(this); | |
| if (this.isOperating) { | |
| log("OPERATION", "Skipping floor check - elevator is busy"); | |
| return; | |
| } | |
| log( | |
| "FLOOR", | |
| `Approaching floor ${floor} going ${this.getCurrentDirection() || "idle"}`, | |
| ); | |
| const passengersToRemove = this._identifyExitingPassengers(floor); | |
| const shouldStop = this._shouldStopAtFloor(floor, passengersToRemove); | |
| if (shouldStop) { | |
| await this._handleFloorStop(floor, passengersToRemove); | |
| } | |
| } | |
| async floorButtonPressed(passenger) { | |
| if (!this.waitingPassengers.has(passenger.startFloor)) { | |
| this.waitingPassengers.set(passenger.startFloor, new Set()); | |
| } | |
| this.waitingPassengers.get(passenger.startFloor).add(passenger); | |
| this.requestOrder.push(passenger); | |
| if (!this.pickUpQueue[passenger.direction].has(passenger.startFloor)) { | |
| this.pickUpQueue[passenger.direction].add(passenger.startFloor); | |
| log( | |
| "BUTTON", | |
| `Floor ${passenger.startFloor} ${passenger.direction} button pressed`, | |
| ); | |
| } | |
| } | |
| cabinButtonPressed(passenger) { | |
| const floor = this.getCurrentFloor(); | |
| const destinationFloor = passenger.destinationFloor; | |
| passenger.state = "inside"; | |
| this.currentPassengers.add(passenger); | |
| this.pickUpQueue[passenger.direction].delete(floor); | |
| this.waitingPassengers.get(floor).delete(passenger); | |
| if (this.destinationQueue.includes(destinationFloor)) { | |
| log("BUTTON", `Floor ${floor} already in destination queue`); | |
| return; | |
| } | |
| this.destinationQueue.push(destinationFloor); | |
| log( | |
| "BUTTON", | |
| `Passenger #${passenger.id} pressed cabin button for floor ${destinationFloor}`, | |
| ); | |
| } | |
| _identifyExitingPassengers(floor) { | |
| const passengersToRemove = new Set(); | |
| for (const passenger of this.currentPassengers) { | |
| if (passenger.destinationFloor === floor) { | |
| passenger.state = "exiting"; | |
| passengersToRemove.add(passenger); | |
| this.destinationQueue = this.destinationQueue.filter( | |
| (f) => f !== floor, | |
| ); | |
| log( | |
| "PASSENGER", | |
| `Passenger #${passenger.id} exiting at floor ${floor}`, | |
| ); | |
| } | |
| } | |
| return passengersToRemove; | |
| } | |
| _shouldWeMove() { | |
| const shouldMove = !!( | |
| this.destinationQueue.length || | |
| this.pickUpQueue.up.size || | |
| this.pickUpQueue.down.size | |
| ); | |
| log( | |
| "DECISION", | |
| `Should move: ${shouldMove} - ` + | |
| `Destinations: ${this.destinationQueue.length}, ` + | |
| `Waiting up: ${this.pickUpQueue.up.size}, ` + | |
| `Waiting down: ${this.pickUpQueue.down.size}`, | |
| ); | |
| return shouldMove; | |
| } | |
| _shouldWeMoveTo(floor) { | |
| if (this.destinationQueue.some((f) => f === floor)) { | |
| log("DECISION", "Destination floor already in destination queue"); | |
| return "same"; | |
| } | |
| if (this.destinationQueue.length > 0) { | |
| const isUp = this.destinationQueue[0] > floor; | |
| const isDown = this.destinationQueue[0] < floor; | |
| if (isUp && isDown) { | |
| log( | |
| "DECISION", | |
| "Found both upward and downward destinations - moving up", | |
| ); | |
| return "up"; | |
| } | |
| if (isUp) { | |
| log("DECISION", "Found upward destinations - moving up"); | |
| return "up"; | |
| } | |
| if (isDown) { | |
| log("DECISION", "Found downward destinations - moving down"); | |
| return "down"; | |
| } | |
| log("DECISION", "Current floor"); | |
| return null; | |
| } | |
| if (this.currentPassengers.size === 0 && this.requestOrder.length > 0) { | |
| const nextRequest = this.requestOrder[0]; | |
| const shouldMoveUp = nextRequest.startFloor > floor; | |
| log( | |
| "DECISION", | |
| `Empty elevator following request order: going ${shouldMoveUp ? "up" : "down"} to floor ${nextRequest.startFloor}`, | |
| ); | |
| return shouldMoveUp ? "up" : "down"; | |
| } | |
| const hasUpwardPickups = | |
| [...this.pickUpQueue.up].some((f) => f > floor) || | |
| [...this.pickUpQueue.down].some((f) => f > floor); | |
| const hasDownwardPickups = | |
| [...this.pickUpQueue.down].some((f) => f < floor) || | |
| [...this.pickUpQueue.up].some((f) => f < floor); | |
| if (!hasUpwardPickups && !hasDownwardPickups) { | |
| log("DECISION", "No pickups - resetting direction"); | |
| return null; | |
| } | |
| if (this.getCurrentDirection() === "up" && !hasUpwardPickups) { | |
| log("DIRECTION", "No more upward requests - allowing direction change"); | |
| this.currentDirection = null; | |
| return "down"; | |
| } | |
| const shouldMoveUp = | |
| (!this.getCurrentDirection() && hasUpwardPickups) || | |
| (this.getCurrentFloor() === HardwareElevator.MIN_FLOOR && | |
| hasUpwardPickups); | |
| return shouldMoveUp ? "up" : "down"; | |
| } | |
| async _move() { | |
| if (this.isMoving || this.isOperating) { | |
| log("MOVEMENT", "Cannot move - elevator is busy"); | |
| return; | |
| } | |
| const floor = this.getCurrentFloor(); | |
| const shouldWeMoveTo = this._shouldWeMoveTo(floor); | |
| log("MOVEMENT", `Evaluating movement from floor ${floor}`); | |
| switch (shouldWeMoveTo) { | |
| case "up": | |
| await this.moveUp(); | |
| break; | |
| case "down": | |
| await this.moveDown(); | |
| break; | |
| case "same": | |
| await this._handleFloorStop( | |
| floor, | |
| this._identifyExitingPassengers(floor), | |
| ); | |
| break; | |
| default: | |
| this.currentDirection = null; | |
| log( | |
| "DIRECTION", | |
| "No requests in current direction - resetting direction", | |
| ); | |
| break; | |
| } | |
| if (shouldWeMoveTo !== null) | |
| await this.onBeforeFloor(); | |
| } | |
| _shouldStopAtFloor(floor, passengersToRemove) { | |
| if (passengersToRemove.size > 0) return true; | |
| if (this.currentPassengers.size === 0) | |
| return this.requestOrder[0]?.startFloor === floor; | |
| const direction = this.getCurrentDirection(); | |
| return ( | |
| this.pickUpQueue[direction]?.size && | |
| this.pickUpQueue[direction].has(floor) | |
| ); | |
| } | |
| async _handleFloorStop(floor, passengersToRemove) { | |
| log("DECISION", `Stopping at floor ${floor}`); | |
| await this.stopAndOpenDoors(); | |
| if (passengersToRemove.size > 0) { | |
| for (const passenger of passengersToRemove) { | |
| this.currentPassengers.delete(passenger); | |
| passenger.state = "arrived"; | |
| log( | |
| "PASSENGER", | |
| `Passenger #${passenger.id} has arrived at destination ${floor}`, | |
| ); | |
| } | |
| this.requestOrder = this.requestOrder.filter( | |
| (r) => !passengersToRemove.has(r), | |
| ); | |
| } | |
| const waitingAtFloor = this.waitingPassengers.get(floor) || new Set(); | |
| const eligiblePassengers = new Set(); | |
| const direction = this.getCurrentDirection(); | |
| if (this.currentPassengers.size === 0) { | |
| const nextRequest = this.requestOrder[0]; | |
| if (nextRequest?.startFloor === floor) { | |
| const firstRequestDirection = nextRequest.direction; | |
| for (const passenger of waitingAtFloor) | |
| if (passenger.direction === firstRequestDirection) | |
| eligiblePassengers.add(passenger); | |
| if (eligiblePassengers.size > 0) { | |
| log( | |
| "PASSENGER", | |
| `${eligiblePassengers.size} passengers eligible to board at floor ${floor}, all going ${firstRequestDirection}`, | |
| ); | |
| } | |
| } | |
| } else { | |
| for (const passenger of waitingAtFloor) | |
| if (passenger.direction === direction) | |
| eligiblePassengers.add(passenger); | |
| if (eligiblePassengers.size > 0) { | |
| log( | |
| "PASSENGER", | |
| `${eligiblePassengers.size} passengers waiting to go ${direction} at floor ${floor}`, | |
| ); | |
| } | |
| } | |
| let boardedCount = 0; | |
| for (const passenger of eligiblePassengers) { | |
| if (this.hasCapacity()) { | |
| this.cabinButtonPressed(passenger); | |
| boardedCount++; | |
| } else { | |
| log( | |
| "CAPACITY", | |
| `Cannot pick up more passengers - elevator full at ${this.currentPassengers.size}/${HardwareElevator.MAX_CAPACITY}`, | |
| ); | |
| break; | |
| } | |
| } | |
| if (boardedCount > 0) { | |
| log("PASSENGER", `${boardedCount} passengers boarded at floor ${floor}`); | |
| } | |
| if (waitingAtFloor.size === 0) { | |
| this.waitingPassengers.delete(floor); | |
| } | |
| await this.onDoorsClosed(); | |
| } | |
| } | |
| async function runElevator() { | |
| const elevator = new Elevator(); | |
| const passengers = [ | |
| { id: 1, startFloor: 4, destinationFloor: 6 }, | |
| { id: 2, startFloor: 4, destinationFloor: 3 }, | |
| { id: 3, startFloor: 3, destinationFloor: 9 }, | |
| { id: 4, startFloor: 6, destinationFloor: 1 }, | |
| ]; | |
| passengers.forEach((passengerData, index) => { | |
| setTimeout( | |
| () => { | |
| const passenger = new Passenger( | |
| passengerData.id, | |
| passengerData.startFloor, | |
| passengerData.destinationFloor, | |
| ); | |
| elevator.floorButtonPressed(passenger); | |
| }, | |
| (index+1) * DELAY_BETWEEN_PASSENGERS * SPEED, | |
| ); | |
| }); | |
| await elevator.run(); | |
| } | |
| runElevator(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment