tasks as &$task) { $task->setTemplate(false); // the task is no longer a template $task->randomize(); // randomize } } // ------------- function getMaxSumMark(): float { $msm = 0.0; foreach ($this->tasks as &$task) { $msm += $task->getMaxMark(); } return $msm; } // ------------- // Store modifications. public function storeMods(): void { $this->testMgr->updateTest($this); } // ------------- // Construct new test based on Game and User objects function __construct(TestMgr &$testMgr, Game|array &$game_array, User &$user = null) { parent::__construct(); $this->testMgr = $testMgr; $this->id = -1; if (is_array($game_array)) { // populating fields from an array $a = &$game_array; $this->id = $a["_id"] ?? -1; $this->gameId = $a["gameid"]; $this->gameName = $a["gamename"]; $this->nickname = $a["nickname"]; $this->state = $a["state"]; $this->timeLimited = $a["time_limited"]; $this->startTime = $a["start_time"]; $this->endTime = $a["end_time"] ?? 0; $this->endLimitTime = $a["end_limit_time"] ?? 0; $this->repeatable = $a["repeatable"]; $this->tasks = TaskFactory::constructFromCollection($a["challenges"]); if (isset($a["summary"])) { $this->summary = TestSummary::fromArray($a["summary"]); } else { // backward compatibility $this->summary = new TestSummary($this->getMaxSumMark(), 0); } } else { // populating fields from Game and User objects $game = &$game_array; $this->endTime = 0; // Fill-in basic properties $this->gameId = $game->getId(); $this->gameName = $game->getName(); $this->tasks = $game->getTasks(); $this->preprocessTasks(); $this->nickname = $user->getNickname(); $this->state = self::TEST_ONGOING; $gp = $game->getProperties(); $this->timeLimited = (($gp["time_limit"] ?: -1) > -1); $now = time(); $this->startTime = $now; if ($this->timeLimited) { $this->endLimitTime = $now + $gp["time_limit"]; } else { $this->endLimitTime = -1; // dummy value, not used, since timeLimited is false } $this->repeatable = $gp["repeatable"]; // Create a blank summary $this->summary = new TestSummary($this->getMaxSumMark(), 0); } // auto-conclude time-constrained test if expired if ($this->timeLimited && $this->isOngoing() && ($this->endLimitTime <= time())) { $this->concludeTest(); $this->endTime = $this->endLimitTime; // date back end time to the limiting value } } // Convert test to array. function toArray(array $omit = []): array { $tasks = []; foreach ($this->tasks as &$t) { $tasks[] = $t->toArray(); } $a = [ "_id" => $this->id, "gameid" => $this->gameId, "nickname" => $this->nickname, "gamename" => $this->gameName, "state" => $this->state, "time_limited" => $this->timeLimited, "start_time" => $this->startTime, "end_time" => $this->endTime, "end_limit_time" => $this->endLimitTime, "repeatable" => $this->repeatable, "challenges" => $tasks, "summary" => $this->summary->toArray() ]; // omit specific fields foreach ($omit as $field) { unset($a[$field]); } return $a; } // Get number of tasks. function getTaskCount(): int { return count($this->tasks); } function isTaskIdInsideBounds(int $tidx): bool { return ($tidx >= 0) && ($tidx < $this->getTaskCount()); } // Save answer. Asserting $safe prevents saving answers to a concluded test. function saveAnswer(int $tidx, int|string $ans, bool $safe = true): bool { if (!$safe || $this->state === self::TEST_ONGOING) { if ($this->isTaskIdInsideBounds($tidx)) { $this->tasks[$tidx]->saveAnswer($ans); $this->commitMods(); return true; } } return false; } // Clear answer. function clearAnswer(int $chidx, bool $safe = true): bool { if (!$safe || $this->state === self::TEST_ONGOING) { if ($this->isTaskIdInsideBounds($chidx)) { $this->tasks[$chidx]->clearAnswer(); $this->commitMods(); return true; } } return false; } // Conclude test. function concludeTest(): void { // summarize points $mark_sum = 0.0; foreach ($this->tasks as &$ch) { $mark_sum += $ch->getMark(); } // set state and fill summary $this->state = self::TEST_CONCLUDED; $this->endTime = time(); $this->summary->setMark($mark_sum); // save test $this->commitMods(); } // -------- public function getId(): int { return $this->id; } public function getStartTime(): int { return $this->startTime; } public function getEndTime(): int { return $this->endTime; } public function getSummary(): TestSummary { return $this->summary; } public function getNickname(): string { return $this->nickname; } public function getGameId(): int { return $this->gameId; } public function isConcluded(): bool { return $this->state === self::TEST_CONCLUDED; } public function isOngoing(): bool { return $this->state === self::TEST_ONGOING; } }