257 lines
7.3 KiB
PHP
257 lines
7.3 KiB
PHP
<?php
|
|
|
|
require_once "TaskFactory.php";
|
|
|
|
require_once "TestSummary.php";
|
|
|
|
class Test extends AutoStoring
|
|
{
|
|
const TEST_ONGOING = "ongoing";
|
|
const TEST_CONCLUDED = "concluded";
|
|
|
|
// ---------
|
|
|
|
public int $id; // ID
|
|
public int $gameId; // ID of associated game
|
|
public string $gameName; // Name of the associated game
|
|
public string $nickname; // Associated user's nickname
|
|
public string $state; // State of the test (ongoing/concluded)
|
|
public bool $timeLimited; // The user is allowed to submit the test in a given period of time.
|
|
public bool $repeatable; // Is the user allowed to take this test multiple times?
|
|
public int $startTime; // Start time (UNIX timestamp)
|
|
public int $endTime; // End time (UNIX timestamp)
|
|
public int $endLimitTime; // Time limit on test submission (UNIX timestamp)
|
|
public TestSummary $summary; // Summmary, if game has ended
|
|
public array $tasks; // Test tasks
|
|
|
|
private TestMgr $testMgr; // Reference to TestMgr managing this Test instance
|
|
|
|
// -------------
|
|
|
|
// Preprocess tasks.
|
|
private function preprocessTasks(): void
|
|
{
|
|
foreach ($this->tasks as &$task) {
|
|
$task->setGovernor($this); // set the task governor
|
|
$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"], $this);
|
|
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 = [], string $mode = "all"): array
|
|
{
|
|
$tasks = [];
|
|
foreach ($this->tasks as &$t) {
|
|
$tasks[] = $t->toArray($mode);
|
|
}
|
|
|
|
$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) {
|
|
$ch->autoCheck();
|
|
$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;
|
|
}
|
|
|
|
public function getGameDir(): string {
|
|
return Game::getGameDirById($this->gameId);
|
|
}
|
|
} |