Last active
August 29, 2015 14:11
-
-
Save datchley/f746f449d3c8b795c4c9 to your computer and use it in GitHub Desktop.
Simple, single elevator implementation in javascript
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
function extend(/* objects */) { | |
var args = [].slice.call(arguments,0), | |
base = args.shift(), | |
len = args.length; | |
while (args.length) { | |
var o = args.pop(); | |
for (var prop in o) { | |
// properties override those existing | |
if (typeof base[prop] === 'undefined' && typeof o[prop] !== 'function') { | |
base[prop] = o[prop]; | |
} | |
} | |
} | |
return base; | |
} | |
/** | |
* Singleton object implementation of the elevator from the smiple-elevator.js | |
*/ | |
var Elevator = (function() { | |
var default_state = { | |
movement: 'idle', | |
current: { floor: 1, direction: null }, | |
target: null, | |
doors: 'closed', | |
running: false | |
}, | |
default_options = { | |
door_wait_time: 1500, | |
start_floor: 1, | |
idle_floor: 1 | |
}, | |
// for comparing request event types | |
qcmp = function(a,b) { | |
return a.floor > b.floor ? 1 : (a.floor < b.floor) ? -1 : 0; | |
}; | |
function Elevator(opts) { | |
// initial request queues | |
this.queues = { | |
'up': [], | |
'down': [], | |
'requests': [] | |
}; | |
this.options = extend(opts || {}, default_options); | |
this.state = default_state; | |
} | |
Elevator.prototype.start = function() { | |
this.state.running = true; | |
this.initEvents(); | |
}; | |
Elevator.prototype.stop = function() { | |
this.state.running = false; | |
this.state.queues['up'] = []; | |
this.state.queues['down'] = []; | |
this.state.queues['requests'] = []; | |
this.state.target = { floor: this.options.idle_floor, direction: (this.state.current.floor > this.state.target) ? 'down' : 'up' }; | |
this.nextAction(); | |
} | |
/** | |
* Initialize events for this elevator. All event data is in the format: | |
* | |
* { floor: {number}, direction: 'up|down' } | |
*/ | |
Elevator.prototype.initEvents = function() { | |
// Handle requests for elevator from a given floor | |
$(document).on('request.from', this.handleFloorRequest.bind(this)); | |
// Handle request within the elevator by button pushes | |
$(document).on('request.to', this.handleButtonRequest.bind(this)); | |
// Process actions on each elevator move (fired internally) | |
$(document).on('elevator.move', this.handleElevatorMove.bind(this)); | |
}; | |
/** | |
* Handle requests for the elevator triggered from an up/down button | |
* push on a given floor. | |
*/ | |
Elevator.prototype.handleFloorRequest = function(ev, data) { | |
// Add this floor request into the queue | |
this.queues['requests'].push(data); | |
this.nextAction(); | |
}; | |
/** | |
* Handle button pushes that request a certain floor from | |
* within the elevator. | |
*/ | |
Elevator.prototype.handleButtonRequest = function(ev, data) { | |
// Handle both up/down queues based on current floor | |
if (data.floor > this.state.current.floor) { | |
this.queues['up'].push(data); | |
this.queues['up'] = this.queues['up'].sort(qcmp); // sort ascending in up queue | |
} | |
if (data.floor < this.state.current.floor) { | |
this.queues['down'].push(data); | |
this.queues['down'] = this.queues['down'].sort(qcmp).reverse(); // sort desc in down queue | |
} | |
this.nextAction(); | |
}; | |
Elevator.prototype.handleElevatorMove = function(ev, data) { | |
var _this = this; | |
this.state.current.floor = data.floor; | |
// Save ourselves some work. | |
// Can we also service a request for this floor (in same direction) while we're here? | |
var servicable_requests = this.queues['requests'].filter(function(o) { | |
return o.floor === _this.state.current.floor && o.direction === _this.state.movement; | |
}); | |
// Are we at our target floor? | |
if (this.state.current.floor == this.state.target.floor) { | |
// stop and let people on/off | |
this.open(); | |
this.setNextTarget(); | |
} | |
// Takes care of requests from this floor during the stop | |
if (servicable_requests.length) { | |
this.open(); | |
// remove requests for this floor (in the same direction) from the requests queue | |
this.queues['requests'] = this.queues['requests'].filter(function(o) { | |
return o.floor != _this.state.current_floor && o.direction != _this.state.movement; | |
}); | |
} | |
// Nothing else to do on this floor now, check the queues | |
this.nextAction(); | |
}; | |
/** | |
* Primary processing logic. On each request event, we check to see what the next action | |
* is that the elevator should take. Setting the appropriate state and triggering a movement | |
* event if a movement is necessary. | |
*/ | |
Elevator.prototype.nextAction = function() { | |
var _this = this; | |
// Can't go anywhere until doors close, | |
// close the doors after wait period and recall ourselves | |
if (this.state.doors == 'open') { | |
setTimeout(function() { | |
_this.close(); | |
_this.nextAction(); | |
}, this.options.door_wait_time); | |
return; | |
} | |
// Attempt to get a target floor for movement if we don't have one | |
if (!this.state.target) { | |
this.setNextTarget(); | |
} | |
if (this.state.target) { | |
this.state.movement = this.state.current.floor < this.state.target.floor ? 'up' : 'down'; | |
var next = this.getNextFloor(); | |
console.log("> [request] at floor [%d] moving (%s) to target %d, next floor = %d", this.state.current.floor, this.state.movement, this.state.target.floor, next); | |
// Trigger a move event that needs to happen | |
$(document).trigger('elevator.move', { floor: next }); | |
} | |
else { | |
// There was no target to move to, so just idle... | |
this.state.movement = 'idle'; | |
this.state.target = undefined; | |
console.log("> idling..., no queued floors or requests!"); | |
} | |
}; | |
/** | |
* Determine the next floor to move to relative to the current_floor and | |
* the current movement direction. | |
*/ | |
Elevator.prototype.getNextFloor = function() { | |
return this.state.movement === 'up' ? (this.state.current.floor + 1) : (this.state.current.floor - 1); | |
}; | |
/** | |
* Sets the next target floor to move to based on the current state | |
* of the request queues and our current movement direction. | |
* Note: Up and Down requests from button pushes get precedence over floor | |
* requests in the queues. | |
*/ | |
Elevator.prototype.setNextTarget = function() { | |
this.state.target = null; | |
if (this.state.movement === 'up') { | |
// Keep processing up requests if we're moveing up | |
if (this.queues['up'].length) | |
this.state.target = this.queues['up'].shift(); | |
else { | |
// Otherwise, see if there are down requests | |
if (this.queues['down'].length) { | |
this.state.movement = 'down'; | |
this.state.target = this.queues['down'].shift(); | |
} | |
} | |
} | |
else if (this.state.movement === 'down') { | |
// Keep processing down requests if we're moving down | |
if (this.queues['down'].length) | |
this.state.target = this.queues['down'].shift(); | |
else { | |
// Otherwise, see if we have up requests | |
if (this.queues['up'].length) { | |
this.state.movement = 'up'; | |
this.state.target = this.queues['up'].shift(); | |
} | |
} | |
} | |
if (!this.state.target && this.queues['requests'].length) { | |
// No up/down requests, so process any floor requests now | |
this.state.target = this.queues['requests'].shift(); | |
} | |
}; | |
/** | |
* Open the elevator doors, if closed. | |
*/ | |
Elevator.prototype.open = function() { | |
if (this.state.doors === 'closed') { | |
this.state.doors = 'open'; | |
console.log("-> opening doors at floor %d, waiting ...", this.state.current.floor); | |
} | |
}; | |
/** | |
* Close the elevator doors. | |
*/ | |
Elevator.prototype.close = function() { | |
this.state.doors = 'closed'; | |
console.log("-> closing doors"); | |
}; | |
return Elevator; | |
})(); |
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
/* | |
* Simple, single elevator implementation example. | |
* Heuristics: | |
* - Requests for the elevator and button presses in the elevator are separate actions. | |
* - Continue traveling in the same direction while there are remaining requests in that same direction. | |
* - If there are no further requests in that direction, then stop and become idle, or change direction | |
* if there are requests in the opposite direction. | |
* - If there no no more requests in either direction, process any requests from floors, in order and following | |
* the current direction. | |
* | |
*/ | |
var up = [4,5], // better as heap structures with 'time' priority | |
down = [2], | |
requests = [3, 5], | |
current_floor = 1, | |
target_floor = null, | |
movement = 'idle', | |
doors = 'closed', | |
door_wait_time = 1500; // 1.5 sec, get on quick! | |
// Button pushes on floor requesting elevator for a given direction | |
$(document).on('request.from', function(ev, data) { | |
requests.push(data); | |
moveNext(); | |
}); | |
// Button pushes in elevator | |
$(document).on('request.to', function(ev, data) { | |
if (data.floor > current_floor) { | |
up.push(data) | |
up = up.sort(); | |
} | |
if (data.floor < current_floor) { | |
down.push(data); | |
down = down.sort().reverse(); | |
} | |
moveNext(); | |
}); | |
// Trigger on each floor as elevator moves: { floor: N } | |
$(document).on('elevator.move', function(ev, data) { | |
current_floor = data.floor; | |
// Can we also service a request for this floor while we're here? | |
var servicable_requests = requests.filter(function(o) { return o.floor === current_floor && o.dir === movement; }); | |
// Are we at our target floor? | |
if (current_floor == target_floor) { | |
// stop and let people on/off | |
open(); | |
target_floor = getNextTarget(); | |
} | |
// Takes care of requests from this floor during the stop | |
if (servicable_requests.length) { | |
open(); | |
servicable_requests.forEach(function(r) { | |
requests.splice(requests.indexOf(r), 1); | |
}); | |
} | |
// Nothing else to do on this floor now, check the queues | |
moveNext(); | |
}); | |
function open() { | |
if (doors === 'closed') { | |
doors = 'open'; | |
console.log("-> opening doors at floor %d, waiting ...", current_floor); | |
} | |
} | |
function close() { | |
doors = 'closed'; | |
console.log("-> closing doors"); | |
} | |
function getNextTarget() { | |
if (movement === 'up') { | |
if (up.length) return up.shift(); | |
else { | |
if (down.length) { | |
movement = 'down'; | |
return down.shift(); | |
} | |
} | |
} | |
if (movement === 'down') { | |
if (down.length) return down.shift(); | |
else { | |
if (up.length) { | |
movement = 'up'; | |
return up.shift(); | |
} | |
} | |
} | |
if (requests.length) return requests.shift(); | |
return undefined; | |
} | |
function moveNext() { | |
var next; | |
// Can't go anywhere until doors close | |
if (doors == 'open') { | |
setTimeout(function() { | |
close(); | |
moveNext(); | |
}, door_wait_time); | |
return; | |
} | |
// Attempt to get a target if we don't have one | |
if (!target_floor) { | |
target_floor = getNextTarget(); | |
} | |
if (target_floor) { | |
movement = current_floor < target_floor ? 'up' : 'down'; | |
next = getNextFloor(); | |
console.log("> [request] at floor [%d] moving (%s) to target %d, next floor = %d", current_floor, movement, target_floor, next); | |
$(document).trigger('elevator.move', { floor: next }); | |
} | |
else { | |
// There was no target to move to, so just idle... | |
movement = 'idle'; | |
target_floor = undefined; | |
console.log("> idling..., no queued floors or requests!"); | |
} | |
} | |
function getNextFloor() { | |
return movement === 'up' ? (current_floor + 1) : (current_floor - 1); | |
} | |
// Simulate using dummy data | |
moveNext(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment