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);
 | 
						|
    }
 | 
						|
} |