- ...
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
|
||||||
{
|
{
|
||||||
$this->input_vars = $input_vars;
|
private static ExpressionLanguage $EXP_LANG; // expression language for linting an evaluation
|
||||||
$this->verilog_form = $verilog_form;
|
private array $input_vars; // array of input variables
|
||||||
$this->tex_form = $tex_form;
|
private string $expression; // the logic function in the bitwise Verilog-form
|
||||||
|
|
||||||
|
public static function convertToVerilogBitwiseForm(string $expression): string
|
||||||
|
{
|
||||||
|
return str_replace(["/", "!", "*", "+"], ["~", "~", "&", "|"], $expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setExpression(string $expression): void
|
||||||
|
{
|
||||||
|
$this->expression = self::convertToVerilogBitwiseForm($expression);
|
||||||
|
}
|
||||||
|
|
||||||
return new LogicFunction($input_vars, $verilog_term, $tex_term);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
static function constructFromCollection(array $c): array {
|
$task->setGovernor($governor);
|
||||||
|
return $task;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function randomize(): void
|
$this->setOutputVariable($a["output_variable"] ?? "f");
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|||||||
490
js/tasks.js
490
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);
|
||||||
@ -74,10 +91,13 @@ class Task extends HTMLElement {
|
|||||||
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
set imgData(data) {
|
||||||
|
switch (this.img_type) {
|
||||||
|
case "url":
|
||||||
|
{
|
||||||
this.img = document.createElement("img");
|
this.img = document.createElement("img");
|
||||||
this.img.classList.add("question-image");
|
this.img.classList.add("question-image");
|
||||||
this.img.src = "";
|
|
||||||
|
|
||||||
|
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);
|
this.task_box.insertBefore(this.img, this.question_span);
|
||||||
}
|
}
|
||||||
|
|
||||||
set imgUrl(url) {
|
|
||||||
url = url.trim();
|
|
||||||
this.img.src = url.trim();
|
|
||||||
this.img.style.display = (url !== "") ? "block" : "none";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@ -291,14 +359,6 @@ class SingleChoiceTask extends PicturedTask {
|
|||||||
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() {
|
||||||
return this.player_answer === this.correct_answer;
|
return this.player_answer === this.correct_answer;
|
||||||
}
|
}
|
||||||
@ -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