setType("numberconversion"); // get instruction word $this->instruction = strtolower(trim($a["instruction"] ?? "10u:2->2u:4")); // expand it $pattern = "/([0-9]+)([suc]):([0-9]+)->([0-9]+)([suc]):([0-9]+)/"; preg_match($pattern, $this->instruction, $matches); // if the instruction was meaningful if (count($matches) == 7) { $this->src_base = (int)$matches[1]; $this->src_rep = $matches[2]; $this->src_n_digits = $matches[3]; $this->dst_base = (int)$matches[4]; $this->dst_rep = $matches[5]; $this->dst_n_digits = $matches[6]; } else { // no valid instruction word has been passed $this->src_base = 10; $this->src_rep = "u"; $this->src_n_digits = 2; $this->dst_base = 2; $this->dst_rep = "u"; $this->dst_n_digits = 4; $this->instruction = $this->src_base . $this->src_rep . ":" . $this->src_n_digits . "->" . $this->dst_base . $this->dst_rep . ":" . $this->dst_n_digits; } $this->source = $a["source"] ?? "---"; $this->correct_answer = $a["correct_answer"] ?? "---"; } public function toArray(): array { $a = parent::toArray(); $a["instruction"] = $this->instruction; $a["source"] = $this->source; $a["correct_answer"] = $this->correct_answer; return $a; } private static function extend(string $num, int $base, int $exnd, bool $comp): string { $fd = (int)(base_convert($num[0], $base, 10)); // get first digit as a number $extd = (string)(($comp && ($fd >= ($base / 2))) ? ($base - 1) : 0); // get the extension digit return str_pad((string)($num), $extd, $extd, STR_PAD_LEFT); // extend to the left } private static function complement(string $num, int $base): string { // convert to an integer $M = (int)(base_convert($num, $base, 10)); // check if the highest digit is less than the half of the base, if not, add a zero prefix $fd = (int)(base_convert($num[0], $base, 10)); // create the basis for forming the complement $H = (string)($base - 1); $K_str = (int)(str_repeat($H, strlen($num))); if ($fd >= ($base / 2)) { // if one more digit is needed... $K_str = $H . $K_str; // prepend with a zero digit } // convert to integer $K = (int)(base_convert($K_str, $base, 10)); // form the base's complement $C = $K - $M + 1; // convert to the final base return base_convert((string)$C, 10, $base); } private static function changeRepresentation(int $num, int $base, string $rep, int $digits): string { $neg = $num < 0; // store if the value is negative $numa_str = (string)(abs($num)); // create the absolute value as a string $numa_str = base_convert($numa_str, 10, $base); // convert to specific base if ($neg) { if ($rep === "s") { $numa_str = self::extend($numa_str, $base, $digits, false); $numa_str = "-" . $numa_str; } else if ($rep === "c") { $numa_str = self::complement($numa_str, $rep); $numa_str = self::extend($numa_str, $base, $digits, true); } } else { $numa_str = self::extend($numa_str, $base, $digits, false); } return $numa_str; } public function randomize(): void { // validate representation marks $invalid = in_array($this->src_rep . $this->dst_rep, ["us", "su"]); if ($invalid) { // fix invalid representation pairs $this->dst_rep = "u"; $this->src_rep = "u"; } // specify the range $max = 1; $min = 0; switch ($this->dst_rep) { case "u": $max = pow($this->dst_base, $this->dst_n_digits) - 1; $min = 0; break; case "s": $max = pow($this->dst_base, $this->dst_n_digits) - 1; $min = -$max; break; case "c": $max = pow($this->dst_base, $this->dst_n_digits - 1) - 1; $min = -($max + 1); break; } // randomize a value $m = random_int($min, $max); // create the question and the answer $this->correct_answer = self::changeRepresentation($m, $this->dst_base, $this->dst_rep, $this->dst_n_digits); $this->source = self::changeRepresentation($m, $this->src_base, $this->src_rep, $this->src_n_digits); } public function getMark(): float { if ($this->hasFlag("acceptwithoutleadingzeros")) { return (ltrim($this->player_answer, " 0") === ltrim($this->correct_answer, "0")) ? 1.0 : 0.0; } else { return (trim($this->player_answer) === trim($this->correct_answer)) ? 1.0 : 0.0; } } }