Mine output simulator

7 min read Original article ↗
/** * 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);