Created
December 21, 2018 15:41
-
-
Save Nemo64/79afb6233be95f43237b693c08c1b70b to your computer and use it in GitHub Desktop.
my solution to the elevator game (not perfect)
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
| { | |
| 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