Beispiel #1
0
 /**
  * Parse a build order
  * @param string $buildOrder
  */
 public function __construct($buildOrder)
 {
     Logger::enter("Parser::_construct");
     $this->checkpoints = array();
     $this->options = array();
     $this->jobs = array();
     // parse line-by-line
     $lines = preg_split("/([\n])/", $buildOrder);
     foreach ($lines as $lineNumber => $line) {
         $this->read($lineNumber, trim($line));
     }
     if (count($this->jobs) == 0) {
         throw_error("No commands found in build order!", "Your build order is empty. For a quick demonstration of the workings of this calculator, click one of the examples listed under <i>Examples of complete build orders</i>.");
     }
     Logger::leave("Parser::_construct");
 }
Beispiel #2
0
 /**
  * Process a single job, update the timeline accordingly, and handle all
  * job-specific tasks.
  * @global Product $SpawnLarvae
  * @global Product $Warpgate
  * @global Product $ScoutingWorker
  * @param Job $job
  * @param bool $intheFuture
  */
 public function process($job, $intheFuture = false)
 {
     global $SpawnLarvae, $Warpgate, $ScoutingWorker;
     Logger::enter("Timeline::process");
     if ($this->debug || Hatcheries::$debug) {
         tracemsg("Timeline::process(" . $job . ", " . ($intheFuture ? "true" : "false") . ")");
     }
     // reset time completed
     $job->timeCompleted = INF;
     // handle mutations up to job start
     foreach ($job->mutations() as $mutation) {
         if ($mutation->time < $job->timeStarted) {
             $this->income->splice($mutation);
         }
     }
     // handle checkpoints up to job start
     if (!$intheFuture) {
         $this->processCheckpoints($job->timeStarted);
     }
     // update all
     if (!$intheFuture) {
         $this->update($job->timeStarted);
     }
     // expend resources
     $this->income->expend($job->mineralCost(), $job->gasCost());
     // when is job completed
     $job->timeCompleted = $job->timeStarted + $job->duration();
     // refund resources
     $this->income->expend(-$job->gasRefund(), 0);
     $this->income->expend(-$job->mineralRefund(), 0);
     // use production queues
     list($queueTypesExpended, $expendAll) = $job->queueTypesExpended();
     if ($queueTypesExpended !== null) {
         list($job->timeCompleted, $queues) = $this->queue($job);
     }
     // use energy
     if ($job->energyCost() > 0) {
         $this->spellcasters->expend($job->spellcasterTypeExpended(), $job->energyCost(), $job->timeStarted, $job->tagsRequired);
     }
     // special case: build is complete in 5 seconds when using a warpgate
     if (isset($queues) && count($queues) == 1) {
         if ($queues[0]->structure == $Warpgate) {
             $job->timeCompleted = $job->timeStarted + 5;
         }
     }
     // use larva
     if ($job->larvaCost() > 0) {
         $this->hatcheries->expend($job->timeStarted, $job->larvaCost(), $job->tagsRequired);
     }
     // new products
     if ($job->productsCreated() !== null) {
         foreach ($job->productsCreated() as $product) {
             if ($product !== null) {
                 // spawn larvae
                 if ($product->uid == $SpawnLarvae->uid) {
                     $this->hatcheries->vomit($job->timeStarted);
                 }
                 // new hatchery
                 if ($product->type & Base && $product->type & Zerg) {
                     $this->hatcheries->add(new Hatchery($job->timeCompleted, 1, $job->tag));
                 }
                 // new spellcaster
                 if ($product->type & Spellcaster) {
                     $this->spellcasters->add(new Spellcaster($product, $job->timeCompleted, $job->tag));
                 }
                 // new farm
                 if ($product->supplyCapacity > 0) {
                     //tracemsg("Adding farm ". $product ." at ". simple_time($job->timeCompleted));
                     $this->farms->add(new Farm($job->timeCompleted, $product->supplyCapacity));
                 }
             }
         }
     }
     // destroy products
     if ($job->productsDestroyed() !== null) {
         foreach ($job->productsDestroyed() as $product) {
             // destroy spellcaster
             if ($product->type & Spellcaster) {
                 $this->spellcasters->remove($product, $job->timeCompleted);
             }
             // destroy farm
             if ($product->supplyCapacity > 0) {
                 $this->farms->remove($product->supplyCapacity, $job->timeCompleted);
             }
         }
     }
     // process mutations
     foreach ($job->mutations() as $mutation) {
         if ($mutation->time >= $job->timeStarted) {
             $this->income->splice($mutation);
         }
     }
     // add or morph production queues
     $queueTypesCreated = $job->queueTypesCreated();
     if ($queueTypesCreated !== null) {
         if (isset($queues) && $job->morph()) {
             $this->queues->morph($queues, $job->timeStarted, $queueTypesCreated, $job->timeCompleted);
         } else {
             foreach ($queueTypesCreated as $queueType) {
                 $this->queues->add(new ProductionQueue($queueType, $job->timeCompleted, $job->tag));
             }
         }
     }
     // create event
     if ($intheFuture) {
         $this->addCheckpoint(new Checkpoint($job->description(), $job->timeStarted, $job->timeCompleted));
     } else {
         $this->log($job->description(), $job->timeStarted, $job->timeCompleted);
     }
     // update supply count
     if ($this->debug) {
         tracemsg("Timeline::process(), supply count = " . $this->supplyCount . " + " . $job->supplyCost(false) . ".");
     }
     $this->supplyCount += $job->supplyCost(false);
     Logger::leave("Timeline::process");
 }
