diff --git a/class/ReportBuilder.php b/class/ReportBuilder.php
index 0a69ef6..94abbfd 100644
--- a/class/ReportBuilder.php
+++ b/class/ReportBuilder.php
@@ -7,7 +7,7 @@ require_once "common_func.php";
class ReportBuilder
{
- static public function getStatsByFilters(int $gameid, string $filter, string $groups, string $ordering)
+ static public function getStatsByFilters(int $gameid, string $filter, string $groups, bool $bestOnly)
{
$groupMgr = new GroupMgr();
$testMgr = new TestMgr();
@@ -18,7 +18,7 @@ class ReportBuilder
$groupFilter = ["nickname", "IN", $nicknames];
// get IDs
- $tests = $testMgr->getResultsByGameId($gameid, $filter, $ordering, true, $groupFilter);
+ $tests = $testMgr->getResultsByGameId($gameid, $filter, "", true, $bestOnly, $groupFilter);
$ids = array_map(fn($test) => $test["_id"], $tests);
// generate stats
@@ -71,12 +71,25 @@ class Answer
{
return "\\answer" . $this->type . "{" . $this->ratio . "}{" . $this->text . "}\n";
}
+
+ // Get answer ratio.
+ public function getRatio(): float
+ {
+ return $this->ratio;
+ }
}
class Challenge
{
private string $question;
private array $answers;
+ private int $fillCount;
+ private int $skipCount;
+
+ // Sort answers by ratio.
+ private function sortAnswers() : void {
+ usort($this->answers, function($a, $b) {return $a->getRatio() < $b->getRatio();});
+ }
function __construct(array $data)
{
@@ -87,6 +100,10 @@ class Challenge
$type = $answer === $data["correct_answer"] ? Answer::CORRECT : Answer::INCORRECT;
$this->answers[] = new Answer($type, $answer, $ratio);
}
+ $this->fillCount = array_sum($data["player_answers"]); // get fill count
+ $this->skipCount = $data["skipped"];
+
+ $this->sortAnswers(); // sort answers by fill ratio
}
public function getQuestion(): string
@@ -99,10 +116,22 @@ class Challenge
return $this->answers;
}
+ public function getFillCount(): int {
+ return $this->fillCount;
+ }
+
+ public function getSkipCount(): int {
+ return $this->skipCount;
+ }
+
+ public function getSubmissionCount() : int {
+ return $this->fillCount + $this->skipCount;
+ }
+
// Generate TeX representation.
public function genTeX(): string
{
- $tex = "\\begin{question}{" . $this->question . "}\n";
+ $tex = "\\begin{question}{" . $this->question . "}{" . $this->fillCount . "}\n";
foreach ($this->answers as &$answer) {
$tex .= $answer->genTeX();
}
@@ -116,6 +145,10 @@ class ReportSection
private string $title;
private array $challenges;
+ private function getNumberOfSubmissions() : int {
+ return count($this->challenges) > 0 ? $this->challenges[0]->getSubmissionCount() : 0;
+ }
+
function __construct(string $title, array $challenges)
{
$this->title = $title;
@@ -135,7 +168,7 @@ class ReportSection
// Generate TeX representation of this report.
function genTeX(): string
{
- $tex = "\\begin{quiz}{" . $this->title . "}\n";
+ $tex = "\\begin{quiz}{" . $this->title . "}{" . $this->getMaxFillingCount() . "}\n";
foreach ($this->challenges as $challenge) {
$tex .= $challenge->genTeX();
}
diff --git a/class/TestMgr.php b/class/TestMgr.php
index 46dce6e..629fe03 100644
--- a/class/TestMgr.php
+++ b/class/TestMgr.php
@@ -406,7 +406,7 @@ class TestMgr
}
// Get test results by game ID.
- function getResultsByGameId(string $gameid, string $filter, string $orderby, bool $exclude_challenge_data, array ...$furtherFilters): array
+ function getResultsByGameId(string $gameid, string $filter, string $orderby, bool $exclude_challenge_data, bool $best_ones_only, array ...$furtherFilters): array
{
$qb = $this->db->createQueryBuilder();
$qb = $qb->where(["gameid", "=", (int)$gameid]);
@@ -448,6 +448,34 @@ class TestMgr
}
$test_data_array = $qb->getQuery()->fetch();
+
+ // if only the best results should be included, then...
+ if ($best_ones_only) {
+ // filter out ongoing ones
+ $tests = array_filter($test_data_array, fn($test) => $test["state"] === Test::TEST_CONCLUDED);
+
+ // sort by result
+ usort($tests, fn($a, $b) => $a["summary"]["percentage"] > $b["summary"]["percentage"]);
+
+ // gather best tests by username here
+ $best_test_ids = [];
+ foreach ($tests as $test) {
+ $nickname = $test["nickname"];
+ if (!in_array($nickname, $best_test_ids)) {
+ $best_tests[$nickname] = $test["_id"];
+ }
+ }
+
+ // just keep values, drop the keys (nicknames)
+ $best_tests = array_values($best_tests);
+
+ // remove non-best results
+ $test_data_array = array_filter($test_data_array, fn($test) => in_array($test["_id"], $best_tests));
+
+ // renumber results
+ $test_data_array = array_values($test_data_array);
+ }
+
return $test_data_array;
}
@@ -487,6 +515,7 @@ class TestMgr
"correct_answer" => $correct_answer,
"player_answers" => array_fill(0, count($challenge["answers"]), 0),
"answer_count" => count($challenge["answers"]),
+ "skipped" => 0
];
$aggregated[$challenge_indices[$idhash]] = $challenge_info; // insert challenge info
}
@@ -495,9 +524,12 @@ class TestMgr
$challenge_idx = $challenge_indices[$idhash];
// add up player answer
- if (trim($challenge["player_answer"]) !== "") {
+ $player_answer = trim($challenge["player_answer"]);
+ if (($player_answer !== "") && ($player_answer != -1)) { // player answered
$answer_idx = array_search($challenge["answers"][$challenge["player_answer"]], $aggregated[$challenge_idx]["answers"]); // transform player answer index to report answer index
$aggregated[$challenge_idx]["player_answers"][(int)$answer_idx]++;
+ } else { // player has not answered or provided an unprocessable answer
+ $aggregated[$challenge_idx]["skipped"]++;
}
}
}
diff --git a/game_manager_frame.php b/game_manager_frame.php
index 1faeef3..3cd948d 100644
--- a/game_manager_frame.php
+++ b/game_manager_frame.php
@@ -146,7 +146,10 @@ if (!get_autologin_state() || (($user_data["privilege"] !== PRIVILEGE_CREATOR) &
+
+
+
+
diff --git a/interface.php b/interface.php
index 5d76a84..024a0e7 100644
--- a/interface.php
+++ b/interface.php
@@ -485,6 +485,7 @@ function get_results_by_gameid(ReqHandler &$rh, array $params): array
$filter = trim($params["filter"] ?? "");
$ordering = trim($params["orderby"] ?? "");
$groups = explode_list(trim($params["groups"] ?? ""));
+ $best_only = trim($params["best_only"] ?? "false") === "true";
$game = $gameMgr->getGame($gameid);
@@ -516,9 +517,9 @@ function get_results_by_gameid(ReqHandler &$rh, array $params): array
// execute filtering
$game_results = null;
if ($group_filter !== []) {
- $game_results = $testMgr->getResultsByGameId($gameid, $filter, $ordering, true, $group_filter);
+ $game_results = $testMgr->getResultsByGameId($gameid, $filter, $ordering, true, $best_only, $group_filter);
} else {
- $game_results = $testMgr->getResultsByGameId($gameid, $filter, $ordering, true);
+ $game_results = $testMgr->getResultsByGameId($gameid, $filter, $ordering, true, $best_only);
}
$result = $game_results;
}
@@ -534,6 +535,7 @@ function generate_report_by_groups(ReqHandler &$rh, array $params): string
$gameid = trim($params["gameid"]);
$filter = trim($params["filter"] ?? "");
$groups = explode_list(trim($params["groups"]));
+ $best_only = trim($params["best_only"] ?? "false") === "true";
$outtype = trim($params["outtype"] ?? "pdf");
// only PDF and TEX are valid
@@ -565,7 +567,7 @@ function generate_report_by_groups(ReqHandler &$rh, array $params): string
// assemble report
$report = new Report($game->getName());
foreach ($groups as $groupname) {
- $stats = ReportBuilder::getStatsByFilters($gameid, $filter, $groupname, "");
+ $stats = ReportBuilder::getStatsByFilters($gameid, $filter, $groupname, $best_only);
$section = new ReportSection($groupname, $stats);
$report->addSection($section);
}
diff --git a/js/gamemgr.js b/js/gamemgr.js
index 1404720..e864cb0 100644
--- a/js/gamemgr.js
+++ b/js/gamemgr.js
@@ -373,12 +373,13 @@ function list_results_by_game(game) {
function generate_game_report_by_groups() {
let gameid = EDITED_GAME["_id"];
- let filter = document.getElementById("report_filter").value.trim()
- let groups = document.getElementById("report_groups").value.trim()
+ let filter = document.getElementById("report_filter").value.trim();
+ let groups = document.getElementById("report_groups").value.trim();
+ let best_only = document.getElementById("best_only").checked ? "true" : "false";
let outtype = document.getElementById("report_output_type").value;
- let report_url = `interface.php?action=generate_report_by_groups&gameid=${gameid}&groups=${groups}&filter=${filter}&outtype=${outtype}`;
- report_url = report_url.replace("#", "%23");
+ let report_url = `interface.php?action=generate_report_by_groups&gameid=${gameid}&groups=${groups}&filter=${filter}&outtype=${outtype}&best_only=${best_only}`;
+ report_url = report_url.replaceAll("#", "%23");
window.open(report_url, "_blank");
}
diff --git a/js/result_analyzer.js b/js/result_analyzer.js
index 58ac09b..9f7136c 100644
--- a/js/result_analyzer.js
+++ b/js/result_analyzer.js
@@ -36,6 +36,7 @@ function fetch_results() {
let filterF = document.getElementById("filter");
let orderbyF = document.getElementById("orderby");
let groupsF = document.getElementById("groups");
+ let best_onlyChk = document.getElementById("best_only");
let filter = autoconvert_datetime(filterF.value.trim());
@@ -46,7 +47,8 @@ function fetch_results() {
gameid: GAMEID,
filter: filter.trim(),
orderby: orderbyF.value.trim(),
- groups: groupsF.value.trim()
+ groups: groupsF.value.trim(),
+ best_only: best_onlyChk.checked ? "true" : "false"
};
request(req).then(resp => {
diff --git a/report/template/report.tex b/report/template/report.tex
index bef3bad..471d090 100644
--- a/report/template/report.tex
+++ b/report/template/report.tex
@@ -1,12 +1,17 @@
+% !TeX spellcheck = hu_HU
+% !TeX encoding = UTF-8
+% !TeX program = xelatex
+
\documentclass[10pt]{article}
%% Importing packages
% General things
\usepackage[magyar]{babel}
-\usepackage[a4paper,margin=1in]{geometry}
+\usepackage[a4paper,margin=10mm,footskip=5mm]{geometry}
\usepackage{fontspec}
\usepackage[fontsize=7pt]{fontsize}
+\usepackage{titlesec}
% For frame layout and content
\usepackage{xcolor}
@@ -28,6 +33,10 @@
\definecolor{colpbb}{HTML}{A0A0A0} % Progressbar background
\definecolor{colanc}{HTML}{176767} % Correct answer background
\definecolor{colani}{HTML}{D3E5E5} % Incorrect answer background
+\definecolor{colsbg}{HTML}{AA8A7D} % Group title background
+\definecolor{colsfg}{HTML}{EFEFEF} % Group title text color
+\definecolor{colqsb}{HTML}{176767} % Quiz sequence number background
+\definecolor{colqsf}{HTML}{EFEFEF} % Quiz sequence number text color
% Setting up outer frames
\mdfsetup{
@@ -52,16 +61,16 @@
% Replacing ToC text
\addto\captionsmagyar{%
- \renewcommand{\contentsname}%
- {\huge Kurzusok}%
+ \renewcommand{\contentsname}%
+ {\huge Csoportok}%
}
% Add dotfill to ToC
\makeatletter
\patchcmd{\l@section}{\hfil}{%
- \leaders\hbox{$\m@th
+ \leaders\hbox{$\m@th
\mkern \@dotsep mu\hbox{.}\mkern \@dotsep
- mu$}\hfill}{}{}
+ mu$}\hfill}{}{}
\makeatother
@@ -83,39 +92,75 @@
\newcommand{\unnumsec}[1]{\section*{#1}%
\addcontentsline{toc}{section}{#1}}
+% Set section title style
+\titleformat{\unnumsec}{\Large\bfseries}{\thesection}{1em}{}
+
% Macros for answers
\newcommand{\answerCorrect}[2]{\progressbar{#1} & \begin{minipage}{0.8\columnwidth}\begin{mdframed}[backgroundcolor=colanc]\color{coltxl}{#2}\end{mdframed}\end{minipage}\\}
\newcommand{\answerIncorrect}[2]{\progressbar[filledcolor=colpbs]{#1} & \begin{minipage}{0.8\columnwidth}\begin{mdframed}[backgroundcolor=colani]\color{coltxd}{#2}\end{mdframed}\end{minipage}\\}
+% Macros for questions.
+\newmdenv[backgroundcolor=colqsb,align=left,innerleftmargin=0.2em,innerrightmargin=0.2em]{qsn} % Question sequence number
+
% Environment for questions. The parameter is the question itself, the contents of the environment should consist of \answer*{}{} macros and nothing else
-\newenvironment{question}[1]{
-\begin{center}
- \begin{mdframed}
- {\color{coltit}\large #1\par}\medskip
- \begin{tabular}{c c}
-}{
- \end{tabular}
- \end{mdframed}
-\end{center}
+\newenvironment{question}[2]{
+ \begin{center}
+ \begin{mdframed}
+ \raisebox{0.05em} {
+ \begin{minipage}{0.6cm}
+ \begin{qsn}[userdefinedwidth=0.5cm]
+ \begin{flushright}
+ \color{colqsf}\arabic{qc}.
+ \end{flushright}
+ \end{qsn}
+ \end{minipage}
+ }
+ %
+ {\color{coltit}\large #1\par}\medskip
+ %
+ {\hspace{1em}\small #2~kitöltő}\vspace{0.6em}\par
+
+ \stepcounter{qc}
+
+ \begin{tabular}{c c}
+ }{
+ \end{tabular}
+
+ \end{mdframed}
+ \end{center}
}
% An environment to create a quiz containing questions. The parameter is either the title of the quiz, or the name of the course
-\newenvironment{quiz}[1]{
- \unnumsec{#1}
-}{
+\newenvironment{quiz}[2]{
+% Create highlighted group title
+ \begin{mdframed}[backgroundcolor=colsbg]
+ {
+ \color{colsfg}
+ \unnumsec{#1}
+ {\small #2~kitöltő}
+ \vspace{0.4em}
+ }
+ \end{mdframed}
+
+ % Reset question counter
+ \setcounter{qc}{1}
+ }{
\clearpage
}
\begin{document}
-\begin{center}
- \Huge \IfFileExists{stats/title.tex}{\input{title.tex}}{Kvíz eredmények}
-\end{center}
+ \begin{center}
+ \Huge \IfFileExists{title.tex}{\input{title.tex}}{Kvíz eredmények}
+ \end{center}
-\Large
-\tableofcontents
-\normalsize
-\clearpage
+ \Large
+ \tableofcontents
+ \normalsize
+ \clearpage
-\inputIfFileExists{content.tex}
+ % Create question counter
+ \newcounter{qc}
+
+ \inputIfFileExists{content.tex}
\end{document}
diff --git a/result_analyzer.php b/result_analyzer.php
index d0b208c..bd71f03 100644
--- a/result_analyzer.php
+++ b/result_analyzer.php
@@ -52,7 +52,8 @@ if (!$gameMgr->getGame($game_id)->isUserContributorOrOwner($user_data["nickname"
-
+
+