|
/** |
|
* 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); |