Skip to content

Instantly share code, notes, and snippets.

@johnorourke
Created December 11, 2020 18:17
Show Gist options
  • Save johnorourke/d20878e4745eedc29b1b8c097e91e506 to your computer and use it in GitHub Desktop.
Save johnorourke/d20878e4745eedc29b1b8c097e91e506 to your computer and use it in GitHub Desktop.
Elevator fun
const o = {
init: function(lifts, floors) {
/*
aim: get a lift to waiting people quickly, get people to their floor quickly
get a lift to waiting people quickly:
- nearest lift
- which isnt full
- already got this floor in its queue
- floors in its queue which are in the direction the user wants to go
= lowest value = select that lift
get people ot their floor quickly
- go to nearest floor whose button has been pressed
- stay in the current direction of travel as long as you have destinations that way
*/
const topFloor = floors.length;
// actual floor buttons pressed:
const liftFloorQueue = lifts.map(() => []);
// current direction:
const liftDirections = lifts.map(() => 0); // 1 = up, -1 = down, 0 = idle
// floors whose up buttons are pressed:
const upButtons = floors.map(() => 0);
// floors whose down buttons are preseed:
const downButtons = floors.map(() => 0);
const weighting = {
nearness: 0.5, // the value is no. of floors
fullness: 1.5, // the value is 0 to 1
inQueueNess: 1.5, // the value is how far down the queue my floor is
goingInDirectionNess: 0.5, // could be very high - eg. if 3 floors queued in wrong direction, value = 3+2+1=6: the value is higher if we have floors to visit in the wrong direction, which are closer to the front of the queue
}
function chooseLiftToCall(floorNum,direction) {
// direction: 1 for up, -1 for down
let currentWeight = 1000; // lower weight is better
let chosenLift = 0;
for(let liftNum in lifts) {
const lift = lifts[liftNum];
const queue = [lift.currentFloor()].concat(lift.getPressedFloors());
// weight for nearness:
const nearness = Math.abs(lift.currentFloor() - floorNum);
// weight for fullness:
const fullness = lift.loadFactor();
// weight for this floor already in this lift's queue, higher for further along in queue
const inQueueNess = queue.indexOf(floorNum);
// weight for floors in queue in the direction user wants to go:
// (eg. if lift is on 2, going to 0, and user wants to go DOWN from 1, this is higher
let queueLen = queue.length;
const goingInDirectionNess = queue.reduce((weight,f)=>{
// could reduce the weight added as we get further down the queue
weight += (Math.sign(f - lift.currentFloor()) == direction) ? 0 : queueLen; // add reversed queue position for each floor in wrong direction
// eg. if we have a queue of 5 destinations, queueLen goes 5 to 1, and positions 4 and 5 are in the wrong direction, we add 2 and 1 = 3
queueLen--;
}, 0);
thisWeight =
(weighting.nearness * nearness) +
(weighting.fullness * fullness) +
(weighting.inQueueNess * inQueueNess) +
(weighting.goingInDirectionNess * goingInDirectionNess);
if(thisWeight < currentWeight) {
chosenLift = liftNum;
currentWeight = thisWeight;
}
}
return chosenLift;
}
function getQueue(lift, liftNum) {
let fullQueue = [];
// first see if we're the best lift for any open calls:
upButtons.forEach((button, floorNum) => {
if(button && floorNum != lift.currentFloor() && chooseLiftToCall(floorNum, liftDirections[liftNum]) == liftNum) {
fullQueue.push(floorNum);
}
});
downButtons.forEach((button, floorNum) => {
if(button && floorNum != lift.currentFloor() && chooseLiftToCall(floorNum, liftDirections[liftNum]) == liftNum) {
fullQueue.push(floorNum);
}
});
fullQueue = fullQueue.concat(lift.getPressedFloors()); // ignore the queue!
return fullQueue;
}
function chooseNextFloor(lift, liftNum, enqueue = false) {
// see what's motivating this lift, and how strong the motivations are...
console.log('choosing floor for lift '+liftNum);
let fullQueue = getQueue(lift, liftNum);
// add the current queue to the floor(s) that are calling us:
upwardsFloors = fullQueue.filter(floorNum => floorNum > lift.currentFloor()).sort((a,b) => a - b);
downwardsFloors = fullQueue.filter(floorNum => floorNum < lift.currentFloor()).sort((a,b) => b - a);
if(liftDirections[liftNum] > 0 && upwardsFloors.length > 0) {
fullQueue = upwardsFloors.concat(downwardsFloors);
} else if(liftDirections[liftNum] < 0 && downwardsFloors.length > 0) {
fullQueue = downwardsFloors.concat(upwardsFloors);
} else {
if(upwardsFloors.length >= downwardsFloors.length) {
fullQueue = upwardsFloors.concat(downwardsFloors);
liftDirections[liftNum] = 1;
} else {
fullQueue = downwardsFloors.concat(upwardsFloors);
liftDirections[liftNum] = -1;
}
}
if(fullQueue.length == 0) {
liftDirections[liftNum] = 0;
}
console.log(`lift ${liftNum} going dir=${liftDirections[liftNum]}, up to ${upwardsFloors}, down to ${downwardsFloors}`);
if(fullQueue.length > 0) {
if(enqueue) {
lift.goToFloor(fullQueue[0]);
} else {
lift.destinationQueue = [fullQueue[0]];
lift.checkDestinationQueue();
}
}
lift.goingUpIndicator(liftDirections[liftNum] >= 0);
lift.goingDownIndicator(liftDirections[liftNum] <= 0);
console.log(`lift ${liftNum} has new queue ${lift.destinationQueue} and direction ${liftDirections[liftNum]}`);
}
lifts.forEach((lift, liftNum) => {
console.log(Date.now() + ' initialising lift ' + liftNum);
lift.on("idle", () => {
console.log(`${Date.now()} lift ${liftNum} is idle on floor ${lift.currentFloor()}`);
chooseNextFloor(lift, liftNum)
});
lift.on("stopped_at_floor", (floorNum) => {
console.log(`${Date.now()} lift ${liftNum} just stopped at ${floorNum}, cf=${lift.currentFloor()}`);
// optimise the floor queue and choose next floor - queue it, don't overwrite the queue
chooseNextFloor(lift, liftNum, true);
// clear downButtons or upButtons[floorNum] = false; depending on direction!
if(liftDirections[liftNum] == 1) {
upButtons[floorNum] = false;
}
if(liftDirections[liftNum] == -1) {
downButtons[floorNum] = false;
}
});
lift.on("floor_button_pressed", floorNum => {
console.log(Date.now() + ' lift '+liftNum+' floor '+floorNum+' was pressed');
liftFloorQueue[liftNum].push(floorNum); // queue it up
// optimise the floor queue and set destination:
chooseNextFloor(lift, liftNum);
});
lift.on("passing_floor", (floorNum, directionName) => {
const direction = directionName == "up" ? 1 : -1;
console.log(`${Date.now()} lift ${liftNum} about to pass ${floorNum}, cf=${lift.currentFloor()} going ${directionName}`);
// decide whether to stop or not
const floorButtons = direction == 1 ? upButtons : downButtons;
if(floorButtons[floorNum]) {
// re-calculate optimal next floor, in case it's this one:
chooseNextFloor(lift, liftNum);
}
});
});
floors.forEach(f => {
f.on("up_button_pressed", () => {
upButtons[f.floorNum()] = true;
selectedLift = chooseLiftToCall(f.floorNum(), 1); // will get called twice - could be optimised
chooseNextFloor(lifts[selectedLift], selectedLift, f.floorNum());
console.log(Date.now() + ' floor '+f.floorNum()+' wants to go up - using ' + selectedLift);
});
f.on("down_button_pressed", () => {
downButtons[f.floorNum()] = true;
selectedLift = chooseLiftToCall(f.floorNum(), -1); // will get called twice - could be optimised
chooseNextFloor(lifts[selectedLift], selectedLift, f.floorNum());
console.log(Date.now() + ' floor '+f.floorNum()+' wants to go down - using ' + selectedLift);
});
});
},
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