- Challenge modularization initiated
This commit is contained in:
parent
c1ba2ec74a
commit
7c533f91f0
@ -38,7 +38,7 @@ class Game extends AutoStoring
|
||||
|
||||
// -------
|
||||
|
||||
static private function patchUpGameDate(array &$a) : void
|
||||
static private function patchUpGameData(array &$a) : void
|
||||
{
|
||||
$version = $a["version"] ?? 0;
|
||||
if ($version < 2) { // update to game version 2
|
||||
@ -49,6 +49,13 @@ class Game extends AutoStoring
|
||||
|
||||
$a["version"] = 2;
|
||||
}
|
||||
|
||||
if ($version < 3) {
|
||||
|
||||
return;
|
||||
|
||||
//$a["version"] = 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Store modifications.
|
||||
@ -106,7 +113,7 @@ class Game extends AutoStoring
|
||||
static function fromArray(GameMgr &$gameMgr, array $a): Game
|
||||
{
|
||||
$id = $a["_id"] ?? -1;
|
||||
self::patchUpGameDate($a);
|
||||
self::patchUpGameData($a);
|
||||
return new Game($gameMgr, $a["name"], $a["description"], $id, $a["owner"], $a["contributors"],
|
||||
$a["game_file_present"], $a["properties"], $a["public"], $a["public_id"], $a["version"]);
|
||||
}
|
||||
@ -136,7 +143,7 @@ class Game extends AutoStoring
|
||||
return $a;
|
||||
}
|
||||
|
||||
// Export challenges to a CSV file.
|
||||
// Export challenges to a CSV file. TODO: ez csak a feleletválasztóshoz lesz jó
|
||||
function exportChallengesToCSV(&$f): void
|
||||
{
|
||||
// load challenges
|
||||
@ -185,7 +192,7 @@ class Game extends AutoStoring
|
||||
|
||||
const CSV_ENCODINGS = ["UTF-8", "Windows-1252"];
|
||||
|
||||
// Import challenges from a CSV table.
|
||||
// Import challenges from a CSV table. TODO: ez csak a feleletválasztós betöltésére lesz jó
|
||||
function importChallengesFromCSV(string $csv_path): array
|
||||
{
|
||||
// convert text encoding into UTF-8
|
||||
|
||||
@ -79,7 +79,7 @@ class Answer
|
||||
}
|
||||
}
|
||||
|
||||
class Challenge
|
||||
class ChallengeReport
|
||||
{
|
||||
private string $question;
|
||||
private array $answers;
|
||||
@ -152,7 +152,7 @@ class ReportSection
|
||||
function __construct(string $title, array $challenges)
|
||||
{
|
||||
$this->title = $title;
|
||||
$this->challenges = array_map(fn($ch) => new Challenge($ch), $challenges);
|
||||
$this->challenges = array_map(fn($ch) => new ChallengeReport($ch), $challenges);
|
||||
}
|
||||
|
||||
function getChallenges(): array
|
||||
|
||||
@ -13,15 +13,15 @@ const TEST_CONCLUDED = "concluded";
|
||||
|
||||
class TestSummary
|
||||
{
|
||||
public int $challengeN; // Number of challenges
|
||||
public int $correctAnswerN; // Number of correct answers
|
||||
public int $maxMark; // Number of challenges
|
||||
public int $mark; // Number of correct answers
|
||||
private float $percentage; // Ratio of correct answers
|
||||
|
||||
// Calculate percentage.
|
||||
private function calculatePercentage(): void
|
||||
{
|
||||
if ($this->challengeN > 0) {
|
||||
$this->percentage = $this->correctAnswerN / (double)$this->challengeN * 100.0;
|
||||
if ($this->maxMark > 0) {
|
||||
$this->percentage = $this->mark / (double)$this->maxMark * 100.0;
|
||||
} else { // avoid division by zero
|
||||
$this->percentage = 0.0;
|
||||
}
|
||||
@ -29,45 +29,266 @@ class TestSummary
|
||||
|
||||
function __construct(int $challengeN, int $correctAnswerN)
|
||||
{
|
||||
$this->challengeN = $challengeN;
|
||||
$this->correctAnswerN = $correctAnswerN;
|
||||
$this->maxMark = $challengeN;
|
||||
$this->mark = $correctAnswerN;
|
||||
$this->calculatePercentage();
|
||||
}
|
||||
|
||||
// Get challenge count.
|
||||
function getChallengeN(): int
|
||||
function getMaxMark(): int
|
||||
{
|
||||
return $this->challengeN;
|
||||
return $this->maxMark;
|
||||
}
|
||||
|
||||
// Get number of correct answers.
|
||||
function getCorrectAnswerN(): int
|
||||
function getMark(): int
|
||||
{
|
||||
return $this->correctAnswerN;
|
||||
return $this->mark;
|
||||
}
|
||||
|
||||
function setCorrectAnswerN(int $correctAnswerN): void
|
||||
function setMark(int $mark): void
|
||||
{
|
||||
$this->correctAnswerN = $correctAnswerN;
|
||||
$this->mark = $mark;
|
||||
$this->calculatePercentage();
|
||||
}
|
||||
|
||||
// Get ratio of correct results.
|
||||
function getPercentage(): float
|
||||
{
|
||||
return ($this->correctAnswerN * 100.0) / $this->challengeN;
|
||||
return ($this->mark * 100.0) / $this->maxMark;
|
||||
}
|
||||
|
||||
// Build from array.
|
||||
static function fromArray(array $a): TestSummary
|
||||
{
|
||||
return new TestSummary($a["challenge_n"], $a["correct_answer_n"]);
|
||||
if (!isset($a["max_mark"]) || !isset($a["mark"])) { // backward compatibility
|
||||
return new TestSummary($a["challenge_n"], $a["correct_answer_n"]);
|
||||
} else {
|
||||
return new TestSummary($a["max_mark"], $a["mark"]);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to array.
|
||||
function toArray(): array
|
||||
{
|
||||
return ["challenge_n" => $this->challengeN, "correct_answer_n" => $this->correctAnswerN, "percentage" => $this->percentage];
|
||||
return ["challenge_n" => $this->maxMark, "correct_answer_n" => $this->mark, "percentage" => $this->percentage];
|
||||
}
|
||||
}
|
||||
|
||||
class Challenge
|
||||
{
|
||||
protected string $type; // challenge type
|
||||
|
||||
protected float $max_mark; // maximum points that can be collected at this challenge
|
||||
|
||||
protected bool $is_template; // this challenge is a template
|
||||
|
||||
function __construct(string $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
$this->is_template = false;
|
||||
$this->max_mark = 1.0;
|
||||
}
|
||||
|
||||
// save answer
|
||||
function saveAnswer(int|string $ans): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// clear answer
|
||||
function clearAnswer(int|string $ans): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// get challenge type
|
||||
function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
function setMaxMark(float $max_mark): void
|
||||
{
|
||||
$this->max_mark = $max_mark;
|
||||
}
|
||||
|
||||
function getMaxMark(): float {
|
||||
return $this->max_mark;
|
||||
}
|
||||
|
||||
function getMark(): float
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
function toArray(): array
|
||||
{
|
||||
return ["type" => $this->type];
|
||||
}
|
||||
|
||||
function setTemplate(bool $is_template): void
|
||||
{
|
||||
$this->is_template = $is_template;
|
||||
}
|
||||
|
||||
function isTemplate(): bool
|
||||
{
|
||||
return $this->is_template;
|
||||
}
|
||||
|
||||
function randomize(): void {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
class PicturedChallenge extends Challenge
|
||||
{
|
||||
protected string $image_url; // the URL of the corresponding image
|
||||
|
||||
function __construct(string $type, array $a = null)
|
||||
{
|
||||
parent::__construct($type);
|
||||
$this->image_url = $a["image_url"] ?? "";
|
||||
}
|
||||
|
||||
function setImageUrl(string $image_url): void
|
||||
{
|
||||
$this->image_url = $image_url;
|
||||
}
|
||||
|
||||
function getImageUrl(): string
|
||||
{
|
||||
return $this->image_url;
|
||||
}
|
||||
|
||||
function toArray(): array
|
||||
{
|
||||
$a = parent::toArray();
|
||||
$a["image_url"] = $this->image_url;
|
||||
return $a;
|
||||
}
|
||||
}
|
||||
|
||||
class SingleChoiceChallenge extends PicturedChallenge
|
||||
{
|
||||
private string $question; // the task title
|
||||
private array $answers; // possible answers
|
||||
private int $correct_answer; // the single correct answer
|
||||
|
||||
private int $player_answer; // answer given by the player
|
||||
|
||||
// -----------------
|
||||
|
||||
function __construct(array $a = null)
|
||||
{
|
||||
parent::__construct("singlechoice", $a);
|
||||
|
||||
$this->question = $a["question"] ?? "";
|
||||
$this->answers = $a["answers"] ?? [];
|
||||
$this->correct_answer = (int)($a["correct_answer"] ?? -1);
|
||||
$this->player_answer = (int)($a["player_answer"] ?? -1);
|
||||
}
|
||||
|
||||
function setQuestion(string $question): void
|
||||
{
|
||||
$this->question = $question;
|
||||
}
|
||||
|
||||
function getQuestion(): string
|
||||
{
|
||||
return $this->question;
|
||||
}
|
||||
|
||||
function addAnswer(string $answer): void
|
||||
{
|
||||
$this->answers[] = $answer;
|
||||
}
|
||||
|
||||
function getAnswers(): array
|
||||
{
|
||||
return $this->answers;
|
||||
}
|
||||
|
||||
function setCorrectAnswer(string $correct_answer): void
|
||||
{
|
||||
$this->correct_answer = $correct_answer;
|
||||
}
|
||||
|
||||
function getCorrectAnswer(): string
|
||||
{
|
||||
return $this->correct_answer;
|
||||
}
|
||||
|
||||
private function isAnswerIdInsideBounds($ansid): bool
|
||||
{
|
||||
return ($ansid >= 0) && ($ansid <= count($this->answers));
|
||||
}
|
||||
|
||||
function saveAnswer(int|string $ans): bool
|
||||
{
|
||||
$ansidx = (int)($ans); // cast answer to integer as it is a number
|
||||
if ($this->isAnswerIdInsideBounds($ansidx)) {
|
||||
$this->player_answer = $ansidx;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function clearAnswer(int|string $ans): bool
|
||||
{
|
||||
$ansidx = (int)($ans); // cast answer to integer as it is a number
|
||||
if ($this->isAnswerIdInsideBounds($ansidx)) {
|
||||
$this->player_answer = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getMark(): float
|
||||
{
|
||||
return ($this->player_answer == $this->correct_answer) ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
function toArray(): array
|
||||
{
|
||||
$a = parent::toArray();
|
||||
$a["question"] = $this->question;
|
||||
$a["answers"] = $this->answers;
|
||||
$a["correct_answer"] = $this->correct_answer;
|
||||
if (!$this->isTemplate()) {
|
||||
$a["player_answer"] = $this->player_answer;
|
||||
}
|
||||
return $a;
|
||||
}
|
||||
|
||||
function randomize(): void{
|
||||
//shuffle($this->answers); // shuffle answers
|
||||
//$this->correct_answer = array_search($this->correct_answer, $this->answers); // remap correct answer
|
||||
}
|
||||
}
|
||||
|
||||
class ChallengeFactory
|
||||
{
|
||||
static function fromArray(array $a): Challenge|null
|
||||
{
|
||||
$type = $a["type"] ?? "singlechoice"; // if the type is missing, then it's a single choice challenge
|
||||
switch ($type) {
|
||||
case "singlechoice":
|
||||
return new SingleChoiceChallenge($a);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static function constructFromCollection(array $c): array {
|
||||
$chgs = [];
|
||||
foreach ($c as $ch) {
|
||||
$chgs[] = ChallengeFactory::fromArray($ch);
|
||||
}
|
||||
return $chgs;
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,14 +320,23 @@ class Test extends AutoStoring
|
||||
private function preprocessChallenges(): void
|
||||
{
|
||||
foreach ($this->challenges as &$ch) {
|
||||
shuffle($ch["answers"]); // shuffle answers
|
||||
$ch["correct_answer"] = array_search($ch["correct_answer"], $ch["answers"]); // remap correct answer
|
||||
$ch["player_answer"] = -1; // create player answer field
|
||||
$ch->randomize();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------
|
||||
|
||||
function getMaxSumMark(): float
|
||||
{
|
||||
$msm = 0.0;
|
||||
foreach ($this->challenges as &$ch) {
|
||||
$msm += $ch->getMaxMark();
|
||||
}
|
||||
return $msm;
|
||||
}
|
||||
|
||||
// -------------
|
||||
|
||||
// Store modifications.
|
||||
public function storeMods(): void
|
||||
{
|
||||
@ -136,11 +366,11 @@ class Test extends AutoStoring
|
||||
$this->endTime = $a["end_time"] ?? 0;
|
||||
$this->endLimitTime = $a["end_limit_time"] ?? 0;
|
||||
$this->repeatable = $a["repeatable"];
|
||||
$this->challenges = $a["challenges"];
|
||||
$this->challenges = ChallengeFactory::constructFromCollection($a["challenges"]);
|
||||
if (isset($a["summary"])) {
|
||||
$this->summary = TestSummary::fromArray($a["summary"]);
|
||||
} else { // backward compatibility
|
||||
$this->summary = new TestSummary(count($a["challenges"]), 0);
|
||||
$this->summary = new TestSummary($this->getMaxSumMark(), 0);
|
||||
}
|
||||
} else { // populating fields from Game and User objects
|
||||
$game = &$game_array;
|
||||
@ -150,7 +380,7 @@ class Test extends AutoStoring
|
||||
// Fill-in basic properties
|
||||
$this->gameId = $game->getId();
|
||||
$this->gameName = $game->getName();
|
||||
$this->challenges = $game->getChallenges();
|
||||
$this->challenges = ChallengeFactory::constructFromCollection($game->getChallenges());
|
||||
$this->preprocessChallenges();
|
||||
$this->nickname = $user->getNickname();
|
||||
|
||||
@ -169,7 +399,7 @@ class Test extends AutoStoring
|
||||
$this->repeatable = $gp["repeatable"];
|
||||
|
||||
// Create a blank summary
|
||||
$this->summary = new TestSummary(count($this->challenges), 0);
|
||||
$this->summary = new TestSummary($this->getMaxSumMark(), 0);
|
||||
}
|
||||
|
||||
// auto-conclude time-constrained test if expired
|
||||
@ -182,6 +412,11 @@ class Test extends AutoStoring
|
||||
// Convert test to array.
|
||||
function toArray(array $omit = []): array
|
||||
{
|
||||
$chgs = [];
|
||||
foreach ($this->challenges as $ch) {
|
||||
$chgs[] = $ch->toArray();
|
||||
}
|
||||
|
||||
$a = [
|
||||
"_id" => $this->id,
|
||||
"gameid" => $this->gameId,
|
||||
@ -193,7 +428,7 @@ class Test extends AutoStoring
|
||||
"end_time" => $this->endTime,
|
||||
"end_limit_time" => $this->endLimitTime,
|
||||
"repeatable" => $this->repeatable,
|
||||
"challenges" => $this->challenges,
|
||||
"challenges" => $chgs,
|
||||
"summary" => $this->summary->toArray()
|
||||
];
|
||||
|
||||
@ -211,12 +446,16 @@ class Test extends AutoStoring
|
||||
return count($this->challenges);
|
||||
}
|
||||
|
||||
function isChallengeIdInsideBounds(int $chidx): bool {
|
||||
return ($chidx >= 0) && ($chidx < $this->getChallengeCount());
|
||||
}
|
||||
|
||||
// Save answer. Asserting $safe prevents saving answers to a concluded test.
|
||||
function saveAnswer(int $chidx, int $ansidx, bool $safe = true): bool
|
||||
function saveAnswer(int $chidx, string $ans, bool $safe = true): bool
|
||||
{
|
||||
if (!$safe || $this->state === self::TEST_ONGOING) {
|
||||
if (($chidx < $this->getChallengeCount()) && ($ansidx < $this->challenges[$chidx]["answers"])) {
|
||||
$this->challenges[$chidx]["player_answer"] = $ansidx;
|
||||
if ($this->isChallengeIdInsideBounds($chidx)) {
|
||||
$this->challenges[$chidx]->saveAnswer($ans);
|
||||
$this->commitMods();
|
||||
return true;
|
||||
}
|
||||
@ -228,8 +467,8 @@ class Test extends AutoStoring
|
||||
function clearAnswer(int $chidx, bool $safe = true): bool
|
||||
{
|
||||
if (!$safe || $this->state === self::TEST_ONGOING) {
|
||||
if ($chidx < $this->getChallengeCount()) {
|
||||
$this->challenges[$chidx]["player_answer"] = -1;
|
||||
if ($this->isChallengeIdInsideBounds($chidx)) {
|
||||
$this->challenges[$chidx]->clearAnswer();
|
||||
$this->commitMods();
|
||||
return true;
|
||||
}
|
||||
@ -240,18 +479,16 @@ class Test extends AutoStoring
|
||||
// Conclude test.
|
||||
function concludeTest(): void
|
||||
{
|
||||
// check the answers
|
||||
$cans_n = 0; // number of correct answers
|
||||
// summarize points
|
||||
$mark_sum = 0.0;
|
||||
foreach ($this->challenges as &$ch) {
|
||||
if ($ch["player_answer"] === $ch["correct_answer"]) {
|
||||
$cans_n++;
|
||||
}
|
||||
$mark_sum += $ch->getMark();
|
||||
}
|
||||
|
||||
// set state and fill summary
|
||||
$this->state = TEST_CONCLUDED;
|
||||
$this->endTime = time();
|
||||
$this->summary->setCorrectAnswerN($cans_n);
|
||||
$this->summary->setMark($mark_sum);
|
||||
|
||||
// save test
|
||||
$this->commitMods();
|
||||
|
||||
@ -4,7 +4,7 @@ require_once "class/TestMgr.php";
|
||||
|
||||
const longopts = [
|
||||
"action:", // execute some CLI action
|
||||
"tick" // tick timed objects (e.g. timed tests)
|
||||
"tick", // tick timed objects (e.g. timed tests)
|
||||
];
|
||||
|
||||
$options = getopt("", longopts);
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
ini_set("display_errors", true);
|
||||
|
||||
require_once "check_maintenance.php";
|
||||
|
||||
//ini_set('display_startup_errors', '1');
|
||||
|
||||
@ -46,7 +46,7 @@ function populate_infobox(test_data, view_only) {
|
||||
time_left_s--;
|
||||
print_timer();
|
||||
if (time_left_s <= 0) {
|
||||
populate_all(test_data["_id"], test_data["gameid"]);
|
||||
populate_all(test_data["_id"], test_data["gameid"], false);
|
||||
clearInterval(INTERVAL_HANDLE);
|
||||
INTERVAL_HANDLE = null;
|
||||
}
|
||||
@ -183,6 +183,6 @@ function submit_test() {
|
||||
testid: TEST_DATA["_id"]
|
||||
}
|
||||
request(req).then(resp => {
|
||||
populate_all(TEST_DATA["_id"], TEST_DATA["gameid"]);
|
||||
populate_all(TEST_DATA["_id"], TEST_DATA["gameid"], false);
|
||||
});
|
||||
}
|
||||
@ -5,7 +5,9 @@
|
||||
<title>SpreadQuiz :: Karbantartás</title>
|
||||
</head>
|
||||
<body>
|
||||
<img src="media/maintenance.png" width="180">
|
||||
<h3> Az oldal karbantartás alatt áll!</h3>
|
||||
<section style="margin: 0 auto; padding-top: 10ex; text-align: center">
|
||||
<img src="media/maintenance.png" style="width: 16vw">
|
||||
<h3 style="font-family: 'Monaco', monospace"> Az oldal karbantartás alatt áll!</h3>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user