Skip to content

Instantly share code, notes, and snippets.

@SinisterMinister
Last active September 23, 2015 16:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SinisterMinister/899e1c1b40220319a9bb to your computer and use it in GitHub Desktop.
Save SinisterMinister/899e1c1b40220319a9bb to your computer and use it in GitHub Desktop.
{
init: function (elevators, floors) {
var DS = this.api.instances.DispatchServiceInstance = new this.api.services.DispatchService(this.api, floors),
ES = this.api.instances.ElevatorServiceInstance = new this.api.services.ElevatorService(this.api, elevators);
DS.on("new_task", function () {
console.info("New task", this);
// if (ES.idleElevatorsAvailable())
// ES.processTask(DS.nextTask());
});
ES.on("idle_elevator_available", function () {
console.info("Elevator idle", this);
// var task = DS.nextTask();
// if (task === undefined)
// return;
// ES.processTask(task);
});
},
update: function (dt, elevators, floors) {
var DS = this.api.instances.DispatchServiceInstance,
ES = this.api.instances.ElevatorServiceInstance;
// GC ALL THE THINGS
DS.gcTasks();
// Delegate the tasks as idle elevators become available
if (ES.idleElevatorsAvailable())
ES.processTask(DS.nextTask());
},
api: {
models: {
Task: function (floorNum, direction) {
this.floorNum = floorNum;
this.direction = direction;
this.timestamp = Date.now();
}
},
instances: {
DispatchServiceInstance: null,
ElevatorServiceInstance: null
},
services: {
DispatchService: (function () {
var DispatchService = function (api, floors) {
this._floors = floors;
this._api = api;
this._taskQueue = [];
this._eventBindings = {};
this._setupBindings();
};
DispatchService.prototype._setupBindings = function() {
console.info("Binding dispatch events");
var self = this;
this._floors.forEach(function (floor) {
floor.handled = {
up: false,
down: false
};
floor.on("up_button_pressed", self._getUpButtonHandler());
floor.on("down_button_pressed", self._getDownButtonHandler());
});
};
/*********************************************************
* *
* Floor Event Handlers *
* *
*********************************************************/
DispatchService.prototype._getUpButtonHandler = function() {
var self = this;
return function () {
self.queueTask(this.floorNum(), "up");
};
};
DispatchService.prototype._getDownButtonHandler = function() {
var self = this;
return function () {
self.queueTask(this.floorNum(), "down");
};
};
/*********************************************************
* *
* Public API *
* *
*********************************************************/
DispatchService.prototype.queueTask = function(floorNum, direction) {
// Create a new task
var task = new this._api.models.Task(floorNum, direction);
// Push the task into the queue
this._taskQueue.push(task);
// Trigger an event
this.trigger("new_task", task);
};
DispatchService.prototype.hasPassengersWaiting = function(floorNum, direction) {
var floor = this.getFloor(floorNum);
return (floor && floor.buttonStates[direction] !== "");
};
DispatchService.prototype.markFloorAsHandled = function(floorNum, direction) {
var floor = this.getFloor(floorNum);
if (floor)
floor.handled[direction] = true;
};
DispatchService.prototype.markFloorAsUnhandled = function(floorNum, direction) {
var floor = this.getFloor(floorNum);
if (floor)
floor.handled[direction] = false;
};
DispatchService.prototype.isFloorHandled = function(floorNum, direction) {
var floor = this.getFloor(floorNum);
return (floor && floor.handled[direction]);
};
DispatchService.prototype.getFloor = function(floorNum) {
var floor = null;
for (var i = this._floors.length - 1; i >= 0; i--) {
if (this._floors[i].floorNum() === floorNum) {
floor = this._floors[i];
break;
}
};
return floor;
};
DispatchService.prototype.getFloorCount = function() {
return this._floors.length;
};
DispatchService.prototype.getTopFloorNum = function() {
return this.getFloorCount() - 1;
};
DispatchService.prototype.nextTask = function() {
if (this._taskQueue.length === 0) console.warn("Task queue is empty");
return this._taskQueue.shift();
};
DispatchService.prototype.findTasks = function(floorNum, direction) {
var tasks = [];
this._taskQueue.forEach(function (task) {
if (task.floorNum === floorNum && task.direction === direction)
tasks.push(task);
});
return tasks;
};
DispatchService.prototype.clearTasks = function(floorNum, direction) {
var tasks = this.findTasks(floorNum, direction),
self = this;
tasks.forEach(function (task) {
self.clearTask(task);
});
};
DispatchService.prototype.clearTask = function(task) {
if (this._taskQueue.indexOf(task) > -1)
this._taskQueue.splice(this._taskQueue.indexOf(task), 1);
};
DispatchService.prototype.gcTasks = function() {
var DS = this;
this._taskQueue.forEach(function (task) {
// Check to see if the floor is still waiting for pickup
if (DS.getFloor(task.floorNum).buttonStates[task.direction] === "") {
console.warn("Clearing task", task, "Button State:", DS.getFloor(task.floorNum).buttonStates[task.direction]);
DS.clearTask(task);
}
});
};
DispatchService.prototype.trigger = function (event, context) {
var self = this,
context = context || self;
// Get the callbacks for the event
if (this._eventBindings[event] !== undefined) {
this._eventBindings[event].forEach(function (callback) {
callback.call(context);
});
}
};
DispatchService.prototype.on = function (event, callback) {
// Setup the container
this._eventBindings[event] = this._eventBindings[event] || [];
// Add the callback
this._eventBindings[event].push(callback);
};
return DispatchService;
})(),
ElevatorService: (function () {
var ElevatorService = function (api, elevators) {
this._api = api;
this._elevators = elevators;
this._idleElevators = [];
this._eventBindings = {};
this._setupBindings();
};
ElevatorService.prototype._setupBindings = function() {
console.info("Binding elevator events");
var self = this;
this._elevators.forEach(function (elevator, idx) {
elevator.service = self;
elevator.ignoreStops = false;
elevator.currentTask = null;
elevator.id = idx
elevator.on("idle", self._getIdleHandler());
elevator.on("floor_button_pressed", self._getFloorButtonHandler());
elevator.on("stopped_at_floor", self._getFloorStopHandler());
elevator.on("passing_floor", self._getFloorPassHandler());
});
};
/*********************************************************
* *
* Elevator Event Handlers *
* *
*********************************************************/
ElevatorService.prototype._getIdleHandler = function() {
var ES = this,
DS = this._api.instances.DispatchServiceInstance;
return function () {
ES._idleElevators.push(this);
ES.trigger("idle_elevator_available", this);
if (DS.getFloorCount() > 6 && ES.getElevatorCount() > 3 && this.currentFloor() !== ES.getIdlePosition(this)) {
ES.sendElevatorToFloor(this, ES.getIdlePosition(this), false, true);
this.destinationQueue.splice(0);
}
};
};
ElevatorService.prototype._getFloorButtonHandler = function() {
var self = this,
DS = this._api.instances.DispatchServiceInstance;
return function (floorNum) {
console.info("Passenger has requested elevator", this.id, "to go to floor", floorNum);
self.sendElevatorToFloor(this, floorNum);
};
};
ElevatorService.prototype._getFloorPassHandler = function() {
var self = this,
DS = self._api.instances.DispatchServiceInstance;
return function (floorNum, direction) {
console.info("Elevator", this.id, "destination queue", this.destinationQueue);
if ( ! self.isButtonPressed(this, floorNum)) {
if (this.ignoreStops) {
console.info("Elevator", this.id, "is skipping floor", floorNum, "as it is ignoring all stops");
return;
}
if (this.loadFactor() > 0.6) {
console.info("Elevator", this.id, "is skipping floor", floorNum, "as it is probably full", this.loadFactor());
return;
}
if ( ! DS.hasPassengersWaiting(floorNum, direction)) {
console.info("Elevator", this.id, "is skipping floor", floorNum, "as there are no passengers waiting");
return;
}
if (DS.isFloorHandled(floorNum, direction)) {
console.info("Elevator", this.id, "is skipping floor", floorNum, "as it has been marked as handled");
return;
}
}
// People are waiting so lets stop
self.sendElevatorToFloor(this, floorNum);
// Mark the elevator as handled
DS.markFloorAsHandled(floorNum, direction);
console.info("Elevator", this.id, "is marking floor", floorNum, "as handled");
};
};
ElevatorService.prototype._getFloorStopHandler = function() {
var self = this,
DS = self._api.instances.DispatchServiceInstance;
return function (floorNum) {
console.info("Elevator", this.id, "has stopped at floor", floorNum);
DS.markFloorAsUnhandled(floorNum, self.getNextDirection(this));
self.clearElevatorTasks(floorNum, self.getNextDirection(this));
self.setDirectionIndicator(this, self.getNextDirection(this));
if (this.currentTask !== null && this.currentTask.floorNum === floorNum) {
this.currentTask = null;
}
this.ignoreStops = false;
};
};
/*********************************************************
* *
* Public API *
* *
*********************************************************/
ElevatorService.prototype.trigger = function (event, context) {
var self = this,
context = context || self;
// Get the callbacks for the event
if (this._eventBindings[event] !== undefined) {
this._eventBindings[event].forEach(function (callback) {
callback.call(context);
});
}
};
ElevatorService.prototype.on = function (event, callback) {
// Setup the container
this._eventBindings[event] = this._eventBindings[event] || [];
// Add the callback
this._eventBindings[event].push(callback);
};
ElevatorService.prototype.sendElevatorToFloor = function(elevator, floorNum, force, remainIdle) {
// Remove from idle elevators
if ( ! remainIdle && this._idleElevators.indexOf(elevator) > -1)
this._idleElevators.splice(this._idleElevators.indexOf(elevator), 1);
if (force && this.getElevatorCount() > 1 ) {
elevator.ignoreStops = true;
return elevator.goToFloor(floorNum, true);
}
if (elevator.destinationQueue.indexOf(floorNum) > -1)
return;
// Set the elevator direction indicator
this.setDirectionIndicator(elevator, this.getDirectionToFloor(elevator, floorNum));
elevator.goToFloor(floorNum);
elevator.destinationQueue.sort(function (a,b){return a-b;});
if (this.getDirectionToFloor(elevator, floorNum) === "down")
elevator.destinationQueue.reverse();
elevator.checkDestinationQueue();
};
ElevatorService.prototype.getIdlePosition = function(elevator) {
if (elevator.id === 0)
return 0;
if (elevator.id === this.getElevatorCount() - 1)
return this._api.instances.DispatchServiceInstance.getTopFloorNum();
var floorCount = this._api.instances.DispatchServiceInstance.getFloorCount() - 2,
elevatorCount = this.getElevatorCount() - 2;
return Math.floor(floorCount / elevatorCount) * elevator.id;
};
ElevatorService.prototype.setDirectionIndicator = function(elevator, direction) {
elevator.goingDownIndicator(false);
elevator.goingUpIndicator(false);
if (direction == "up")
elevator.goingUpIndicator(true);
if (direction == "down")
elevator.goingDownIndicator(true);
};
ElevatorService.prototype.getDirectionToFloor = function(elevator, floorNum) {
if (elevator.currentFloor() < floorNum)
return "up";
else
return "down";
};
ElevatorService.prototype.getClosestIdleElevator = function(floorNum) {
var currentBest = null;
// Find the best idle elevator
for (var i = this._idleElevators.length - 1; i >= 0; i--) {
if (currentBest === null)
currentBest = this._idleElevators[i];
else {
if (this.getElevatorDistance(this._idleElevators[i], floorNum) < this.getElevatorDistance(currentBest, floorNum)) {
// Found a better elevator
currentBest = this._idleElevators[i];
}
}
};
return currentBest;
};
ElevatorService.prototype.isButtonPressed = function(elevator, floorNum) {
var floors = elevator.getPressedFloors();
for (var i = floors.length - 1; i >= 0; i--) {
if (floors[i] === floorNum)
return true;
};
return false;
};
ElevatorService.prototype.getElevatorCount = function() {
return this._elevators.length;
};
ElevatorService.prototype.getElevatorDistance = function (elevator, floorNum) {
return Math.abs(elevator.currentFloor() - floorNum);
};
ElevatorService.prototype.idleElevatorsAvailable = function() {
return this._idleElevators.length > 0;
};
ElevatorService.prototype.processTask = function(task) {
if (task === undefined)
return;
var elevator = this.getClosestIdleElevator(task.floorNum);
if (elevator === null) {
console.error("Could not find an elevator to service the task", this._idleElevators);
return false;
}
console.info("Delegating task", task, "to elevator", elevator.id);
elevator.currentTask = task;
this.sendElevatorToFloor(elevator, task.floorNum, this.getElevatorCount() > 2);
this._api.instances.DispatchServiceInstance.markFloorAsHandled(task.floorNum, this.getDirectionToFloor(elevator, task.floorNum));
return true;
};
ElevatorService.prototype.clearElevatorTasks = function(floorNum, direction) {
var ES = this;
this._elevators.forEach(function (elevator) {
if (elevator.currentTask !== null && elevator.currentTask.floorNum === floorNum && elevator.currentTask.direction === direction) {
// Clear the task
elevator.currentTask = null;
// Reset the queue to only what's currently pressed
var floors = elevator.getPressedFloors(),
closest = null;
// Find the closest
floors.forEach(function (floor) {
if (closest === null || ES.getElevatorDistance(elevator, closest) > ES.getElevatorDistance(elevator, floor))
closest = floor;
});
if (closest !== null) {
// Sort the floors
floors.sort(function (a,b) {return a-b;})
// Reverse the sort based on direction
if (ES.getDirectionToFloor(elevator, closest) === "down")
floors.reverse();
// Set the new destination queue
elevator.destinationQueue = floors;
elevator.checkDestinationQueue();
}
// Stop the elevator if necessary
if (elevator.destinationQueue.length === 0) {
console.warn("Elevator", elevator.id, "going idle as nothing is left to do.");
elevator.trigger("idle");
}
}
});
};
ElevatorService.prototype.getNextDirection = function(elevator) {
var DS = this._api.instances.DispatchServiceInstance;
if (elevator.currentTask !== null && elevator.currentTask.floorNum === elevator.currentFloor())
return elevator.currentTask.direction;
if (elevator.destinationQueue.length > 0)
return this.getDirectionToFloor(elevator, elevator.destinationQueue[0]);
if (elevator.currentFloor() === 0)
return "up";
if (elevator.currentFloor() === DS.getTopFloorNum())
return "down";
if (DS.hasPassengersWaiting(elevator.currentFloor(), "up"))
return "up";
if (DS.hasPassengersWaiting(elevator.currentFloor(), "down"))
return "down";
return "stopped";
};
return ElevatorService;
})()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment