Skip to content

Instantly share code, notes, and snippets.

@Nemo64
Created December 21, 2018 15:41
Show Gist options
  • Select an option

  • Save Nemo64/79afb6233be95f43237b693c08c1b70b to your computer and use it in GitHub Desktop.

Select an option

Save Nemo64/79afb6233be95f43237b693c08c1b70b to your computer and use it in GitHub Desktop.
my solution to the elevator game (not perfect)
{
init: function(elevators, floors) {
this.elevators = elevators;
this.floors = floors;
this.todos = [];
this.time = 0;
this.overSizeTheashold = 6;
for (let floor of floors) {
floor.on('down_button_pressed', () => this.addFloorButtonPush(floor, 'down'));
floor.on('up_button_pressed', () => this.addFloorButtonPush(floor, 'up'));
}
let elevatorIndex = 0;
for (let elevator of elevators) {
elevator.currentTodo = null;
elevator.elevatorNum = elevatorIndex++;
elevator.on('idle', () => this.elevatorIdle(elevator));
elevator.on('stopped_at_floor', floorNum => this.elevatorStoppedAtFloor(elevator, floorNum));
elevator.on('floor_button_pressed', floorNum => this.addElevatorButtonPush(elevator, floorNum));
}
window.program = this;
},
addFloorButtonPush: function (floor, direction) {
// this would break if ppl would press twice but since we only have angles...
// well actually the current implementation would simply be less performant if the same task would exist twice
// if this becomes a concern, then add a check if the task already exists
this.todos.push({
type: 'incoming',
floor: floor,
floorNum: floor.floorNum(),
forElevator: null,
currentElevator: null,
direction: direction,
time: this.time,
halts: null
});
let idleElevators = _.where(this.elevators, {currentTodo: null});
for (let elevator of idleElevators) {
this.findAndExecuteTask(elevator);
}
},
addElevatorButtonPush: function (elevator, floorNum) {
this.todos.push({
type: 'outgoing',
floor: this.floors[floorNum],
floorNum: floorNum,
forElevator: elevator,
currentElevator: null,
direction: null,
time: this.time,
halts: 0
});
if (elevator.currentTodo === null) {
this.findAndExecuteTask(elevator);
}
},
getTasksForElevator: function (elevator) {
return this.todos.filter(todo => {
return todo.forElevator === null || todo.forElevator === elevator;
});
},
elevatorIdle: function (elevator) {
//console.log('idle', elevator.elevatorNum, elevator.currentFloor());
// up the halt counter of our outgoing tasks if the current task was incoming
if (elevator.currentTodo && elevator.currentTodo.type === 'incoming') {
let outgoingTodos = _.where(this.todos, {type: 'outgoing', forElevator: elevator});
for (let todo of outgoingTodos) {
todo.halts += 1;
}
}
// if present, kill the currently executed task
if (elevator.currentTodo) {
let currentTodo = elevator.currentTodo;
elevator.currentTodo = null;
currentTodo.currentElevator = null;
// the task will be removed by the filter below
//this.removeTasks([currentTodo]);
}
// also kill all other tasks that might have been done on this floor
this.removeTasks(this.getTasksForElevator(elevator).filter(todo => {
if (todo.floorNum !== elevator.currentFloor()) {
return false;
}
if (todo.direction === 'up' && elevator.goingUpIndicator() == false) {
return false;
}
if (todo.direction === 'down' && elevator.goingDownIndicator() == false) {
return false;
}
return true;
}));
// find the best task to do
let todo = this.findAndExecuteTask(elevator);
// No task found? try again with both indicators enabled
// This technically shouldn't happen as the elevator would have to be empty which is covered above.
// If it isn't empty than at least one person should be on the way in the direction the indicator indicates.
//if (todo === null && (elevator.goingUpIndicator() == false || elevator.goingDownIndicator() == false)) {
// elevator.goingUpIndicator(true);
// elevator.goingDownIndicator(true);
// let todo = this.findAndExecuteTask(elevator);
// console.warn("Elevator " + elevator.elevatorNum + " found no task even though it isn't empty.", todo);
//}
},
elevatorStoppedAtFloor: function (elevator, floorNum) {
if (elevator.currentTodo && elevator.currentTodo.direction !== null) {
return;
}
// if this elevator is oversized, don't control indicators
//if (elevator.maxPassengerCount() > this.overSizeTheashold) {
// return;
//}
let todos = this.getTasksForElevator(elevator);
let todosAbove = todos.filter(todo => todo.floorNum > floorNum);
let todosBelow = todos.filter(todo => todo.floorNum < floorNum);
if (todosAbove.length <= 0 && todosBelow.length <= 0) {
elevator.goingUpIndicator(true);
elevator.goingDownIndicator(true);
} else if (todosAbove.length <= 0) {
if (floorNum < this.floors.length - 1) {
// only disable the up indicator if there are floors above us (save some moves)
elevator.goingUpIndicator(false);
}
elevator.goingDownIndicator(true);
} else if (todosBelow.length <= 0) {
elevator.goingUpIndicator(true);
if (floorNum > 0) {
// only disable the down indicator if there are floors below us (save some moves)
elevator.goingDownIndicator(false);
}
}
},
removeTasks: function (todos) {
for (let todo of todos) {
let todoIndex = this.todos.indexOf(todo);
this.todos.splice(todoIndex, 1);
if (todo.currentElevator !== null) {
todo.currentElevator.currentTodo = null;
console.warn("removeTask removed a currently active task. This is probably wrong", todo);
}
}
},
findAndExecuteTask: function (elevator) {
let todo = this.findTask(elevator);
if (todo === null) {
return null;
}
this.executeTask(elevator, todo);
return todo;
},
findTask: function (elevator) {
let todos = this.getTasksForElevator(elevator);
todos = _.sortBy(todos, todo => this.prioritizeTaskForElevator(elevator, todo) * -1);
// consider all todos from best priority to worst
for (let todo of todos) {
let priority = this.prioritizeTaskForElevator(elevator, todo);
// check if the task is already done by another elevator and than check if we are better suited for that job
if (todo.currentElevator !== null && todo.currentElevator !== elevator) {
let otherPriority = this.prioritizeTaskForElevator(todo.currentElevator, todo);
if (priority <= otherPriority) {
continue;
}
}
// TODO check other todos with this floor as target.
// It may be possible to predict if another elevator will soon take this task
// FIXME oversize elevators can take on 2 incoming tasks on the same floor at once
// this should also be handled as it could result in unnessesary visits
return todo;
}
return null;
},
executeTask: function (elevator, todo) {
// if we are already doing that job than don't set it again (and save move count)
if (elevator.currentTodo === todo) {
return false;
}
// if the elevator is currently doing something else, then we need to properly leave the task for another elevator to accomplish
if (elevator.currentTodo !== null) {
elevator.currentTodo.currentElevator = null;
elevator.currentTodo = null;
}
// declare that this elevator will now execute this task
elevator.currentTodo = todo;
if (todo.currentElevator !== null) {
// the other find process should be aware of this elevator an not steal it back
this.findAndExecuteTask(todo.currentElevator);
}
// mark the task as ours after the other elevator has found a new one.
if (todo.currentElevator !== null) {
console.warn("Something went wrong, elevator task should have been switched");
}
todo.currentElevator = elevator;
// start doing that task
if (elevator.destinationDirection() === 'stopped' && todo.floorNum === elevator.currentFloor()) {
console.log("elevator passively executes task.", elevator, todo);
} else {
console.log("elevator now executes task.", elevator, todo);
elevator.stop();
elevator.goToFloor(todo.floorNum);
}
// control direction indicators
let direction = todo.direction;
if (direction === null) {
direction = todo.floorNum < elevator.currentFloor() ? 'down' : 'up';
}
// set the indicators except if we are on the outer floors where it doesn't matter if we set the indicatos
// that way it'll save a few moves
if (todo.floorNum < this.floors.length - 1) {
elevator.goingUpIndicator(direction === 'up');
}
if (todo.floorNum > 0) {
elevator.goingDownIndicator(direction === 'down');
}
},
prioritizeTaskForElevator: function (elevator, todo) {
// The priority is roughly based on the distance between the current and the target floor.
let priority = 0.0;
// sanity check. This task shouldn't even be considered for that elevator
if (todo.forElevator !== null && todo.forElevator !== elevator) {
return -Infinity;
}
// The closer a floor is the higher the priority
let floorDistance = Math.abs(elevator.currentFloor() - todo.floor.floorNum());
priority += this.floors.length - floorDistance;
// some challanges don't want ppl to wait so we should consider time a bit
let timeDifference = Math.round(this.time - todo.time);
priority += timeDifference * 0.2;
// some modifiers only apply if someone is on board
if (elevator.loadFactor() > 0.0) {
// if someone pressed in a direction we don't go, ignore that task (only incoming task have a direction)
if (todo.direction === 'up' && elevator.goingUpIndicator() == false) {
return -Infinity;
}
if (todo.direction === 'down' && elevator.goingDownIndicator() == false) {
return -Infinity;
}
// prioritize going in the direction the indicator says
if (elevator.goingUpIndicator() == false && todo.floorNum > elevator.currentFloor()) {
priority -= 3;
}
if (elevator.goingDownIndicator() == false && todo.floorNum < elevator.currentFloor()) {
priority -= 3;
}
}
// prefere not to break when already on the way in one direction
if (todo.floorNum >= elevator.currentFloor() && elevator.destinationDirection() === 'up') {
priority += 3;
}
if (todo.floorNum <= elevator.currentFloor() && elevator.destinationDirection() === 'down') {
priority += 3;
}
// the weight of the elevator is a factor between 0.0 and 1.0
// however, ppl have a weight of 55 to 100
// https://github.com/magwo/elevatorsaga/blob/9a2441af8976dc872c55ea6a5462b2a8ce50660b/world.js#L32
// I want x < 1.0 meaning that there is definitly space available
let correctedWeight = Math.min(1.0, elevator.loadFactor() / 0.55);
let weightFactor = Math.pow(correctedWeight, elevator.maxPassengerCount());
switch (todo.type) {
case 'incoming':
// if there is an outgoing task on the same floor:
let outgoingTodo = _.findWhere(this.todo, {type: 'outgoing', forElevator: elevator, floorNum: todo.floorNum});
if (outgoingTodo) {
// add some aspects of the outgoing priority
priority += outgoingTodo.halts;
priority += Math.round(this.time - outgoingTodo.time) * 0.2;
} else {
// if there is no outgoing task on the same floor, reduce the priority depending on weight
priority *= 1.0 - weightFactor;
}
// if this elevator is bigger than the others, give it extra priority for taking ppl who waited longer
// i assume that a longer wait means more ppl on the floor
let overSizeAmount = elevator.maxPassengerCount() - this.overSizeTheashold;
if (overSizeAmount > 0) {
priority += (timeDifference - overSizeAmount) * overSizeAmount;
}
break;
case 'outgoing':
// the more halts we did since a floor was requested for stops, the more we should consider halting there
priority += todo.halts;
// The more we weigh the more we should consider letting ppl out first.
priority *= 1.0 + weightFactor;
// TODO also check how close other outgoing tasks are
break;
}
// TODO maybe also weigh in if another elevator is already on the way in that direction/floor
return priority;
},
update: function(dt, elevators, floors) {
this.time += dt;
// for debugging
let logRows = [];
for (let floor of floors) {
let logs = ['floor' + this.pad(floor.floorNum(), 2)];
let todos = _.where(this.todos, {floorNum: floor.floorNum()});
logs.push([
_.findWhere(todos, {type: 'incoming', direction: 'up'}) ? '▲' : ' ',
_.findWhere(todos, {type: 'incoming', direction: 'down'}) ? '▼' : ' '
].join(''));
for (let elevator of this.elevators) {
let priorities = todos.map(todo => this.prioritizeTaskForElevator(elevator, todo));
let highestPriority = _.max(priorities);
let direction = '■';
switch (elevator.destinationDirection()) {
case "up": direction = '▲'; break;
case "down": direction = '▼'; break;
}
logs.push([
_.contains(todos, elevator.currentTodo) ? '➔' : ' ',
elevator.currentFloor() === floor.floorNum() ? direction : ' ',
_.findWhere(todos, {type: 'outgoing', forElevator: elevator}) ? '◀' : ' ',
this.pad(highestPriority <= -Infinity ? '' : highestPriority.toFixed(1).slice(0, 5), 5)
].join(''));
}
logRows.push(logs.join(" | "));
}
let msg = logRows.reverse().join("\n");
if (msg !== this.lastMsg) {
console.log("time: " + this.time.toFixed(1) + "\n" + msg);
this.lastMsg = msg;
}
},
pad: function (str, length) {
str = String(str);
while (str.length < length) {
str = ' ' + str;
}
return str;
},
getTodosByFloor: function () {
let floors = [];
for (let floor of this.floors) {
floors.push(_.where(this.todos, {floorNum: floor.floorNum()}));
}
return floors;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment