Skip to content

Instantly share code, notes, and snippets.

@michalc
Last active September 1, 2024 14:01
Show Gist options
  • Save michalc/2265ba741927d45eac6f66ab4247efff to your computer and use it in GitHub Desktop.
Save michalc/2265ba741927d45eac6f66ab4247efff to your computer and use it in GitHub Desktop.
My Elevator Saga solution
{
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