Skip to content

Instantly share code, notes, and snippets.

@CodingNinja
Last active August 29, 2015 14:06
Show Gist options
  • Save CodingNinja/86d84cec11bae0f4ec66 to your computer and use it in GitHub Desktop.
Save CodingNinja/86d84cec11bae0f4ec66 to your computer and use it in GitHub Desktop.
Mine output simulator
/**
* Dump Truck simulator
*
* Simple simulator that emulates a mining environment and allows
* one to plot the potential output of a mines operation.
*
* @author David Mann <ninja@codingninja.com.au>
*/
var currentTime = 1, runTime = ((60*1)*60), events = [], tonnageCarried = 0, maxSimultaneousDump = 1;
var Truck = function(ref, tonnage, kmPh, dumpTime) {
var that = this;
that.tonnage = tonnage;
that.kmPh = kmPh;
that.dumpTime = dumpTime;
that.full = false;
that.location = null;
that.busy = false;
that.markEmpty = function(loc) {
events.push({
name: that.toString() + ' completed dumping',
location: loc.toString(),
truck: that.toString(),
time: currentTime
});
that.full = false;
};
that.tick = function() {
};
that.isEmpty = function() {
return that.full === false;
};
that.markFull = function(loc) {
events.push({
name: that.toString() + ' full ' + loc.toString(),
truck: that.toString(),
location: loc.toString(),
time: currentTime
});
tonnageCarried += that.tonnage;
return that.full = true;
};
/**
* Calculate the time taken to travel x distance
* @param Number x The distance
* @return {[type]} [description]
*/
that.timeForDistance = function(x) {
return Math.ceil((x / kmPh) * 60) * 60;
};
/**
* Get the time it would take for that truck to travel to loc;
* @param Location loc The location
* @return {[type]} [description]
*/
that.timeToLocation = function(loc) {
return Math.ceil((loc.getDistanceFromLocation(that.location) / kmPh) * 60) * 60;
};
/**
* Get the time the truck would arrive at location
* @param {[type]} loc [description]
* @return {[type]} [description]
*/
that.getArrivalTime = function(loc) {
// if(loc === that.location && that.empty) {
// return that.willArriveAt();
// }
return that.timeToLocation(loc) + currentTime;
};
/**
* Find out how long it will take to fill the this truck at location
* @param {[type]} location The location that will load the truck
* @return {[type]} [description]
*/
that.timeToFill = function (location) {
if(location.isDumpSite()) {
return that.dumpTime;
}
return Math.ceil(that.tonnage / location.shovelCap) * location.shovelTime;
};
that.toString = function() {
return ref;
};
return that;
};
var Location = function(ref, isDumpSite, shovelCap, shovelTime) {
var that = this;
that.ref = ref;
that.isDumpSite = isDumpSite;
that.shovelCap = shovelCap;
that.shovelTime = shovelTime;
that.enRoute = {};
that.shovelWaiting = {};
that.shovelBusy = false;
that.shovelNextFreeAt = null;
that.estimatedQueue = 0;
that.shovelWaitingCount = 0;
that.trucksInBays = 0;
that.distanceFromLocation = {};
that.shovelInUse = {};
that.tick = function() {
for(arrivalTime in that.enRoute) {
if(Number(currentTime) === Number(arrivalTime)) {
var arrivingNow = that.enRoute[arrivalTime];
for (var i = arrivingNow.length - 1; i >= 0; i--) {
var truck = arrivingNow[i];
events.push({
name: truck.toString() + ' arrived at ' + (that.isDumpSite() ? 'dump': 'fill') + ' site "' + that.toString() + '"',
truck: truck.toString(),
location: that.toString(),
time: currentTime
});
that.notifyShovel(truck);
};
delete that.enRoute[arrivalTime];
}
}
that.processShovel();
that.shovelBusy = that.assertShovelBusy();
};
that.processShovel = function() {
if(!(typeof that.shovelInUse[currentTime] === 'undefined')) {
for (var i = that.shovelInUse[currentTime].length - 1; i >= 0; i--) {
var truckReady = that.shovelInUse[currentTime][i];
delete that.shovelInUse[currentTime][i];
that.trucksInBays--;
if(that.isDumpSite()) {
truckReady.markEmpty(that);
}else{
truckReady.markFull(that);
}
truckReady.busy = false;
};
}
if(!(typeof that.shovelWaiting[currentTime] === 'undefined')) {
for (var i = that.shovelWaiting[currentTime].length - 1; i >= 0; i--) {
var truckReady = that.shovelWaiting[currentTime][i];
that.startShovel(truckReady, that.calculateShovelTime(truckReady) + currentTime);
that.shovelWaitingCount--;
delete that.shovelWaiting[currentTime][i];
};
}
};
that.startShovel = function(truck, shovelTime) {
that.trucksInBays++;
events.push({
name: that.toString() + ' shovel started to '+(that.isDumpSite() ? 'dump': 'fill')+' ' + truck.toString() + ' complete at ' + shovelTime,
truck: truck.toString(),
location: that.toString(),
time: currentTime
});
if(typeof that.shovelInUse[shovelTime] === 'undefined') {
that.shovelInUse[shovelTime] = [];
}
that.shovelInUse[shovelTime].push(truck);
}
that.notifyShovel = function(truck) {
var shovelNextFreeAt = that.calculateShovelNextFreeAt();
events.push({
name: truck.toString() + ' waiting for free shovel at ' + that.toString() + '(Queue size: ' + that.shovelWaitingCount + ')',
truck: truck.toString(),
location: that.toString(),
time: currentTime
});
if(typeof that.shovelWaiting[shovelNextFreeAt] === 'undefined') {
that.shovelWaiting[shovelNextFreeAt] = [];
}
that.shovelWaiting[shovelNextFreeAt].push(truck);
that.shovelWaitingCount++;
that.shovelNextFreeAt = shovelNextFreeAt + that.calculateShovelTime(truck);
};
that.calculateShovelNextFreeAt = function() {
return Math.max(that.shovelNextFreeAt, currentTime) || (currentTime);
};
that.assertShovelBusy = function() {
if(that.isDumpSite() && that.trucksInBays >= maxSimultaneousDump) {
return true;
}else{
return that.trucksInBays > 0;
}
return that.calculateShovelNextFreeAt() <= currentTime;;
};
that.calculateShovelTime = function(truck) {
if(that.isDumpSite()) {
return truck.dumpTime;
}else{
return Math.ceil((truck.tonnage / that.shovelCap) * that.shovelTime);
}
};
/**
* Record that a truck is on it's way here
* @param {[type]} truck [description]
* @return {[type]} [description]
*/
that.notifyEnRoute = function(truck) {
var arrivalTime = truck.getArrivalTime(that);
if(typeof that.enRoute[arrivalTime] === 'undefined') {
that.enRoute[arrivalTime] = [];
}
truck.location = that;
truck.busy = true;
that.enRoute[arrivalTime].push(truck);
// Minus the current time so that the queue is just a queue time, not "end of queue" time
events.push({
name: truck.toString() + ' enroute to ' + (that.isDumpSite() ? 'dump': 'fill') + ' site "' + that.toString() + '", arrive at ' + arrivalTime + ' and start ' + (that.isDumpSite() ? 'dumping': 'loading') + ' at ' + Math.max(that.estimatedQueue, arrivalTime),
truck: truck.toString(),
location: that.toString(),
time: currentTime,
});
that.estimatedQueue = arrivalTime + (Math.max(that.estimatedQueue - currentTime, 0)) + truck.timeToFill(that);
};
/**
* Is this a dump site?
*
* @return {Boolean} True if this location is used for dumping or false if used for filling
*/
that.isDumpSite = function() {
return isDumpSite === true;
};
/**
* Get the trucks en route to this location
* @return {[type]} [description]
*/
that.getEnRoute = function(compareTime) {
var enRoute = [];
if(typeof compareTime === 'undefined') {
compareTime = currentTime;
}
for(arrivalTime in that.enRoute) {
if(arrivalTime >= compareTime) {
enRoute = enRoute.concat(that.enRoute[arrivalTime]);
}
}
return enRoute;
};
/**
* Get the trucks en route to this location
* @return {[type]} [description]
*/
that.getQueueTimeAt = function(arrivalTime) {
var enRoute = that.getEnRoute(arrivalTime);
return enRoute;
};
/**
* Get the trucks en route to this location
* @return {[type]} [description]
*/
that.getQueueTime = function(truck) {
return that.getQueueTimeAt(truck.getArrivalTime(that));
};
that.getDistanceFromLocation = function(loc) {
return that.distanceFromLocation[loc.ref];
};
that.setDistanceFromLocation = function(loc, distance) {
if(that.getDistanceFromLocation(loc) !== distance) {
that.distanceFromLocation[loc.ref] = distance;
loc.setDistanceFromLocation(that, distance);
}
};
/**
* Return the name of the location
* @return {[type]} [description]
*/
that.toString = function() {
return ref;
};
return that;
};
// Locations available
// Is dump site? | Shovel Cap | Shovel time
var loc1 = new Location('loc1', true), // Dump site
loc2 = new Location('loc2', true), // Dump site
loc3 = new Location('loc3', true), // Dump site
loc4 = new Location('loc4', false, 45, 60), // Fill site
loc5 = new Location('loc5', false, 45, 60), // Fill site
loc6 = new Location('loc6', false, 35, 60); // Fill site
// Set the distance between fill sites and dump sites
// The distance setter will set the inverse
// relationship so no we are not required to
// explicitly set distances on both locations
loc1.setDistanceFromLocation(loc4, 200);
loc1.setDistanceFromLocation(loc5, 400);
loc1.setDistanceFromLocation(loc6, 600);
loc2.setDistanceFromLocation(loc4, 100);
loc2.setDistanceFromLocation(loc5, 200);
loc2.setDistanceFromLocation(loc6, 300);
loc3.setDistanceFromLocation(loc4, 500);
loc3.setDistanceFromLocation(loc5, 1000);
loc3.setDistanceFromLocation(loc6, 1500);
//loc1.setDistanceFromLocation(loc3, 3);
// All locations grouped together
var locations = [
loc1,
loc2,
loc3,
loc4,
loc5,
loc6
];
// Trucks available
// Tonnage Km/Ph Dump Time
var truck1 = new Truck('truck1', 200, 3500, 60),
truck2 = new Truck('truck2', 200, 3500, 60),
truck3 = new Truck('truck3', 200, 3500, 60),
truck4 = new Truck('truck4', 200, 3500, 60),
truck5 = new Truck('truck5', 200, 3500, 60),
truck6 = new Truck('truck6', 200, 3500, 60);
// Set both trucks at initial dump site
truck1.location = loc1;
truck2.location = loc2;
truck3.location = loc3;
truck4.location = loc1;
truck5.location = loc2;
truck6.location = loc3;
// All trucks grouped together
var trucks = [
truck1,
truck2,
truck3,
truck4,
truck5,
truck6
];
// Take the current second and look at what needs to go where and send them off
function moveTrucks () {
var truckNum;
for(truckNum in trucks) {
trucks[truckNum].tick();
}
for(locationNum in locations) {
locations[locationNum].tick();
}
for(truckNum in trucks) {
var truck = trucks[truckNum];
if(truck.busy) {
continue;
}else if(truck.isEmpty()) {
var locationTo = null, minQueueTime = null, locationNum;
for(locationNum in locations) {
var location = locations[locationNum],
queueTime = location.estimatedQueue - currentTime;
if(location.isDumpSite() || location.ref === truck.location.ref) {
continue;
}
if(minQueueTime === null || minQueueTime > queueTime) {
minQueueTime = queueTime;
locationTo = location;
}
}
if(locationTo) {
locationTo.notifyEnRoute(truck);
}
}else if(truck.isEmpty() === false) {
var locationTo = null, minQueueTime = null, locationNum;
for(locationNum in locations) {
var location = locations[locationNum],
queueTime = location.estimatedQueue - currentTime;
if(!location.isDumpSite() || location.ref === truck.location.ref) {
continue;
}
if(minQueueTime === null || minQueueTime > queueTime) {
minQueueTime = queueTime;
locationTo = location;
}
}
if(locationTo) {
locationTo.notifyEnRoute(truck);
}
}
}
}
while(currentTime < runTime) {
moveTrucks();
currentTime++;
}
console.table(events);
console.log('Total tonnage: %d', tonnageCarried);
// setInterval(moveTrucks, 1000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment