- ...
This commit is contained in:
		
							parent
							
								
									1fa2924abd
								
							
						
					
					
						commit
						0e0490eafb
					
				@ -4,6 +4,8 @@ require_once "vendor/autoload.php";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
require_once "AutoStoring.php";
 | 
					require_once "AutoStoring.php";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require_once "Utils.php";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Game extends AutoStoring
 | 
					class Game extends AutoStoring
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public const DEFAULT_GAME_PROPERTIES = [
 | 
					    public const DEFAULT_GAME_PROPERTIES = [
 | 
				
			||||||
@ -161,7 +163,15 @@ class Game extends AutoStoring
 | 
				
			|||||||
    // Get game directory NAME with path. Does not check if the game directory exists or not.
 | 
					    // Get game directory NAME with path. Does not check if the game directory exists or not.
 | 
				
			||||||
    function getGameDir(): string
 | 
					    function getGameDir(): string
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return GAMEMEDIA_DIR . DIRECTORY_SEPARATOR . $this->getId();
 | 
					        return self::getGameDirById($this->getId());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function getAccompanyingFilePath(string $file_name): string {
 | 
				
			||||||
 | 
					        return $this->getGameDir() . DIRECTORY_SEPARATOR . $file_name;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static function getGameDirById(int $id): string {
 | 
				
			||||||
 | 
					        return GAMEMEDIA_DIR . DIRECTORY_SEPARATOR . $id;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Get game file NAME with path. Does not check whether the game file is in place or not.
 | 
					    // Get game file NAME with path. Does not check whether the game file is in place or not.
 | 
				
			||||||
@ -225,7 +235,7 @@ class Game extends AutoStoring
 | 
				
			|||||||
                // construct task record
 | 
					                // construct task record
 | 
				
			||||||
                $a = [
 | 
					                $a = [
 | 
				
			||||||
                    "question" => trim($csvline[0]),
 | 
					                    "question" => trim($csvline[0]),
 | 
				
			||||||
                    "image_url" => trim($csvline[1]),
 | 
					                    "image_data" => trim($csvline[1]),
 | 
				
			||||||
                    "correct_answer" => 0,
 | 
					                    "correct_answer" => 0,
 | 
				
			||||||
                    "answers" => array_filter(array_slice($csvline, 2), function ($v) {
 | 
					                    "answers" => array_filter(array_slice($csvline, 2), function ($v) {
 | 
				
			||||||
                        return trim($v) !== "";
 | 
					                        return trim($v) !== "";
 | 
				
			||||||
@ -233,8 +243,9 @@ class Game extends AutoStoring
 | 
				
			|||||||
                ];
 | 
					                ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // if an image is attached to the task, then give a random name to the image
 | 
					                // if an image is attached to the task, then give a random name to the image
 | 
				
			||||||
                if ($a["image_url"] !== "") {
 | 
					                if ($a["image_data"] !== "") {
 | 
				
			||||||
                    $a["image_url"] = $this->obfuscateAttachedImage($a["image_url"]);
 | 
					                    $a["image_data"] = $this->obfuscateAttachedImage($a["image_data"]);
 | 
				
			||||||
 | 
					                    $a["image_type"] = "url";
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // store the task
 | 
					                // store the task
 | 
				
			||||||
@ -285,7 +296,7 @@ class Game extends AutoStoring
 | 
				
			|||||||
            $row = &$table[$i]; // fetch row
 | 
					            $row = &$table[$i]; // fetch row
 | 
				
			||||||
            $a = [ // create initializing array
 | 
					            $a = [ // create initializing array
 | 
				
			||||||
                "question" => trim($row[0]),
 | 
					                "question" => trim($row[0]),
 | 
				
			||||||
                "image_url" => trim($row[1]),
 | 
					                "image_data" => trim($row[1]),
 | 
				
			||||||
                "correct_answer" => 0,
 | 
					                "correct_answer" => 0,
 | 
				
			||||||
                "answers" => array_filter(array_slice($row, 2), function ($v) {
 | 
					                "answers" => array_filter(array_slice($row, 2), function ($v) {
 | 
				
			||||||
                    return trim($v ?? "") !== "";
 | 
					                    return trim($v ?? "") !== "";
 | 
				
			||||||
@ -293,8 +304,9 @@ class Game extends AutoStoring
 | 
				
			|||||||
            ];
 | 
					            ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // obfuscate image filename
 | 
					            // obfuscate image filename
 | 
				
			||||||
            if ($a["image_url"] !== "") {
 | 
					            if ($a["image_data"] !== "") {
 | 
				
			||||||
                $a["image_url"] = $this->obfuscateAttachedImage($a["image_url"]);
 | 
					                $a["image_data"] = $this->obfuscateAttachedImage($a["image_data"]);
 | 
				
			||||||
 | 
					                $a["image_type"] = "url";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // create the task
 | 
					            // create the task
 | 
				
			||||||
@ -327,7 +339,8 @@ class Game extends AutoStoring
 | 
				
			|||||||
        return -1;
 | 
					        return -1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static function explodeFlags(string $fs): array {
 | 
					    private static function explodeFlags(string $fs): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        $flags = explode(",", trim($fs));
 | 
					        $flags = explode(",", trim($fs));
 | 
				
			||||||
        return array_filter($flags, fn($v) => trim($v) !== "");
 | 
					        return array_filter($flags, fn($v) => trim($v) !== "");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -371,9 +384,9 @@ class Game extends AutoStoring
 | 
				
			|||||||
            $a = [
 | 
					            $a = [
 | 
				
			||||||
                "flags" => Game::explodeFlags($row[0] ?? ""),
 | 
					                "flags" => Game::explodeFlags($row[0] ?? ""),
 | 
				
			||||||
                "type" => strtolower($select_fn(["Típus", "Type"])),
 | 
					                "type" => strtolower($select_fn(["Típus", "Type"])),
 | 
				
			||||||
                "generator" => $select_fn(["Generátor", "Generator"]),
 | 
					                "image_data" => $select_fn(["Kép", "Image"]),
 | 
				
			||||||
                "image_url" => $select_fn(["Kép", "Image"]),
 | 
					 | 
				
			||||||
                "question" => $select_fn(["Kérdés", "Question"]),
 | 
					                "question" => $select_fn(["Kérdés", "Question"]),
 | 
				
			||||||
 | 
					                "lua_script" => $select_fn(["Lua"]),
 | 
				
			||||||
            ];
 | 
					            ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // convert into
 | 
					            // convert into
 | 
				
			||||||
@ -383,15 +396,35 @@ class Game extends AutoStoring
 | 
				
			|||||||
                    $a["correct_answer"] = 0;
 | 
					                    $a["correct_answer"] = 0;
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                case "openended":
 | 
					                case "openended":
 | 
				
			||||||
                    $a["correct_answers"] = $extract_unlabeled_fn();
 | 
					                    $a["correct_answer"] = $extract_unlabeled_fn();
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                case "numberconversion":
 | 
					                case "numberconversion":
 | 
				
			||||||
                    $a["instruction"] = $row[$fuc];
 | 
					                    $a["instruction"] = $row[$fuc];
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
 | 
					                case "truthtable":
 | 
				
			||||||
 | 
					                    $a["input_variables"] = Utils::str2a($row[$fuc]);
 | 
				
			||||||
 | 
					                    $a["output_variable"] = $row[$fuc + 1];
 | 
				
			||||||
 | 
					                    $a["expression"] = $row[$fuc + 2];
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case "verilog":
 | 
				
			||||||
 | 
					                    $a["test_bench_fn"] = $row[$fuc];
 | 
				
			||||||
 | 
					                    $a["correct_answer"] = file_get_contents($this->getAccompanyingFilePath($row[$fuc + 1]));
 | 
				
			||||||
 | 
					                    if (isset($row[$fuc + 2])) {
 | 
				
			||||||
 | 
					                        $a["player_answer"] = file_get_contents($this->getAccompanyingFilePath($row[$fuc + 2]));
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        $a["player_answer"] = "";
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // obfuscate image filename
 | 
				
			||||||
 | 
					            if ($a["image_data"] !== "") {
 | 
				
			||||||
 | 
					                $a["image_data"] = $this->obfuscateAttachedImage($a["image_data"]);
 | 
				
			||||||
 | 
					                $a["image_type"] = "url";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // generate the task
 | 
					            // generate the task
 | 
				
			||||||
            $this->tasks[] = TaskFactory::fromArray($a);
 | 
					            $this->tasks[] = TaskFactory::fromArray($a, $this);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $result["n"] = $n - 1;
 | 
					        $result["n"] = $n - 1;
 | 
				
			||||||
 | 
				
			|||||||
@ -2,93 +2,95 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
 | 
					use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LogicFunction
 | 
					require_once "PythonUtils.php";
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public array $input_vars;
 | 
					 | 
				
			||||||
    public string $verilog_form;
 | 
					 | 
				
			||||||
    public string $tex_form;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function __construct(array $input_vars = [], string $verilog_form = "", string $tex_form = "")
 | 
					class LogicFunction implements JsonSerializable
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private static ExpressionLanguage $EXP_LANG; // expression language for linting an evaluation
 | 
				
			||||||
 | 
					    private array $input_vars; // array of input variables
 | 
				
			||||||
 | 
					    private string $expression; // the logic function in the bitwise Verilog-form
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function convertToVerilogBitwiseForm(string $expression): string
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->input_vars = $input_vars;
 | 
					        return str_replace(["/", "!", "*", "+"], ["~", "~", "&", "|"], $expression);
 | 
				
			||||||
        $this->verilog_form = $verilog_form;
 | 
					 | 
				
			||||||
        $this->tex_form = $tex_form;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getTruthTable(): array {
 | 
					    public static function collectVariables(string $expression): array
 | 
				
			||||||
        $tt = [];
 | 
					    {
 | 
				
			||||||
 | 
					        preg_match_all("/\w/", $expression, $variables);
 | 
				
			||||||
 | 
					        return array_filter(array_unique($variables[0]), fn($v) => !empty($v) && !is_numeric($v[0]));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function __construct(string $expression = "", array $input_vars = [])
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->setExpression($expression);
 | 
				
			||||||
 | 
					        $this->setInputVars(($input_vars === []) ? self::collectVariables($this->expression) : $input_vars);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getTruthTable(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        $N = count($this->input_vars);
 | 
					        $N = count($this->input_vars);
 | 
				
			||||||
        $M = pow(2, $N);
 | 
					        if ($N == 0) {
 | 
				
			||||||
 | 
					            return "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $exp_lang = new ExpressionLanguage();
 | 
					        $M = pow(2, $N);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $vars = [];
 | 
					        $vars = [];
 | 
				
			||||||
        foreach ($this->input_vars as $var) {
 | 
					        foreach ($this->input_vars as $var) {
 | 
				
			||||||
            $vars[$var] = 0;
 | 
					            $vars[$var] = 0;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $cooked_form = str_replace(["&", "|", "~"], ["&&", "||", "!"], $this->verilog_form);
 | 
					        $expression = $this->getExpression("verilog_logic");
 | 
				
			||||||
        printf("Cooked: %s\n", $cooked_form);
 | 
					//        printf("Cooked: %s\n", $cooked_form);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $tt = [];
 | 
				
			||||||
        for ($i = 0; $i < $M; $i++) {
 | 
					        for ($i = 0; $i < $M; $i++) {
 | 
				
			||||||
            for ($k = 0; $k < $N; $k++) {
 | 
					            for ($k = 0; $k < $N; $k++) {
 | 
				
			||||||
                $vars[$this->input_vars[$k]] = (($i >> ($N - $k - 1)) & 1) === 1;
 | 
					                $vars[$this->input_vars[$k]] = (($i >> ($N - $k - 1)) & 1) === 1;
 | 
				
			||||||
                printf("%d ", $vars[$this->input_vars[$k]]);
 | 
					//                printf("%d ", $vars[$this->input_vars[$k]]);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            $out = $exp_lang->evaluate($cooked_form, $vars);
 | 
					            $out = self::$EXP_LANG->evaluate($expression, $vars);
 | 
				
			||||||
            printf("%d\n", $out);
 | 
					//            printf("%d\n", $out);
 | 
				
			||||||
            $tt[] = $out;
 | 
					            $tt[] = $out;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $tt;
 | 
					        return join("", array_map(fn($r) => ($r === True) ? 1 : 0, $tt));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static function genRandom(array $input_vars, int $min_depth = 2, int $max_depth = 3): LogicFunction
 | 
					    public static function genRandom(array $input_vars, int $min_depth = 2, int $max_depth = 3): LogicFunction
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        function genTerm(array $vars, int $ftn, int $tn, int $mind, int $maxd, bool $top = true, int $opindex = 1): array
 | 
					        function genTerm(array $vars, int $ftn, int $tn, int $mind, int $maxd, bool $top = true, int $opindex = 1): string
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            $verilog_term = "";
 | 
					            $term = "";
 | 
				
			||||||
            $tex_term = "";
 | 
					 | 
				
			||||||
            $m = max($ftn, random_int(1, $tn));
 | 
					            $m = max($ftn, random_int(1, $tn));
 | 
				
			||||||
            if ((($maxd === 0) || ($m === 1)) && ($ftn === 0)) {
 | 
					            if ((($maxd === 0) || ($m === 1)) && ($ftn === 0)) {
 | 
				
			||||||
                $neg = random_int(0, 1) === 1;
 | 
					                $neg = random_int(0, 1) === 1;
 | 
				
			||||||
                $var = $vars[array_rand($vars, 1)];
 | 
					                $var = $vars[array_rand($vars, 1)];
 | 
				
			||||||
                $verilog_term = ($neg ? "~" : "") . $var;
 | 
					                $term = ($neg ? "~" : "") . $var;
 | 
				
			||||||
                $tex_term = $neg ? ("\\overline{" . $var . "}") : $var;
 | 
					 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                $depth = random_int(0, max(0, $maxd - 1));
 | 
					                $depth = random_int(0, max(0, $maxd - 1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                $verilog_ops = [" & ", " | "];
 | 
					                $verilog_ops = [" & ", " | "];
 | 
				
			||||||
                $tex_ops = ["", " | "];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                $verilog_op = $verilog_ops[$opindex];
 | 
					                $verilog_op = $verilog_ops[$opindex];
 | 
				
			||||||
                $tex_op = $tex_ops[$opindex];
 | 
					                $term = !$top ? "(" : "";
 | 
				
			||||||
 | 
					 | 
				
			||||||
                $verilog_term = !$top ? "(" : "";
 | 
					 | 
				
			||||||
                $tex_term = !$top ? "\\left(" : "";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                $nextopindex = ($opindex === 0) ? 1 : 0;
 | 
					                $nextopindex = ($opindex === 0) ? 1 : 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                for ($i = 0; $i < $m; $i++) {
 | 
					                for ($i = 0; $i < $m; $i++) {
 | 
				
			||||||
                    $term = genTerm($vars, (($mind - 1) > 0) ? $ftn : 0, $tn, $mind - 1, $depth, false, $nextopindex);
 | 
					                    $subterm = genTerm($vars, (($mind - 1) > 0) ? $ftn : 0, $tn, $mind - 1, $depth, false, $nextopindex);
 | 
				
			||||||
                    $verilog_term .= $term["verilog"];
 | 
					                    $term .= $subterm;
 | 
				
			||||||
                    $tex_term .= $term["tex"];
 | 
					 | 
				
			||||||
                    if ($i < $m - 1) {
 | 
					                    if ($i < $m - 1) {
 | 
				
			||||||
                        $verilog_term .= $verilog_op;
 | 
					                        $term .= $verilog_op;
 | 
				
			||||||
                        $tex_term .= $tex_op;
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                $verilog_term .= !$top ? ")" : "";
 | 
					                $term .= !$top ? ")" : "";
 | 
				
			||||||
                $tex_term .= !$top ? "\\right)" : "";
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return ["verilog" => $verilog_term, "tex" => $tex_term];
 | 
					            return $term;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $term = genTerm($input_vars, count($input_vars), count($input_vars), $min_depth, $max_depth);
 | 
					        $term = genTerm($input_vars, count($input_vars), count($input_vars), $min_depth, $max_depth);
 | 
				
			||||||
 | 
					        return new LogicFunction($term, $input_vars);
 | 
				
			||||||
        return new LogicFunction($input_vars, $term["verilog"], $term["tex"]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static function genRandomDF($input_vars): LogicFunction
 | 
					    public static function genRandomDF($input_vars): LogicFunction
 | 
				
			||||||
@ -97,57 +99,157 @@ class LogicFunction
 | 
				
			|||||||
        $states = pow(2, $N);
 | 
					        $states = pow(2, $N);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $verilog_term = "";
 | 
					        $verilog_term = "";
 | 
				
			||||||
        $tex_term = "";
 | 
					 | 
				
			||||||
        for ($i = 0; $i < $states; $i++) {
 | 
					        for ($i = 0; $i < $states; $i++) {
 | 
				
			||||||
 | 
					            $inside = "";
 | 
				
			||||||
            $verilog_inside = "";
 | 
					 | 
				
			||||||
            $tex_inside = "";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            $omit = random_int(0, 1); // omit the variable or not?
 | 
					            $omit = random_int(0, 1); // omit the variable or not?
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!$omit) {
 | 
					            if (!$omit) {
 | 
				
			||||||
                for ($j = 0; $j < $N; $j++) {
 | 
					                for ($j = 0; $j < $N; $j++) {
 | 
				
			||||||
                    $neg = !($i & (1 << $j)); // is it an inverted variable?
 | 
					                    $neg = !($i & (1 << $j)); // is it an inverted variable?
 | 
				
			||||||
                    $term = $input_vars[$j];
 | 
					                    $term = $input_vars[$j];
 | 
				
			||||||
                    if ($neg) {
 | 
					                    if ($neg) {
 | 
				
			||||||
                        $verilog_inside .= "~" . $term;
 | 
					                        $inside .= "~" . $term;
 | 
				
			||||||
                        $tex_inside .= "\\overline{" . $term . "}";
 | 
					 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        $verilog_inside .= $term;
 | 
					                        $inside .= $term;
 | 
				
			||||||
                        $tex_inside .= $term;
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if ($j < ($N - 1)) {
 | 
					                    if ($j < ($N - 1)) {
 | 
				
			||||||
                        $verilog_inside .= " & ";
 | 
					                        $inside .= " & ";
 | 
				
			||||||
                        $tex_inside .= "";
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            //$verilog_inside = rtrim($verilog_inside, "&");
 | 
					            if ($inside !== "") {
 | 
				
			||||||
            //$tex_inside = rtrim($tex_inside, "\\&");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if ($verilog_inside !== "") {
 | 
					 | 
				
			||||||
                $verilog_term .= "(";
 | 
					                $verilog_term .= "(";
 | 
				
			||||||
                $tex_term .= "\\left(";
 | 
					                $verilog_term .= $inside;
 | 
				
			||||||
 | 
					 | 
				
			||||||
                $verilog_term .= $verilog_inside;
 | 
					 | 
				
			||||||
                $tex_term .= $tex_inside;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                $verilog_term .= ")";
 | 
					                $verilog_term .= ")";
 | 
				
			||||||
                $tex_term .= "\\right)";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (($i < ($states - 1)) && !$omit) {
 | 
					                if (($i < ($states - 1)) && !$omit) {
 | 
				
			||||||
                    $verilog_term .= " | ";
 | 
					                    $verilog_term .= " | ";
 | 
				
			||||||
                    $tex_term .= " | ";
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $verilog_term = rtrim($verilog_term, "| ");
 | 
					        $verilog_term = rtrim($verilog_term, "| ");
 | 
				
			||||||
        $tex_term = rtrim($tex_term, "| ");
 | 
					        return new LogicFunction($verilog_term, $input_vars);
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return new LogicFunction($input_vars, $verilog_term, $tex_term);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					
 | 
				
			||||||
 | 
					    public function setExpression(string $expression): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->expression = self::convertToVerilogBitwiseForm($expression);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getExpression(string $fmt = "verilog_bitwise"): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        switch (strtolower($fmt)) {
 | 
				
			||||||
 | 
					            case "verilog_logic":
 | 
				
			||||||
 | 
					                return str_replace(["&", "|", "~"], ["&&", "||", "!"], $this->expression);
 | 
				
			||||||
 | 
					            case "tex":
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                $tex_form = str_replace([" | ", " & ", "(", ")"], [" + ", " \\cdot ", "\\left(", "\\right)"], $this->expression);
 | 
				
			||||||
 | 
					                return preg_replace("/~([a-zA-Z0-9_])/", '\\overline{$1}', $tex_form);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					            case "verilog_bitwise":
 | 
				
			||||||
 | 
					                return $this->expression;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getInputVars(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->input_vars;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function setInputVars(array $input_vars): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->input_vars = $input_vars;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getNStates(): int
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return pow(2, count($this->input_vars));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function isValid(): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            self::$EXP_LANG->lint($this->expression, $this->input_vars);
 | 
				
			||||||
 | 
					        } catch (Exception $e) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function toArray(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return ["expression" => $this->expression, "input_vars" => $this->input_vars];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function jsonSerialize()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->toArray();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function fromArray(array $a): LogicFunction
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return new LogicFunction($a["expression"] ?? "", $a["input_vars"] ?? []);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // general minterm regex: ([/!~]*[a-zA-Z_][a-zA-Z0-9_]*[&]{1,2})*([/!~]*[a-zA-Z_][a-zA-Z0-9_]*)
 | 
				
			||||||
 | 
					    // specific regex: ([\/!~]*(<input vars>)[&]{1,2})*([\/!~]*(<input vars>))
 | 
				
			||||||
 | 
					    public static function isCorrectDNF(array $input_vars, string $exp): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $exp = trim($exp); // trim spaces
 | 
				
			||||||
 | 
					        $minterms = explode("|", $exp); // break up the expression into minterms
 | 
				
			||||||
 | 
					        $minterms = array_map(fn($mt) => trim($mt, " ()\t"), $minterms); // strip the parentheses off the minterms
 | 
				
			||||||
 | 
					        $minterms = array_map(fn($mt) => str_replace(" ", "", $mt), $minterms); // remove spaces
 | 
				
			||||||
 | 
					        $ivars = implode("|", $input_vars); // create | separated list of input vars to be used with the regular expression
 | 
				
			||||||
 | 
					        $regex = "/([\/!~]*(${ivars})[&]{1,2})*([\/!~]*(${ivars}))/"; // specific regular expression
 | 
				
			||||||
 | 
					        foreach ($minterms as $minterm) {
 | 
				
			||||||
 | 
					            if (preg_match($regex, $minterm) !== 1) { // generally try to match the minterm
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            preg_match_all("/[\/!~]*(${ivars})[&]*/", $minterm, $matches); // fetch variables
 | 
				
			||||||
 | 
					            $vars = $matches[1] ?? [];
 | 
				
			||||||
 | 
					            sort($vars); // sort detected variables
 | 
				
			||||||
 | 
					            sort($input_vars); // sort input variables
 | 
				
			||||||
 | 
					            if ($vars !== $input_vars) { // ensure each variable occurs just once
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function initStatic(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        self::$EXP_LANG = new ExpressionLanguage();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function toDNF(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $N = count($this->input_vars);
 | 
				
			||||||
 | 
					        $M = pow(2, count($this->input_vars));
 | 
				
			||||||
 | 
					        $tt = $this->getTruthTable();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $minterms = [];
 | 
				
			||||||
 | 
					        for ($i = 0; $i < $M; $i++) {
 | 
				
			||||||
 | 
					            $r = $tt[$i];
 | 
				
			||||||
 | 
					            if ($r == "1") {
 | 
				
			||||||
 | 
					                $term = "(";
 | 
				
			||||||
 | 
					                for ($j = 0; $j < $N; $j++) {
 | 
				
			||||||
 | 
					                    $inv = (($i >> ($N - $j - 1)) & 1) ? "~" : "";
 | 
				
			||||||
 | 
					                    $term .= $inv . $this->input_vars[$j];
 | 
				
			||||||
 | 
					                    if ($j < ($N - 1)) {
 | 
				
			||||||
 | 
					                        $term .= " & ";
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                $term .= ")";
 | 
				
			||||||
 | 
					                $minterms[] = $term;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return join(" | ", $minterms);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function drawNetwork(string $fn, string $outvar = "f"): void {
 | 
				
			||||||
 | 
					        PythonUtils::execPy("draw_logic_network.py", [ $this->getExpression(), $outvar, $fn ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LogicFunction::initStatic();
 | 
				
			||||||
							
								
								
									
										54
									
								
								class/LogicUtils.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								class/LogicUtils.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LogicUtils
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public 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), $exnd, $extd, STR_PAD_LEFT); // extend to the left
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public 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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public 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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								class/PythonUtils.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								class/PythonUtils.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require_once "Utils.php";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PythonUtils
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private const VENV = Utils::WORKSPACE_DIR . DIRECTORY_SEPARATOR . "venv";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function execPy(string $script, array $args): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $venv = getcwd() . DIRECTORY_SEPARATOR . self::VENV; // compose full venv path
 | 
				
			||||||
 | 
					        $ws = getcwd() . DIRECTORY_SEPARATOR . Utils::WORKSPACE_DIR; // compose full workspace path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //$source_cmd = "source " . $venv . DIRECTORY_SEPARATOR . "bin" . DIRECTORY_SEPARATOR . "activate";
 | 
				
			||||||
 | 
					        $flattened_args = join(" ", array_map(fn($arg) => "'$arg'", $args)); // prepare arguments for use on command line
 | 
				
			||||||
 | 
					        $python_cmd = "bash $ws" . DIRECTORY_SEPARATOR . "py_exec.sh \"$ws" . DIRECTORY_SEPARATOR . $script . "\" " . $flattened_args . " 2>&1";
 | 
				
			||||||
 | 
					        $ret = shell_exec($python_cmd); // execute python script
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										128
									
								
								class/Task.php
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								class/Task.php
									
									
									
									
									
								
							@ -2,23 +2,69 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class Task implements JsonSerializable
 | 
					class Task implements JsonSerializable
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    protected string $type; // task type
 | 
					    private string $type; // task type
 | 
				
			||||||
    protected string $question; // the task title
 | 
					    private string $question; // the task title
 | 
				
			||||||
    protected mixed $player_answer; // answer given by the player
 | 
					    protected mixed $player_answer; // answer given by the player
 | 
				
			||||||
    protected mixed $correct_answer;
 | 
					    protected mixed $correct_answer;
 | 
				
			||||||
    protected float $max_mark; // maximum points that can be collected at this task
 | 
					    private float $max_mark; // maximum points that can be collected at this task
 | 
				
			||||||
    protected bool $is_template; // this task is a template
 | 
					    private float $mark; // earned points
 | 
				
			||||||
    protected array $flags; // task flags
 | 
					    private bool $is_template; // this task is a template
 | 
				
			||||||
 | 
					    private array $flags; // task flags
 | 
				
			||||||
 | 
					    private string $lua_script; // path to the corresponding Lua script
 | 
				
			||||||
 | 
					    private Game|Test|null $governor; // object that governs this task
 | 
				
			||||||
 | 
					    private LuaSandbox|null $lua_sandbox; // Lua sandbox, initially NULL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // -------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function addLuaLibraries(): void {
 | 
				
			||||||
 | 
					        // register member methods
 | 
				
			||||||
 | 
					        $method_names = get_class_methods($this);
 | 
				
			||||||
 | 
					        $methods = [];
 | 
				
			||||||
 | 
					        foreach ($method_names as $method_name) {
 | 
				
			||||||
 | 
					            $methods[$method_name] = fn() => [ call_user_func(array(&$this, $method_name), ...func_get_args()) ];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        $this->lua_sandbox->registerLibrary("task", $methods);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // register generic functionality
 | 
				
			||||||
 | 
					        $this->lua_sandbox->registerLibrary("php", [
 | 
				
			||||||
 | 
					            "print" => function($str) {
 | 
				
			||||||
 | 
					                printf("%s\n", $str);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "replace" => "str_replace",
 | 
				
			||||||
 | 
					            "replace_field" => function($field, $replacement, $str) {
 | 
				
			||||||
 | 
					                return [ str_replace("{{" . $field . "}}", $replacement, $str) ];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private function createLuaSandbox(): void {
 | 
				
			||||||
 | 
					        if ($this->lua_sandbox === null) {
 | 
				
			||||||
 | 
					            $this->lua_sandbox = new LuaSandbox;
 | 
				
			||||||
 | 
					            $this->addLuaLibraries();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private function luaCall(string $lua_function): void {
 | 
				
			||||||
 | 
					        $this->createLuaSandbox();
 | 
				
			||||||
 | 
					        $implementation = file_get_contents($this->getGameDir() . DIRECTORY_SEPARATOR . $this->lua_script);
 | 
				
			||||||
 | 
					        $function_call = "$lua_function()";
 | 
				
			||||||
 | 
					        $joined_code = $implementation . "\n\n" . $function_call;
 | 
				
			||||||
 | 
					        $fn = $this->lua_sandbox->loadString($joined_code);
 | 
				
			||||||
 | 
					        $fn->call();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function __construct(string $type, array &$a = null)
 | 
					    function __construct(string $type, array &$a = null)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->type = $type;
 | 
					        $this->type = $type;
 | 
				
			||||||
        $this->is_template = $a["is_template"] ?? false;
 | 
					        $this->is_template = $a["is_template"] ?? false;
 | 
				
			||||||
        $this->max_mark = $a["max_mark"] ?? 1.0;
 | 
					        $this->max_mark = $a["max_mark"] ?? 1.0;
 | 
				
			||||||
 | 
					        $this->mark = $a["mark"] ?? -1;
 | 
				
			||||||
        $this->question = $a["question"] ?? "";
 | 
					        $this->question = $a["question"] ?? "";
 | 
				
			||||||
        $this->flags = $a["flags"] ?? [];
 | 
					        $this->flags = $a["flags"] ?? [];
 | 
				
			||||||
        $this->player_answer = $a["player_answer"] ?? null;
 | 
					        $this->player_answer = $a["player_answer"] ?? null;
 | 
				
			||||||
        $this->correct_answer = $a["correct_answer"] ?? null;
 | 
					        $this->correct_answer = $a["correct_answer"] ?? null;
 | 
				
			||||||
 | 
					        $this->lua_script = $a["lua_script"] ?? "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->governor = null;
 | 
				
			||||||
 | 
					        $this->lua_sandbox = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function setQuestion(string $question): void
 | 
					    function setQuestion(string $question): void
 | 
				
			||||||
@ -65,22 +111,48 @@ class Task implements JsonSerializable
 | 
				
			|||||||
        return $this->max_mark;
 | 
					        return $this->max_mark;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function getMark(): float
 | 
					    function setMark(float $mark): void {
 | 
				
			||||||
    {
 | 
					        $this->mark = $mark;
 | 
				
			||||||
        return 1.0;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function toArray(): array
 | 
					    function getMark(): float
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->mark;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function luaCheck(): void {
 | 
				
			||||||
 | 
					        //$lua = new Lua($this->getGameDir() . DIRECTORY_SEPARATOR . $this->lua_script);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected function staticCheck(): void {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function autoCheck(): void {
 | 
				
			||||||
 | 
					        if ($this->lua_script !== "") {
 | 
				
			||||||
 | 
					            $this->luaCheck();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            $this->staticCheck();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function toArray(string $mode = "all"): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $a = [
 | 
					        $a = [
 | 
				
			||||||
            "type" => $this->type,
 | 
					            "type" => $this->type,
 | 
				
			||||||
            "question" => $this->question,
 | 
					            "question" => $this->question,
 | 
				
			||||||
            "max_mark" => $this->max_mark,
 | 
					            "max_mark" => $this->max_mark,
 | 
				
			||||||
            "is_template" => $this->is_template,
 | 
					            "mark" => $this->mark,
 | 
				
			||||||
            "flags" => $this->flags,
 | 
					 | 
				
			||||||
            "correct_answer" => $this->correct_answer,
 | 
					            "correct_answer" => $this->correct_answer,
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($mode === "all") {
 | 
				
			||||||
 | 
					            $a["is_template"] = $this->is_template;
 | 
				
			||||||
 | 
					            $a["flags"] = $this->flags;
 | 
				
			||||||
 | 
					            $a["lua_script"] = $this->lua_script;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!$this->isTemplate()) {
 | 
					        if (!$this->isTemplate()) {
 | 
				
			||||||
            $a["player_answer"] = $this->player_answer;
 | 
					            $a["player_answer"] = $this->player_answer;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -118,6 +190,16 @@ class Task implements JsonSerializable
 | 
				
			|||||||
        return in_array($flag, $this->flags);
 | 
					        return in_array($flag, $this->flags);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function setLuaScript(string $lua_script): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->lua_script = $lua_script;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getLuaScript(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->lua_script;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function getPlayerAnswer(): mixed {
 | 
					    function getPlayerAnswer(): mixed {
 | 
				
			||||||
        return $this->player_answer;
 | 
					        return $this->player_answer;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -137,8 +219,32 @@ class Task implements JsonSerializable
 | 
				
			|||||||
        return $this->correct_answer;
 | 
					        return $this->correct_answer;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function luaRandomize(): void {
 | 
				
			||||||
 | 
					        $this->luaCall("randomize");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function randomize(): void
 | 
					    function randomize(): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        if ($this->lua_script !== "") {
 | 
				
			||||||
 | 
					            $this->luaRandomize();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function setGovernor(Game|Test|null &$governor): void {
 | 
				
			||||||
 | 
					        $this->governor =  &$governor;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function &getGovernor(): Game|Test|null {
 | 
				
			||||||
 | 
					        return $this->governor;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function getGameDir(): string {
 | 
				
			||||||
 | 
					        $gov = $this->getGovernor();
 | 
				
			||||||
 | 
					        if ($gov == null) {
 | 
				
			||||||
 | 
					            return "";
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return $gov->getGameDir();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -6,30 +6,44 @@ require_once "Tasks/OpenEndedTask.php";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
require_once "Tasks/NumberConversionTask.php";
 | 
					require_once "Tasks/NumberConversionTask.php";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require_once "Tasks/TruthTableTask.php";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require_once "Tasks/VerilogTask.php";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TaskFactory
 | 
					class TaskFactory
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    static function fromArray(array $a): Task|null
 | 
					    static function fromArray(array $a, Game|Test|null &$governor = null): Task|null
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $type = $a["type"] ?? "singlechoice"; // if the type is missing, then it's a single choice task
 | 
					        $type = $a["type"] ?? "singlechoice"; // if the type is missing, then it's a single choice task
 | 
				
			||||||
        switch ($type) {
 | 
					        switch ($type) {
 | 
				
			||||||
            case "singlechoice":
 | 
					            case "singlechoice":
 | 
				
			||||||
                return new SingleChoiceTask($a);
 | 
					                $task = new SingleChoiceTask($a);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case "openended":
 | 
					            case "openended":
 | 
				
			||||||
                return new OpenEndedTask($a);
 | 
					                $task = new OpenEndedTask($a);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case "numberconversion":
 | 
					            case "numberconversion":
 | 
				
			||||||
                return new NumberConversionTask($a);
 | 
					                $task = new NumberConversionTask($a);
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					            case "truthtable":
 | 
				
			||||||
 | 
					                $task = new TruthTableTask($a);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case "verilog":
 | 
				
			||||||
 | 
					                $task = new VerilogTask($a);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return null;
 | 
					        $task->setGovernor($governor);
 | 
				
			||||||
 | 
					        return $task;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static function constructFromCollection(array $c): array {
 | 
					    static function constructFromCollection(array $c, Game|Test|null &$governor = null): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        $chgs = [];
 | 
					        $chgs = [];
 | 
				
			||||||
        foreach ($c as $ch) {
 | 
					        foreach ($c as $ch) {
 | 
				
			||||||
            $chgs[] = TaskFactory::fromArray($ch);
 | 
					            $chgs[] = TaskFactory::fromArray($ch, $governor);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return $chgs;
 | 
					        return $chgs;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
require_once "OpenEndedTask.php";
 | 
					require_once "OpenEndedTask.php";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require_once "class/LogicUtils.php";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NumberConversionTask extends OpenEndedTask
 | 
					class NumberConversionTask extends OpenEndedTask
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    protected string $instruction; // instruction word
 | 
					    protected string $instruction; // instruction word
 | 
				
			||||||
@ -52,9 +54,9 @@ class NumberConversionTask extends OpenEndedTask
 | 
				
			|||||||
        $this->correct_answer = $a["correct_answer"] ?? "---";
 | 
					        $this->correct_answer = $a["correct_answer"] ?? "---";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function toArray(): array
 | 
					    public function toArray(string $mode = "all"): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $a = parent::toArray();
 | 
					        $a = parent::toArray($mode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $a["instruction"] = $this->instruction;
 | 
					        $a["instruction"] = $this->instruction;
 | 
				
			||||||
        $a["source"] = $this->source;
 | 
					        $a["source"] = $this->source;
 | 
				
			||||||
@ -63,58 +65,10 @@ class NumberConversionTask extends OpenEndedTask
 | 
				
			|||||||
        return $a;
 | 
					        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
 | 
					    public function randomize(): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        parent::randomize();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // validate representation marks
 | 
					        // validate representation marks
 | 
				
			||||||
        $invalid = in_array($this->src_rep . $this->dst_rep, ["us", "su"]);
 | 
					        $invalid = in_array($this->src_rep . $this->dst_rep, ["us", "su"]);
 | 
				
			||||||
        if ($invalid) { // fix invalid representation pairs
 | 
					        if ($invalid) { // fix invalid representation pairs
 | 
				
			||||||
@ -144,16 +98,18 @@ class NumberConversionTask extends OpenEndedTask
 | 
				
			|||||||
        $m = random_int($min, $max);
 | 
					        $m = random_int($min, $max);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // create the question and the answer
 | 
					        // create the question and the answer
 | 
				
			||||||
        $this->correct_answer = self::changeRepresentation($m, $this->dst_base, $this->dst_rep, $this->dst_n_digits);
 | 
					        $this->correct_answer = LogicUtils::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);
 | 
					        $this->source = LogicUtils::changeRepresentation($m, $this->src_base, $this->src_rep, $this->src_n_digits);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getMark(): float
 | 
					    public function staticCheck(): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        $mark = 0.0;
 | 
				
			||||||
        if ($this->hasFlag("acceptwithoutleadingzeros")) {
 | 
					        if ($this->hasFlag("acceptwithoutleadingzeros")) {
 | 
				
			||||||
            return (ltrim($this->player_answer, " 0") === ltrim($this->correct_answer, "0")) ? 1.0 : 0.0;
 | 
					            $mark = (ltrim($this->player_answer, " 0") === ltrim($this->correct_answer, "0")) ? 1.0 : 0.0;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            return (trim($this->player_answer) === trim($this->correct_answer)) ? 1.0 : 0.0;
 | 
					            $mark = (trim($this->player_answer) === trim($this->correct_answer)) ? 1.0 : 0.0;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        $this->setMark($mark);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -8,7 +8,8 @@ class OpenEndedTask extends PicturedTask
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        parent::__construct("openended", $a);
 | 
					        parent::__construct("openended", $a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->correct_answer = $a["correct_answer"] ?? null;
 | 
					        $this->correct_answer = $a["correct_answer"] ?? [];
 | 
				
			||||||
 | 
					        $this->player_answer = $this->player_answer ?? "";
 | 
				
			||||||
        $this->setMaxMark(1.0);
 | 
					        $this->setMaxMark(1.0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -20,7 +21,7 @@ class OpenEndedTask extends PicturedTask
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        // collect transformations
 | 
					        // collect transformations
 | 
				
			||||||
        $transform_fns = [];
 | 
					        $transform_fns = [];
 | 
				
			||||||
        foreach ($this->flags as $flag) {
 | 
					        foreach ($this->getFlags() as $flag) {
 | 
				
			||||||
            switch ($flag) {
 | 
					            switch ($flag) {
 | 
				
			||||||
                case "makeuppercase":
 | 
					                case "makeuppercase":
 | 
				
			||||||
                    $transform_fns[] = "strtoupper";
 | 
					                    $transform_fns[] = "strtoupper";
 | 
				
			||||||
@ -45,18 +46,14 @@ class OpenEndedTask extends PicturedTask
 | 
				
			|||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function clearAnswer(): void
 | 
					    public function staticCheck(): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->player_answer = "";
 | 
					        $mark = in_array($this->player_answer, $this->correct_answer) ? 1.0 : 0.0;
 | 
				
			||||||
 | 
					        $this->setMark($mark);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getMark(): float
 | 
					    function toArray(string $mode = "all"): array {
 | 
				
			||||||
    {
 | 
					        $a = parent::toArray($mode);
 | 
				
			||||||
        return in_array($this->player_answer, $this->correct_answer) ? 1.0 : 0.0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function toArray(): array {
 | 
					 | 
				
			||||||
        $a = parent::toArray();
 | 
					 | 
				
			||||||
        $a["correct_answer"] = $this->correct_answer;
 | 
					        $a["correct_answer"] = $this->correct_answer;
 | 
				
			||||||
        return $a;
 | 
					        return $a;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -4,28 +4,39 @@ require_once "class/Task.php";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class PicturedTask extends Task
 | 
					class PicturedTask extends Task
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    protected string $image_url; // the URL of the corresponding image
 | 
					    private string $image_data; // image data or the URL
 | 
				
			||||||
 | 
					    private string $image_type; // the type of the image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function __construct(string $type, array &$a = null)
 | 
					    function __construct(string $type, array &$a = null)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        parent::__construct($type, $a);
 | 
					        parent::__construct($type, $a);
 | 
				
			||||||
        $this->image_url = $a["image_url"] ?? "";
 | 
					        $this->image_data = $a["image_data"] ?? ($a["image_url"] ?? "");
 | 
				
			||||||
 | 
					        $this->image_type = $a["image_type"] ?? "none";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function setImageUrl(string $image_url): void
 | 
					    function setImageData(string $image_data): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->image_url = $image_url;
 | 
					        $this->image_data = $image_data;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function getImageUrl(): string
 | 
					    function getImageData(): string
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->image_url;
 | 
					        return $this->image_data;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function toArray(): array
 | 
					    function setImageType(string $image_type): void {
 | 
				
			||||||
 | 
					        $this->image_type = $image_type;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function getImageType(): string {
 | 
				
			||||||
 | 
					        return $this->image_type;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function toArray(string $mode = "all"): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $a = parent::toArray();
 | 
					        $a = parent::toArray($mode);
 | 
				
			||||||
        $a["image_url"] = $this->image_url;
 | 
					        $a["image_data"] = $this->image_data;
 | 
				
			||||||
 | 
					        $a["image_type"] = $this->image_type;
 | 
				
			||||||
        return $a;
 | 
					        return $a;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -32,7 +32,7 @@ class SingleChoiceTask extends PicturedTask
 | 
				
			|||||||
        return $this->answers;
 | 
					        return $this->answers;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private function isAnswerIdInsideBounds($ansid): bool
 | 
					    private function isAnswerIdInsideBounds(int $ansid): bool
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return ($ansid >= 0) && ($ansid <= count($this->answers));
 | 
					        return ($ansid >= 0) && ($ansid <= count($this->answers));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -53,20 +53,23 @@ class SingleChoiceTask extends PicturedTask
 | 
				
			|||||||
        $this->player_answer = -1;
 | 
					        $this->player_answer = -1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function getMark(): float
 | 
					    function staticCheck(): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return ($this->player_answer == $this->correct_answer) ? 1.0 : 0.0;
 | 
					        $mark = ($this->player_answer == $this->correct_answer) ? 1.0 : 0.0;
 | 
				
			||||||
 | 
					        $this->setMark($mark);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function toArray(): array
 | 
					    function toArray(string $mode = "all"): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $a = parent::toArray();
 | 
					        $a = parent::toArray($mode);
 | 
				
			||||||
        $a["answers"] = $this->answers;
 | 
					        $a["answers"] = $this->answers;
 | 
				
			||||||
        $a["correct_answer"] = $this->correct_answer;
 | 
					        $a["correct_answer"] = $this->correct_answer;
 | 
				
			||||||
        return $a;
 | 
					        return $a;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function randomize(): void{
 | 
					    function randomize(): void{
 | 
				
			||||||
 | 
					        parent::randomize();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $ordering = range(0, count($this->answers) - 1); // create an ordered range
 | 
					        $ordering = range(0, count($this->answers) - 1); // create an ordered range
 | 
				
			||||||
        shuffle($ordering); // shuffle indices
 | 
					        shuffle($ordering); // shuffle indices
 | 
				
			||||||
        $shans = [];
 | 
					        $shans = [];
 | 
				
			||||||
 | 
				
			|||||||
@ -2,17 +2,71 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
require_once "OpenEndedTask.php";
 | 
					require_once "OpenEndedTask.php";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TruthTableTask extends OpenEndedTask
 | 
					require_once "class/LogicFunction.php";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require_once "class/Utils.php";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TruthTableTask extends PicturedTask
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    private LogicFunction $lf; // logic functions
 | 
				
			||||||
 | 
					    private string $output_variable; // output variable
 | 
				
			||||||
    public function __construct(array $a = null)
 | 
					    public function __construct(array $a = null)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        parent::__construct($a);
 | 
					        parent::__construct("truthtable", $a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->setType("verilog");
 | 
					        if (isset($a["function"])) { // fetching from a JSON-stored object
 | 
				
			||||||
 | 
					            $this->lf = LogicFunction::fromArray($a["function"]);
 | 
				
			||||||
 | 
					        } else if (isset($a["expression"], $a["input_variables"])) { // building from the scratch
 | 
				
			||||||
 | 
					            $this->lf = new LogicFunction($a["expression"], $a["input_variables"]);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            $this->lf = new LogicFunction();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->setOutputVariable($a["output_variable"] ?? "f");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function randomize(): void
 | 
					    public function staticCheck(): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return parent::randomize(); // TODO: Change the autogenerated stub
 | 
					        $ans_tt = $this->player_answer;
 | 
				
			||||||
 | 
					        $cans_tt = $this->lf->getTruthTable();
 | 
				
			||||||
 | 
					        $errs = 0;
 | 
				
			||||||
 | 
					        for ($i = 0; $i < $this->lf->getNStates(); $i++) {
 | 
				
			||||||
 | 
					            if (($ans_tt[$i] ?? " ") != $cans_tt[$i]) {
 | 
				
			||||||
 | 
					                $errs++;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        $mark = ($errs === 0) ? 1.0 : 0.0;
 | 
				
			||||||
 | 
					        $this->setMark($mark);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function setOutputVariable(string $ovar): void {
 | 
				
			||||||
 | 
					        $this->output_variable = $ovar;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getOutputVariable(): string {
 | 
				
			||||||
 | 
					        return $this->output_variable;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function setLogicFunction(LogicFunction $lf): void {
 | 
				
			||||||
 | 
					        $this->lf = $lf;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getLogicFunction(): LogicFunction {
 | 
				
			||||||
 | 
					        return $this->lf;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function toArray(string $mode = "all"): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $a = parent::toArray($mode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($mode === "all") {
 | 
				
			||||||
 | 
					            $a["function"] = $this->lf->toArray();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $a["correct_answer"] = $this->lf->getTruthTable();
 | 
				
			||||||
 | 
					        $a["input_variables"] = $this->lf->getInputVars();
 | 
				
			||||||
 | 
					        $a["output_variable"] = $this->output_variable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $a;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										107
									
								
								class/Tasks/VerilogTask.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								class/Tasks/VerilogTask.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require_once "PicturedTask.php";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class VerilogTask extends PicturedTask
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private string $test_bench_fn; // test bench file name
 | 
				
			||||||
 | 
					    private string $explanation; // short explanation for the marking
 | 
				
			||||||
 | 
					    private const FAILED_MARK = "[FAILED]"; // mark at the beginning of testbench results indicating a failed combination
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function __construct(array &$a = null)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        parent::__construct("verilog", $a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->explanation = $a["explanation"] ?? "";
 | 
				
			||||||
 | 
					        $this->test_bench_fn = $a["test_bench_fn"] ?? "";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function verifyCode(): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // check that no $function calls are in the code
 | 
				
			||||||
 | 
					        if (str_contains($this->player_answer, "$")) {
 | 
				
			||||||
 | 
					            $this->explanation .= "A kód nem tartalmazhat \$függvényhívásokat!\n";
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private function executeTest(): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // store the user's answer
 | 
				
			||||||
 | 
					        $module_code_fn = tempnam(sys_get_temp_dir(), "verilogtask_user_module_");
 | 
				
			||||||
 | 
					        file_put_contents($module_code_fn, $this->getPlayerAnswer());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // modify the test bench and save into a separate file
 | 
				
			||||||
 | 
					        $test_bench_fn = tempnam(sys_get_temp_dir(), "verilogtask_test_bench_");
 | 
				
			||||||
 | 
					        $include_line = "`include \"$module_code_fn\"\n\n";
 | 
				
			||||||
 | 
					        $tb = $include_line . file_get_contents($this->getGameDir() . DIRECTORY_SEPARATOR . $this->test_bench_fn);
 | 
				
			||||||
 | 
					        file_put_contents($test_bench_fn, $tb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // run the simulation
 | 
				
			||||||
 | 
					        $output_fn = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid("verilogtask_output_");
 | 
				
			||||||
 | 
					        $iverilog_cmd = "iverilog $test_bench_fn -o $output_fn 2>&1";
 | 
				
			||||||
 | 
					        $compilation_log = shell_exec($iverilog_cmd);
 | 
				
			||||||
 | 
					        $failed_count = 0;
 | 
				
			||||||
 | 
					        if (!is_null($compilation_log)) {
 | 
				
			||||||
 | 
					            $compilation_log = str_replace([$module_code_fn, $test_bench_fn], ["[kód]", "[tesztkörnyezet]"], $compilation_log);
 | 
				
			||||||
 | 
					            $this->explanation .= "Fordítási hiba:\n\n" . (string)($compilation_log);
 | 
				
			||||||
 | 
					            $failed_count = PHP_INT_MAX;
 | 
				
			||||||
 | 
					            goto cleanup;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (file_exists($output_fn)) {
 | 
				
			||||||
 | 
					            $tb_output = shell_exec("$output_fn");
 | 
				
			||||||
 | 
					            $tb_output_lines = array_map(fn($line) => trim($line), explode("\n", $tb_output));
 | 
				
			||||||
 | 
					            $failed_trimlen = strlen(self::FAILED_MARK);
 | 
				
			||||||
 | 
					            foreach ($tb_output_lines as $line) {
 | 
				
			||||||
 | 
					                if (str_starts_with($line, self::FAILED_MARK)) {
 | 
				
			||||||
 | 
					                    $this->explanation .= substr($line, $failed_trimlen) . "\n";
 | 
				
			||||||
 | 
					                    $failed_count++;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ($failed_count == 0) {
 | 
				
			||||||
 | 
					                $this->explanation .= "Minden rendben! :)";
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                $this->explanation = "$failed_count db hiba:\n\n" . $this->explanation;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cleanup:
 | 
				
			||||||
 | 
					        // remove the temporary files
 | 
				
			||||||
 | 
					        @unlink($module_code_fn);
 | 
				
			||||||
 | 
					        @unlink($test_bench_fn);
 | 
				
			||||||
 | 
					        @unlink($output_fn);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $failed_count == 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function staticCheck(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->explanation = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // verify code
 | 
				
			||||||
 | 
					        $mark = 0.0;
 | 
				
			||||||
 | 
					        if ($this->verifyCode()) {
 | 
				
			||||||
 | 
					            // run the simulation
 | 
				
			||||||
 | 
					            $test_ok = $this->executeTest();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $mark = $test_ok ? 1.0 : 0.0;  // FIXME
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        $this->setMark($mark);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function toArray(string $mode = "all"): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $a = parent::toArray($mode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $a["explanation"] = $this->explanation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($mode == "all") {
 | 
				
			||||||
 | 
					            $a["test_bench_fn"] = $this->test_bench_fn;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $a;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -32,6 +32,7 @@ class Test extends AutoStoring
 | 
				
			|||||||
    private function preprocessTasks(): void
 | 
					    private function preprocessTasks(): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        foreach ($this->tasks as &$task) {
 | 
					        foreach ($this->tasks as &$task) {
 | 
				
			||||||
 | 
					            $task->setGovernor($this); // set the task governor
 | 
				
			||||||
            $task->setTemplate(false); // the task is no longer a template
 | 
					            $task->setTemplate(false); // the task is no longer a template
 | 
				
			||||||
            $task->randomize(); // randomize
 | 
					            $task->randomize(); // randomize
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -79,7 +80,7 @@ class Test extends AutoStoring
 | 
				
			|||||||
            $this->endTime = $a["end_time"] ?? 0;
 | 
					            $this->endTime = $a["end_time"] ?? 0;
 | 
				
			||||||
            $this->endLimitTime = $a["end_limit_time"] ?? 0;
 | 
					            $this->endLimitTime = $a["end_limit_time"] ?? 0;
 | 
				
			||||||
            $this->repeatable = $a["repeatable"];
 | 
					            $this->repeatable = $a["repeatable"];
 | 
				
			||||||
            $this->tasks = TaskFactory::constructFromCollection($a["challenges"]);
 | 
					            $this->tasks = TaskFactory::constructFromCollection($a["challenges"], $this);
 | 
				
			||||||
            if (isset($a["summary"])) {
 | 
					            if (isset($a["summary"])) {
 | 
				
			||||||
                $this->summary = TestSummary::fromArray($a["summary"]);
 | 
					                $this->summary = TestSummary::fromArray($a["summary"]);
 | 
				
			||||||
            } else { // backward compatibility
 | 
					            } else { // backward compatibility
 | 
				
			||||||
@ -123,11 +124,11 @@ class Test extends AutoStoring
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Convert test to array.
 | 
					    // Convert test to array.
 | 
				
			||||||
    function toArray(array $omit = []): array
 | 
					    function toArray(array $omit = [], string $mode = "all"): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $tasks = [];
 | 
					        $tasks = [];
 | 
				
			||||||
        foreach ($this->tasks as &$t) {
 | 
					        foreach ($this->tasks as &$t) {
 | 
				
			||||||
            $tasks[] = $t->toArray();
 | 
					            $tasks[] = $t->toArray($mode);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $a = [
 | 
					        $a = [
 | 
				
			||||||
@ -195,6 +196,7 @@ class Test extends AutoStoring
 | 
				
			|||||||
        // summarize points
 | 
					        // summarize points
 | 
				
			||||||
        $mark_sum = 0.0;
 | 
					        $mark_sum = 0.0;
 | 
				
			||||||
        foreach ($this->tasks as &$ch) {
 | 
					        foreach ($this->tasks as &$ch) {
 | 
				
			||||||
 | 
					            $ch->autoCheck();
 | 
				
			||||||
            $mark_sum += $ch->getMark();
 | 
					            $mark_sum += $ch->getMark();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -248,4 +250,8 @@ class Test extends AutoStoring
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->state === self::TEST_ONGOING;
 | 
					        return $this->state === self::TEST_ONGOING;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function getGameDir(): string {
 | 
				
			||||||
 | 
					        return Game::getGameDirById($this->gameId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								class/Utils.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								class/Utils.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Utils
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public const WORKSPACE_DIR = "workspace";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function str2kv(string $str): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        preg_match_all("/([^,= ]+)=([^,= ]+)/", $str, $r);
 | 
				
			||||||
 | 
					        return array_combine($r[1], $r[2]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function str2a(string $str): array {
 | 
				
			||||||
 | 
					        return explode(",", $str);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static function getWorkspaceDir(): string {
 | 
				
			||||||
 | 
					        return getcwd() . DIRECTORY_SEPARATOR . self::WORKSPACE_DIR;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -45,10 +45,19 @@ if (isset($options["action"])) {
 | 
				
			|||||||
            break;
 | 
					            break;
 | 
				
			||||||
        case "gen_random":
 | 
					        case "gen_random":
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                //$lf = LogicFunction::genRandom(["a", "b", "c"], 2, 4);
 | 
					                $lf = LogicFunction::genRandom(["a", "b", "c"], 2, 4);
 | 
				
			||||||
                $lf = LogicFunction::genRandomDF(["a", "b", "c"]);
 | 
					                //$lf = LogicFunction::genRandomDF(["a", "b", "c"]);
 | 
				
			||||||
                printf("Verilog-form: %s\nTeX-form: %s\n", $lf->verilog_form, $lf->tex_form);
 | 
					                printf("Verilog-form: %s\nTeX-form: %s\n", $lf->getExpression(), $lf->getExpression("tex"));
 | 
				
			||||||
                $lf->getTruthTable();
 | 
					                print_r($lf->getTruthTable());
 | 
				
			||||||
 | 
					                print_r($lf->toDNF());
 | 
				
			||||||
 | 
					                $lf->drawNetwork("TESTING/network.svg");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case "verify":
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                printf("Verifying expression\n");
 | 
				
			||||||
 | 
					                $ok = LogicFunction::isCorrectDNF(["a", "b", "c"], "(a & ~b & c) | (b & ~c & a)");
 | 
				
			||||||
 | 
					                printf("%d\n", $ok);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@
 | 
				
			|||||||
    "ext-mbstring" : "*",
 | 
					    "ext-mbstring" : "*",
 | 
				
			||||||
    "ext-zip": "*",
 | 
					    "ext-zip": "*",
 | 
				
			||||||
    "ext-fileinfo": "*",
 | 
					    "ext-fileinfo": "*",
 | 
				
			||||||
 | 
					    "ext-luasandbox": "*",
 | 
				
			||||||
    "phpoffice/phpspreadsheet": "^5.1",
 | 
					    "phpoffice/phpspreadsheet": "^5.1",
 | 
				
			||||||
    "symfony/expression-language": "^7.3"
 | 
					    "symfony/expression-language": "^7.3"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -258,7 +258,7 @@ function get_player_test(ReqHandler &$rh, array $params): array
 | 
				
			|||||||
    $test = access_test_data($params["testid"]);
 | 
					    $test = access_test_data($params["testid"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if ($test !== null) {
 | 
					    if ($test !== null) {
 | 
				
			||||||
        $test_data_with_current_time = $test->toArray();
 | 
					        $test_data_with_current_time = $test->toArray(mode: "public");
 | 
				
			||||||
        if ($test->isOngoing()) {
 | 
					        if ($test->isOngoing()) {
 | 
				
			||||||
            exclude_correct_answers($test_data_with_current_time["challenges"]);
 | 
					            exclude_correct_answers($test_data_with_current_time["challenges"]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -897,7 +897,8 @@ function import_users_from_csv(ReqHandler &$rh, array $params): string
 | 
				
			|||||||
function execute_cli_command(ReqHandler &$rh, array $params): string
 | 
					function execute_cli_command(ReqHandler &$rh, array $params): string
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    $args = $params["cmd"];
 | 
					    $args = $params["cmd"];
 | 
				
			||||||
    $cmdline = "php cli_actions.php $args";
 | 
					    $phpargs = "-dxdebug.default_enable=1 -dxdebug.remote_enable=1 -dxdebug.remote_autostart=1 -dxdebug.remote_port=9001 -dxdebug.remote_host=127.0.0.1 -dxdebug.remote_mode=req -dxdebug.idekey=PHPSTORM -dxdebug.mode=debug -dxdebug.discover_client_host=true -dxdebug.start_with_request=yes";
 | 
				
			||||||
 | 
					    $cmdline = "php $phpargs cli_actions.php $args";
 | 
				
			||||||
    $resp = "=> " . $cmdline . "\n\n";
 | 
					    $resp = "=> " . $cmdline . "\n\n";
 | 
				
			||||||
    $resp .= shell_exec($cmdline);
 | 
					    $resp .= shell_exec($cmdline);
 | 
				
			||||||
    //$resp = shell_exec("php -v");
 | 
					    //$resp = shell_exec("php -v");
 | 
				
			||||||
 | 
				
			|||||||
@ -88,7 +88,7 @@ function list_corresponding_results(gameid) {
 | 
				
			|||||||
           let test_summary_record = document.createElement("section");
 | 
					           let test_summary_record = document.createElement("section");
 | 
				
			||||||
           test_summary_record.classList.add("test-summary-record");
 | 
					           test_summary_record.classList.add("test-summary-record");
 | 
				
			||||||
           test_summary_record.addEventListener("click", () => {
 | 
					           test_summary_record.addEventListener("click", () => {
 | 
				
			||||||
              open_test(record["testid"]);
 | 
					              open_test(record["testid"], gameid);
 | 
				
			||||||
           });
 | 
					           });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
           let sequence_number_sec = document.createElement("section");
 | 
					           let sequence_number_sec = document.createElement("section");
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										504
									
								
								js/tasks.js
									
									
									
									
									
								
							
							
						
						
									
										504
									
								
								js/tasks.js
									
									
									
									
									
								
							@ -1,5 +1,6 @@
 | 
				
			|||||||
class Task extends HTMLElement {
 | 
					class Task extends HTMLElement {
 | 
				
			||||||
    static sequence_number = 0;
 | 
					    static sequence_number = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(type) {
 | 
					    constructor(type) {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -9,6 +10,8 @@ class Task extends HTMLElement {
 | 
				
			|||||||
        this.view_only = false;
 | 
					        this.view_only = false;
 | 
				
			||||||
        this.upload_answer_cb = null;
 | 
					        this.upload_answer_cb = null;
 | 
				
			||||||
        this.player_answer = null;
 | 
					        this.player_answer = null;
 | 
				
			||||||
 | 
					        this.correct_answer = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.shadow = this.attachShadow({mode: "open"});
 | 
					        this.shadow = this.attachShadow({mode: "open"});
 | 
				
			||||||
        this.createStyle();
 | 
					        this.createStyle();
 | 
				
			||||||
@ -44,14 +47,28 @@ class Task extends HTMLElement {
 | 
				
			|||||||
                border-bottom-right-radius: 0.3em;
 | 
					                border-bottom-right-radius: 0.3em;
 | 
				
			||||||
                border-top-left-radius: 0.3em;
 | 
					                border-top-left-radius: 0.3em;
 | 
				
			||||||
                width: 2em;
 | 
					                width: 2em;
 | 
				
			||||||
 | 
					                z-index: 10;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            section.answer-container {
 | 
					            section.answer-container {
 | 
				
			||||||
                /* (empty) */
 | 
					                /* (empty) */
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            section.bad-answer {
 | 
				
			||||||
 | 
					                background-color: #e5d8d3;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            code {
 | 
					            code {
 | 
				
			||||||
                font-family: 'Monaco', monospace;
 | 
					                font-family: 'Monaco', monospace;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
 | 
					            section#correct-answer {
 | 
				
			||||||
 | 
					                display: none;
 | 
				
			||||||
 | 
					                width: 100%;
 | 
				
			||||||
 | 
					                margin-top: 0.5em;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            section#correct-answer[visible="true"] {
 | 
				
			||||||
 | 
					                display: block;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            @media only screen and (max-width: 800px) {
 | 
					            @media only screen and (max-width: 800px) {
 | 
				
			||||||
                section.task {
 | 
					                section.task {
 | 
				
			||||||
                   width: calc(100vw - 3em);
 | 
					                   width: calc(100vw - 3em);
 | 
				
			||||||
@ -73,11 +90,14 @@ class Task extends HTMLElement {
 | 
				
			|||||||
        answer_container.classList.add("answer-container");
 | 
					        answer_container.classList.add("answer-container");
 | 
				
			||||||
        let seq_num_section = document.createElement("section");
 | 
					        let seq_num_section = document.createElement("section");
 | 
				
			||||||
        seq_num_section.classList.add("seq-num");
 | 
					        seq_num_section.classList.add("seq-num");
 | 
				
			||||||
        seq_num_section.innerText = `${this.sequence_number+1}.`;
 | 
					        seq_num_section.innerText = `${this.sequence_number + 1}.`;
 | 
				
			||||||
 | 
					        let ca_section = document.createElement("section");
 | 
				
			||||||
 | 
					        ca_section.id = "correct-answer";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        task_box.append(question_span);
 | 
					        task_box.append(question_span);
 | 
				
			||||||
        task_box.append(answer_container);
 | 
					        task_box.append(answer_container);
 | 
				
			||||||
        task_box.append(seq_num_section);
 | 
					        task_box.append(seq_num_section);
 | 
				
			||||||
 | 
					        task_box.append(ca_section);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.shadow.append(task_box);
 | 
					        this.shadow.append(task_box);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -85,11 +105,14 @@ class Task extends HTMLElement {
 | 
				
			|||||||
        this.question_span = question_span;
 | 
					        this.question_span = question_span;
 | 
				
			||||||
        this.answer_container = answer_container;
 | 
					        this.answer_container = answer_container;
 | 
				
			||||||
        this.seq_num_section = seq_num_section;
 | 
					        this.seq_num_section = seq_num_section;
 | 
				
			||||||
 | 
					        this.ca_section = ca_section;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    connectedCallback() {}
 | 
					    connectedCallback() {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    disconnectedCallback() {}
 | 
					    disconnectedCallback() {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    get type() {
 | 
					    get type() {
 | 
				
			||||||
        return this.task_type;
 | 
					        return this.task_type;
 | 
				
			||||||
@ -113,6 +136,7 @@ class Task extends HTMLElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    set isConcluded(concluded) {
 | 
					    set isConcluded(concluded) {
 | 
				
			||||||
        this.concluded = concluded;
 | 
					        this.concluded = concluded;
 | 
				
			||||||
 | 
					        this.ca_section.setAttribute("visible", this.concluded ? "true" : "false");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    get isViewOnly() {
 | 
					    get isViewOnly() {
 | 
				
			||||||
@ -135,6 +159,14 @@ class Task extends HTMLElement {
 | 
				
			|||||||
        return this.player_answer;
 | 
					        return this.player_answer;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    set correctAnswer(correct_answer) {
 | 
				
			||||||
 | 
					        this.correct_answer = correct_answer;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get correctAnswer() {
 | 
				
			||||||
 | 
					        return this.correct_answer;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    set uploadAnswerCb(cb) {
 | 
					    set uploadAnswerCb(cb) {
 | 
				
			||||||
        this.upload_answer_cb = cb;
 | 
					        this.upload_answer_cb = cb;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -145,22 +177,39 @@ class Task extends HTMLElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    displayCorrectAnswer() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fromArray(a) {
 | 
					    fromArray(a) {
 | 
				
			||||||
        this.setQuestion(a["question"]);
 | 
					        this.setQuestion(a["question"]);
 | 
				
			||||||
        this.playerAnswer = a["player_answer"];
 | 
					        this.playerAnswer = a["player_answer"];
 | 
				
			||||||
 | 
					        this.correctAnswer = a["correct_answer"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mark = a["mark"];
 | 
				
			||||||
 | 
					        if ((mark !== undefined) && (mark !== null) && (mark > -1) && (mark !== a["max_mark"])) {
 | 
				
			||||||
 | 
					            this.task_box.classList.add("bad-answer");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.isConcluded) {
 | 
				
			||||||
 | 
					            this.displayCorrectAnswer();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PicturedTask extends Task {
 | 
					class PicturedTask extends Task {
 | 
				
			||||||
    constructor(type) {
 | 
					    constructor(type) {
 | 
				
			||||||
        super(type);
 | 
					        super(type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.img = null;
 | 
				
			||||||
 | 
					        this.img_type = "none";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    createStyle() {
 | 
					    createStyle() {
 | 
				
			||||||
        super.createStyle();
 | 
					        super.createStyle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.css.innerHTML += `
 | 
					        this.css.innerHTML += `
 | 
				
			||||||
            img.question-image {
 | 
					            .question-image {
 | 
				
			||||||
                display: none;
 | 
					                display: none;
 | 
				
			||||||
                position: relative;
 | 
					                position: relative;
 | 
				
			||||||
                margin: 1em auto;
 | 
					                margin: 1em auto;
 | 
				
			||||||
@ -172,23 +221,45 @@ class PicturedTask extends Task {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    createElements() {
 | 
					    createElements() {
 | 
				
			||||||
        super.createElements();
 | 
					        super.createElements();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.img = document.createElement("img");
 | 
					 | 
				
			||||||
        this.img.classList.add("question-image");
 | 
					 | 
				
			||||||
        this.img.src = "";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.task_box.insertBefore(this.img, this.question_span);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    set imgUrl(url) {
 | 
					    set imgData(data) {
 | 
				
			||||||
        url = url.trim();
 | 
					        switch (this.img_type) {
 | 
				
			||||||
        this.img.src = url.trim();
 | 
					            case "url":
 | 
				
			||||||
        this.img.style.display = (url !== "") ? "block" : "none";
 | 
					            {
 | 
				
			||||||
 | 
					                this.img = document.createElement("img");
 | 
				
			||||||
 | 
					                this.img.classList.add("question-image");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                data = data.trim();
 | 
				
			||||||
 | 
					                this.img.src = data.trim();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					            case "svg":
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                this.img = document.createElementNS("http://www.w3.org/2000/svg", "svg");
 | 
				
			||||||
 | 
					                this.img.classList.add("question-image");
 | 
				
			||||||
 | 
					                this.img.innerHTML = data;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.img != null) {
 | 
				
			||||||
 | 
					            this.img.style.display = (data !== "") ? "block" : "none";
 | 
				
			||||||
 | 
					            this.task_box.insertBefore(this.img, this.question_span);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    get imgUrl() {
 | 
					    get imgData() {
 | 
				
			||||||
        return this.img.src;
 | 
					        return this.img.src;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    set imgType(t) {
 | 
				
			||||||
 | 
					        this.img_type = t;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get imgType() {
 | 
				
			||||||
 | 
					        return this.img_type;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SingleChoiceTask extends PicturedTask {
 | 
					class SingleChoiceTask extends PicturedTask {
 | 
				
			||||||
@ -227,9 +298,6 @@ class SingleChoiceTask extends PicturedTask {
 | 
				
			|||||||
                background-color: #176767;
 | 
					                background-color: #176767;
 | 
				
			||||||
                color: whitesmoke;
 | 
					                color: whitesmoke;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            section.bad-answer {
 | 
					 | 
				
			||||||
                background-color: #e5d8d3;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            section.bad-answer section.answer input[type="radio"]:checked+label:not(.correct-answer) {
 | 
					            section.bad-answer section.answer input[type="radio"]:checked+label:not(.correct-answer) {
 | 
				
			||||||
                background-color: #aa8a7d;
 | 
					                background-color: #aa8a7d;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -288,15 +356,7 @@ class SingleChoiceTask extends PicturedTask {
 | 
				
			|||||||
            this.answer_container.append(answer_section);
 | 
					            this.answer_container.append(answer_section);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        MathJax.typeset([ this.task_box ]);
 | 
					        MathJax.typeset([this.task_box]);
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    set correctAnswer(correct_answer) {
 | 
					 | 
				
			||||||
        this.correct_answer = correct_answer;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    get correctAnswer() {
 | 
					 | 
				
			||||||
        return this.correct_answer;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    get isCorrect() {
 | 
					    get isCorrect() {
 | 
				
			||||||
@ -306,7 +366,6 @@ class SingleChoiceTask extends PicturedTask {
 | 
				
			|||||||
    fromArray(a) {
 | 
					    fromArray(a) {
 | 
				
			||||||
        super.fromArray(a);
 | 
					        super.fromArray(a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.correctAnswer = a["correct_answer"];
 | 
					 | 
				
			||||||
        this.setAnswers(a["answers"]);
 | 
					        this.setAnswers(a["answers"]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -355,6 +414,10 @@ class OpenEndedTask extends PicturedTask {
 | 
				
			|||||||
        this.answer_tf = answer_tf;
 | 
					        this.answer_tf = answer_tf;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    displayCorrectAnswer() {
 | 
				
			||||||
 | 
					        this.ca_section.innerHTML = "<b>Lehetséges megoldások:</b><br style='margin-bottom: 0.3em'>" + this.correctAnswer.join(", <i>VAGY</i><br>");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fromArray(a) {
 | 
					    fromArray(a) {
 | 
				
			||||||
        super.fromArray(a);
 | 
					        super.fromArray(a);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -439,15 +502,23 @@ class NumberConversionTask extends OpenEndedTask {
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fromArray(a) {
 | 
					    displayCorrectAnswer() {
 | 
				
			||||||
        super.fromArray(a);
 | 
					        this.ca_section.innerHTML = `Megoldás: <code>${this.correctAnswer}</code><sub>(${this.dst_base})</sub>`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fromArray(a) {
 | 
				
			||||||
        const regex = /([0-9]+)([suc]):([0-9]+)->([0-9]+)([suc]):([0-9]+)/g;
 | 
					        const regex = /([0-9]+)([suc]):([0-9]+)->([0-9]+)([suc]):([0-9]+)/g;
 | 
				
			||||||
        let parts = [ ...a["instruction"].matchAll(regex) ][0];
 | 
					        let parts = [...a["instruction"].matchAll(regex)][0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.src_base = parts[1];
 | 
				
			||||||
 | 
					        this.dst_base = parts[4];
 | 
				
			||||||
 | 
					        this.src_len = parts[6];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let src_exp = `${a["source"]}<sub>(${parts[1]})</sub> =`;
 | 
					        let src_exp = `${a["source"]}<sub>(${parts[1]})</sub> =`;
 | 
				
			||||||
        let dst_exp = `<sub>(${parts[4]})</sub> <i>(${parts[6]} digiten)</i>`;
 | 
					        let dst_exp = `<sub>(${parts[4]})</sub> <i>(${parts[6]} digiten)</i>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super.fromArray(a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.src_sec.innerHTML = src_exp;
 | 
					        this.src_sec.innerHTML = src_exp;
 | 
				
			||||||
        this.dst_sec.innerHTML = dst_exp;
 | 
					        this.dst_sec.innerHTML = dst_exp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -459,7 +530,378 @@ class NumberConversionTask extends OpenEndedTask {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Switch extends HTMLElement {
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        super();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.state = "-";
 | 
				
			||||||
 | 
					        this.highlight = "-";
 | 
				
			||||||
 | 
					        this.M = 2;
 | 
				
			||||||
 | 
					        this.disabled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.shadow = this.attachShadow({mode: "open"});
 | 
				
			||||||
 | 
					        this.createStyle();
 | 
				
			||||||
 | 
					        this.createElements();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    createStyle() {
 | 
				
			||||||
 | 
					        this.css = document.createElement("style");
 | 
				
			||||||
 | 
					        this.css.innerHTML = `
 | 
				
			||||||
 | 
					            section.frame {
 | 
				
			||||||
 | 
					                display: inline-block;
 | 
				
			||||||
 | 
					                border: 1pt solid black;                
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            section.button {
 | 
				
			||||||
 | 
					                display: inline-block;
 | 
				
			||||||
 | 
					                padding: 0.2em 0.5em;
 | 
				
			||||||
 | 
					                cursor: pointer;
 | 
				
			||||||
 | 
					                font-family: 'Monaco', monospace;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            section.button[disabled="false"]:hover {
 | 
				
			||||||
 | 
					                background-color: #408d8d;
 | 
				
			||||||
 | 
					                color: white;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            section.button[highlight="true"] {
 | 
				
			||||||
 | 
					                background-color: #aa8a7d;
 | 
				
			||||||
 | 
					                color: white;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            section.button[selected="true"] {
 | 
				
			||||||
 | 
					                background-color: #176767;
 | 
				
			||||||
 | 
					                color: white;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            section.button:not(:last-child) {
 | 
				
			||||||
 | 
					                border-right: 1pt dotted black;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        `;
 | 
				
			||||||
 | 
					        this.shadow.append(this.css);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    createElements() {
 | 
				
			||||||
 | 
					        let frame = document.createElement("section");
 | 
				
			||||||
 | 
					        frame.classList.add("frame");
 | 
				
			||||||
 | 
					        let btns = [];
 | 
				
			||||||
 | 
					        for (let i = 0; i < this.M; i++) {
 | 
				
			||||||
 | 
					            let btn = document.createElement("section");
 | 
				
			||||||
 | 
					            btn.classList.add("button");
 | 
				
			||||||
 | 
					            btn.innerText = i.toString();
 | 
				
			||||||
 | 
					            btn.id = "btn_" + i.toString();
 | 
				
			||||||
 | 
					            btn.setAttribute("disabled", "false");
 | 
				
			||||||
 | 
					            btns.push(btn);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            btn.addEventListener("click", (e) => {
 | 
				
			||||||
 | 
					                if (!this.disabled) {
 | 
				
			||||||
 | 
					                    this.setState(i);
 | 
				
			||||||
 | 
					                    this.dispatchEvent(new Event("change"));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            frame.append(btn);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        document.createElement("section");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.frame = frame;
 | 
				
			||||||
 | 
					        this.btns = btns;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.shadow.append(frame);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.setDisabled(false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setState(state) {
 | 
				
			||||||
 | 
					        this.state = state;
 | 
				
			||||||
 | 
					        for (let i = 0; i < this.M; i++) {
 | 
				
			||||||
 | 
					            this.btns[i].setAttribute("selected", (i.toString() === this.state.toString()) ? "true" : "false");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setHighlight(hl) {
 | 
				
			||||||
 | 
					        this.highlight = hl;
 | 
				
			||||||
 | 
					        for (let i = 0; i < this.M; i++) {
 | 
				
			||||||
 | 
					            this.btns[i].setAttribute("highlight", (i.toString() === this.highlight.toString()) ? "true" : "false");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getState() {
 | 
				
			||||||
 | 
					        return this.state;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setDisabled(disabled) {
 | 
				
			||||||
 | 
					        this.disabled = disabled;
 | 
				
			||||||
 | 
					        this.frame.setAttribute("disabled", disabled ? "true" : "false");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TruthTableTask extends PicturedTask {
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        super("truthtable");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.input_variables = [];
 | 
				
			||||||
 | 
					        this.output_variable = "";
 | 
				
			||||||
 | 
					        this.output_switches = [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    createStyle() {
 | 
				
			||||||
 | 
					        super.createStyle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.css.innerHTML += `
 | 
				
			||||||
 | 
					            table#tt {
 | 
				
			||||||
 | 
					                border: 1.5pt solid #176767;
 | 
				
			||||||
 | 
					                margin: 0.5em auto;
 | 
				
			||||||
 | 
					                border-spacing: 0;
 | 
				
			||||||
 | 
					                font-family: 'Monaco', monospace;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            table#tt tr:not(:last-child) td {
 | 
				
			||||||
 | 
					                border-bottom: 1.2pt solid black;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            table#tt th {
 | 
				
			||||||
 | 
					                border-bottom: 1.5pt dotted black
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            table#tt td, table#tt th {
 | 
				
			||||||
 | 
					                min-width: 3ch;
 | 
				
			||||||
 | 
					                text-align: center;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            table#tt td:last-child, table#tt th:last-child {
 | 
				
			||||||
 | 
					                border-left: 1.5pt dashed black;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        `;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    createElements() {
 | 
				
			||||||
 | 
					        super.createElements();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let tt = document.createElement("table");
 | 
				
			||||||
 | 
					        tt.id = "tt";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.answer_container.append(tt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.tt = tt;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updatePlayerAnswer() {
 | 
				
			||||||
 | 
					        let pa = "";
 | 
				
			||||||
 | 
					        for (let i = 0; i < this.output_switches.length; i++) {
 | 
				
			||||||
 | 
					            pa += this.output_switches[i].getState();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        super.playerAnswer = pa;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    buildTTable() {
 | 
				
			||||||
 | 
					        let N = this.input_variables.length;
 | 
				
			||||||
 | 
					        let M = (1 << N);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let inside = "<tr>"
 | 
				
			||||||
 | 
					        for (let i = 0; i < N; i++) {
 | 
				
			||||||
 | 
					            inside += "<th>" + this.input_variables[i] + "</th>";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        inside += "<th>" + this.output_variable + "</th>";
 | 
				
			||||||
 | 
					        inside += "</tr>";
 | 
				
			||||||
 | 
					        for (let i = 0; i < M; i++) {
 | 
				
			||||||
 | 
					            inside += "<tr>";
 | 
				
			||||||
 | 
					            for (let j = 0; j < N; j++) {
 | 
				
			||||||
 | 
					                inside += "<td>" + ((i >> (N - j - 1)) & 1).toString() + "</td>";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            inside += "<td><slide-switch id='out_" + i + "'></slide-switch></td>"
 | 
				
			||||||
 | 
					            inside += "</tr>";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.tt.innerHTML = inside;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let i = 0; i < M; i++) {
 | 
				
			||||||
 | 
					            let sw = this.shadow.getElementById("out_" + i);
 | 
				
			||||||
 | 
					            sw.addEventListener("change", () => {
 | 
				
			||||||
 | 
					                this.updatePlayerAnswer();
 | 
				
			||||||
 | 
					                this.uploadAnswer();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            sw.setDisabled(this.isConcluded || this.isViewOnly);
 | 
				
			||||||
 | 
					            this.output_switches[i] = sw;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.playerAnswer = "-".repeat(M);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    set playerAnswer(playerAnswer) {
 | 
				
			||||||
 | 
					        if (playerAnswer !== null) {
 | 
				
			||||||
 | 
					            super.playerAnswer = playerAnswer;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let i = 0; i < this.output_switches.length; i++) {
 | 
				
			||||||
 | 
					            let sw = this.output_switches[i];
 | 
				
			||||||
 | 
					            let pac = this.playerAnswer.charAt(i);
 | 
				
			||||||
 | 
					            if (!this.isConcluded) {
 | 
				
			||||||
 | 
					                sw.setState(pac);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                let cac = this.correctAnswer.charAt(i);
 | 
				
			||||||
 | 
					                if (cac !== pac) {
 | 
				
			||||||
 | 
					                    sw.setHighlight(pac);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                sw.setState(cac);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get playerAnswer() {
 | 
				
			||||||
 | 
					        return super.playerAnswer;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fromArray(a) {
 | 
				
			||||||
 | 
					        super.fromArray(a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.input_variables = a["input_variables"];
 | 
				
			||||||
 | 
					        this.output_variable = a["output_variable"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.buildTTable();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.playerAnswer = a["player_answer"];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class VerilogTask extends PicturedTask {
 | 
				
			||||||
 | 
					    //static observedAttributes = ["language"]
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        super("verilog");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    createStyle() {
 | 
				
			||||||
 | 
					        super.createStyle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.css.innerHTML += `
 | 
				
			||||||
 | 
					            section.editor-sec {
 | 
				
			||||||
 | 
					                margin-top: 1em;
 | 
				
			||||||
 | 
					                min-height: 24em;
 | 
				
			||||||
 | 
					                font-family: 'JetBrains Mono', monospace;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            div.ace_content {
 | 
				
			||||||
 | 
					                font-variant-ligatures: none;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            section#explain-sec {
 | 
				
			||||||
 | 
					                font-family: 'JetBrains Mono', monospace;
 | 
				
			||||||
 | 
					                margin-top: 0.5em;
 | 
				
			||||||
 | 
					                width: calc(100% - 0.4em);
 | 
				
			||||||
 | 
					                padding: 0.2em;
 | 
				
			||||||
 | 
					                background: #e5e5e57f;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            section#correct-answer-title {
 | 
				
			||||||
 | 
					                padding: 1em 0 0.5em 0.2em;
 | 
				
			||||||
 | 
					                font-weight: bold;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        `;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    createElements() {
 | 
				
			||||||
 | 
					        super.createElements();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let editor_sec = document.createElement("section");
 | 
				
			||||||
 | 
					        editor_sec.classList.add("editor-sec");
 | 
				
			||||||
 | 
					        this.answer_container.append(editor_sec);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let editor = ace.edit(editor_sec, {
 | 
				
			||||||
 | 
					            theme: "ace/theme/chrome",
 | 
				
			||||||
 | 
					            mode: "ace/mode/verilog"
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        editor.renderer.attachToShadowRoot();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        editor.addEventListener("blur", () => {
 | 
				
			||||||
 | 
					            this.uploadAnswer();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let explain_sec = document.createElement("section");
 | 
				
			||||||
 | 
					        explain_sec.id = "explain-sec";
 | 
				
			||||||
 | 
					        this.ca_section.append(explain_sec);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let solution_title_sec = document.createElement("section");
 | 
				
			||||||
 | 
					        solution_title_sec.innerText = "Egy lehetséges megoldás:";
 | 
				
			||||||
 | 
					        solution_title_sec.id = "correct-answer-title";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.ca_section.append(solution_title_sec);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let solution_sec = document.createElement("section");
 | 
				
			||||||
 | 
					        solution_sec.id = "solution-sec";
 | 
				
			||||||
 | 
					        solution_sec.classList.add("editor-sec");
 | 
				
			||||||
 | 
					        let solution_editor = ace.edit(solution_sec, {
 | 
				
			||||||
 | 
					            theme: "ace/theme/chrome",
 | 
				
			||||||
 | 
					            mode: "ace/mode/verilog",
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        solution_editor.setReadOnly(true);
 | 
				
			||||||
 | 
					        solution_editor.renderer.attachToShadowRoot();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.ca_section.append(solution_sec);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.editor_sec = editor_sec;
 | 
				
			||||||
 | 
					        this.editor = editor;
 | 
				
			||||||
 | 
					        this.explain_sec = explain_sec;
 | 
				
			||||||
 | 
					        this.solution_title_sec = solution_title_sec;
 | 
				
			||||||
 | 
					        this.solution_sec = solution_sec;
 | 
				
			||||||
 | 
					        this.solution_editor = solution_editor;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    set playerAnswer(player_answer) {
 | 
				
			||||||
 | 
					        this.editor.setValue(player_answer);
 | 
				
			||||||
 | 
					        this.editor.clearSelection();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get playerAnswer() {
 | 
				
			||||||
 | 
					        return this.editor.getValue();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    set correctAnswer(player_answer) {
 | 
				
			||||||
 | 
					        this.solution_editor.setValue(player_answer);
 | 
				
			||||||
 | 
					        this.solution_editor.clearSelection();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get correctAnswer() {
 | 
				
			||||||
 | 
					        return super.correctAnswer;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fromArray(a) {
 | 
				
			||||||
 | 
					        super.fromArray(a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.explain_sec.innerText = a["explanation"];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateEditorFreezeState() {
 | 
				
			||||||
 | 
					        this.editor.setReadOnly(this.isConcluded || this.isViewOnly);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    set isConcluded(concluded) {
 | 
				
			||||||
 | 
					        super.isConcluded = concluded;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.updateEditorFreezeState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (concluded) {
 | 
				
			||||||
 | 
					            this.solution_editor.setValue(this.correctAnswer);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get isConcluded() {
 | 
				
			||||||
 | 
					        return super.isConcluded;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    set isViewOnly(viewOnly) {
 | 
				
			||||||
 | 
					        super.isViewOnly = viewOnly;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.updateEditorFreezeState();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get isViewOnly() {
 | 
				
			||||||
 | 
					        return super.isViewOnly;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // attributeChangedCallback(name, oldVal, newVal) {
 | 
				
			||||||
 | 
					    //     switch (name) {
 | 
				
			||||||
 | 
					    //         case "language":
 | 
				
			||||||
 | 
					    //             editor.session.setMode("ace/mode/" + newVal.toLowerCase());
 | 
				
			||||||
 | 
					    //             break;
 | 
				
			||||||
 | 
					    //     }
 | 
				
			||||||
 | 
					    // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
customElements.define('singlechoice-task', SingleChoiceTask);
 | 
					customElements.define('singlechoice-task', SingleChoiceTask);
 | 
				
			||||||
customElements.define('openended-task', OpenEndedTask);
 | 
					customElements.define('openended-task', OpenEndedTask);
 | 
				
			||||||
customElements.define('numberconversion-task', NumberConversionTask);
 | 
					customElements.define('numberconversion-task', NumberConversionTask);
 | 
				
			||||||
 | 
					customElements.define('truthtable-task', TruthTableTask);
 | 
				
			||||||
 | 
					customElements.define('slide-switch', Switch);
 | 
				
			||||||
 | 
					customElements.define('verilog-task', VerilogTask);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ function submit_command() {
 | 
				
			|||||||
        request(req).then((resp) => {
 | 
					        request(req).then((resp) => {
 | 
				
			||||||
            terminal_output.value += resp + "\n\n";
 | 
					            terminal_output.value += resp + "\n\n";
 | 
				
			||||||
            terminal_output.scrollTo(0, terminal_output.scrollHeight);
 | 
					            terminal_output.scrollTo(0, terminal_output.scrollHeight);
 | 
				
			||||||
            terminal_input.value = "";
 | 
					            //terminal_input.value = "";
 | 
				
			||||||
            terminal_input.disabled = false;
 | 
					            terminal_input.disabled = false;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -74,12 +74,17 @@ function populate_tasks(tasks, concluded, view_only = false, gameid) {
 | 
				
			|||||||
    let test_display = document.getElementById("test_display");
 | 
					    let test_display = document.getElementById("test_display");
 | 
				
			||||||
    test_display.innerHTML = "";
 | 
					    test_display.innerHTML = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Task.sequence_number = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tasks.forEach((task) => {
 | 
					    tasks.forEach((task) => {
 | 
				
			||||||
        let task_element = document.createElement(`${task["type"]}-task`);
 | 
					        let task_element = document.createElement(`${task["type"]}-task`);
 | 
				
			||||||
        task_element.uploadAnswerCb = save_answer;
 | 
					        task_element.uploadAnswerCb = save_answer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (task["image_url"] !== "") {
 | 
					        task_element.imgType = task["image_type"];
 | 
				
			||||||
            task_element.imgUrl = `interface.php?action=get_image&gameid=${gameid}&img_url=${task["image_url"]}`
 | 
					        if (task["image_type"] === "url" && task["image_data"] !== "") {
 | 
				
			||||||
 | 
					            task_element.imgData = `interface.php?action=get_image&gameid=${gameid}&img_url=${task["image_data"]}`
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            task_element.imgData = task["image_data"];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        task_element.isConcluded = concluded;
 | 
					        task_element.isConcluded = concluded;
 | 
				
			||||||
        task_element.isViewOnly = view_only;
 | 
					        task_element.isViewOnly = view_only;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
@import url('https://fonts.googleapis.com/css2?family=Autour+One&family=Kanit:wght@500&display=swap');
 | 
					@import url('https://fonts.googleapis.com/css2?family=Autour+One&family=Kanit:wght@500&display=swap');
 | 
				
			||||||
@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0");
 | 
					@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0");
 | 
				
			||||||
 | 
					@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
body {
 | 
					body {
 | 
				
			||||||
    font-family: 'Autour One', sans-serif;
 | 
					    font-family: 'Autour One', sans-serif;
 | 
				
			||||||
 | 
				
			|||||||
@ -36,6 +36,7 @@ if ($testid === "") {
 | 
				
			|||||||
    <script id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
 | 
					    <script id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
 | 
				
			||||||
    <script type="text/javascript" src="https://nturley.github.io/netlistsvg/elk.bundled.js"></script>
 | 
					    <script type="text/javascript" src="https://nturley.github.io/netlistsvg/elk.bundled.js"></script>
 | 
				
			||||||
    <script type="text/javascript" src="https://nturley.github.io/netlistsvg/built/netlistsvg.bundle.js"></script>
 | 
					    <script type="text/javascript" src="https://nturley.github.io/netlistsvg/built/netlistsvg.bundle.js"></script>
 | 
				
			||||||
 | 
					    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.43.3/ace.min.js" integrity="sha512-BHJlu9vUXVrcxhRwbBdNv3uTsbscp8pp3LJ5z/sw9nBJUegkNlkcZnvODRgynJWhXMCsVUGZlFuzTrr5I2X3sQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
<section id="test_display">
 | 
					<section id="test_display">
 | 
				
			||||||
@ -68,7 +69,7 @@ if ($testid === "") {
 | 
				
			|||||||
    </section>
 | 
					    </section>
 | 
				
			||||||
</section>
 | 
					</section>
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
    populate_all("<?=$testid ?>", "<?=$gameid ?>", <?=$view_only ?>);
 | 
					    populate_all("<?=$testid ?>", "<?=$gameid ?>", <?=$view_only ? "true" : "false" ?>);
 | 
				
			||||||
    window.onbeforeunload = () => {
 | 
					    window.onbeforeunload = () => {
 | 
				
			||||||
        if (TEST_DATA["state"] !== "concluded") {
 | 
					        if (TEST_DATA["state"] !== "concluded") {
 | 
				
			||||||
            save_all_answers();
 | 
					            save_all_answers();
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								workspace/py_exec.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								workspace/py_exec.sh
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					source /home/epagris/VCSDEV/SpreadQuiz/workspace/venv/bin/activate
 | 
				
			||||||
 | 
					python "$@"
 | 
				
			||||||
 | 
					deactivate
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user