Beispiel #3
0
 /**
  * Schedule as a floating job that can be squeezed in without delaying the
  * fixed job. If there is supply gap before the fixed job, a floating
  * jobs is scheduled as needed to bridge the gap, possibly delaying the
  * fixed job.
  * @param Job $fixedJob Fixed job
  * @param bool $recurring If set, consider either only recurring or
  * non-recurring jobs.
  * @return bool True, if a job could be squeezed in.
  */
 private function squeeze($fixedJob, $recurring = null)
 {
     Logger::enter("Scheduler::squeeze");
     if (self::$debug) {
         tracemsg("Scheduler::squeeze(" . $fixedJob . ", " . ($recurring === null ? "null" : ($recurring ? "true" : "false")) . ")");
     }
     // squeezing is mandatory if the fixed job is unavailable
     $mandatory = $fixedJob->availability->status != Availability::Available;
     if (self::$debug) {
         tracemsg("Scheduler::squeeze(), squeezing is " . ($mandatory ? "" : "not ") . "mandatory!");
     }
     // choose candidates
     if ($recurring === null) {
         $candidates = array_merge($this->_floatingJobs, $this->_recurringJobs);
     } elseif ($recurring) {
         $candidates = $this->_recurringJobs;
     } else {
         $candidates = $this->_floatingJobs;
     }
     if (self::$debug) {
         tracemsg("Scheduler::squeeze(), choosing from " . count($candidates) . " candidates!");
     }
     // ignore recurring candidates that build the same product as the fixed job
     if (!$mandatory && $fixedJob->productBuilt() !== null) {
         foreach ($candidates as $key => $job) {
             if ($job->recurring && $job->productBuilt() !== null && $job->productBuilt()->uid == $fixedJob->productBuilt()->uid) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), eliminating " . $job . ", which builds the same product as " . $fixedJob . ".");
                 }
                 unset($candidates[$key]);
             }
         }
     }
     // ignore candidates that are not available before fixed job starts
     // if mandatory, instead ignore candidates that are not available ever
     foreach ($candidates as $key => $job) {
         $this->_timeline->calculate($job, $this->_scheduledJobs);
         if (!$mandatory && $job->timeStarted > $fixedJob->timeStarted) {
             if (self::$debug) {
                 tracemsg("Scheduler::squeeze(), eliminating " . $job . ", which is available at " . simple_time($job->timeStarted) . ", but fixed job starts at " . simple_time($fixedJob->timeStarted));
             }
             unset($candidates[$key]);
         } elseif ($mandatory && $job->timeStarted === INF) {
             if (self::$debug) {
                 tracemsg("Scheduler::squeeze(), eliminating " . $job . ", which is unavailable because " . $job->availability);
             }
             unset($candidates[$key]);
         }
     }
     // ignore jobs that affect supply the wrong way
     if (isset($fixedJob->triggerSupply)) {
         $supplyGap = $fixedJob->triggerSupply - $this->_timeline->supplyCount;
         if (self::$debug) {
             tracemsg("Scheduler::squeeze(), supply gap is " . $supplyGap . ", current supply count is " . $this->_timeline->supplyCount . ", fixed job is triggered at " . $fixedJob->triggerSupply);
         }
         foreach ($candidates as $key => $job) {
             if ($supplyGap == 0 && $job->supplyCost(true) != 0) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), eliminating <i>" . $job . "</i>; Supply gap is " . $supplyGap . ", job's supply cost is " . $job->supplyCost(true));
                 }
                 unset($candidates[$key]);
             } elseif ($supplyGap > 0 && ($job->supplyCost(true) > $supplyGap || $job->supplyCost(true) < 0)) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), #3 Eliminating " . $job);
                 }
                 unset($candidates[$key]);
             } elseif ($supplyGap < 0 && ($job->supplyCost(true) < $supplyGap || $job->supplyCost(true) > 0)) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), #4 Eliminating " . $job);
                 }
                 unset($candidates[$key]);
             }
         }
     }
     // if not mandatory, ignore jobs that exceed surplus minerals or gas
     if (!$mandatory) {
         foreach ($candidates as $key => $job) {
             // always allow jobs that don't cost resources
             if ($job->mineralCost() == 0 && $job->gasCost() == 0) {
                 continue;
             }
             // the job affects income
             $mutations = $job->mutations();
             $mutations->sort();
             if (count($mutations) > 0) {
                 // calculate job start & complete time
                 $jobComplete = $this->_timeline->whenComplete($job);
                 // set up alternate reality income
                 $income = clone $this->_timeline->income;
                 foreach ($mutations as $mutation) {
                     $income->splice($mutation);
                 }
                 // the job does not affect income
             } else {
                 $income = $this->_timeline->income;
             }
             // if surplus is not great enough
             list($gasSurplus, $mineralSurplus) = $income->surplus($fixedJob->timeStarted);
             if (round($gasSurplus) < $job->gasCost() + $fixedJob->gasCost()) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), eliminating " . $job . ". Gas needed for both jobs is " . ($job->gasCost() + $fixedJob->gasCost()) . ", gas surplus is " . $gasSurplus . ".");
                 }
                 unset($candidates[$key]);
             }
             if (round($mineralSurplus) < $job->mineralCost() + $fixedJob->mineralCost()) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), eliminating " . $job . ". Mineral needed for both jobs is " . ($job->mineralCost() + $fixedJob->mineralCost()) . ", mineral surplus is " . $mineralSurplus . ".");
                 }
                 unset($candidates[$key]);
             }
         }
     }
     // if not mandatory, ignore jobs whose larvae, production queue
     // or spellcaster usage would stall fixed job
     if (!$mandatory) {
         foreach ($candidates as $key => $job) {
             if (!$this->_timeline->canAccommodate($job, $fixedJob)) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), #16 Eliminating " . $job);
                 }
                 unset($candidates[$key]);
             }
         }
     }
     // ignore candidates that exceed supply gap
     $supplyGap = $this->supplyGap();
     foreach ($candidates as $key => $job) {
         if ($supplyGap >= 0 && $job->supplyCost(true) > $supplyGap) {
             if (self::$debug) {
                 tracemsg("Scheduler::squeeze(), #9 Eliminating " . $job);
             }
             unset($candidates[$key]);
         }
     }
     // ignore jobs that cause fixed job to exceed supply capacity
     foreach ($candidates as $job) {
         if ($job->supplyCost(true) > 0) {
             // how much supply capacity is needed
             $supplyNeeded = $this->_timeline->supplyCount + $fixedJob->supplyCost(true) + $job->supplyCost(true);
             // discard candidate if supply capacity is not available
             $time = $this->_timeline->farms->when($supplyNeeded);
             if (!$mandatory && $time > $fixedJob->timeStarted) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), #14 Eliminating " . $job);
                 }
                 unset($candidates[$key]);
             } elseif ($mandatory && $time === INF) {
                 if (self::$debug) {
                     tracemsg("Scheduler::squeeze(), #15 Eliminating " . $job);
                 }
                 unset($candidates[$key]);
             }
         }
     }
     // no floating jobs are available
     if (count($candidates) == 0) {
         if (self::$debug) {
             tracemsg("Scheduler::squeeze(), all jobs were eliminated!");
         }
         // if mandatory, throw an error
         if ($mandatory) {
             $this->reportUnavailable(array($fixedJob));
         }
         Logger::leave("Scheduler::squeeze");
         return false;
     }
     // choose earliest available job
     $job = $this->earliest($candidates);
     // process floating job
     if (self::$debug) {
         $report = "";
         foreach ($candidates as $candidate) {
             $report .= (isset($notFirst) ? ", " : "") . $candidate . "(" . simple_time($candidate->timeStarted) . ")";
             $notFirst = true;
         }
         tracemsg("Scheduler::squeeze(), remaining candidates are " . $report);
     }
     if (self::$debug) {
         tracemsg("Scheduler::squeeze(), chosen " . $job);
     }
     $this->process($job);
     // reschedule, if recurring
     if ($job->recurring) {
         $this->_recurringJobs[] = clone $job;
     }
     Logger::leave("Scheduler::squeeze");
     return true;
 }
Beispiel #4
0
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with sc2calc.org. If not, see <http://www.gnu.org/licenses/>.
/**
 * @package sc2calc.org
 * @copyright 2010 Jasper Abraham Visser
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
$logtime = microtime(true);
set_time_limit(1);
require "include.php";
require "logger.php";
Logger::enter("update.php, require()");
require "farm.php";
require "hatchery.php";
require "income.php";
require "job.php";
require "mutation.php";
require "parser.php";
require "product.php";
require "queue.php";
require "scheduler.php";
require "spellcaster.php";
require "timeline.php";
Logger::leave("update.php, require()");
// parse input
$parser = new Parser(_GET("buildOrder"));
$unscheduledJobs = $parser->jobs;