Skip to content

Instantly share code, notes, and snippets.

@datchley
Last active August 29, 2015 14:11
Show Gist options
  • Save datchley/f746f449d3c8b795c4c9 to your computer and use it in GitHub Desktop.
Save datchley/f746f449d3c8b795c4c9 to your computer and use it in GitHub Desktop.
Simple, single elevator implementation in javascript
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;
})();
/*
* 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