- LogicTask, LogicTaskBase added

- LuaUtils extended
...
This commit is contained in:
Wiesner András 2025-10-12 11:26:15 +02:00
parent 0c76214bec
commit 4a2079cf10
20 changed files with 465 additions and 152 deletions

View File

@ -362,9 +362,18 @@ class Game extends AutoStoring
$columns = Game::getTableColumnIndices($header); // fetch column names $columns = Game::getTableColumnIndices($header); // fetch column names
// start iterating over tasks // start iterating over tasks
$count = 0;
for ($i = 1; $i < $n; $i++) { for ($i = 1; $i < $n; $i++) {
$row = &$table[$i]; // fetch row $row = &$table[$i]; // fetch row
$flags = Game::explodeFlags($row[0] ?? ""); // get flags
if (in_array("hidden", $flags)) { // skip hidden tasks
continue;
}
// count in this task
$count++;
// prepare a function that looks up the fields referenced by their labels // prepare a function that looks up the fields referenced by their labels
$select_fn = function (array $cols) use (&$row, &$columns) { $select_fn = function (array $cols) use (&$row, &$columns) {
for ($i = 0; $i < count($cols); $i++) { for ($i = 0; $i < count($cols); $i++) {
@ -382,7 +391,7 @@ class Game extends AutoStoring
// fetch generic fields // fetch generic fields
$a = [ $a = [
"flags" => Game::explodeFlags($row[0] ?? ""), "flags" => $flags,
"type" => strtolower($select_fn(["Típus", "Type"])), "type" => strtolower($select_fn(["Típus", "Type"])),
"image_data" => $select_fn(["Kép", "Image"]), "image_data" => $select_fn(["Kép", "Image"]),
"question" => $select_fn(["Kérdés", "Question"]), "question" => $select_fn(["Kérdés", "Question"]),
@ -403,6 +412,7 @@ class Game extends AutoStoring
$a["instruction"] = $row[$fuc]; $a["instruction"] = $row[$fuc];
break; break;
case "truthtable": case "truthtable":
case "logicfunction":
$a["input_variables"] = Utils::str2a($row[$fuc]); $a["input_variables"] = Utils::str2a($row[$fuc]);
$a["output_variable"] = $row[$fuc + 1]; $a["output_variable"] = $row[$fuc + 1];
$a["expression"] = $row[$fuc + 2]; $a["expression"] = $row[$fuc + 2];
@ -426,9 +436,18 @@ class Game extends AutoStoring
// generate the task // generate the task
$this->tasks[] = TaskFactory::fromArray($a, $this); $this->tasks[] = TaskFactory::fromArray($a, $this);
// assign scoring strategy
$sct = $select_fn(["Pontozás", "Scoring"]);
if ($sct !== "") {
$sct_fields = Utils::str2kv($sct);
foreach ($sct_fields as $key => $value) {
$sct_fields[$key] = $value;
}
}
} }
$result["n"] = $n - 1; $result["n"] = $count;
return $result; return $result;
} }

View File

@ -51,11 +51,12 @@ class LogicFunction implements JsonSerializable
// printf("%d ", $vars[$this->input_vars[$k]]); // printf("%d ", $vars[$this->input_vars[$k]]);
} }
$out = self::$EXP_LANG->evaluate($expression, $vars); $out = self::$EXP_LANG->evaluate($expression, $vars);
$out_str = $out ? "1" : "0";
// printf("%d\n", $out); // printf("%d\n", $out);
$tt[] = $out; $tt[] = $out_str;
} }
return join("", array_map(fn($r) => ($r === True) ? 1 : 0, $tt)); return join("", $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
@ -93,7 +94,7 @@ class LogicFunction implements JsonSerializable
return new LogicFunction($term, $input_vars); return new LogicFunction($term, $input_vars);
} }
public static function genRandomDF($input_vars): LogicFunction public static function genRandomDNF($input_vars): LogicFunction
{ {
$N = count($input_vars); $N = count($input_vars);
$states = pow(2, $N); $states = pow(2, $N);
@ -101,7 +102,7 @@ class LogicFunction implements JsonSerializable
$verilog_term = ""; $verilog_term = "";
for ($i = 0; $i < $states; $i++) { for ($i = 0; $i < $states; $i++) {
$inside = ""; $inside = "";
$omit = random_int(0, 1); // omit the variable or not? $omit = random_int(0, 1); // omit the term 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?

34
class/LuaUtils.php Normal file
View File

@ -0,0 +1,34 @@
<?php
require_once "LogicFunction.php";
class LuaUtils
{
public static function l2pA(array $la): array {
$r = [];
$i = 0;
foreach ($la as $v) {
$r[$i++] = $v;
}
return $r;
}
public static function registerLuaLibs(LuaSandbox &$sandbox): void {
self::registerLogicUtils($sandbox);
}
public static function registerLogicUtils(LuaSandbox &$sandbox): void {
$sandbox->registerLibrary("logic", [
"extend" => fn() => [ call_user_func(array(LogicUtils::class, "extend"), ...func_get_args()) ],
"complement" => fn() => [ call_user_func(array(LogicUtils::class, "complement"), ...func_get_args()) ],
"changeRepresentation" => fn() => [ call_user_func(array(LogicUtils::class, "changeRepresentation"), ...func_get_args()) ],
"genRandomLogicFunction" => fn($iv, $mind, $maxd) => [ call_user_func(array(LogicFunction::class, "genRandom"), self::l2pA($iv), $mind, $maxd)->getExpression() ],
"genRandomLogicFunctionDNF" => fn($iv) => [ call_user_func(array(LogicFunction::class, "genRandomDNF"), self::l2pA($iv))->getExpression() ],
"isLogicFunctionValid" => fn($expr) => [ (new LogicFunction($expr))->isValid() ],
"isLogicFunctionADNF" => fn($iv, $expr) => [ call_user_func(array(LogicFunction::class, "isCorrectDNF"), self::l2pA($iv), $expr) ],
"collectVariablesFromLogicFunction" => fn($expr) => call_user_func(array(LogicFunction::class, "collectVariables"), $expr),
"convertLogicFunctionToTruthTable" => fn($expr, $iv) => [ (new LogicFunction($expr, self::l2pA($iv)))->getTruthTable() ],
"drawLogicFunction" => fn($expr, $iv, $ov) => [ (new LogicFunction($expr, self::l2pA($iv)))->drawNetwork($ov) ],
]);
}
}

View File

@ -37,6 +37,8 @@ class Task implements JsonSerializable
return [ str_replace("{{" . $field . "}}", $replacement, $str) ]; return [ str_replace("{{" . $field . "}}", $replacement, $str) ];
} }
]); ]);
LuaUtils::registerLuaLibs($this->lua_sandbox);
} }
private function createLuaSandbox(): void { private function createLuaSandbox(): void {
if ($this->lua_sandbox === null) { if ($this->lua_sandbox === null) {
@ -116,7 +118,7 @@ class Task implements JsonSerializable
} }
function setMark(float $mark): void { function setMark(float $mark): void {
$this->mark = $mark; $this->mark = max($mark, 0.0);
} }
function getMark(): float function getMark(): float
@ -129,7 +131,7 @@ class Task implements JsonSerializable
} }
protected function staticCheck(): void { protected function staticCheck(): void {
return; $this->mark = $this->max_mark;
} }
function autoCheck(): void { function autoCheck(): void {
@ -249,7 +251,6 @@ class Task implements JsonSerializable
if ($this->lua_script !== "") { if ($this->lua_script !== "") {
$this->luaRandomize(); $this->luaRandomize();
} }
return;
} }
function setGovernor(Game|Test|null &$governor): void { function setGovernor(Game|Test|null &$governor): void {

View File

@ -10,6 +10,8 @@ require_once "Tasks/TruthTableTask.php";
require_once "Tasks/VerilogTask.php"; require_once "Tasks/VerilogTask.php";
require_once "Tasks/LogicFunctionTask.php";
class TaskFactory class TaskFactory
{ {
static function fromArray(array $a, Game|Test|null &$governor = null): Task|null static function fromArray(array $a, Game|Test|null &$governor = null): Task|null
@ -28,6 +30,9 @@ class TaskFactory
case "truthtable": case "truthtable":
$task = new TruthTableTask($a); $task = new TruthTableTask($a);
break; break;
case "logicfunction":
$task = new LogicFunctionTask($a);
break;
case "verilog": case "verilog":
$task = new VerilogTask($a); $task = new VerilogTask($a);
break; break;

View File

@ -0,0 +1,59 @@
<?php
require_once "LogicTaskBase.php";
class LogicFunctionTask extends LogicTaskBase
{
private float $missing_minterm_score = -1.0; // score for a missing minterm
public function __construct(array $a = null)
{
parent::__construct("logicfunction", $a);
$this->missing_minterm_score = $a["missing_minterm_score"] ?? -1.0;
if (!$this->hasFlag("dnf")) {
$this->setCorrectAnswer($this->getLogicFunction()->getExpression());
} else {
$this->setCorrectAnswer($this->getLogicFunction()->toDNF());
}
}
public function toArray(string $mode = "all"): array
{
$a = parent::toArray($mode);
if ($this->hasFlag("attachtruthtable")) {
$a["truthtable"] = $this->getTruthtable();
}
if ($mode == "all") {
$a["missing_minterm_score"] = $this->missing_minterm_score;
}
return $a;
}
public function getMissingMintermScore(): float
{
return $this->missing_minterm_score;
}
public function setMissingMintermScore(float $missing_minterm_score): void
{
$this->missing_minterm_score = $missing_minterm_score;
}
protected function staticCheck(): void
{
$mark = 0.0;
$pa = $this->player_answer ?? "";
$iv = $this->getLogicFunction()->getInputVars();
$palf = new LogicFunction($pa, $iv);
if ($palf->isValid() && ((!$this->hasFlag("dnf")) || LogicFunction::isCorrectDNF($iv, $pa))) { // if the function is valid AND (if enabled) it is a correct DNF
$patt = $palf->getTruthTable();
$errs = $this->getTTDiffCntToCA($patt);
$mark = $this->getMaxMark() + $errs * $this->getMissingMintermScore();
}
$this->setMark($mark);
}
}

View File

@ -0,0 +1,111 @@
<?php
require_once "PicturedTask.php";
require_once "class/LogicFunction.php";
require_once "class/Utils.php";
require_once "class/LuaUtils.php";
class LogicTaskBase extends PicturedTask
{
private LogicFunction $lf; // logic function
private string $output_variable; // output variable
public function __construct(string $type, array $a = null)
{
parent::__construct($type, $a);
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->output_variable = $a["output_variable"] ?? "f";
}
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;
}
protected function getTTDiffCntToCA(string $ott): int {
$cans_tt = $this->getTruthTable();
$errs = 0;
for ($i = 0; $i < $this->getLogicFunction()->getNStates(); $i++) {
if (($ott[$i] ?? " ") != $cans_tt[$i]) {
$errs++;
}
}
return $errs;
}
public function toArray(string $mode = "all"): array
{
$a = parent::toArray($mode);
if ($mode === "all") {
$a["function"] = $this->lf->toArray();
}
$a["output_variable"] = $this->output_variable;
$a["input_variables"] = $this->lf->getInputVars();
return $a;
}
public function randomize(): void
{
parent::randomize();
if ($this->hasFlag("drawnetwork")) {
$this->setImageData($this->lf->drawNetwork($this->output_variable));
$this->setImageType("html");
}
}
// ---- Lua specific ----
public function generateRandomFunction(array $input_vars, int $min_depth, int $max_depth): void {
$this->lf = LogicFunction::genRandom(LuaUtils::l2pA($input_vars), $min_depth, $max_depth);
}
public function generateRandomDF(array $input_vars): void {
$this->lf = LogicFunction::genRandomDNF(LuaUtils::l2pA($input_vars));
}
public function getFunctionAsDNF(): string {
return $this->lf->toDNF();
}
public function setLogicFunctionExpr(string $expr, array $input_vars = []): void {
$this->lf = new LogicFunction($expr, LuaUtils::l2pA($input_vars));
}
public function getLogicFunctionExpr(string $fmt = "verilog_bitwise"): string {
return $this->lf->getExpression($fmt);
}
public function getLogicFunctionDNF(): string {
return $this->lf->toDNF();
}
public function getTruthTable(): string {
return $this->lf->getTruthTable();
}
}

View File

@ -106,9 +106,9 @@ class NumberConversionTask extends OpenEndedTask
{ {
$mark = 0.0; $mark = 0.0;
if ($this->hasFlag("acceptwithoutleadingzeros")) { if ($this->hasFlag("acceptwithoutleadingzeros")) {
$mark = (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")) ? $this->getMaxMark() : 0.0;
} else { } else {
$mark = (trim($this->player_answer) === trim($this->correct_answer)) ? 1.0 : 0.0; $mark = (trim($this->player_answer) === trim($this->correct_answer)) ? $this->getMaxMark() : 0.0;
} }
$this->setMark($mark); $this->setMark($mark);
} }

View File

@ -48,7 +48,7 @@ class OpenEndedTask extends PicturedTask
public function staticCheck(): void public function staticCheck(): void
{ {
$mark = in_array($this->player_answer, $this->correct_answer) ? 1.0 : 0.0; $mark = in_array($this->player_answer, $this->correct_answer) ? $this->getMaxMark() : 0.0;
$this->setMark($mark); $this->setMark($mark);
} }

View File

@ -32,6 +32,16 @@ class PicturedTask extends Task
return $this->image_type; return $this->image_type;
} }
function setUrlData(string $url_data): void {
$this->setImageData($url_data);
$this->setImageType("url");
}
function setHtmlData(string $svg_data): void {
$this->setImageData($svg_data);
$this->setImageType("html");
}
function toArray(string $mode = "all"): array function toArray(string $mode = "all"): array
{ {
$a = parent::toArray($mode); $a = parent::toArray($mode);

View File

@ -55,7 +55,7 @@ class SingleChoiceTask extends PicturedTask
function staticCheck(): void function staticCheck(): void
{ {
$mark = ($this->player_answer == $this->correct_answer) ? 1.0 : 0.0; $mark = ($this->player_answer == $this->correct_answer) ? $this->getMaxMark() : 0.0;
$this->setMark($mark); $this->setMark($mark);
} }

View File

@ -1,119 +1,42 @@
<?php <?php
require_once "OpenEndedTask.php"; require_once "LogicTaskBase.php";
require_once "class/LogicFunction.php"; class TruthTableTask extends LogicTaskBase
require_once "class/Utils.php";
class TruthTableTask extends PicturedTask
{ {
private LogicFunction $lf; // logic functions private float $bad_line_score; // points for a bad line in the truth table
private string $output_variable; // output variable
public function __construct(array $a = null) public function __construct(array $a = null)
{ {
parent::__construct("truthtable", $a); parent::__construct("truthtable", $a);
if (isset($a["function"])) { // fetching from a JSON-stored object $this->bad_line_score = $a["bad_line_score"] ?? -1.0;
$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"); function setBadLineScore(float $bad_line_score): void {
$this->bad_line_score = $bad_line_score;
}
function getBadLineScore(): float {
return $this->bad_line_score;
} }
public function staticCheck(): void public function staticCheck(): void
{ {
$ans_tt = $this->player_answer; $errs = $this->getTTDiffCntToCA($this->player_answer ?? "");
$cans_tt = $this->lf->getTruthTable(); $mark = $this->getMaxMark() + $errs * $this->getBadLineScore();
$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); $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 static function sanitizeIndices(array $a): array {
$r = [];
$i = 0;
foreach ($a as $v) {
$r[$i++] = $v;
}
return $r;
}
public function generateRandomFunction(array $input_vars, int $min_depth, int $max_depth): void {
$this->lf = LogicFunction::genRandom(self::sanitizeIndices($input_vars), $min_depth, $max_depth);
}
public function generateRandomDF(array $input_vars): void {
$this->lf = LogicFunction::genRandomDF(self::sanitizeIndices($input_vars));
}
public function getFunctionAsDNF(): string {
return $this->lf->toDNF();
}
public function isValidDNF(): bool {
return LogicFunction::isCorrectDNF($this->lf->getInputVars(), $this->lf->getExpression());
}
public function setLogicFunctionExpr(string $expr, array $input_vars = []): void {
$this->lf = new LogicFunction($expr, self::sanitizeIndices($input_vars));
}
public function getLogicFunctionExpr(string $fmt = "verilog_bitwise"): string {
return $this->lf->getExpression($fmt);
}
public function getLogicFunctionDNF(): string {
return $this->lf->toDNF();
}
public function toArray(string $mode = "all"): array public function toArray(string $mode = "all"): array
{ {
$a = parent::toArray($mode); $a = parent::toArray($mode);
if ($mode === "all") { if ($mode === "all") {
$a["function"] = $this->lf->toArray(); $a["bad_line_score"] = $this->bad_line_score;
} }
$a["correct_answer"] = $this->lf->getTruthTable(); $a["correct_answer"] = $this->getLogicFunction()->getTruthTable();
$a["input_variables"] = $this->lf->getInputVars();
$a["output_variable"] = $this->output_variable;
return $a; return $a;
} }
public function randomize(): void
{
parent::randomize();
if ($this->hasFlag("drawnetwork")) {
$this->setImageData($this->lf->drawNetwork($this->output_variable));
$this->setImageType("svg");
}
}
} }

View File

@ -6,6 +6,7 @@ class VerilogTask extends PicturedTask
{ {
private string $test_bench_fn; // test bench file name private string $test_bench_fn; // test bench file name
private string $compile_log; // short explanation for the marking private string $compile_log; // short explanation for the marking
private float $error_score; // points received for an error
private const FAILED_MARK = "[FAILED]"; // mark at the beginning of testbench results indicating a failed combination private const FAILED_MARK = "[FAILED]"; // mark at the beginning of testbench results indicating a failed combination
public function __construct(array &$a = null) public function __construct(array &$a = null)
@ -14,6 +15,7 @@ class VerilogTask extends PicturedTask
$this->compile_log = $a["compile_log"] ?? ""; $this->compile_log = $a["compile_log"] ?? "";
$this->test_bench_fn = $a["test_bench_fn"] ?? ""; $this->test_bench_fn = $a["test_bench_fn"] ?? "";
$this->error_score = $a["error_score"] ?? -1.0;
} }
private function verifyCode(): bool private function verifyCode(): bool
@ -26,7 +28,7 @@ class VerilogTask extends PicturedTask
return true; return true;
} }
private function executeTest(): bool private function executeTest(): int
{ {
// store the user's answer // store the user's answer
$module_code_fn = tempnam(sys_get_temp_dir(), "verilogtask_user_module_"); $module_code_fn = tempnam(sys_get_temp_dir(), "verilogtask_user_module_");
@ -74,7 +76,17 @@ class VerilogTask extends PicturedTask
@unlink($test_bench_fn); @unlink($test_bench_fn);
@unlink($output_fn); @unlink($output_fn);
return $failed_count == 0; return $failed_count;
}
public function setErrorScore(float $error_score): void
{
$this->error_score = $error_score;
}
public function getErrorScore(): float
{
return $this->error_score;
} }
public function staticCheck(): void public function staticCheck(): void
@ -85,10 +97,12 @@ class VerilogTask extends PicturedTask
$mark = 0.0; $mark = 0.0;
if ($this->verifyCode()) { if ($this->verifyCode()) {
// run the simulation // run the simulation
$test_ok = $this->executeTest(); $failed_count = $this->executeTest();
if ($failed_count != PHP_INT_MAX) {
$mark = $test_ok ? 1.0 : 0.0; // FIXME $mark = $this->getMaxMark() + $failed_count * $this->error_score;
}
} }
$this->setMark($mark); $this->setMark($mark);
} }
@ -100,6 +114,7 @@ class VerilogTask extends PicturedTask
if ($mode == "all") { if ($mode == "all") {
$a["test_bench_fn"] = $this->test_bench_fn; $a["test_bench_fn"] = $this->test_bench_fn;
$a["error_score"] = $this->error_score;
} }
return $a; return $a;

View File

@ -7,7 +7,22 @@ class Utils
public static function str2kv(string $str): array public static function str2kv(string $str): array
{ {
preg_match_all("/([^,= ]+)=([^,= ]+)/", $str, $r); preg_match_all("/([^,= ]+)=([^,= ]+)/", $str, $r);
return array_combine($r[1], $r[2]); $a = array_combine($r[1], $r[2]);
foreach ($a as &$v) {
if (is_numeric($v)) { // is it a numeric value?
if (((int)$v) == ((double)$v)) { // is it an integer?
$v = (int)$v;
} else { // is it a float?
$v = (double)$v;
}
} else if (in_array(strtolower($v), ["true", "false"]) ) { // it's a boolean
$v = $v === "true";
} else if (str_starts_with($v, '"') && str_ends_with($v, '"')) { // it's a string
$v = substr($v, 1, strlen($v) - 2); // strip leading and trailing quotes
}
}
return $a;
} }
public static function str2a(string $str): array { public static function str2a(string $str): array {

View File

@ -36,19 +36,26 @@ class Task extends HTMLElement {
margin-bottom: 0.5em; margin-bottom: 0.5em;
border-radius: 0.3em; border-radius: 0.3em;
} }
section.seq-num { section.seq-num, section.mark {
display: block; display: block;
position: absolute; position: absolute;
right: 0; right: 0;
padding: 0.5em; padding: 0.5em;
bottom: 0;
background-color: #176767; background-color: #176767;
color: whitesmoke; color: whitesmoke;
border-bottom-right-radius: 0.3em;
border-top-left-radius: 0.3em;
width: 2em; width: 2em;
z-index: 10; z-index: 10;
} }
section.seq-num {
bottom: 0;
border-bottom-right-radius: 0.3em;
border-top-left-radius: 0.3em;
}
section.mark {
top: 0;
border-top-right-radius: 0.3em;
border-bottom-left-radius: 0.3em;
}
section.answer-container { section.answer-container {
/* (empty) */ /* (empty) */
} }
@ -56,7 +63,7 @@ class Task extends HTMLElement {
background-color: #e5d8d3; background-color: #e5d8d3;
} }
code { code {
font-family: 'Monaco', monospace; font-family: 'Source Code Pro', monospace;
} }
section#correct-answer { section#correct-answer {
@ -96,6 +103,8 @@ class Task extends HTMLElement {
question_span.classList.add("question"); question_span.classList.add("question");
let answer_container = document.createElement("section"); let answer_container = document.createElement("section");
answer_container.classList.add("answer-container"); answer_container.classList.add("answer-container");
let mark_section = document.createElement("section");
mark_section.classList.add("mark");
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}.`;
@ -106,6 +115,7 @@ class Task extends HTMLElement {
task_box.append(question_span); task_box.append(question_span);
task_box.append(answer_container); task_box.append(answer_container);
task_box.append(mark_section);
task_box.append(seq_num_section); task_box.append(seq_num_section);
task_box.append(ca_section); task_box.append(ca_section);
task_box.append(comment_sec); task_box.append(comment_sec);
@ -115,6 +125,7 @@ class Task extends HTMLElement {
this.task_box = task_box; this.task_box = task_box;
this.question_span = question_span; this.question_span = question_span;
this.answer_container = answer_container; this.answer_container = answer_container;
this.mark_section = mark_section;
this.seq_num_section = seq_num_section; this.seq_num_section = seq_num_section;
this.ca_section = ca_section; this.ca_section = ca_section;
this.comment_sec = comment_sec; this.comment_sec = comment_sec;
@ -198,10 +209,21 @@ class Task extends HTMLElement {
} }
displayMarking() {
if (this.isConcluded) {
this.mark_section.innerText = `${this.mark}/${this.max_mark}`;
} else {
this.mark_section.innerText = `${this.max_mark}`;
}
this.mark_section.innerHTML += `<span style="font-size: 70%">p</span>`
}
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"]; this.correctAnswer = a["correct_answer"];
this.max_mark = a["max_mark"];
this.mark = a["mark"];
let comment = a["comment"]; let comment = a["comment"];
if ((comment !== undefined) && (comment !== null) && (comment !== "")) { if ((comment !== undefined) && (comment !== null) && (comment !== "")) {
@ -217,6 +239,8 @@ class Task extends HTMLElement {
if (this.isConcluded) { if (this.isConcluded) {
this.displayCorrectAnswer(); this.displayCorrectAnswer();
} }
this.displayMarking();
} }
} }
@ -258,7 +282,7 @@ class PicturedTask extends Task {
this.img.src = data.trim(); this.img.src = data.trim();
} }
break; break;
case "svg": case "html":
{ {
this.img = document.createElement("section"); this.img = document.createElement("section");
this.img.classList.add("question-image"); this.img.classList.add("question-image");
@ -393,6 +417,8 @@ class SingleChoiceTask extends PicturedTask {
class OpenEndedTask extends PicturedTask { class OpenEndedTask extends PicturedTask {
constructor() { constructor() {
super("openended"); super("openended");
this.ca_prefix = "<b>Lehetséges megoldások:</b>";
} }
createStyle() { createStyle() {
@ -400,7 +426,7 @@ class OpenEndedTask extends PicturedTask {
this.css.innerHTML += ` this.css.innerHTML += `
input[type="text"] { input[type="text"] {
font-family: 'Monaco', monospaced; font-family: 'Source Code Pro', monospace;
border-width: 0 0 2.2pt 0; border-width: 0 0 2.2pt 0;
background-color: transparent; background-color: transparent;
width: calc(100% - 4em); width: calc(100% - 4em);
@ -434,7 +460,9 @@ class OpenEndedTask extends PicturedTask {
} }
displayCorrectAnswer() { displayCorrectAnswer() {
this.ca_section.innerHTML = "<b>Lehetséges megoldások:</b><br style='margin-bottom: 0.3em'>" + this.correctAnswer.join(", <i>VAGY</i><br>"); if (this.correctAnswer !== null) {
this.ca_section.innerHTML = this.ca_prefix + "<br style='margin-bottom: 0.3em'>" + this.correctAnswer.join(", <i>VAGY</i><br>");
}
} }
fromArray(a) { fromArray(a) {
@ -442,12 +470,15 @@ class OpenEndedTask extends PicturedTask {
} }
set playerAnswer(player_answer) { set playerAnswer(player_answer) {
if (player_answer === null) {
player_answer = "";
}
super.playerAnswer = player_answer; super.playerAnswer = player_answer;
this.answer_tf.value = player_answer; this.answer_tf.value = this.playerAnswer;
} }
get playerAnswer() { get playerAnswer() {
return this.player_answer; return super.playerAnswer;
} }
updateAnswerFieldState() { updateAnswerFieldState() {
@ -489,7 +520,7 @@ class NumberConversionTask extends OpenEndedTask {
section#src, section#dst { section#src, section#dst {
position: relative; position: relative;
display: inline-block; display: inline-block;
font-family: 'Monaco', monospace; font-family: 'Source Code Pro', monospace;
color: #176767; color: #176767;
} }
section#src { section#src {
@ -574,7 +605,7 @@ class Switch extends HTMLElement {
display: inline-block; display: inline-block;
padding: 0.2em 0.5em; padding: 0.2em 0.5em;
cursor: pointer; cursor: pointer;
font-family: 'Monaco', monospace; font-family: 'Source Code Pro', monospace;
} }
section.button[disabled="false"]:hover { section.button[disabled="false"]:hover {
background-color: #408d8d; background-color: #408d8d;
@ -651,6 +682,28 @@ class Switch extends HTMLElement {
} }
} }
var truth_table_css = `
table#tt {
border: 1.5pt solid #176767;
margin: 0.5em auto;
border-spacing: 0;
font-family: 'Source Code Pro', 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;
}
`;
class TruthTableTask extends PicturedTask { class TruthTableTask extends PicturedTask {
constructor() { constructor() {
super("truthtable"); super("truthtable");
@ -663,27 +716,7 @@ class TruthTableTask extends PicturedTask {
createStyle() { createStyle() {
super.createStyle(); super.createStyle();
this.css.innerHTML += ` this.css.innerHTML += truth_table_css;
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() { createElements() {
@ -775,6 +808,91 @@ class TruthTableTask extends PicturedTask {
} }
} }
class LogicFunctionTask extends OpenEndedTask {
constructor() {
super();
this.type = "logicfunction";
}
createStyle() {
super.createStyle();
this.css.innerHTML += truth_table_css;
this.css.innerHTML += `
section#assignment-sec {
position: relative;
display: inline-block;
font-family: 'Source Code Pro', monospace;
color: #176767;
margin-right: 1ch;
}
input[type="text"] {
min-width: 5em;
width: 85%;
font-family: 'Source Code Pro', monospace;
}
`;
}
createElements() {
super.createElements();
let assignment_sec = document.createElement("section");
assignment_sec.id = "assignment-sec";
this.answer_container.insertBefore(assignment_sec, this.answer_tf);
this.assignment_sec = assignment_sec;
}
drawTruthTable() {
let N = this.input_variables.length;
let M = (1 << N);
let table = "<table id='tt'>";
table += "<tr>";
for (let i = 0; i < N; i++) {
table += "<th>" + this.input_variables[i] + "</th>";
}
table += "<th>" + this.output_variable + "</th>";
table += "</tr>";
for (let i = 0; i < M; i++) {
table += "<tr>";
for (let j = 0; j < N; j++) {
table += "<td>" + ((i >> (N - j - 1)) & 1).toString() + "</td>";
}
table += "<td>" + this.truth_table.charAt(i) + "</td>"
table += "</tr>";
}
table += "</table>";
this.imgType = "html";
this.imgData = table;
}
fromArray(a) {
super.fromArray(a);
this.input_variables = a["input_variables"];
this.output_variable = a["output_variable"];
this.truth_table = a["truthtable"];
this.assignment_sec.innerHTML = `${a["output_variable"]} = `;
if ((this.truth_table !== undefined) && (this.truth_table !== null) && (this.truth_table !== "")) {
this.drawTruthTable();
}
}
set correctAnswer(correct_answer) {
super.correctAnswer = [ "<code>" + correct_answer + "</code>" ];
}
get correctAnswer() {
return super.correctAnswer;
}
}
class VerilogTask extends PicturedTask { class VerilogTask extends PicturedTask {
//static observedAttributes = ["language"] //static observedAttributes = ["language"]
constructor() { constructor() {
@ -920,7 +1038,9 @@ class VerilogTask extends PicturedTask {
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('slide-switch', Switch);
customElements.define('truthtable-task', TruthTableTask);
customElements.define('logicfunction-task', LogicFunctionTask);
customElements.define('verilog-task', VerilogTask); customElements.define('verilog-task', VerilogTask);

View File

@ -7,7 +7,7 @@
<body> <body>
<section style="margin: 0 auto; padding-top: 10ex; text-align: center"> <section style="margin: 0 auto; padding-top: 10ex; text-align: center">
<img src="media/maintenance.png" style="width: 16vw"> <img src="media/maintenance.png" style="width: 16vw">
<h3 style="font-family: 'Monaco', monospace"> Az oldal karbantartás alatt áll!</h3> <h3 style="font-family: 'Courier New', monospace"> Az oldal karbantartás alatt áll!</h3>
</section> </section>
</body> </body>
</html> </html>

View File

@ -47,9 +47,9 @@ if (!$gameMgr->getGame($game_id)->isUserContributorOrOwner($user_data["nickname"
<body> <body>
<section style="margin-bottom: 0.3em"> <section style="margin-bottom: 0.3em">
<input type="text" placeholder="Szűrőfeltétel" id="filter" style="font-family: 'Monaco', monospace; width: 50em;"> <input type="text" placeholder="Szűrőfeltétel" id="filter" style="font-family: 'Source Code Pro', monospace; width: 50em;">
<input type="text" placeholder="Csoportok" id="groups" style="font-family: 'Monaco', monospace; width: 30em;"> <input type="text" placeholder="Csoportok" id="groups" style="font-family: 'Source Code Pro', monospace; width: 30em;">
<input type="text" placeholder="Rendezés" id="orderby" style="font-family: 'Monaco', monospace; width: 30em;"> <input type="text" placeholder="Rendezés" id="orderby" style="font-family: 'Source Code Pro', monospace; width: 30em;">
<input type="button" value="Szűrés" onclick="fetch_results()"> <input type="button" value="Szűrés" onclick="fetch_results()">
<input type="button" value="Jelentés előállítása" onclick="generate_report()"> <input type="button" value="Jelentés előállítása" onclick="generate_report()">
<input type="button" value="Kijelöltek törlése" onclick="delete_tests()"><br> <input type="button" value="Kijelöltek törlése" onclick="delete_tests()"><br>

View File

@ -135,7 +135,7 @@ span.answer[correct=true] {
} }
.terminal-style { .terminal-style {
font-family: 'Monaco', monospace; font-family: 'Source Code Pro', monospace;
width: 40em; width: 40em;
font-size: 12pt; font-size: 12pt;
} }

View File

@ -49,7 +49,7 @@ section.answer input[type="radio"]:checked+label:not(.correct-answer) {
} }
code { code {
font-family: 'Monaco', monospace; font-family: 'Source Code Pro', monospace;
} }
img.question-image { img.question-image {

View File

@ -1,6 +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'); @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap');
body { body {
font-family: 'Autour One', sans-serif; font-family: 'Autour One', sans-serif;