SpreadQuiz/class/Tasks/NumberConversionTask.php
2025-09-30 22:44:06 +02:00

159 lines
5.6 KiB
PHP

<?php
require_once "OpenEndedTask.php";
class NumberConversionTask extends OpenEndedTask
{
protected string $instruction; // instruction word
protected string $source; // source
// runtime variables -----
private int $src_base; // source number system
private string $src_rep; // source representation
private int $src_n_digits; // minimum number of digits in the source
private int $dst_base; // destination number system
private string $dst_rep; // destination representation
private int $dst_n_digits; // number of digits in the destination
// -------------------------
public function __construct(array &$a = null)
{
parent::__construct($a);
$this->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;
}
}
}