Last active
September 1, 2024 14:01
-
-
Save michalc/2265ba741927d45eac6f66ab4247efff to your computer and use it in GitHub Desktop.
My Elevator Saga solution
This file contains 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
{ | |
init: function(elevators, floors) { | |
const intSort = function(a, b) { | |
if (a < b) return -1; | |
if (a > b) return 1; | |
return 0; | |
} | |
const floorWantsUp = function(floorNum) { | |
return floors[floorNum].buttonStates.up === 'activated'; | |
} | |
const floorWantsDown = function(floorNum) { | |
return floors[floorNum].buttonStates.down === 'activated'; | |
} | |
const floorsAboveWantingUp = function(floorNum) { | |
return floors.filter((f) => f.floorNum() > floorNum && f.buttonStates.up === 'activated' && !anyElevatorsGoingToFloorUp(f.floorNum())).map(f => f.floorNum()) | |
} | |
const anyFloorsAboveWantingUp = function(floorNum) { | |
return floorsAboveWantingUp(floorNum).length > 0; | |
} | |
const floorsAboveWantingDown = function(floorNum) { | |
return floors.filter((f) => f.floorNum() > floorNum && f.buttonStates.down === 'activated' && !anyElevatorsGoingToFloorDown(f.floorNum())).map(f => f.floorNum()); | |
} | |
const anyFloorsAboveWantingDown = function(floorNum) { | |
return floorsAboveWantingDown(floorNum).length > 0; | |
} | |
const floorsBelowWantingUp = function(floorNum) { | |
return floors.filter((f) => f.floorNum() < floorNum && f.buttonStates.up === 'activated' && !anyElevatorsGoingToFloorUp(f.floorNum())).map(f => f.floorNum()) | |
} | |
const anyFloorsBelowWantingUp = function(floorNum) { | |
return floorsBelowWantingUp(floorNum).length > 0; | |
} | |
const floorsBelowWantingDown = function(floorNum) { | |
return floors.filter((f) => f.floorNum() < floorNum && f.buttonStates.down === 'activated' && !anyElevatorsGoingToFloorDown(f.floorNum())).map(f => f.floorNum()); | |
} | |
const anyFloorsBelowWantingDown = function(floorNum) { | |
return floorsBelowWantingDown(floorNum).length > 0; | |
} | |
const elevatorFloorsUp = function(elevator) { | |
return elevator.getPressedFloors().filter((f) => f > elevator.currentFloor()) | |
} | |
const elevatorWantsUp = function(elevator) { | |
return elevatorFloorsUp(elevator).length > 0; | |
} | |
const elevatorFloorsDown = function(elevator) { | |
return elevator.getPressedFloors().filter((f) => f < elevator.currentFloor()) | |
} | |
const elevatorWantsDown = function(elevator) { | |
return elevatorFloorsDown(elevator).length > 0; | |
} | |
const anyOtherElevatorsWithLightOnOnFloor = function(elevator, floorNum) { | |
return elevators.filter((el) => el !== elevator && el.currentFloor() === floorNum && el.destinationDirection() == 'stopped' && (el.goingUpIndicator() || el.goingDownIndicator())).length > 0; | |
} | |
const anyElevatorsGoingToFloorUp = function(floorNum) { | |
return elevators.filter((el) => el.goingUpIndicator() && el.destinationQueue.length !== 0 && floorNum === el.destinationQueue[0]).length > 0; | |
} | |
const anyElevatorsGoingToFloorDown = function(floorNum) { | |
return elevators.filter((el) => el.goingDownIndicator() && el.destinationQueue.length !== 0 && floorNum === el.destinationQueue[0]).length > 0; | |
} | |
const idleElevators = function() { | |
return elevators.filter(e => e.destinationQueue.length === 0 && e.getPressedFloors().length === 0 && e.loadFactor() == 0) | |
} | |
const goToFloorNow = function(elevator, floorNum) { | |
const currentFloor = elevator.currentFloor(); | |
const q = elevator.destinationQueue; | |
q[0] = floorNum | |
elevator.goingUpIndicator(q.length === 0 || q[0] > currentFloor); | |
elevator.goingDownIndicator(q.length === 0 || q[0] < currentFloor); | |
elevator.checkDestinationQueue(); | |
} | |
const elevatorIsFullWhileWaiting = function(elevator) { | |
return elevator.loadFactor() > 1/6; | |
} | |
const elevatorIsFullWhilePassing = function(elevator) { | |
return elevator.loadFactor() > 3/4; | |
} | |
const closestIdleToFloor = function(floorNum, dir) { | |
const idle = idleElevators(); | |
if (idle.length) { | |
closest = idle.toSorted((a, b) => { | |
const distA = Math.abs(floorNum - a.currentFloor()); | |
const distB = Math.abs(floorNum - b.currentFloor()); | |
// const aLights = a.goingUpIndicator() || a.goingDownIndicator(); | |
// const bLights = b.goingUpIndicator() || b.goingDownIndicator(); | |
if (distA < distB) return -1; | |
if (distA > distB) return 1; | |
// if (!aLights && bLights) return -1; | |
// if (aLights && !bLights) return 1; | |
return 0; | |
})[0]; | |
goToFloorNow(closest, floorNum); | |
// We override the direction indicator - might be heading up but will be going down afterwards | |
// This means we won't stop on the way even if we can. Suspect this is good to | |
// keep waiting times for the elevator down | |
closest.goingUpIndicator(dir === 'up'); | |
closest.goingDownIndicator(dir === 'down'); | |
} | |
} | |
floors.forEach((floor) => { | |
// If there are no elevators heading directly to this floor, prod the closest idle one | |
floor.on("up_button_pressed", function() { | |
if (anyElevatorsGoingToFloorUp(floor.floorNum())) return; | |
closestIdleToFloor(floor.floorNum(), 'up'); | |
}); | |
floor.on("down_button_pressed", function() { | |
if (anyElevatorsGoingToFloorDown(floor.floorNum())) return; | |
closestIdleToFloor(floor.floorNum(), 'down'); | |
}); | |
}); | |
elevators.forEach((elevator, i) => { | |
if (i !== 0) { | |
// Only allow the first lift to pick up passengers on the ground floor on init, | |
// which a) makes it quicker to fill up, and b) frees up the others to respond | |
// to button presses on the other floors | |
const floorsPerElevator = Math.floor(floors.length / (elevators.length - 1)); | |
goToFloorNow(elevator, Math.min(floorsPerElevator * i, floors.length -1)); | |
elevator.goingUpIndicator(true); | |
elevator.goingDownIndicator(true); | |
} | |
const moveIfReady = function() { | |
const currentFloor = elevator.currentFloor(); | |
const pressedFloors = elevator.getPressedFloors(); | |
const q = elevator.destinationQueue; | |
if (currentFloor === 0 && !elevatorIsFullWhileWaiting(elevator)) { | |
return; | |
} | |
// Prioritise buttons already pressed in the lift | |
// By logic elsewhere, we must already have a direction | |
if (elevator.goingUpIndicator() && elevatorWantsUp(elevator)) { | |
goToFloorNow(elevator, pressedFloors.toSorted(intSort)[0]); | |
} else if (elevator.goingDownIndicator() && elevatorWantsDown(elevator)) { | |
goToFloorNow(elevator, pressedFloors.toSorted(intSort).toReversed()[0]); | |
} else if (elevator.goingUpIndicator() && anyFloorsAboveWantingUp(currentFloor)) { | |
goToFloorNow(elevator, floorsAboveWantingUp(currentFloor).toSorted(intSort)[0]) | |
} else if (elevator.goingUpIndicator() && anyFloorsAboveWantingDown(currentFloor)) { | |
goToFloorNow(elevator, floorsAboveWantingDown(currentFloor).toSorted(intSort).toReversed()[0]); | |
elevator.goingUpIndicator(false); | |
elevator.goingDownIndicator(true); | |
} else if (elevator.goingDownIndicator() && anyFloorsBelowWantingDown(currentFloor)) { | |
goToFloorNow(elevator, floorsBelowWantingDown(currentFloor).toSorted(intSort).toReversed()[0]); | |
} else if (elevator.goingDownIndicator() && anyFloorsBelowWantingUp(currentFloor)) { | |
goToFloorNow(elevator, floorsBelowWantingUp(currentFloor).toSorted(intSort)[0]); | |
elevator.goingUpIndicator(true); | |
elevator.goingDownIndicator(false); | |
} | |
} | |
elevator.on("floor_button_pressed", function() { | |
moveIfReady() | |
}); | |
elevator.on("idle", function() { | |
moveIfReady() | |
}); | |
elevator.on("stopped_at_floor", function(floorNum) { | |
// This is the only opportunity to set the lights so people will notice. Later | |
// changes have no effect on people waiting (even new people waiting) | |
if (elevator.currentFloor() === 0 && anyOtherElevatorsWithLightOnOnFloor(elevator, floorNum) && !elevatorWantsUp(elevator)) { | |
if (anyFloorsAboveWantingDown(elevator.currentFloor())) { | |
goToFloorNow(elevator, floorsAboveWantingDown(elevator.currentFloor()).toSorted(intSort).toReversed()[0]); | |
elevator.goingUpIndicator(false); | |
elevator.goingDownIndicator(true); | |
} else { | |
goToFloorNow(elevator, Math.floor(floors.length/2)); | |
elevator.goingUpIndicator(false); | |
elevator.goingDownIndicator(false); | |
} | |
} else if (elevator.goingUpIndicator() && (elevatorWantsUp(elevator) || floorWantsUp(floorNum) || anyFloorsAboveWantingUp(floorNum) || anyFloorsAboveWantingDown(floorNum))) { | |
// The indicator is right - we continue up | |
} else if (elevator.goingDownIndicator() && (elevatorWantsDown(elevator) || floorWantsDown(floorNum) || anyFloorsBelowWantingUp(floorNum) || anyFloorsBelowWantingDown(floorNum))) { | |
// The indicator is right - we continue down | |
} else if (elevatorWantsDown(elevator)) { | |
elevator.goingUpIndicator(false); | |
elevator.goingDownIndicator(true); | |
} else if (elevatorWantsUp(elevator)) { | |
elevator.goingUpIndicator(true); | |
elevator.goingDownIndicator(false); | |
} else if (floorWantsUp(floorNum)) { | |
elevator.goingUpIndicator(true); | |
elevator.goingDownIndicator(false); | |
} else if (floorWantsDown(floorNum)) { | |
elevator.goingUpIndicator(false); | |
elevator.goingDownIndicator(true); | |
} else { | |
// No plan | |
// - People would then enter the lift and a direction will be chosen | |
// - Or could be called by another floor in either direction | |
elevator.goingUpIndicator(true); | |
elevator.goingDownIndicator(true); | |
} | |
}); | |
elevator.on("passing_floor", function(floorNum, direction) { | |
// If we can pick up passengers on the way, do it | |
const buttonStates = floors[floorNum].buttonStates; | |
if (elevatorIsFullWhilePassing(elevator)) return; | |
if (direction == 'up' && anyElevatorsGoingToFloorUp(floorNum)) return; | |
if (direction == 'up' && !(buttonStates.up == 'activated' && elevator.goingUpIndicator() && elevator.destinationDirection() == 'up')) return; | |
if (direction == 'down' && anyElevatorsGoingToFloorDown(floorNum)) return; | |
if (direction == 'down' && !(buttonStates.down == 'activated' && elevator.goingDownIndicator() && elevator.destinationDirection() == 'down')) return; | |
goToFloorNow(elevator, floorNum); | |
}); | |
}); | |
}, | |
update: function(dt, elevators, floors) { | |
// We normally don't need to do anything here | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment