Example #1
0
 /**
  * Parse a single line of a build order
  * @param int $lineNumber
  * @param string $line
  */
 public function read($lineNumber, $line)
 {
     global $ScoutingWorker, $Drone;
     $line = str_replace(",", "", $line);
     $jobStack = array();
     // split line into commands
     $commands = preg_split("/(&|>|\\s+and\\s+|\\s+then\\s+)/", $line, null, PREG_SPLIT_DELIM_CAPTURE);
     for ($i = 0; $i < count($commands); $i += 2) {
         $command = trim($commands[$i]);
         $operator = $i == 0 ? null : trim($commands[$i - 1]);
         // wipe slate
         unset($job);
         unset($product);
         unset($triggerGas);
         unset($triggerMineral);
         unset($triggerSupply);
         // extract trigger from command
         if (empty($operator)) {
             // trigger is a supply number
             if (preg_match("/^(?P<supply>\\d+)\\s+(?P<command>.+)\$/", $command, $terms)) {
                 $triggerSupply = (int) $terms["supply"];
                 $command = trim($terms["command"]);
                 // trigger is a resource number
             } elseif (preg_match("/^@(?P<amount>\\d+)\\s+(?P<resource>minerals?|gas)\\s+(?P<command>.+)\$/", $command, $terms)) {
                 if (strcasecmp($terms["resource"], "gas") == 0) {
                     $triggerGas = $terms["amount"];
                 } else {
                     $triggerMineral = $terms["amount"];
                 }
                 $command = trim($terms["command"]);
             }
         }
         // repeating or recurring
         unset($repeat);
         $recurring = false;
         if (preg_match("/^\\s*(?P<command>.*)\\s*\\[(?P<repeat>\\d+)\\]\\s*\$/", $command, $terms)) {
             $repeat = (int) $terms["repeat"];
             $command = trim($terms["command"]);
         } elseif (preg_match("/^\\s*(?P<command>.*)\\s*\\[(?P<repeat>auto)\\]\\s*\$/i", $command, $terms)) {
             $recurring = true;
             $command = trim($terms["command"]);
         } elseif (preg_match("/^\\s*(?P<repeat>constant)\\s+(?P<command>.*)\\s*\$/i", $command, $terms)) {
             $recurring = true;
             $command = trim($terms["command"]);
         }
         // required tags
         if (preg_match("/^\\s*(?P<command>.*?)\\s+from\\s+(?P<tags>#[a-zA-Z0-9]+(\\s*(\\s+and\\s+)?\\s*#[a-zA-Z0-9]+)*)\\s*\$/i", $command, $terms)) {
             if (preg_match_all("/#(?<tags>[a-zA-Z0-9]+)/", $terms["tags"], $tags)) {
                 $tagsRequired = $tags["tags"];
                 $command = trim($terms["command"]);
             } else {
                 unset($tagsRequired);
             }
         } else {
             unset($tagsRequired);
         }
         // tag
         if (preg_match("/^\\s*(?P<command>.*?)\\s*#(?P<tag>[a-zA-Z0-9]+)\\s*\$/", $command, $terms)) {
             $tag = $terms["tag"];
             $command = trim($terms["command"]);
         } else {
             unset($tag);
         }
         // chrono boost
         if (preg_match("/^\\s*(?P<command>.*?)\\s*(?P<chronoboost>\\*+)\\s*\$/", $command, $terms)) {
             $chronoboost = strlen($terms["chronoboost"]);
             $command = trim($terms["command"]);
         } else {
             $chronoboost = 0;
         }
         // skip empty command
         if ($command == "") {
             continue;
         }
         // scout
         if (preg_match("/^\\s*scout\\s*" . RegexDelay . "?/i", $command, $terms)) {
             if (!empty($terms["delay"])) {
                 $delay = (int) $terms["delay"];
             } else {
                 $delay = 0;
             }
             $job = new ScoutJob($delay);
             // option
         } elseif (preg_match("/^\\s*#(?P<name>[\\w\\s]+)=(?P<value>[a-zA-Z0-9\\s]+)\$/i", $command, $terms)) {
             $this->options[trim($terms["name"])] = trim($terms["value"]);
             // comment
         } elseif (preg_match("/^\\s*#.*\$/i", $command, $terms)) {
             // transfer workers to product constructed in previous job
         } elseif (preg_match("/^\\s*(transfer\\s+|\\s*[+])(?P<workers>\\d+)\\s*" . RegexWorker . "?\\s*" . RegexDelay . "?\$/i", $command, $terms)) {
             // transfer target
             unset($transferTarget);
             if (count($jobStack) != 0 && array_top($jobStack)->productBuilt() !== null) {
                 $transferTarget = array_top($jobStack)->productBuilt();
             }
             if (count($jobStack) > 0 && $transferTarget->type & Base) {
                 $mutation = new TransferMutation((int) $terms["workers"]);
             } elseif (count($jobStack) > 0 && $transferTarget->type & Geyser) {
                 $mutation = new Mutation(-$terms["workers"], $terms["workers"]);
             } else {
                 throw_error("Line <i>" . ($lineNumber + 1) . "</i> : Transfer workers where?", "You can only use the syntax <em>transfer 3 workers</em> or <em>+3</em> directly after a job that builds a base or a geyser. In other cases, please write something like <em>put 3 workers on gas</em> or <em>+3 on gas</em>.");
             }
             if (isset($terms["delay"])) {
                 $mutation->delay = (int) $terms["delay"];
             }
             $job = new MutateJob($mutation);
             // transfer workers off one resource to another
         } elseif (preg_match("/^\\s*(?P<verb>put|take|-|[+])\\s*(?P<workers>\\d+)(\\s+" . RegexWorker . ")?\\s+(?P<preposition>on|off)\\s+(?P<resource>gas|minerals?)\\s*" . RegexDelay . "?/i", $command, $terms)) {
             $positive = strcasecmp($terms["verb"], "put") == 0 || $terms["verb"] == "+";
             if ($positive xor strcasecmp($terms["preposition"], "on") == 0) {
                 throw_error("Line <i>" . ($lineNumber + 1) . "</i> : You can't <i>" . ($positive ? "put" : "take") . "</i> miners <i>" . $terms["preposition"] . "</i> a resource.");
             }
             if ($positive xor strcasecmp($terms["resource"], "gas") == 0) {
                 $mutation = new Mutation($terms["workers"], -$terms["workers"]);
             } else {
                 $mutation = new Mutation(-$terms["workers"], $terms["workers"]);
             }
             if (isset($terms["delay"])) {
                 $mutation->delay = (int) $terms["delay"];
             }
             $job = new MutateJob($mutation);
             // checkpoint
         } elseif (preg_match("/^\\s*(?P<minutes>[0-9]{1,2}):(?<seconds>[0-9]{2})\\s+checkpoint\\s*\$/i", $command, $terms)) {
             $this->checkpoints[] = (int) $terms["minutes"] * 60 + (int) $terms["seconds"];
             //tracemsg("Checkpoint at ". ((int)$terms["minutes"] * 60 + (int)$terms["seconds"]) ." seconds");
             // cancel
         } elseif (preg_match("/^Cancel " . RegexProduct() . "\\s*\$/i", $command, $terms)) {
             // create job
             $product = Product::byName(trim($terms["product"]));
             if (empty($product)) {
                 throw_error("Line <i>" . ($lineNumber + 1) . "</i> : Unknown object <i>" . $terms["product"] . "</i>", "For a complete list of units, structures, upgrades, morphs and abilities, please refer to <a href=\"list.php\" target=\"_blank\">this list</a>. If you are trying to do something other than building, please check the <i>single line examples</i> for the syntax of the other commands. The syntax is not case-sensitive, but it is very specific in the spelling.");
             } else {
                 $job = new CancelJob($product);
             }
             // trick
         } elseif (preg_match("/^(?P<plural>double\\s+)?" . RegexProduct("pledge_product") . "\\s+trick(\\s+into\\s+" . RegexProduct("turn_product") . ")?\\s*\$/i", $command, $terms)) {
             // parse pledge
             $pledgeProduct = Product::byName($terms["pledge_product"]);
             if (empty($pledgeProduct)) {
                 throw_error("Line <i>" . ($lineNumber + 1) . "</i> : Unknown object <i>" . $terms["product"] . "</i>", "For a complete list of units, structures, upgrades, morphs and abilities, please refer to <a href=\"list.php\" target=\"_blank\">this list</a>. If you are trying to do something other than building, please check the <i>single line examples</i> for the syntax of the other commands. The syntax is not case-sensitive, but it is very specific in the spelling.");
             }
             $pledgeCount = empty($terms["plural"]) ? 1 : 2;
             // parse turn
             if (empty($terms["turn_product"])) {
                 $turnProduct = $Drone;
                 $turnCount = $pledgeCount;
             } else {
                 $turnProduct = Product::byName($terms["turn_product"]);
                 if (empty($turnProduct)) {
                     throw_error("Line <i>" . ($lineNumber + 1) . "</i> : Unknown object <i>" . $terms["product"] . "</i>", "For a complete list of units, structures, upgrades, morphs and abilities, please refer to <a href=\"list.php\" target=\"_blank\">this list</a>. If you are trying to do something other than building, please check the <i>single line examples</i> for the syntax of the other commands. The syntax is not case-sensitive, but it is very specific in the spelling.");
                 }
                 $turnCount = isset($repeat) ? $repeat : 1;
             }
             unset($repeat);
             // create job
             $job = new TrickJob($pledgeProduct, $pledgeCount, $turnProduct, $turnCount);
             // fake
         } elseif (preg_match("/^\\s*fake " . RegexProduct("pledge_product") . "\\s*\$/i", $command, $terms)) {
             // parse pledge
             $pledgeProduct = Product::byName($terms["pledge_product"]);
             if (empty($pledgeProduct)) {
                 throw_error("Line <i>" . ($lineNumber + 1) . "</i> : Unknown object <i>" . $terms["product"] . "</i>", "For a complete list of units, structures, upgrades, morphs and abilities, please refer to <a href=\"list.php\" target=\"_blank\">this list</a>. If you are trying to do something other than building, please check the <i>single line examples</i> for the syntax of the other commands. The syntax is not case-sensitive, but it is very specific in the spelling.");
             }
             $pledgeCount = 1;
             // parse turn
             $turnProduct = null;
             $turnCount = 0;
             // create job
             $job = new TrickJob($pledgeProduct, $pledgeCount, $turnProduct, $turnCount);
             // kill
         } elseif (preg_match("/^\\s*kill " . RegexProduct("product") . "\\s*\$/i", $command, $terms)) {
             // parse product
             $product = Product::byName($terms["product"]);
             if (empty($product)) {
                 throw_error("Line <i>" . ($lineNumber + 1) . "</i> : Unknown object <i>" . $terms["product"] . "</i>", "For a complete list of units, structures, upgrades, morphs and abilities, please refer to <a href=\"list.php\" target=\"_blank\">this list</a>. If you are trying to do something other than building, please check the <i>single line examples</i> for the syntax of the other commands. The syntax is not case-sensitive, but it is very specific in the spelling.");
             } elseif ($product->type & Structure) {
                 throw_error("Line <i>" . ($lineNumber + 1) . "</i> : Cannot kill structure <i>" . $terms["product"] . "</i>");
             }
             // create job
             $job = new KillJob($product);
             // build
         } elseif (preg_match("/^(?P<proxy>proxy\\s+)?" . RegexProduct() . "(?P<priority>\\s*!)?\\s*" . RegexInitiate . "?\$/i", $command, $terms)) {
             // create job
             $product = Product::byName(trim($terms["product"]));
             if (empty($product)) {
                 throw_error("Line <i>" . ($lineNumber + 1) . "</i> : Unknown command <i>" . $command . "</i>", "For a complete list of units, structures, upgrades, morphs and abilities, please refer to <a href=\"list.php\" target=\"_blank\">this list</a>. If you are trying to do something other than building, please check the <i>single line examples</i> for the syntax of the other commands. The syntax is not case-sensitive, but it is very specific in the spelling.");
             } else {
                 $job = new BuildJob($product);
             }
             // initiate at given resource amount
             if (isset($terms["initiate_amount"]) && isset($terms["initiate_resource"])) {
                 if (strcasecmp($terms["initiate_resource"], "gas") == 0) {
                     $job->initiateGas = (int) $terms["initiate_amount"];
                 } else {
                     $job->initiateMineral = (int) $terms["initiate_amount"];
                 }
             }
             // proxy
             if (!empty($terms["proxy"])) {
                 $job->queueTypeExpended = $ScoutingWorker;
             }
             // priority job
             if (isset($terms["priority"])) {
                 $job->superPriority = true;
             }
             // unknown command
         } else {
             throw_error("Line <i>" . ($lineNumber + 1) . "</i> : Unknown command <i>" . $command . "</i>", "For a complete list of units, structures, upgrades, morphs and abilities, please refer to <a href=\"list.php\" target=\"_blank\">this list</a>. If you are trying to do something other than building, please check the <i>single line examples</i> for the syntax of the other commands. The syntax is not case-sensitive, but it is very specific in the spelling.");
         }
         if (isset($job)) {
             // trigger is the previous job
             if (!isset($triggerGas) && !isset($triggerMineral) && !isset($triggerSupply)) {
                 if (count($jobStack) == 0) {
                     throw_error("Line <i>" . ($lineNumber + 1) . "</i> : There is no trigger to this job.", "The trigger for a job can either by a supply count (for example <em>12 Gateway</em>) or an amount of resources (for example <em>@100 gas take 3 off gas</em>). A job that appears at the start of a line must have one of these triggers.");
                 } else {
                     $dependency = new Dependency(array_top($jobStack), $operator == ">" || strcasecmp($operator, "then") == 0 ? Dependency::AtCompletion : Dependency::AtStart);
                 }
             }
             // set triggers
             if (isset($dependency)) {
                 $job->dependency = $dependency;
             }
             if (isset($triggerGas)) {
                 $job->triggerGas = $triggerGas;
             }
             if (isset($triggerMineral)) {
                 $job->triggerMineral = $triggerMineral;
             }
             if (isset($triggerSupply)) {
                 $job->triggerSupply = $triggerSupply;
             }
             // if job is recurring and its production queue appears in the
             // stack, tag the queue and have job require that tag
             if (!isset($tagsRequired) && isset($product) && isset($product->expends) && $recurring) {
                 unset($queueJob);
                 for ($j = count($jobStack) - 1; $j >= 0 && !isset($queueJob); $j--) {
                     if ($jobStack[$j]->productBuilt() !== null) {
                         foreach ($product->expends as $expended) {
                             if ($expended->uid == $jobStack[$j]->productBuilt()->uid) {
                                 $queueJob = $jobStack[$j];
                                 break;
                             }
                         }
                     }
                 }
                 if (isset($queueJob)) {
                     if (!isset($queueJob->tag)) {
                         $queueJob->tag = uniqid();
                     }
                     $tagsRequired = array($queueJob->tag);
                 }
             }
             // tag
             if (isset($tag)) {
                 $job->tag = $tag;
             }
             // require tags
             if (isset($tagsRequired)) {
                 $job->tagsRequired = $tagsRequired;
             }
             // chrono boost
             if ($chronoboost && (!isset($product) || $product->type & Structure || ($product->type & Protoss) == 0)) {
                 throw_error("Line <i>" . ($lineNumber + 1) . "</i> : Could not chrono boost <i>" . $command . "</i>");
             }
             $job->chronoboost = $chronoboost;
             // add job(s)
             $this->jobs[] = $job;
             $jobStack[] = $job;
             // repeat job
             for ($j = 1; $j < (isset($repeat) ? $repeat : 1); $j++) {
                 $job = clone $job;
                 $job->dependency = new Dependency(array_top($jobStack), Dependency::AtStart);
                 if (isset($job->triggerSupply)) {
                     $job->triggerSupply += $job->supplyCost();
                 }
                 if (isset($product) && $product->type & Morph) {
                     $job->tagsRequired = null;
                 }
                 unset($job->triggerGas);
                 unset($job->triggerMineral);
                 $this->jobs[] = $job;
                 $jobStack[] = $job;
             }
             // recur job after the first go
             if ($recurring) {
                 $job = clone $job;
                 $job->dependency = new Dependency(array_top($jobStack), Dependency::AtStart);
                 unset($job->triggerGas);
                 unset($job->triggerMineral);
                 unset($job->triggerSupply);
                 $job->recurring = $recurring;
                 $this->jobs[] = $job;
             }
         }
     }
 }