mirror of https://github.com/acanas/swad-core.git
3736 lines
125 KiB
C
3736 lines
125 KiB
C
// swad_match.c: matches in games using remote control
|
|
|
|
/*
|
|
SWAD (Shared Workspace At a Distance),
|
|
is a web platform developed at the University of Granada (Spain),
|
|
and used to support university teaching.
|
|
|
|
This file is part of SWAD core.
|
|
Copyright (C) 1999-2023 Antonio CaƱas Vargas
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
/*****************************************************************************/
|
|
/********************************* Headers ***********************************/
|
|
/*****************************************************************************/
|
|
|
|
#define _GNU_SOURCE // For asprintf
|
|
#include <linux/limits.h> // For PATH_MAX
|
|
#include <stddef.h> // For NULL
|
|
#include <stdio.h> // For asprintf
|
|
#include <stdlib.h> // For free
|
|
#include <string.h> // For string functions
|
|
|
|
#include "swad_action_list.h"
|
|
#include "swad_alert.h"
|
|
#include "swad_box.h"
|
|
#include "swad_database.h"
|
|
#include "swad_date.h"
|
|
#include "swad_error.h"
|
|
#include "swad_form.h"
|
|
#include "swad_game.h"
|
|
#include "swad_game_database.h"
|
|
#include "swad_global.h"
|
|
#include "swad_group_database.h"
|
|
#include "swad_HTML.h"
|
|
#include "swad_match.h"
|
|
#include "swad_match_database.h"
|
|
#include "swad_match_result.h"
|
|
#include "swad_parameter_code.h"
|
|
#include "swad_question_database.h"
|
|
#include "swad_role.h"
|
|
#include "swad_setting.h"
|
|
#include "swad_test.h"
|
|
|
|
/*****************************************************************************/
|
|
/************** External global variables from others modules ****************/
|
|
/*****************************************************************************/
|
|
|
|
extern struct Globals Gbl;
|
|
|
|
/*****************************************************************************/
|
|
/***************************** Private constants *****************************/
|
|
/*****************************************************************************/
|
|
|
|
#define Mch_ICON_CLOSE "fas fa-times"
|
|
#define Mch_ICON_PLAY "fas fa-play"
|
|
#define Mch_ICON_PAUSE "fas fa-pause"
|
|
#define Mch_ICON_PREVIOUS "fas fa-step-backward"
|
|
#define Mch_ICON_NEXT "fas fa-step-forward"
|
|
#define Mch_ICON_RESULTS "fas fa-chart-bar"
|
|
|
|
#define Mch_COUNTDOWN_SECONDS_LARGE 60
|
|
#define Mch_COUNTDOWN_SECONDS_MEDIUM 30
|
|
#define Mch_COUNTDOWN_SECONDS_SMALL 10
|
|
|
|
/*****************************************************************************/
|
|
/******************************* Private types *******************************/
|
|
/*****************************************************************************/
|
|
|
|
typedef enum
|
|
{
|
|
Mch_CHANGE_STATUS_BY_STUDENT,
|
|
Mch_REFRESH_STATUS_BY_SERVER,
|
|
} Mch_Update_t;
|
|
|
|
/*****************************************************************************/
|
|
/***************************** Private variables *****************************/
|
|
/*****************************************************************************/
|
|
|
|
long Mch_MchCodBeingPlayed; // Used to refresh game via AJAX
|
|
|
|
/*****************************************************************************/
|
|
/***************************** Private prototypes ****************************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_SetMchCodBeingPlayed (long MchCod);
|
|
|
|
static void Mch_PutIconsInListOfMatches (void *Games);
|
|
static void Mch_PutIconToCreateNewMatch (struct Gam_Games *Games);
|
|
|
|
static void Mch_ListOneOrMoreMatches (struct Gam_Games *Games,
|
|
unsigned NumMatches,
|
|
MYSQL_RES *mysql_res);
|
|
static void Mch_ListOneOrMoreMatchesHeading (bool ICanEditMatches);
|
|
static bool Mch_CheckIfICanEditMatches (void);
|
|
static bool Mch_CheckIfICanEditThisMatch (const struct Mch_Match *Match);
|
|
static bool Mch_CheckIfVisibilityOfResultsCanBeChanged (const struct Mch_Match *Match);
|
|
static void Mch_ListOneOrMoreMatchesIcons (struct Gam_Games *Games,
|
|
const struct Mch_Match *Match,
|
|
const char *Anchor);
|
|
static void Mch_ListOneOrMoreMatchesTitleGrps (const struct Mch_Match *Match,
|
|
const char *Anchor);
|
|
static void Mch_GetAndWriteNamesOfGrpsAssociatedToMatch (const struct Mch_Match *Match);
|
|
static void Mch_ListOneOrMoreMatchesStatus (struct Mch_Match *Match,unsigned NumQsts);
|
|
static void Mch_PutParMchCod (void *MchCod);
|
|
static void Mch_ListOneOrMoreMatchesResult (struct Gam_Games *Games,
|
|
const struct Mch_Match *Match);
|
|
static void Mch_ListOneOrMoreMatchesResultStd (struct Gam_Games *Games,
|
|
const struct Mch_Match *Match);
|
|
static void Mch_ListOneOrMoreMatchesResultTch (struct Gam_Games *Games,
|
|
const struct Mch_Match *Match);
|
|
|
|
static void Mch_GetMatchDataFromRow (MYSQL_RES *mysql_res,
|
|
struct Mch_Match *Match);
|
|
|
|
static void Mch_PutFormExistingMatch (struct Gam_Games *Games,
|
|
const struct Mch_Match *Match,
|
|
const char *Anchor);
|
|
static void Mch_PutFormNewMatch (const struct Gam_Game *Game);
|
|
static void Mch_ShowLstGrpsToEditMatch (long MchCod);
|
|
static void Mch_UpdateMatchTitleAndGrps (const struct Mch_Match *Match);
|
|
|
|
static long Mch_CreateMatch (long GamCod,char Title[Mch_MAX_BYTES_TITLE + 1]);
|
|
static void Mch_CreateIndexes (long GamCod,long MchCod);
|
|
static void Mch_ReorderAnswer (long MchCod,unsigned QstInd,
|
|
const struct Qst_Question *Question);
|
|
static void Mch_CreateGrps (long MchCod);
|
|
static void Mch_UpdateMatchStatusInDB (const struct Mch_Match *Match);
|
|
|
|
static void Mch_UpdateElapsedTimeInQuestion (const struct Mch_Match *Match);
|
|
static void Mch_GetElapsedTimeInQuestion (const struct Mch_Match *Match,
|
|
struct Dat_Time *Time);
|
|
static void Mch_GetElapsedTimeInMatch (const struct Mch_Match *Match,
|
|
struct Dat_Time *Time);
|
|
static void Mch_GetElapsedTime (unsigned NumRows,MYSQL_RES *mysql_res,
|
|
struct Dat_Time *Time);
|
|
|
|
static void Mch_SetMatchStatusToPrev (struct Mch_Match *Match);
|
|
static void Mch_SetMatchStatusToPrevQst (struct Mch_Match *Match);
|
|
static void Mch_SetMatchStatusToStart (struct Mch_Match *Match);
|
|
|
|
static void Mch_SetMatchStatusToNext (struct Mch_Match *Match);
|
|
static void Mch_SetMatchStatusToNextQst (struct Mch_Match *Match);
|
|
static void Mch_SetMatchStatusToEnd (struct Mch_Match *Match);
|
|
|
|
static void Mch_ShowMatchStatusForTch (struct Mch_Match *Match);
|
|
static void Mch_ShowMatchStatusForStd (struct Mch_Match *Match,Mch_Update_t Update);
|
|
|
|
static void Mch_ShowLeftColumnTch (struct Mch_Match *Match);
|
|
static void Mch_ShowRefreshablePartTch (struct Mch_Match *Match);
|
|
static void Mch_WriteElapsedTimeInMch (struct Mch_Match *Match);
|
|
static void Mch_WriteElapsedTimeInQst (struct Mch_Match *Match);
|
|
static void Mch_WriteNumRespondersQst (struct Mch_Match *Match);
|
|
static void Mch_PutFormCountdown (struct Mch_Match *Match,long Seconds,const char *Color);
|
|
static void Mch_PutCountdownAndHourglassIcon (struct Mch_Match *Match);
|
|
static void Mch_PutFormsCountdown (struct Mch_Match *Match);
|
|
|
|
static void Mch_ShowRightColumnTch (const struct Mch_Match *Match);
|
|
static void Mch_ShowLeftColumnStd (const struct Mch_Match *Match,
|
|
const struct Mch_UsrAnswer *UsrAnswer);
|
|
static void Mch_ShowRightColumnStd (struct Mch_Match *Match,
|
|
const struct Mch_UsrAnswer *UsrAnswer,
|
|
Mch_Update_t Update);
|
|
|
|
static void Mch_ShowNumQstInMch (const struct Mch_Match *Match);
|
|
static void Mch_PutMatchControlButtons (const struct Mch_Match *Match);
|
|
static void Mch_ShowFormColumns (const struct Mch_Match *Match);
|
|
static void Mch_PutParNumCols (unsigned NumCols);
|
|
|
|
static void Mch_ShowMatchTitleTch (const struct Mch_Match *Match);
|
|
static void Mch_ShowMatchTitleStd (const struct Mch_Match *Match);
|
|
|
|
static void Mch_PutCheckboxResult (const struct Mch_Match *Match);
|
|
static void Mch_PutIfAnswered (const struct Mch_Match *Match,bool Answered);
|
|
static void Mch_PutIconToRemoveMyAnswer (const struct Mch_Match *Match);
|
|
static void Mch_ShowQuestionAndAnswersTch (const struct Mch_Match *Match);
|
|
static void Mch_WriteAnswersMatchResult (const struct Mch_Match *Match,
|
|
struct Qst_Question *Question,
|
|
const char *Class,bool ShowResult);
|
|
static void Mch_WriteChoiceAnsViewMatch (const struct Mch_Match *Match,
|
|
struct Qst_Question *Question,
|
|
const char *Class,bool ShowResult);
|
|
static void Mch_ShowQuestionAndAnswersStd (const struct Mch_Match *Match,
|
|
const struct Mch_UsrAnswer *UsrAnswer,
|
|
Mch_Update_t Update);
|
|
|
|
static void Mch_ShowMatchScore (const struct Mch_Match *Match);
|
|
static void Mch_DrawEmptyScoreRow (unsigned NumRow,double MinScore,double MaxScore);
|
|
static void Mch_DrawScoreRow (double Score,double MinScore,double MaxScore,
|
|
unsigned NumRow,unsigned NumUsrs,unsigned MaxUsrs);
|
|
static const char *Mch_GetClassBorder (unsigned NumRow);
|
|
|
|
static void Mch_PutParNumOpt (unsigned NumOpt);
|
|
static unsigned Mch_GetParNumOpt (void);
|
|
|
|
static void Mch_PutBigButton (Act_Action_t NextAction,const char *Id,
|
|
long MchCod,const char *Icon,const char *Txt);
|
|
static void Mch_PutBigButtonHidden (const char *Icon);
|
|
static void Mch_PutBigButtonClose (void);
|
|
|
|
static void Mch_ShowWaitImage (const char *Txt);
|
|
|
|
/*****************************************************************************/
|
|
/*************** Set/Get match code of the match being played ****************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_SetMchCodBeingPlayed (long MchCod)
|
|
{
|
|
Mch_MchCodBeingPlayed = MchCod;
|
|
}
|
|
|
|
long Mch_GetMchCodBeingPlayed (void)
|
|
{
|
|
return Mch_MchCodBeingPlayed;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************************* Reset match *******************************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_ResetMatch (struct Mch_Match *Match)
|
|
{
|
|
Dat_StartEndTime_t StartEndTime;
|
|
|
|
/***** Initialize to empty match *****/
|
|
Match->MchCod = -1L;
|
|
Match->GamCod = -1L;
|
|
Match->UsrCod = -1L;
|
|
for (StartEndTime = (Dat_StartEndTime_t) 0;
|
|
StartEndTime <= (Dat_StartEndTime_t) (Dat_NUM_START_END_TIME - 1);
|
|
StartEndTime++)
|
|
Match->TimeUTC[StartEndTime] = (time_t) 0;
|
|
Match->Title[0] = '\0';
|
|
Match->Status.QstInd = 0;
|
|
Match->Status.QstCod = -1L;
|
|
Match->Status.QstStartTimeUTC = (time_t) 0;
|
|
Match->Status.Showing = Mch_SHOWING_DEFAULT;
|
|
Match->Status.Countdown = 0;
|
|
Match->Status.NumCols = 1;
|
|
Match->Status.ShowQstResults = false;
|
|
Match->Status.ShowUsrResults = false;
|
|
Match->Status.Playing = false;
|
|
Match->Status.NumPlayers = 0;
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
/************************* List the matches of a game ************************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_ListMatches (struct Gam_Games *Games,
|
|
bool PutFormNewMatch)
|
|
{
|
|
extern const char *Hlp_ASSESSMENT_Games_matches;
|
|
extern const char *Txt_Matches;
|
|
MYSQL_RES *mysql_res;
|
|
unsigned NumMatches;
|
|
|
|
/***** Get data of matches from database *****/
|
|
NumMatches = Mch_DB_GetMatchesInGame (&mysql_res,Games->Game.GamCod);
|
|
|
|
/***** Begin box *****/
|
|
Box_BoxBegin ("100%",Txt_Matches,
|
|
Mch_PutIconsInListOfMatches,Games,
|
|
Hlp_ASSESSMENT_Games_matches,Box_NOT_CLOSABLE);
|
|
|
|
/***** Select whether show only my groups or all groups *****/
|
|
switch (Gbl.Usrs.Me.Role.Logged)
|
|
{
|
|
case Rol_NET:
|
|
case Rol_TCH:
|
|
case Rol_SYS_ADM:
|
|
if (Gbl.Crs.Grps.NumGrps)
|
|
{
|
|
Set_BeginSettingsHead ();
|
|
Grp_ShowFormToSelWhichGrps (ActSeeOneGam,
|
|
Gam_PutPars,Games);
|
|
Set_EndSettingsHead ();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/***** Show the table with the matches *****/
|
|
if (NumMatches)
|
|
Mch_ListOneOrMoreMatches (Games,NumMatches,mysql_res);
|
|
|
|
/***** Free structure that stores the query result *****/
|
|
DB_FreeMySQLResult (&mysql_res);
|
|
|
|
/***** Put button to play a new match in this game *****/
|
|
switch (Gbl.Usrs.Me.Role.Logged)
|
|
{
|
|
case Rol_NET:
|
|
case Rol_TCH:
|
|
case Rol_SYS_ADM:
|
|
if (PutFormNewMatch)
|
|
Mch_PutFormNewMatch (&Games->Game); // Form to fill in data and start playing a new match
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/***** End box *****/
|
|
Box_BoxEnd ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************** Get match data using its code ************************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_GetMatchDataByCod (struct Mch_Match *Match)
|
|
{
|
|
MYSQL_RES *mysql_res;
|
|
|
|
/***** Get data of match from database *****/
|
|
if (Mch_DB_GetMatchDataByCod (&mysql_res,Match->MchCod)) // Match found...
|
|
/* Get match data from row */
|
|
Mch_GetMatchDataFromRow (mysql_res,Match);
|
|
else
|
|
/* Initialize to empty match */
|
|
Mch_ResetMatch (Match);
|
|
|
|
/***** Free structure that stores the query result *****/
|
|
DB_FreeMySQLResult (&mysql_res);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/****************** Put icons in list of matches of a game *******************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_PutIconsInListOfMatches (void *Games)
|
|
{
|
|
bool ICanEditMatches;
|
|
|
|
if (Games)
|
|
{
|
|
/***** Put icon to create a new match in current game *****/
|
|
ICanEditMatches = Mch_CheckIfICanEditMatches ();
|
|
if (ICanEditMatches)
|
|
Mch_PutIconToCreateNewMatch ((struct Gam_Games *) Games);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************* Put icon to create a new match ************************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_PutIconToCreateNewMatch (struct Gam_Games *Games)
|
|
{
|
|
Ico_PutContextualIconToAdd (ActReqNewMch,Mch_NEW_MATCH_SECTION_ID,
|
|
Gam_PutPars,Games);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*********************** List game matches for edition ***********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ListOneOrMoreMatches (struct Gam_Games *Games,
|
|
unsigned NumMatches,
|
|
MYSQL_RES *mysql_res)
|
|
{
|
|
unsigned NumMatch;
|
|
unsigned UniqueId;
|
|
struct Mch_Match Match;
|
|
char *Anchor;
|
|
bool ICanEditMatches = Mch_CheckIfICanEditMatches ();
|
|
|
|
/***** Trivial check *****/
|
|
if (!NumMatches)
|
|
return;
|
|
|
|
/***** Reset match *****/
|
|
Mch_ResetMatch (&Match);
|
|
|
|
/***** Begin table *****/
|
|
HTM_TABLE_BeginWidePadding (2);
|
|
|
|
/***** Write the heading *****/
|
|
Mch_ListOneOrMoreMatchesHeading (ICanEditMatches);
|
|
|
|
/***** Write rows *****/
|
|
for (NumMatch = 0, UniqueId = 1, The_ResetRowColor ();
|
|
NumMatch < NumMatches;
|
|
NumMatch++, UniqueId++, The_ChangeRowColor ())
|
|
{
|
|
/***** Get match data from row *****/
|
|
Mch_GetMatchDataFromRow (mysql_res,&Match);
|
|
|
|
if (Mch_CheckIfICanPlayThisMatchBasedOnGrps (&Match))
|
|
{
|
|
/***** Build anchor string *****/
|
|
if (asprintf (&Anchor,"mch_%ld",Match.MchCod) < 0)
|
|
Err_NotEnoughMemoryExit ();
|
|
|
|
/***** First row for this match with match data ****/
|
|
/* Begin first row */
|
|
HTM_TR_Begin (NULL);
|
|
|
|
/* Icons */
|
|
if (ICanEditMatches)
|
|
Mch_ListOneOrMoreMatchesIcons (Games,&Match,Anchor);
|
|
|
|
/* Match player */
|
|
Mch_ListOneOrMoreMatchesAuthor (&Match);
|
|
|
|
/* Start/end date/time */
|
|
Mch_ListOneOrMoreMatchesTimes (&Match,UniqueId);
|
|
|
|
/* Title and groups */
|
|
Mch_ListOneOrMoreMatchesTitleGrps (&Match,Anchor);
|
|
|
|
/* Number of players who have played the match */
|
|
Mch_ListOneOrMoreMatchesNumPlayers (&Match);
|
|
|
|
/* Match status */
|
|
Mch_ListOneOrMoreMatchesStatus (&Match,Games->Game.NumQsts);
|
|
|
|
/* Match result visible? */
|
|
Mch_ListOneOrMoreMatchesResult (Games,&Match);
|
|
|
|
/* End first row */
|
|
HTM_TR_End ();
|
|
|
|
/***** Second row for this match used for edition ****/
|
|
if (Gbl.Action.Act == ActEdiMch && // Editing...
|
|
Match.MchCod == Games->MchCod.Selected) // ...this match
|
|
/***** Check if I can edit this match *****/
|
|
if (Mch_CheckIfICanEditThisMatch (&Match))
|
|
{
|
|
/* Begin second row */
|
|
HTM_TR_Begin (NULL);
|
|
|
|
/* Form to edit match */
|
|
HTM_TD_Begin ("colspan=\"8\" class=\"LT %s\"",
|
|
The_GetColorRows ());
|
|
Mch_PutFormExistingMatch (Games,&Match,Anchor); // Form to fill in data and edit this match
|
|
HTM_TD_End ();
|
|
|
|
/* End second row */
|
|
HTM_TR_End ();
|
|
}
|
|
|
|
/***** Free anchor string *****/
|
|
free (Anchor);
|
|
}
|
|
}
|
|
|
|
/***** End table *****/
|
|
HTM_TABLE_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Put a column for match start and end times ****************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ListOneOrMoreMatchesHeading (bool ICanEditMatches)
|
|
{
|
|
extern const char *Txt_ROLES_SINGUL_Abc[Rol_NUM_ROLES][Usr_NUM_SEXS];
|
|
extern const char *Txt_START_END_TIME[Dat_NUM_START_END_TIME];
|
|
extern const char *Txt_Match;
|
|
extern const char *Txt_Players;
|
|
extern const char *Txt_Status;
|
|
extern const char *Txt_Results;
|
|
|
|
/***** Begin row *****/
|
|
HTM_TR_Begin (NULL);
|
|
|
|
/***** Column for icons *****/
|
|
if (ICanEditMatches)
|
|
HTM_TH_Empty (1);
|
|
|
|
/***** The rest of columns *****/
|
|
HTM_TH (Txt_ROLES_SINGUL_Abc[Rol_TCH][Usr_SEX_UNKNOWN],HTM_HEAD_LEFT );
|
|
HTM_TH (Txt_START_END_TIME[Gam_ORDER_BY_START_DATE] ,HTM_HEAD_LEFT );
|
|
HTM_TH (Txt_START_END_TIME[Gam_ORDER_BY_END_DATE ] ,HTM_HEAD_LEFT );
|
|
HTM_TH (Txt_Match ,HTM_HEAD_LEFT );
|
|
HTM_TH (Txt_Players ,HTM_HEAD_RIGHT );
|
|
HTM_TH (Txt_Status ,HTM_HEAD_CENTER);
|
|
HTM_TH (Txt_Results ,HTM_HEAD_CENTER);
|
|
|
|
/***** End row *****/
|
|
HTM_TR_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*********************** Check if I can edit matches *************************/
|
|
/*****************************************************************************/
|
|
|
|
static bool Mch_CheckIfICanEditMatches (void)
|
|
{
|
|
static const bool ICanEditMatches[Rol_NUM_ROLES] =
|
|
{
|
|
[Rol_NET ] = true,
|
|
[Rol_TCH ] = true,
|
|
[Rol_SYS_ADM] = true,
|
|
};
|
|
|
|
return ICanEditMatches[Gbl.Usrs.Me.Role.Logged];
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Check if I can edit (remove/resume) a match ***************/
|
|
/*****************************************************************************/
|
|
|
|
static bool Mch_CheckIfICanEditThisMatch (const struct Mch_Match *Match)
|
|
{
|
|
switch (Gbl.Usrs.Me.Role.Logged)
|
|
{
|
|
case Rol_NET:
|
|
return (Match->UsrCod == Gbl.Usrs.Me.UsrDat.UsrCod); // Only if I am the creator
|
|
case Rol_TCH:
|
|
case Rol_SYS_ADM:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*********** Check if visibility of match results can be changed *************/
|
|
/*****************************************************************************/
|
|
|
|
static bool Mch_CheckIfVisibilityOfResultsCanBeChanged (const struct Mch_Match *Match)
|
|
{
|
|
if (Match->Status.ShowUsrResults || // Results are currently visible
|
|
Match->Status.Showing == Mch_END) // Match has finished
|
|
if (Mch_CheckIfICanEditThisMatch (Match))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************************* Put a column for icons ****************************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ListOneOrMoreMatchesIcons (struct Gam_Games *Games,
|
|
const struct Mch_Match *Match,
|
|
const char *Anchor)
|
|
{
|
|
HTM_TD_Begin ("class=\"BT %s\"",The_GetColorRows ());
|
|
|
|
if (Mch_CheckIfICanEditThisMatch (Match))
|
|
{
|
|
Games->Game.GamCod = Match->GamCod;
|
|
Games->MchCod.Current = Match->MchCod;
|
|
|
|
/***** Put icon to remove the match *****/
|
|
Ico_PutContextualIconToRemove (ActReqRemMch,NULL,
|
|
Mch_PutParsEdit,Games);
|
|
|
|
/***** Put icon to edit the match *****/
|
|
Ico_PutContextualIconToEdit (ActEdiMch,Anchor,
|
|
Mch_PutParsEdit,Games);
|
|
}
|
|
else
|
|
Ico_PutIconRemovalNotAllowed ();
|
|
|
|
HTM_TD_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************* Put a column for teacher who created the match ****************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_ListOneOrMoreMatchesAuthor (const struct Mch_Match *Match)
|
|
{
|
|
/***** Match author (teacher) *****/
|
|
HTM_TD_Begin ("class=\"LT %s\"",The_GetColorRows ());
|
|
Usr_WriteAuthor1Line (Match->UsrCod,false);
|
|
HTM_TD_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Put a column for match start and end times ****************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_ListOneOrMoreMatchesTimes (const struct Mch_Match *Match,unsigned UniqueId)
|
|
{
|
|
Dat_StartEndTime_t StartEndTime;
|
|
char *Id;
|
|
|
|
for (StartEndTime = (Dat_StartEndTime_t) 0;
|
|
StartEndTime <= (Dat_StartEndTime_t) (Dat_NUM_START_END_TIME - 1);
|
|
StartEndTime++)
|
|
{
|
|
if (asprintf (&Id,"mch_time_%u_%u",(unsigned) StartEndTime,UniqueId) < 0)
|
|
Err_NotEnoughMemoryExit ();
|
|
HTM_TD_Begin ("id=\"%s\" class=\"LT %s_%s %s\"",
|
|
Id,
|
|
Match->Status.Showing == Mch_END ? "DATE_RED" :
|
|
"DATE_GREEN",
|
|
The_GetSuffix (),
|
|
The_GetColorRows ());
|
|
Dat_WriteLocalDateHMSFromUTC (Id,Match->TimeUTC[StartEndTime],
|
|
Gbl.Prefs.DateFormat,Dat_SEPARATOR_BREAK,
|
|
true,true,true,0x7);
|
|
HTM_TD_End ();
|
|
free (Id);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Put a column for match title and grous ********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ListOneOrMoreMatchesTitleGrps (const struct Mch_Match *Match,
|
|
const char *Anchor)
|
|
{
|
|
extern const char *Txt_Play;
|
|
extern const char *Txt_Resume;
|
|
|
|
HTM_TD_Begin ("class=\"LT %s\"",The_GetColorRows ());
|
|
HTM_ARTICLE_Begin (Anchor);
|
|
|
|
/***** Match title *****/
|
|
Frm_BeginForm (Gbl.Usrs.Me.Role.Logged == Rol_STD ? ActJoiMch :
|
|
ActResMch);
|
|
ParCod_PutPar (ParCod_Mch,Match->MchCod);
|
|
|
|
HTM_BUTTON_Submit_Begin (Gbl.Usrs.Me.Role.Logged == Rol_STD ? Txt_Play :
|
|
Txt_Resume,
|
|
"class=\"LT BT_LINK ASG_TITLE_%s\"",
|
|
The_GetSuffix ());
|
|
HTM_Txt (Match->Title);
|
|
HTM_BUTTON_End ();
|
|
|
|
Frm_EndForm ();
|
|
|
|
/***** Groups whose students can answer this match *****/
|
|
if (Gbl.Crs.Grps.NumGrps)
|
|
Mch_GetAndWriteNamesOfGrpsAssociatedToMatch (Match);
|
|
|
|
HTM_ARTICLE_End ();
|
|
HTM_TD_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************* Get and write the names of the groups of a match **************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_GetAndWriteNamesOfGrpsAssociatedToMatch (const struct Mch_Match *Match)
|
|
{
|
|
extern const char *Txt_Group;
|
|
extern const char *Txt_Groups;
|
|
extern const char *Txt_and;
|
|
extern const char *Txt_The_whole_course;
|
|
MYSQL_RES *mysql_res;
|
|
MYSQL_ROW row;
|
|
unsigned NumGrps;
|
|
unsigned NumGrp;
|
|
|
|
/***** Get groups associated to a match from database *****/
|
|
NumGrps = Mch_DB_GetGrpNamesAssociatedToMatch (&mysql_res,Match->MchCod);
|
|
|
|
HTM_DIV_Begin ("class=\"ASG_GRP_%s\"",The_GetSuffix ());
|
|
|
|
/***** Write heading *****/
|
|
HTM_TxtColonNBSP (NumGrps == 1 ? Txt_Group :
|
|
Txt_Groups);
|
|
|
|
/***** Write groups *****/
|
|
if (NumGrps) // Groups found...
|
|
{
|
|
/* Get and write the group types and names */
|
|
for (NumGrp = 0;
|
|
NumGrp < NumGrps;
|
|
NumGrp++)
|
|
{
|
|
/* Get next group */
|
|
row = mysql_fetch_row (mysql_res);
|
|
|
|
/* Write group type name and group name */
|
|
HTM_TxtF ("%s %s",row[0],row[1]);
|
|
|
|
if (NumGrps >= 2)
|
|
{
|
|
if (NumGrp == NumGrps - 2)
|
|
HTM_TxtF (" %s ",Txt_and);
|
|
if (NumGrps >= 3)
|
|
if (NumGrp < NumGrps - 2)
|
|
HTM_Txt (", ");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
HTM_TxtF ("%s %s",Txt_The_whole_course,Gbl.Hierarchy.Crs.ShrtName);
|
|
|
|
HTM_DIV_End ();
|
|
|
|
/***** Free structure that stores the query result *****/
|
|
DB_FreeMySQLResult (&mysql_res);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************* Put a column for number of players **********************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_ListOneOrMoreMatchesNumPlayers (const struct Mch_Match *Match)
|
|
{
|
|
/***** Number of players who have answered any question in the match ******/
|
|
HTM_TD_Begin ("class=\"RT DAT_%s %s\"",
|
|
The_GetSuffix (),The_GetColorRows ());
|
|
HTM_Unsigned (Mch_DB_GetNumUsrsWhoHavePlayedMch (Match->MchCod));
|
|
HTM_TD_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************** Put a column for match status ************************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ListOneOrMoreMatchesStatus (struct Mch_Match *Match,unsigned NumQsts)
|
|
{
|
|
HTM_TD_Begin ("class=\"CT DAT_%s %s\"",
|
|
The_GetSuffix (),The_GetColorRows ());
|
|
|
|
if (Match->Status.Showing != Mch_END) // Match not over
|
|
{
|
|
/* Current question index / total of questions */
|
|
HTM_DIV_Begin ("class=\"DAT_%s\"",The_GetSuffix ());
|
|
HTM_TxtF ("%u/%u",Match->Status.QstInd,NumQsts);
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/* Icon to join match or resume match */
|
|
Lay_PutContextualLinkOnlyIcon (Gbl.Usrs.Me.Role.Logged == Rol_STD ? ActJoiMch :
|
|
ActResMch,
|
|
NULL,
|
|
Mch_PutParMchCod,&Match->MchCod,
|
|
Match->Status.Showing == Mch_END ? "flag-checkered.svg" :
|
|
"play.svg",Ico_BLACK);
|
|
|
|
HTM_TD_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************** Write parameter with code of match *********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_PutParMchCod (void *MchCod)
|
|
{
|
|
if (MchCod)
|
|
ParCod_PutPar (ParCod_Mch,*((long *) MchCod));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/**************** Put a column for visibility of match result ****************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ListOneOrMoreMatchesResult (struct Gam_Games *Games,
|
|
const struct Mch_Match *Match)
|
|
{
|
|
static void (*Function[Rol_NUM_ROLES]) (struct Gam_Games *Games,
|
|
const struct Mch_Match *Match) =
|
|
{
|
|
[Rol_STD ] = Mch_ListOneOrMoreMatchesResultStd,
|
|
[Rol_NET ] = Mch_ListOneOrMoreMatchesResultTch,
|
|
[Rol_TCH ] = Mch_ListOneOrMoreMatchesResultTch,
|
|
[Rol_SYS_ADM] = Mch_ListOneOrMoreMatchesResultTch,
|
|
};
|
|
|
|
HTM_TD_Begin ("class=\"CT DAT_%s %s\"",
|
|
The_GetSuffix (),The_GetColorRows ());
|
|
|
|
if (Function[Gbl.Usrs.Me.Role.Logged])
|
|
Function[Gbl.Usrs.Me.Role.Logged] (Games,Match);
|
|
else
|
|
Err_WrongRoleExit ();
|
|
|
|
HTM_TD_End ();
|
|
}
|
|
|
|
static void Mch_ListOneOrMoreMatchesResultStd (struct Gam_Games *Games,
|
|
const struct Mch_Match *Match)
|
|
{
|
|
/***** Is match result visible or hidden? *****/
|
|
if (Match->Status.ShowUsrResults)
|
|
{
|
|
/* Result is visible by me */
|
|
Games->Game.GamCod = Match->GamCod;
|
|
Games->MchCod.Current = Match->MchCod;
|
|
Lay_PutContextualLinkOnlyIcon (ActSeeMyMchResMch,MchRes_RESULTS_BOX_ID,
|
|
Mch_PutParsEdit,Games,
|
|
"trophy.svg",Ico_BLACK);
|
|
}
|
|
else
|
|
/* Result is forbidden to me */
|
|
Ico_PutIconNotVisible ();
|
|
}
|
|
|
|
static void Mch_ListOneOrMoreMatchesResultTch (struct Gam_Games *Games,
|
|
const struct Mch_Match *Match)
|
|
{
|
|
extern const char *Txt_Visible_results;
|
|
extern const char *Txt_Hidden_results;
|
|
|
|
Games->Game.GamCod = Match->GamCod;
|
|
Games->MchCod.Current = Match->MchCod;
|
|
|
|
/***** Show match results *****/
|
|
if (Mch_CheckIfICanEditThisMatch (Match))
|
|
Lay_PutContextualLinkOnlyIcon (ActSeeUsrMchResMch,MchRes_RESULTS_BOX_ID,
|
|
Mch_PutParsEdit,Games,
|
|
"trophy.svg",Ico_BLACK);
|
|
|
|
/***** Check if visibility of session results can be changed *****/
|
|
if (Mch_CheckIfVisibilityOfResultsCanBeChanged (Match))
|
|
{
|
|
/* I can edit visibility */
|
|
if (Match->Status.ShowUsrResults)
|
|
Lay_PutContextualLinkOnlyIcon (ActChgVisResMchUsr,NULL,
|
|
Mch_PutParsEdit,Games,
|
|
"eye.svg",Ico_GREEN);
|
|
else
|
|
Lay_PutContextualLinkOnlyIcon (ActChgVisResMchUsr,NULL,
|
|
Mch_PutParsEdit,Games,
|
|
"eye-slash.svg",Ico_RED);
|
|
}
|
|
else
|
|
{
|
|
/* I can not edit visibility */
|
|
if (Match->Status.ShowUsrResults)
|
|
Ico_PutIconOff ("eye.svg" ,Ico_GREEN,Txt_Visible_results);
|
|
else
|
|
Ico_PutIconOff ("eye-slash.svg",Ico_RED ,Txt_Hidden_results );
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************** Toggle visibility of match results *********************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_ToggleVisResultsMchUsr (void)
|
|
{
|
|
struct Gam_Games Games;
|
|
struct Mch_Match Match;
|
|
|
|
/***** Reset games context *****/
|
|
Gam_ResetGames (&Games);
|
|
Gam_ResetGame (&Games.Game);
|
|
Mch_ResetMatch (&Match);
|
|
|
|
/***** Get and check parameters *****/
|
|
Mch_GetAndCheckPars (&Games,&Match);
|
|
|
|
/***** Check if visibility of match results can be changed *****/
|
|
if (!Mch_CheckIfVisibilityOfResultsCanBeChanged (&Match))
|
|
Err_NoPermissionExit ();
|
|
|
|
/***** Toggle visibility of match results *****/
|
|
Match.Status.ShowUsrResults = !Match.Status.ShowUsrResults;
|
|
Mch_DB_UpdateVisResultsMchUsr (Match.MchCod,Match.Status.ShowUsrResults);
|
|
|
|
/***** Show current game *****/
|
|
Gam_ShowOnlyOneGame (&Games,
|
|
false, // Do not list game questions
|
|
false); // Do not put form to start new match
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************** Get game data from a database row **********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_GetMatchDataFromRow (MYSQL_RES *mysql_res,
|
|
struct Mch_Match *Match)
|
|
{
|
|
MYSQL_ROW row;
|
|
Dat_StartEndTime_t StartEndTime;
|
|
long LongNum;
|
|
|
|
/***** Get next row from result *****/
|
|
row = mysql_fetch_row (mysql_res);
|
|
/*
|
|
row[ 0] MchCod
|
|
row[ 1] GamCod
|
|
row[ 2] UsrCod
|
|
row[ 3] UNIX_TIMESTAMP(StartTime)
|
|
row[ 4] UNIX_TIMESTAMP(EndTime)
|
|
row[ 5] Title
|
|
*/
|
|
/***** Get match data *****/
|
|
/* Code of the match (row[0]) */
|
|
if ((Match->MchCod = Str_ConvertStrCodToLongCod (row[0])) <= 0)
|
|
Err_WrongMatchExit ();
|
|
|
|
/* Code of the game (row[1]) */
|
|
if ((Match->GamCod = Str_ConvertStrCodToLongCod (row[1])) <= 0)
|
|
Err_WrongGameExit ();
|
|
|
|
/* Get match teacher (row[2]) */
|
|
Match->UsrCod = Str_ConvertStrCodToLongCod (row[2]);
|
|
|
|
/* Get start/end times (row[3], row[4] hold start/end UTC times) */
|
|
for (StartEndTime = (Dat_StartEndTime_t) 0;
|
|
StartEndTime <= (Dat_StartEndTime_t) (Dat_NUM_START_END_TIME - 1);
|
|
StartEndTime++)
|
|
Match->TimeUTC[StartEndTime] = Dat_GetUNIXTimeFromStr (row[3 + StartEndTime]);
|
|
|
|
/* Get the title of the match (row[5]) */
|
|
if (row[5])
|
|
Str_Copy (Match->Title,row[5],sizeof (Match->Title) - 1);
|
|
else
|
|
Match->Title[0] = '\0';
|
|
|
|
/***** Get current match status *****/
|
|
/*
|
|
row[ 6] QstInd
|
|
row[ 7] QstCod
|
|
row[ 8] Showing
|
|
row[ 9] Countdown
|
|
row[10] NumCols
|
|
row[11] ShowQstResults
|
|
row[12] ShowUsrResults
|
|
*/
|
|
/* Current question index (row[6]) */
|
|
Match->Status.QstInd = Str_ConvertStrToUnsigned (row[6]);
|
|
|
|
/* Current question code (row[7]) */
|
|
Match->Status.QstCod = Str_ConvertStrCodToLongCod (row[7]);
|
|
|
|
/* Get what to show (stem, answers, results) (row(8)) */
|
|
Match->Status.Showing = Mch_DB_GetShowingFromStr (row[8]);
|
|
|
|
/* Get countdown (row[9]) */
|
|
Match->Status.Countdown = Str_ConvertStrCodToLongCod (row[9]);
|
|
|
|
/* Get number of columns (row[10]) */
|
|
LongNum = Str_ConvertStrCodToLongCod (row[10]);
|
|
Match->Status.NumCols = (LongNum <= 1 ) ? 1 :
|
|
((LongNum >= Mch_MAX_COLS) ? Mch_MAX_COLS :
|
|
(unsigned) LongNum);
|
|
|
|
/* Get whether to show question results or not (row(11)) */
|
|
Match->Status.ShowQstResults = (row[11][0] == 'Y');
|
|
|
|
/* Get whether to show user results or not (row(12)) */
|
|
Match->Status.ShowUsrResults = (row[12][0] == 'Y');
|
|
|
|
/***** Get whether the match is being played or not *****/
|
|
if (Match->Status.Showing == Mch_END) // Match over
|
|
Match->Status.Playing = false;
|
|
else // Match not over
|
|
Match->Status.Playing = Mch_DB_CheckIfMatchIsBeingPlayed (Match->MchCod);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************** Request the removal of a match (game instance) ***************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_ReqRemMatch (void)
|
|
{
|
|
extern const char *Txt_Do_you_really_want_to_remove_the_match_X;
|
|
struct Gam_Games Games;
|
|
struct Mch_Match Match;
|
|
|
|
/***** Reset games context *****/
|
|
Gam_ResetGames (&Games);
|
|
Gam_ResetGame (&Games.Game);
|
|
Mch_ResetMatch (&Match);
|
|
|
|
/***** Get and check parameters *****/
|
|
Mch_GetAndCheckPars (&Games,&Match);
|
|
|
|
/***** Show question and button to remove question *****/
|
|
Ale_ShowAlertRemove (ActRemMch,NULL,
|
|
Mch_PutParsEdit,&Games,
|
|
Txt_Do_you_really_want_to_remove_the_match_X,
|
|
Match.Title);
|
|
|
|
/***** Show current game *****/
|
|
Gam_ShowOnlyOneGame (&Games,
|
|
false, // Do not list game questions
|
|
false); // Do not put form to start new match
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************** Remove a match (game instance) ***********************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_RemoveMatch (void)
|
|
{
|
|
extern const char *Txt_Match_X_removed;
|
|
struct Gam_Games Games;
|
|
struct Mch_Match Match;
|
|
|
|
/***** Reset games context *****/
|
|
Gam_ResetGames (&Games);
|
|
Gam_ResetGame (&Games.Game);
|
|
Mch_ResetMatch (&Match);
|
|
|
|
/***** Get and check parameters *****/
|
|
Mch_GetAndCheckPars (&Games,&Match);
|
|
|
|
/***** Check if I can remove this match *****/
|
|
if (!Mch_CheckIfICanEditThisMatch (&Match))
|
|
Err_NoPermissionExit ();
|
|
|
|
/***** Remove the match from all database tables *****/
|
|
Mch_DB_RemoveMatchFromAllTables (Match.MchCod);
|
|
|
|
/***** Write message *****/
|
|
Ale_ShowAlert (Ale_SUCCESS,Txt_Match_X_removed,
|
|
Match.Title);
|
|
|
|
/***** Show current game *****/
|
|
Gam_ShowOnlyOneGame (&Games,
|
|
false, // Do not list game questions
|
|
false); // Do not put form to start new match
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************** Remove match in game from all tables *******************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_RemoveMatchesInGameFromAllTables (long GamCod)
|
|
{
|
|
/***** Remove matches from secondary tables *****/
|
|
Mch_DB_RemoveMatchesInGameFromOtherTable (GamCod,"mch_players");
|
|
Mch_DB_RemoveMatchesInGameFromOtherTable (GamCod,"mch_playing");
|
|
Mch_DB_RemoveMatchesInGameFromOtherTable (GamCod,"mch_results");
|
|
Mch_DB_RemoveMatchesInGameFromOtherTable (GamCod,"mch_answers");
|
|
Mch_DB_RemoveMatchesInGameFromOtherTable (GamCod,"mch_times");
|
|
Mch_DB_RemoveMatchesInGameFromOtherTable (GamCod,"mch_groups");
|
|
Mch_DB_RemoveMatchesInGameFromOtherTable (GamCod,"mch_indexes");
|
|
|
|
/***** Remove matches in game from main table *****/
|
|
Mch_DB_RemoveMatchesInGameFromMainTable (GamCod);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************* Remove match in course from all tables ******************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_RemoveMatchesInCourseFromAllTables (long CrsCod)
|
|
{
|
|
/***** Remove matches from secondary tables *****/
|
|
Mch_DB_RemoveMatchesInCrsFromOtherTable (CrsCod,"mch_players");
|
|
Mch_DB_RemoveMatchesInCrsFromOtherTable (CrsCod,"mch_playing");
|
|
Mch_DB_RemoveMatchesInCrsFromOtherTable (CrsCod,"mch_results");
|
|
Mch_DB_RemoveMatchesInCrsFromOtherTable (CrsCod,"mch_answers");
|
|
Mch_DB_RemoveMatchesInCrsFromOtherTable (CrsCod,"mch_times");
|
|
Mch_DB_RemoveMatchesInCrsFromOtherTable (CrsCod,"mch_groups");
|
|
Mch_DB_RemoveMatchesInCrsFromOtherTable (CrsCod,"mch_indexes");
|
|
|
|
/***** Remove matches in course from main table *****/
|
|
Mch_DB_RemoveMatchesInCrsFromMainTable (CrsCod);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*************** Remove matches made by user in all courses ******************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_RemoveMatchesMadeByUsrInAllCrss (long UsrCod)
|
|
{
|
|
/***** Remove student from secondary tables *****/
|
|
Mch_DB_RemoveMatchesMadeByUsrFromTable (UsrCod,"mch_players");
|
|
Mch_DB_RemoveMatchesMadeByUsrFromTable (UsrCod,"mch_results");
|
|
Mch_DB_RemoveMatchesMadeByUsrFromTable (UsrCod,"mch_answers");
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Remove matches made by user in a course *******************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_RemoveMatchesMadeByUsrInCrs (long UsrCod,long CrsCod)
|
|
{
|
|
/***** Remove student from secondary tables *****/
|
|
Mch_DB_RemoveMatchesMadeByUsrInCrsFromTable (UsrCod,CrsCod,"mch_players");
|
|
Mch_DB_RemoveMatchesMadeByUsrInCrsFromTable (UsrCod,CrsCod,"mch_results");
|
|
Mch_DB_RemoveMatchesMadeByUsrInCrsFromTable (UsrCod,CrsCod,"mch_answers");
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************************ Edit a match (game instance) ***********************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_EditMatch (void)
|
|
{
|
|
struct Gam_Games Games;
|
|
struct Mch_Match Match;
|
|
|
|
/***** Reset games context *****/
|
|
Gam_ResetGames (&Games);
|
|
Gam_ResetGame (&Games.Game);
|
|
Mch_ResetMatch (&Match);
|
|
|
|
/***** Get and check parameters *****/
|
|
Mch_GetAndCheckPars (&Games,&Match);
|
|
|
|
/***** Check if I can edit this match *****/
|
|
if (!Mch_CheckIfICanEditThisMatch (&Match))
|
|
Err_NoPermissionExit ();
|
|
|
|
/***** Show current game *****/
|
|
Gam_ShowOnlyOneGame (&Games,
|
|
false, // Do not list game questions
|
|
false); // Do not put form to start new match
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*********************** Params used to edit a match *************************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_PutParsEdit (void *Games)
|
|
{
|
|
if (Games)
|
|
{
|
|
Gam_PutPars (Games);
|
|
ParCod_PutPar (ParCod_Mch,((struct Gam_Games *) Games)->MchCod.Current);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************************** Get and check parameters *************************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_GetAndCheckPars (struct Gam_Games *Games,
|
|
struct Mch_Match *Match)
|
|
{
|
|
/***** Get parameters *****/
|
|
/* Get parameters of game */
|
|
if ((Games->Game.GamCod = Gam_GetPars (Games)) <= 0)
|
|
Err_WrongGameExit ();
|
|
Grp_GetParWhichGroups ();
|
|
Gam_GetGameDataByCod (&Games->Game);
|
|
|
|
/* Get match code */
|
|
Match->MchCod = ParCod_GetAndCheckPar (ParCod_Mch);
|
|
Mch_GetMatchDataByCod (Match);
|
|
|
|
/***** Ensure parameters are correct *****/
|
|
if (Games->Game.GamCod != Match->GamCod)
|
|
Err_WrongGameExit ();
|
|
if (Games->Game.CrsCod != Gbl.Hierarchy.Crs.CrsCod)
|
|
Err_WrongGameExit ();
|
|
|
|
/***** Initialize context *****/
|
|
Games->MchCod.Current =
|
|
Games->MchCod.Selected = Match->MchCod;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************* Put a form to change and existing match *****************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_PutFormExistingMatch (struct Gam_Games *Games,
|
|
const struct Mch_Match *Match,
|
|
const char *Anchor)
|
|
{
|
|
extern const char *Hlp_ASSESSMENT_Games_matches;
|
|
extern const char *Txt_Title;
|
|
extern const char *Txt_Save_changes;
|
|
|
|
/***** Begin form *****/
|
|
Frm_BeginFormAnchor (ActChgMch,Anchor);
|
|
Mch_PutParsEdit (Games);
|
|
|
|
/***** Begin box and table *****/
|
|
Box_BoxTableBegin (NULL,Match->Title,
|
|
NULL,NULL,
|
|
Hlp_ASSESSMENT_Games_matches,Box_CLOSABLE,2);
|
|
|
|
/***** Match title *****/
|
|
HTM_TR_Begin (NULL);
|
|
|
|
/* Label */
|
|
Frm_LabelColumn ("RT","Title",Txt_Title);
|
|
|
|
/* Data */
|
|
HTM_TD_Begin ("class=\"LT\"");
|
|
HTM_INPUT_TEXT ("Title",Mch_MAX_CHARS_TITLE,Match->Title,
|
|
HTM_DONT_SUBMIT_ON_CHANGE,
|
|
"id=\"Title\" size=\"45\" class=\"INPUT_%s\""
|
|
" required=\"required\"",
|
|
The_GetSuffix ());
|
|
HTM_TD_End ();
|
|
|
|
HTM_TR_End ();
|
|
|
|
/***** Groups *****/
|
|
Mch_ShowLstGrpsToEditMatch (Match->MchCod);
|
|
|
|
/***** End table *****/
|
|
HTM_TABLE_End ();
|
|
|
|
/***** Put button to submit the form *****/
|
|
Btn_PutConfirmButton (Txt_Save_changes);
|
|
|
|
/***** End box *****/
|
|
Box_BoxEnd ();
|
|
|
|
/***** End form *****/
|
|
Frm_EndForm ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************** Put a form to create a new match *********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_PutFormNewMatch (const struct Gam_Game *Game)
|
|
{
|
|
extern const char *Hlp_ASSESSMENT_Games_matches;
|
|
extern const char *Txt_Match;
|
|
extern const char *Txt_Title;
|
|
extern const char *Txt_Play;
|
|
|
|
/***** Begin section for a new match *****/
|
|
HTM_SECTION_Begin (Mch_NEW_MATCH_SECTION_ID);
|
|
|
|
/***** Begin form *****/
|
|
Frm_BeginForm (ActNewMch);
|
|
ParCod_PutPar (ParCod_Gam,Game->GamCod);
|
|
Gam_PutParQstInd (0); // Start by first question in game
|
|
|
|
/***** Begin box and table *****/
|
|
Box_BoxTableBegin (NULL,Txt_Match,
|
|
NULL,NULL,
|
|
Hlp_ASSESSMENT_Games_matches,Box_NOT_CLOSABLE,2);
|
|
|
|
/***** Match title *****/
|
|
HTM_TR_Begin (NULL);
|
|
|
|
/* Label */
|
|
Frm_LabelColumn ("RT","Title",Txt_Title);
|
|
|
|
/* Data */
|
|
HTM_TD_Begin ("class=\"LT\"");
|
|
HTM_INPUT_TEXT ("Title",Mch_MAX_CHARS_TITLE,Game->Title,
|
|
HTM_DONT_SUBMIT_ON_CHANGE,
|
|
"id=\"Title\" size=\"45\" class=\"INPUT_%s\""
|
|
" required=\"required\"",
|
|
The_GetSuffix ());
|
|
HTM_TD_End ();
|
|
|
|
HTM_TR_End ();
|
|
|
|
/***** Groups *****/
|
|
Mch_ShowLstGrpsToEditMatch (-1L);
|
|
|
|
/***** End table *****/
|
|
HTM_TABLE_End ();
|
|
|
|
/***** Put icon to submit the form *****/
|
|
HTM_INPUT_IMAGE (Cfg_URL_ICON_PUBLIC,"play.svg",
|
|
Txt_Play,
|
|
"class=\"CONTEXT_OPT ICO_HIGHLIGHT ICO64x64\"");
|
|
|
|
/***** End box *****/
|
|
Box_BoxEnd ();
|
|
|
|
/***** End form *****/
|
|
Frm_EndForm ();
|
|
|
|
/***** End section for a new match *****/
|
|
HTM_SECTION_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Show list of groups to create a new match *****************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ShowLstGrpsToEditMatch (long MchCod)
|
|
{
|
|
extern const char *Txt_Groups;
|
|
extern const char *Txt_The_whole_course;
|
|
unsigned NumGrpTyp;
|
|
|
|
/***** Get list of groups types and groups in this course *****/
|
|
Grp_GetListGrpTypesAndGrpsInThisCrs (Grp_ONLY_GROUP_TYPES_WITH_GROUPS);
|
|
|
|
if (Gbl.Crs.Grps.GrpTypes.NumGrpTypes)
|
|
{
|
|
/***** Begin box and table *****/
|
|
HTM_TR_Begin (NULL);
|
|
|
|
HTM_TD_Begin ("class=\"RT FORM_IN_%s\"",The_GetSuffix ());
|
|
HTM_TxtColon (Txt_Groups);
|
|
HTM_TD_End ();
|
|
|
|
HTM_TD_Begin ("class=\"LT\"");
|
|
|
|
Box_BoxTableBegin ("95%",NULL,
|
|
NULL,NULL,
|
|
NULL,Box_NOT_CLOSABLE,0);
|
|
|
|
/***** First row: checkbox to select the whole course *****/
|
|
HTM_TR_Begin (NULL);
|
|
|
|
HTM_TD_Begin ("colspan=\"7\" class=\"LM DAT_%s\"",
|
|
The_GetSuffix ());
|
|
HTM_LABEL_Begin (NULL);
|
|
HTM_INPUT_CHECKBOX ("WholeCrs",HTM_DONT_SUBMIT_ON_CHANGE,
|
|
"id=\"WholeCrs\" value=\"Y\"%s"
|
|
" onclick=\"uncheckChildren(this,'GrpCods')\"",
|
|
Grp_DB_CheckIfAssociatedToGrps ("mch_groups",
|
|
"MchCod",
|
|
MchCod) ? "" :
|
|
" checked=\"checked\"");
|
|
HTM_TxtF ("%s %s",Txt_The_whole_course,Gbl.Hierarchy.Crs.ShrtName);
|
|
HTM_LABEL_End ();
|
|
HTM_TD_End ();
|
|
|
|
HTM_TR_End ();
|
|
|
|
/***** List the groups for each group type *****/
|
|
for (NumGrpTyp = 0;
|
|
NumGrpTyp < Gbl.Crs.Grps.GrpTypes.NumGrpTypes;
|
|
NumGrpTyp++)
|
|
if (Gbl.Crs.Grps.GrpTypes.LstGrpTypes[NumGrpTyp].NumGrps)
|
|
Grp_ListGrpsToEditAsgAttSvyEvtMch (&Gbl.Crs.Grps.GrpTypes.LstGrpTypes[NumGrpTyp],
|
|
Grp_MATCH,MchCod);
|
|
|
|
/***** End table and box *****/
|
|
Box_BoxTableEnd ();
|
|
|
|
HTM_TD_End ();
|
|
|
|
HTM_TR_End ();
|
|
}
|
|
|
|
/***** Free list of groups types and groups in this course *****/
|
|
Grp_FreeListGrpTypesAndGrps ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************* Create a new match (by a teacher) *********************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_CreateNewMatch (void)
|
|
{
|
|
long GamCod;
|
|
char Title[Mch_MAX_BYTES_TITLE + 1];
|
|
|
|
/***** Get form parameters *****/
|
|
/* Get match code */
|
|
GamCod = ParCod_GetAndCheckPar (ParCod_Gam);
|
|
|
|
/* Get match title */
|
|
Par_GetParText ("Title",Title,Mch_MAX_BYTES_TITLE);
|
|
|
|
/* Get groups for this match */
|
|
Grp_GetParCodsSeveralGrps ();
|
|
|
|
/***** Create a new match *****/
|
|
Mch_SetMchCodBeingPlayed (Mch_CreateMatch (GamCod,Title));
|
|
|
|
/***** Free memory for list of selected groups *****/
|
|
Grp_FreeListCodSelectedGrps ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************************ Change a match (by a teacher) **********************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_ChangeMatch (void)
|
|
{
|
|
struct Gam_Games Games;
|
|
struct Mch_Match Match;
|
|
|
|
/***** Reset games context *****/
|
|
Gam_ResetGames (&Games);
|
|
Gam_ResetGame (&Games.Game);
|
|
Mch_ResetMatch (&Match);
|
|
|
|
/***** Get and check parameters *****/
|
|
Mch_GetAndCheckPars (&Games,&Match);
|
|
|
|
/***** Check if I can update this match *****/
|
|
if (!Mch_CheckIfICanEditThisMatch (&Match))
|
|
Err_NoPermissionExit ();
|
|
|
|
/***** Get match title and groups *****/
|
|
/* Get match title */
|
|
Par_GetParText ("Title",Match.Title,Mch_MAX_BYTES_TITLE);
|
|
|
|
/* Get groups for this match */
|
|
Grp_GetParCodsSeveralGrps ();
|
|
|
|
/***** Update match *****/
|
|
Mch_UpdateMatchTitleAndGrps (&Match);
|
|
|
|
/***** Free memory for list of selected groups *****/
|
|
Grp_FreeListCodSelectedGrps ();
|
|
|
|
/***** Show current game *****/
|
|
Gam_ShowOnlyOneGame (&Games,
|
|
false, // Do not list game questions
|
|
false); // Do not put form to start new match
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*************** Update title and groups of an existing match ****************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_UpdateMatchTitleAndGrps (const struct Mch_Match *Match)
|
|
{
|
|
/***** Update match title into database *****/
|
|
Mch_DB_UpdateMatchTitle (Match->MchCod,Match->Title);
|
|
|
|
/***** Update groups associated to the match *****/
|
|
Mch_DB_RemoveMatchFromTable (Match->MchCod,"mch_groups"); // Remove all groups associated to this match
|
|
if (Gbl.Crs.Grps.LstGrpsSel.NumGrps)
|
|
Mch_CreateGrps (Match->MchCod); // Associate selected groups
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******* Show button to actually start / resume a match (by a teacher) *******/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_ResumeMatch (void)
|
|
{
|
|
struct Mch_Match Match;
|
|
|
|
/***** Reset match *****/
|
|
Mch_ResetMatch (&Match);
|
|
|
|
/***** Remove old players.
|
|
This function must be called by a teacher
|
|
before getting match status. *****/
|
|
Mch_DB_RemoveOldPlaying ();
|
|
|
|
/***** Get data of the match from database *****/
|
|
Match.MchCod = Mch_GetMchCodBeingPlayed ();
|
|
Mch_GetMatchDataByCod (&Match);
|
|
|
|
/***** Check if I have permission to resume match *****/
|
|
if (!Mch_CheckIfICanEditThisMatch (&Match))
|
|
Err_NoPermissionExit ();
|
|
|
|
/***** Update match status in database *****/
|
|
Mch_UpdateMatchStatusInDB (&Match);
|
|
|
|
/***** Show current match status *****/
|
|
HTM_DIV_Begin ("id=\"match\" class=\"MCH_CONT\"");
|
|
Mch_ShowMatchStatusForTch (&Match);
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************** Create a new match in a game *************************/
|
|
/*****************************************************************************/
|
|
|
|
static long Mch_CreateMatch (long GamCod,char Title[Mch_MAX_BYTES_TITLE + 1])
|
|
{
|
|
long MchCod;
|
|
|
|
/***** Insert this new match into database *****/
|
|
MchCod = Mch_DB_CreateMatch (GamCod,Title);
|
|
|
|
/***** Create indexes for answers *****/
|
|
Mch_CreateIndexes (GamCod,MchCod);
|
|
|
|
/***** Create groups associated to the match *****/
|
|
if (Gbl.Crs.Grps.LstGrpsSel.NumGrps)
|
|
Mch_CreateGrps (MchCod);
|
|
|
|
return MchCod;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*********************** Create indexes for a match **************************/
|
|
/*****************************************************************************/
|
|
/* Everytime a new match is created,
|
|
the answers of each shufflable question are shuffled.
|
|
The shuffling is stored in a table of indexes
|
|
that will be read when showing a match */
|
|
|
|
static void Mch_CreateIndexes (long GamCod,long MchCod)
|
|
{
|
|
MYSQL_RES *mysql_res;
|
|
MYSQL_ROW row;
|
|
unsigned NumQsts;
|
|
unsigned NumQst;
|
|
struct Qst_Question Question;
|
|
unsigned QstInd;
|
|
|
|
/***** Get questions of the game *****/
|
|
NumQsts = Gam_DB_GetGameQuestionsFull (&mysql_res,GamCod);
|
|
|
|
/***** For each question in game... *****/
|
|
for (NumQst = 0;
|
|
NumQst < NumQsts;
|
|
NumQst++)
|
|
{
|
|
/***** Create test question *****/
|
|
Qst_QstConstructor (&Question);
|
|
|
|
/***** Get question data *****/
|
|
row = mysql_fetch_row (mysql_res);
|
|
/*
|
|
gam_questions.QstCod row[0]
|
|
gam_questions.QstInd row[1]
|
|
tst_questions.AnsType row[2]
|
|
tst_questions.Shuffle row[3]
|
|
*/
|
|
|
|
/* Get question code (row[0]) */
|
|
if ((Question.QstCod = Str_ConvertStrCodToLongCod (row[0])) <= 0)
|
|
Err_WrongQuestionExit ();
|
|
|
|
/* Get question index (row[1]) */
|
|
QstInd = Str_ConvertStrToUnsigned (row[1]);
|
|
|
|
/* Get answer type (row[2]) */
|
|
Question.Answer.Type = Qst_ConvertFromStrAnsTypDBToAnsTyp (row[2]);
|
|
if (Question.Answer.Type != Qst_ANS_UNIQUE_CHOICE)
|
|
Err_WrongAnswerExit ();
|
|
|
|
/* Get shuffle (row[3]) */
|
|
Question.Answer.Shuffle = (row[3][0] == 'Y');
|
|
|
|
/***** Reorder answer *****/
|
|
Mch_ReorderAnswer (MchCod,QstInd,&Question);
|
|
|
|
/***** Destroy test question *****/
|
|
Qst_QstDestructor (&Question);
|
|
}
|
|
|
|
/***** Free structure that stores the query result *****/
|
|
DB_FreeMySQLResult (&mysql_res);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************* Reorder answers of a match question *********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ReorderAnswer (long MchCod,unsigned QstInd,
|
|
const struct Qst_Question *Question)
|
|
{
|
|
MYSQL_RES *mysql_res;
|
|
MYSQL_ROW row;
|
|
unsigned NumAnss;
|
|
unsigned NumAns;
|
|
unsigned AnsInd;
|
|
char StrOneAnswer[Cns_MAX_DECIMAL_DIGITS_UINT + 1];
|
|
char StrAnswersOneQst[Qst_MAX_BYTES_ANSWERS_ONE_QST + 1];
|
|
|
|
/***** Initialize list of answers to empty string *****/
|
|
StrAnswersOneQst[0] = '\0';
|
|
|
|
/***** Get suffled/not-shuffled answers indexes of question *****/
|
|
NumAnss = Qst_DB_GetShuffledAnswersIndexes (&mysql_res,Question);
|
|
|
|
/***** For each answer in question... *****/
|
|
for (NumAns = 0;
|
|
NumAns < NumAnss;
|
|
NumAns++)
|
|
{
|
|
row = mysql_fetch_row (mysql_res);
|
|
|
|
/* Get answer index (row[0]) */
|
|
AnsInd = Str_ConvertStrToUnsigned (row[0]);
|
|
snprintf (StrOneAnswer,sizeof (StrOneAnswer),"%u",AnsInd);
|
|
|
|
/* Concatenate answer index to list of answers */
|
|
if (NumAns)
|
|
Str_Concat (StrAnswersOneQst,",",sizeof (StrAnswersOneQst) - 1);
|
|
Str_Concat (StrAnswersOneQst,StrOneAnswer,sizeof (StrAnswersOneQst) - 1);
|
|
}
|
|
|
|
/***** Free structure that stores the query result *****/
|
|
DB_FreeMySQLResult (&mysql_res);
|
|
|
|
/***** Create entry for this question in table of match indexes *****/
|
|
Mch_DB_CreateQstIndexes (MchCod,QstInd,StrAnswersOneQst);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Get indexes for a question from database ******************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_GetIndexes (long MchCod,unsigned QstInd,
|
|
unsigned Indexes[Qst_MAX_OPTIONS_PER_QUESTION])
|
|
{
|
|
char StrIndexesOneQst[Qst_MAX_BYTES_INDEXES_ONE_QST + 1];
|
|
|
|
/***** Get indexes for a question from database *****/
|
|
Mch_DB_GetIndexes (MchCod,QstInd,StrIndexesOneQst);
|
|
if (!StrIndexesOneQst[0])
|
|
Err_WrongAnswerIndexExit ();
|
|
|
|
/***** Get indexes from string *****/
|
|
TstPrn_GetIndexesFromStr (StrIndexesOneQst,Indexes);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************* Create groups associated to a match *********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_CreateGrps (long MchCod)
|
|
{
|
|
unsigned NumGrpSel;
|
|
|
|
for (NumGrpSel = 0;
|
|
NumGrpSel < Gbl.Crs.Grps.LstGrpsSel.NumGrps;
|
|
NumGrpSel++)
|
|
Mch_DB_AssociateGroupToMatch (MchCod,
|
|
Gbl.Crs.Grps.LstGrpsSel.GrpCods[NumGrpSel]);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Insert/update a game match being played *******************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_UpdateMatchStatusInDB (const struct Mch_Match *Match)
|
|
{
|
|
/***** Update match status in database *****/
|
|
Mch_DB_UpdateMatchStatus (Match);
|
|
|
|
/***** Update match as being/not-being played */
|
|
if (Match->Status.Playing) // Match is being played
|
|
/* Update match as being played */
|
|
Mch_DB_UpdateMatchAsBeingPlayed (Match->MchCod);
|
|
else // Match is paused, not being played
|
|
/* Update match as not being played */
|
|
Mch_DB_RemoveMatchFromBeingPlayed (Match->MchCod);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********** Update elapsed time in current question (by a teacher) ***********/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_UpdateElapsedTimeInQuestion (const struct Mch_Match *Match)
|
|
{
|
|
/***** Update elapsed time in current question in database *****/
|
|
if (Match->Status.Playing && // Match is being played
|
|
Match->Status.Showing != Mch_START &&
|
|
Match->Status.Showing != Mch_END)
|
|
Mch_DB_UpdateElapsedTimeInQuestion (Match->MchCod,Match->Status.QstInd);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************* Get elapsed time in a match question ********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_GetElapsedTimeInQuestion (const struct Mch_Match *Match,
|
|
struct Dat_Time *Time)
|
|
{
|
|
MYSQL_RES *mysql_res;
|
|
unsigned NumRows;
|
|
|
|
/***** Query database *****/
|
|
NumRows = Mch_DB_GetElapsedTimeInQuestion (&mysql_res,
|
|
Match->MchCod,
|
|
Match->Status.QstInd);
|
|
|
|
/***** Get elapsed time from query result *****/
|
|
Mch_GetElapsedTime (NumRows,mysql_res,Time);
|
|
|
|
/***** Free structure that stores the query result *****/
|
|
DB_FreeMySQLResult (&mysql_res);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*********************** Get elapsed time in a match *************************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_GetElapsedTimeInMatch (const struct Mch_Match *Match,
|
|
struct Dat_Time *Time)
|
|
{
|
|
MYSQL_RES *mysql_res;
|
|
unsigned NumRows;
|
|
|
|
/***** Query database *****/
|
|
NumRows = Mch_DB_GetElapsedTimeInMatch (&mysql_res,Match->MchCod);
|
|
|
|
/***** Get elapsed time from query result *****/
|
|
Mch_GetElapsedTime (NumRows,mysql_res,Time);
|
|
|
|
/***** Free structure that stores the query result *****/
|
|
DB_FreeMySQLResult (&mysql_res);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*********************** Get elapsed time in a match *************************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_GetElapsedTime (unsigned NumRows,MYSQL_RES *mysql_res,
|
|
struct Dat_Time *Time)
|
|
{
|
|
MYSQL_ROW row;
|
|
bool ElapsedTimeGotFromDB = false;
|
|
|
|
/***** Get time from H...H:MM:SS string *****/
|
|
if (NumRows)
|
|
{
|
|
row = mysql_fetch_row (mysql_res);
|
|
|
|
if (row[0])
|
|
/* Get the elapsed time (row[0]) */
|
|
if (sscanf (row[0],"%u:%02u:%02u",
|
|
&Time->Hour,
|
|
&Time->Minute,
|
|
&Time->Second) == 3)
|
|
ElapsedTimeGotFromDB = true;
|
|
}
|
|
|
|
/***** Initialize time to default value (0) *****/
|
|
if (!ElapsedTimeGotFromDB)
|
|
Time->Hour =
|
|
Time->Minute =
|
|
Time->Second = 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************** Play/pause match (by a teacher) **********************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_PlayPauseMatch (void)
|
|
{
|
|
struct Mch_Match Match;
|
|
|
|
/***** Reset match *****/
|
|
Mch_ResetMatch (&Match);
|
|
|
|
/***** Remove old players.
|
|
This function must be called by a teacher
|
|
before getting match status. *****/
|
|
Mch_DB_RemoveOldPlaying ();
|
|
|
|
/***** Get data of the match from database *****/
|
|
Match.MchCod = Mch_GetMchCodBeingPlayed ();
|
|
Mch_GetMatchDataByCod (&Match);
|
|
|
|
/***** Update status *****/
|
|
if (Match.Status.Playing) // Match is being played ==> pause it
|
|
Match.Status.Playing = false; // Pause match
|
|
else // Match is paused, not being played ==> play it
|
|
/* If not over, update status */
|
|
if (Match.Status.Showing != Mch_END) // Match not over
|
|
Match.Status.Playing = true; // Start/resume match
|
|
|
|
/***** Update match status in database *****/
|
|
Mch_UpdateMatchStatusInDB (&Match);
|
|
|
|
/***** Show current match status *****/
|
|
HTM_DIV_Begin ("id=\"match\" class=\"MCH_CONT\"");
|
|
Mch_ShowMatchStatusForTch (&Match);
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******* Change number of columns in answers of a match (by a teacher) *******/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_ChangeNumColsMch (void)
|
|
{
|
|
struct Mch_Match Match;
|
|
|
|
/***** Reset match *****/
|
|
Mch_ResetMatch (&Match);
|
|
|
|
/***** Remove old players.
|
|
This function must be called by a teacher
|
|
before getting match status. *****/
|
|
Mch_DB_RemoveOldPlaying ();
|
|
|
|
/***** Get data of the match from database *****/
|
|
Match.MchCod = Mch_GetMchCodBeingPlayed ();
|
|
Mch_GetMatchDataByCod (&Match);
|
|
|
|
/***** Get number of columns *****/
|
|
Match.Status.NumCols = (unsigned)
|
|
Par_GetParUnsignedLong ("NumCols",
|
|
1,
|
|
Mch_MAX_COLS,
|
|
Mch_NUM_COLS_DEFAULT);
|
|
|
|
/***** Update match status in database *****/
|
|
Mch_UpdateMatchStatusInDB (&Match);
|
|
|
|
/***** Show current match status *****/
|
|
HTM_DIV_Begin ("id=\"match\" class=\"MCH_CONT\"");
|
|
Mch_ShowMatchStatusForTch (&Match);
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********* Toggle the display of results in a match (by a teacher) ***********/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_ToggleVisResultsMchQst (void)
|
|
{
|
|
struct Mch_Match Match;
|
|
|
|
/***** Reset match *****/
|
|
Mch_ResetMatch (&Match);
|
|
|
|
/***** Remove old players.
|
|
This function must be called by a teacher
|
|
before getting match status. *****/
|
|
Mch_DB_RemoveOldPlaying ();
|
|
|
|
/***** Get data of the match from database *****/
|
|
Match.MchCod = Mch_GetMchCodBeingPlayed ();
|
|
Mch_GetMatchDataByCod (&Match);
|
|
|
|
/***** Update status *****/
|
|
Match.Status.ShowQstResults = !Match.Status.ShowQstResults; // Toggle display
|
|
if (Match.Status.Showing == Mch_RESULTS &&
|
|
!Match.Status.ShowQstResults)
|
|
Match.Status.Showing = Mch_ANSWERS; // Hide results
|
|
|
|
/***** Update match status in database *****/
|
|
Mch_UpdateMatchStatusInDB (&Match);
|
|
|
|
/***** Show current match status *****/
|
|
HTM_DIV_Begin ("id=\"match\" class=\"MCH_CONT\"");
|
|
Mch_ShowMatchStatusForTch (&Match);
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************* Show previous question in a match (by a teacher) **************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_BackMatch (void)
|
|
{
|
|
struct Mch_Match Match;
|
|
|
|
/***** Reset match *****/
|
|
Mch_ResetMatch (&Match);
|
|
|
|
/***** Remove old players.
|
|
This function must be called by a teacher
|
|
before getting match status. *****/
|
|
Mch_DB_RemoveOldPlaying ();
|
|
|
|
/***** Get data of the match from database *****/
|
|
Match.MchCod = Mch_GetMchCodBeingPlayed ();
|
|
Mch_GetMatchDataByCod (&Match);
|
|
|
|
/***** Update status *****/
|
|
Mch_SetMatchStatusToPrev (&Match);
|
|
|
|
/***** Update match status in database *****/
|
|
Mch_UpdateMatchStatusInDB (&Match);
|
|
|
|
/***** Show current match status *****/
|
|
HTM_DIV_Begin ("id=\"match\" class=\"MCH_CONT\"");
|
|
Mch_ShowMatchStatusForTch (&Match);
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*************** Show next question in a match (by a teacher) ****************/
|
|
/*****************************************************************************/
|
|
|
|
void Mch_ForwardMatch (void)
|
|
{
|
|
struct Mch_Match Match;
|
|
|
|
/***** Reset match *****/
|
|
Mch_ResetMatch (&Match);
|
|
|
|
/***** Remove old players.
|
|
This function must be called by a teacher
|
|
before getting match status. *****/
|
|
Mch_DB_RemoveOldPlaying ();
|
|
|
|
/***** Get data of the match from database *****/
|
|
Match.MchCod = Mch_GetMchCodBeingPlayed ();
|
|
Mch_GetMatchDataByCod (&Match);
|
|
|
|
/***** Update status *****/
|
|
Mch_SetMatchStatusToNext (&Match);
|
|
|
|
/***** Update match status in database *****/
|
|
Mch_UpdateMatchStatusInDB (&Match);
|
|
|
|
/***** Show current match status *****/
|
|
HTM_DIV_Begin ("id=\"match\" class=\"MCH_CONT\"");
|
|
Mch_ShowMatchStatusForTch (&Match);
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************** Set match status to previous (backward) status ***************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_SetMatchStatusToPrev (struct Mch_Match *Match)
|
|
{
|
|
/***** What to show *****/
|
|
switch (Match->Status.Showing)
|
|
{
|
|
case Mch_START:
|
|
Mch_SetMatchStatusToStart (Match);
|
|
break;
|
|
case Mch_STEM:
|
|
case Mch_END:
|
|
Mch_SetMatchStatusToPrevQst (Match);
|
|
break;
|
|
case Mch_ANSWERS:
|
|
Match->Status.Showing = Mch_STEM;
|
|
break;
|
|
case Mch_RESULTS:
|
|
Match->Status.Showing = Mch_ANSWERS;
|
|
break;
|
|
}
|
|
Match->Status.Countdown = -1L; // No countdown
|
|
|
|
/***** Force showing results to false when match is not at the end *****/
|
|
Match->Status.ShowUsrResults = false; // Force results to be hidden
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/****************** Set match status to previous question ********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_SetMatchStatusToPrevQst (struct Mch_Match *Match)
|
|
{
|
|
/***** Get index of the previous question *****/
|
|
Match->Status.QstInd = Gam_DB_GetPrevQuestionIndexInGame (Match->GamCod,
|
|
Match->Status.QstInd);
|
|
if (Match->Status.QstInd) // Start of questions not reached
|
|
{
|
|
Match->Status.QstCod = Gam_DB_GetQstCodFromQstInd (Match->GamCod,
|
|
Match->Status.QstInd);
|
|
Match->Status.Showing = Match->Status.ShowQstResults ? Mch_RESULTS :
|
|
Mch_ANSWERS;
|
|
}
|
|
else // Start of questions reached
|
|
Mch_SetMatchStatusToStart (Match);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************************ Set match status to start **************************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_SetMatchStatusToStart (struct Mch_Match *Match)
|
|
{
|
|
Match->Status.QstInd = 0; // Before first question
|
|
Match->Status.QstCod = -1L;
|
|
Match->Status.Playing = false;
|
|
Match->Status.Showing = Mch_START;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/**************** Set match status to next (forward) status ******************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_SetMatchStatusToNext (struct Mch_Match *Match)
|
|
{
|
|
/***** What to show *****/
|
|
switch (Match->Status.Showing)
|
|
{
|
|
case Mch_START:
|
|
Mch_SetMatchStatusToNextQst (Match);
|
|
break;
|
|
case Mch_STEM:
|
|
Match->Status.Showing = Mch_ANSWERS;
|
|
break;
|
|
case Mch_ANSWERS:
|
|
if (Match->Status.ShowQstResults)
|
|
Match->Status.Showing = Mch_RESULTS;
|
|
else
|
|
Mch_SetMatchStatusToNextQst (Match);
|
|
break;
|
|
case Mch_RESULTS:
|
|
Mch_SetMatchStatusToNextQst (Match);
|
|
break;
|
|
case Mch_END:
|
|
Mch_SetMatchStatusToEnd (Match);
|
|
break;
|
|
}
|
|
Match->Status.Countdown = -1L; // No countdown
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/****************** Set match status to next question ************************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_SetMatchStatusToNextQst (struct Mch_Match *Match)
|
|
{
|
|
/***** Get index of the next question *****/
|
|
Match->Status.QstInd = Gam_DB_GetNextQuestionIndexInGame (Match->GamCod,
|
|
Match->Status.QstInd);
|
|
|
|
/***** Get question code *****/
|
|
if (Match->Status.QstInd < Gam_AFTER_LAST_QUESTION) // End of questions not reached
|
|
{
|
|
Match->Status.QstCod = Gam_DB_GetQstCodFromQstInd (Match->GamCod,
|
|
Match->Status.QstInd);
|
|
Match->Status.Showing = Mch_STEM;
|
|
}
|
|
else // End of questions reached
|
|
Mch_SetMatchStatusToEnd (Match);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************************* Set match status to end ***************************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_SetMatchStatusToEnd (struct Mch_Match *Match)
|
|
{
|
|
Match->Status.QstInd = Gam_AFTER_LAST_QUESTION; // After last question
|
|
Match->Status.QstCod = -1L;
|
|
Match->Status.Playing = false;
|
|
Match->Status.Showing = Mch_END;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******* Show current match status (number, question, answers, button) *******/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ShowMatchStatusForTch (struct Mch_Match *Match)
|
|
{
|
|
/***** Left column *****/
|
|
Mch_ShowLeftColumnTch (Match);
|
|
|
|
/***** Right column *****/
|
|
Mch_ShowRightColumnTch (Match);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************ Show current question being played for a student ***************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ShowMatchStatusForStd (struct Mch_Match *Match,Mch_Update_t Update)
|
|
{
|
|
struct Mch_UsrAnswer UsrAnswer;
|
|
|
|
/***** Can I play this match? *****/
|
|
if (!Mch_CheckIfICanPlayThisMatchBasedOnGrps (Match))
|
|
Err_NoPermissionExit ();
|
|
|
|
/***** Get student's answer to this question
|
|
(<0 ==> no answer) *****/
|
|
Mch_GetQstAnsFromDB (Match->MchCod,
|
|
Gbl.Usrs.Me.UsrDat.UsrCod,
|
|
Match->Status.QstInd,
|
|
&UsrAnswer);
|
|
|
|
/***** Left column *****/
|
|
Mch_ShowLeftColumnStd (Match,&UsrAnswer);
|
|
|
|
/***** Right column *****/
|
|
Mch_ShowRightColumnStd (Match,&UsrAnswer,Update);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************ Check if I belong to any of the groups of a match **************/
|
|
/*****************************************************************************/
|
|
|
|
bool Mch_CheckIfICanPlayThisMatchBasedOnGrps (const struct Mch_Match *Match)
|
|
{
|
|
switch (Gbl.Usrs.Me.Role.Logged)
|
|
{
|
|
case Rol_STD:
|
|
/***** Check if I belong to any of the groups
|
|
associated to the match *****/
|
|
return Mch_DB_CheckIfICanPlayThisMatchBasedOnGrps (Match->MchCod);
|
|
case Rol_NET:
|
|
/***** Only if I am the creator *****/
|
|
return (Match->UsrCod == Gbl.Usrs.Me.UsrDat.UsrCod);
|
|
case Rol_TCH:
|
|
case Rol_SYS_ADM:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*********** Show left column when playing a match (as a teacher) ************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ShowLeftColumnTch (struct Mch_Match *Match)
|
|
{
|
|
/***** Begin left container *****/
|
|
HTM_DIV_Begin ("class=\"MCH_LEFT_TCH\"");
|
|
|
|
/***** Refreshable part *****/
|
|
HTM_DIV_Begin ("id=\"match_left\" class=\"MCH_REFRESHABLE_TEACHER\"");
|
|
Mch_ShowRefreshablePartTch (Match);
|
|
HTM_DIV_End ();
|
|
|
|
/***** Put forms to start countdown *****/
|
|
Mch_PutFormsCountdown (Match);
|
|
|
|
/***** Buttons *****/
|
|
Mch_PutMatchControlButtons (Match);
|
|
|
|
/***** Put forms to choice which projects to show *****/
|
|
Set_BeginSettingsHead ();
|
|
Mch_ShowFormColumns (Match);
|
|
Set_EndSettingsHead ();
|
|
|
|
/***** Write button to request viewing results *****/
|
|
Mch_PutCheckboxResult (Match);
|
|
|
|
/***** End left container *****/
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Show left refreshable part for teachers *******************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ShowRefreshablePartTch (struct Mch_Match *Match)
|
|
{
|
|
/***** Write elapsed time in match *****/
|
|
Mch_WriteElapsedTimeInMch (Match);
|
|
|
|
/***** Write number of question *****/
|
|
Mch_ShowNumQstInMch (Match);
|
|
|
|
/***** Write elapsed time in question *****/
|
|
Mch_WriteElapsedTimeInQst (Match);
|
|
|
|
/***** Number of users who have responded this question *****/
|
|
Mch_WriteNumRespondersQst (Match);
|
|
|
|
/***** Write hourglass *****/
|
|
Mch_PutCountdownAndHourglassIcon (Match);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************** Write elapsed time in current match ********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_WriteElapsedTimeInMch (struct Mch_Match *Match)
|
|
{
|
|
struct Dat_Time Time;
|
|
|
|
HTM_DIV_Begin ("class=\"MCH_TOP CT\"");
|
|
|
|
/***** Get elapsed time in match *****/
|
|
Mch_GetElapsedTimeInMatch (Match,&Time);
|
|
|
|
/***** Write elapsed time in hh:mm'ss" format *****/
|
|
Dat_WriteHoursMinutesSeconds (&Time);
|
|
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/****************** Write elapsed time in current question *******************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_WriteElapsedTimeInQst (struct Mch_Match *Match)
|
|
{
|
|
struct Dat_Time Time;
|
|
|
|
HTM_DIV_Begin ("class=\"MCH_TIME_QST\"");
|
|
|
|
switch (Match->Status.Showing)
|
|
{
|
|
case Mch_START:
|
|
case Mch_END:
|
|
HTM_Hyphen (); // Do not write elapsed time
|
|
break;
|
|
default:
|
|
Mch_GetElapsedTimeInQuestion (Match,&Time);
|
|
Dat_WriteHoursMinutesSeconds (&Time);
|
|
break;
|
|
}
|
|
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*************** Write number of responders to a match question **************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_WriteNumRespondersQst (struct Mch_Match *Match)
|
|
{
|
|
extern const char *Txt_MATCH_respond;
|
|
|
|
/***** Begin block *****/
|
|
HTM_DIV_Begin ("class=\"MCH_NUM_ANSWERERS\"");
|
|
|
|
HTM_Txt (Txt_MATCH_respond);
|
|
HTM_BR ();
|
|
HTM_STRONG_Begin ();
|
|
|
|
/***** Write number of responders *****/
|
|
switch (Match->Status.Showing)
|
|
{
|
|
case Mch_START:
|
|
case Mch_END:
|
|
HTM_Hyphen (); // Do not write number of responders
|
|
break;
|
|
default:
|
|
HTM_Unsigned (Mch_DB_GetNumUsrsWhoAnsweredQst (Match->MchCod,
|
|
Match->Status.QstInd));
|
|
break;
|
|
}
|
|
|
|
/***** Write number of players *****/
|
|
if (Match->Status.Playing) // Match is being played
|
|
{
|
|
/* Get current number of players */
|
|
Match->Status.NumPlayers = Mch_DB_GetNumPlayers (Match->MchCod);
|
|
|
|
/* Show current number of players */
|
|
HTM_TxtF ("/%u",Match->Status.NumPlayers);
|
|
}
|
|
|
|
HTM_STRONG_End ();
|
|
|
|
/***** End block *****/
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*************** Write current countdown and hourglass icon ******************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_PutCountdownAndHourglassIcon (struct Mch_Match *Match)
|
|
{
|
|
extern const char *Txt_Countdown;
|
|
const char *Class;
|
|
const char *Icon;
|
|
|
|
/***** Set hourglass icon depending on countdown *****/
|
|
if (Match->Status.Showing == Mch_END) // Match over
|
|
{
|
|
Class = "BT_LINK_OFF MCH_BUTTON_HIDDEN MCH_GREEN";
|
|
Icon = "fa-hourglass-start";
|
|
}
|
|
else if (Match->Status.Countdown < 0) // No countdown
|
|
{
|
|
Class = "BT_LINK_OFF MCH_BUTTON_OFF MCH_GREEN";
|
|
Icon = "fa-hourglass-start";
|
|
}
|
|
else if (Match->Status.Countdown > Mch_COUNTDOWN_SECONDS_MEDIUM) // Countdown in progress
|
|
{
|
|
Class = "BT_LINK_OFF MCH_BUTTON_OFF MCH_LIMEGREEN";
|
|
Icon = "fa-hourglass-start";
|
|
}
|
|
else if (Match->Status.Countdown > Mch_COUNTDOWN_SECONDS_SMALL) // Countdown in progress
|
|
{
|
|
Class = "BT_LINK_OFF MCH_BUTTON_OFF MCH_YELLOW";
|
|
Icon = "fa-hourglass-half";
|
|
}
|
|
else // Countdown about to end
|
|
{
|
|
Class = "BT_LINK_OFF MCH_BUTTON_OFF MCH_RED";
|
|
Icon = "fa-hourglass-end";
|
|
}
|
|
|
|
/***** Write countdown and put hourglass icon *****/
|
|
HTM_DIV_Begin ("class=\"MCH_SHOW_HOURGLASS\"");
|
|
HTM_DIV_Begin ("class=\"MCH_BIGBUTTON_CONT\"");
|
|
HTM_BUTTON_Begin (Txt_Countdown,"class=\"%s\"",Class);
|
|
|
|
/* Countdown */
|
|
if (Match->Status.Countdown > 0)
|
|
HTM_TxtF (" %02ld″",Match->Status.Countdown);
|
|
else
|
|
HTM_NBSP ();
|
|
HTM_BR ();
|
|
|
|
/* Icon */
|
|
HTM_TxtF ("<i class=\"fas %s\"></i>",Icon);
|
|
|
|
HTM_BUTTON_End ();
|
|
HTM_DIV_End ();
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******************** Put all forms to start countdowns **********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_PutFormsCountdown (struct Mch_Match *Match)
|
|
{
|
|
/***** Begin container *****/
|
|
HTM_DIV_Begin ("class=\"MCH_SHOW_HOURGLASS\"");
|
|
|
|
/***** Put forms to start countdown *****/
|
|
Mch_PutFormCountdown (Match,-1 ,"MCH_GREEN" );
|
|
Mch_PutFormCountdown (Match,Mch_COUNTDOWN_SECONDS_LARGE ,"MCH_LIMEGREEN");
|
|
Mch_PutFormCountdown (Match,Mch_COUNTDOWN_SECONDS_MEDIUM,"MCH_YELLOW" );
|
|
Mch_PutFormCountdown (Match,Mch_COUNTDOWN_SECONDS_SMALL ,"MCH_RED" );
|
|
|
|
/***** End container *****/
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/****** Put a form to start a countdown with a given number of seconds *******/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_PutFormCountdown (struct Mch_Match *Match,long Seconds,const char *Color)
|
|
{
|
|
extern const char *Txt_Countdown;
|
|
char *OnSubmit;
|
|
bool PutForm = Match->Status.Showing != Mch_END;
|
|
|
|
if (PutForm)
|
|
{
|
|
/***** Begin form *****/
|
|
if (asprintf (&OnSubmit,"updateMatchTch('match_left',"
|
|
"'act=%ld&ses=%s&MchCod=%ld&Countdown=%ld');"
|
|
" return false;", // return false is necessary to not submit form
|
|
Act_GetActCod (ActMchCntDwn),Gbl.Session.Id,
|
|
Match->MchCod,Seconds) < 0)
|
|
Err_NotEnoughMemoryExit ();
|
|
Frm_BeginFormOnSubmit (ActUnk,OnSubmit);
|
|
}
|
|
|
|
/***** Put icon *****/
|
|
HTM_DIV_Begin ("class=\"MCH_SMALLBUTTON_CONT %s\"",Color);
|
|
|
|
HTM_BUTTON_Submit_Begin (PutForm ? Txt_Countdown :
|
|
NULL,
|
|
PutForm ? "class=\"BT_LINK MCH_BUTTON_ON\"" :
|
|
"class=\"BT_LINK_OFF MCH_BUTTON_HIDDEN\"");
|
|
|
|
HTM_NBSP ();
|
|
if (Seconds >= 0)
|
|
HTM_TxtF ("%ld″",Seconds);
|
|
else
|
|
{
|
|
HTM_Txt ("∞");
|
|
HTM_NBSP ();
|
|
}
|
|
|
|
HTM_BUTTON_End ();
|
|
|
|
HTM_DIV_End ();
|
|
|
|
/***** End form *****/
|
|
if (PutForm)
|
|
{
|
|
Frm_EndForm ();
|
|
free (OnSubmit);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********** Show right column when playing a match (as a teacher) ************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ShowRightColumnTch (const struct Mch_Match *Match)
|
|
{
|
|
/***** Begin right container *****/
|
|
HTM_DIV_Begin ("class=\"MCH_RIGHT_TCH\"");
|
|
|
|
/***** Top row: match title *****/
|
|
Mch_ShowMatchTitleTch (Match);
|
|
|
|
/***** Bottom row: current question and possible answers *****/
|
|
if (Match->Status.Showing == Mch_END) // Match over
|
|
Mch_ShowMatchScore (Match);
|
|
else // Match not over
|
|
Mch_ShowQuestionAndAnswersTch (Match);
|
|
|
|
/***** End right container *****/
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*********** Show left column when playing a match (as a student) ************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ShowLeftColumnStd (const struct Mch_Match *Match,
|
|
const struct Mch_UsrAnswer *UsrAnswer)
|
|
{
|
|
bool Answered = UsrAnswer->NumOpt >= 0;
|
|
|
|
/***** Begin left container *****/
|
|
HTM_DIV_Begin ("class=\"MCH_LEFT_STD\"");
|
|
|
|
/***** Top *****/
|
|
HTM_DIV_Begin ("class=\"MCH_TOP CT\"");
|
|
HTM_DIV_End ();
|
|
|
|
/***** Write number of question *****/
|
|
Mch_ShowNumQstInMch (Match);
|
|
|
|
switch (Match->Status.Showing)
|
|
{
|
|
case Mch_START:
|
|
case Mch_END:
|
|
break;
|
|
default:
|
|
/***** Write whether question is answered or not *****/
|
|
Mch_PutIfAnswered (Match,Answered);
|
|
|
|
if (Match->Status.Playing && // Match is being played
|
|
Match->Status.Showing == Mch_ANSWERS && // Teacher's screen is showing question answers
|
|
Answered) // I have answered this question
|
|
/***** Put icon to remove my answet *****/
|
|
Mch_PutIconToRemoveMyAnswer (Match);
|
|
break;
|
|
}
|
|
|
|
/***** End left container *****/
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********** Show right column when playing a match (as a student) ************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ShowRightColumnStd (struct Mch_Match *Match,
|
|
const struct Mch_UsrAnswer *UsrAnswer,
|
|
Mch_Update_t Update)
|
|
{
|
|
extern const char *Txt_Please_wait_;
|
|
|
|
/***** Begin right container *****/
|
|
HTM_DIV_Begin ("class=\"MCH_RIGHT_STD\"");
|
|
|
|
/***** Top row *****/
|
|
Mch_ShowMatchTitleStd (Match);
|
|
|
|
/***** Bottom row *****/
|
|
if (Match->Status.Playing) // Match is being played
|
|
{
|
|
if (Match->Status.Showing == Mch_END) // Match over
|
|
Mch_ShowWaitImage (Txt_Please_wait_);
|
|
else // Match not over
|
|
{
|
|
HTM_DIV_Begin ("class=\"MCH_BOTTOM\"");
|
|
|
|
/***** Update players ******/
|
|
if (Mch_RegisterMeAsPlayerInMatch (Match))
|
|
{
|
|
if (Match->Status.Showing == Mch_ANSWERS) // Teacher's screen is showing question answers
|
|
/* Show current question and possible answers */
|
|
Mch_ShowQuestionAndAnswersStd (Match,UsrAnswer,Update);
|
|
}
|
|
else
|
|
Ale_ShowAlert (Ale_ERROR,"You can not join this match.");
|
|
|
|
HTM_DIV_End ();
|
|
}
|
|
}
|
|
else // Match is not being played
|
|
Mch_ShowWaitImage (Txt_Please_wait_);
|
|
|
|
/***** End right container *****/
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************* Show number of question in game ***********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ShowNumQstInMch (const struct Mch_Match *Match)
|
|
{
|
|
extern const char *Txt_MATCH_Start;
|
|
extern const char *Txt_MATCH_End;
|
|
unsigned NumQsts = Gam_DB_GetNumQstsGame (Match->GamCod);
|
|
|
|
HTM_DIV_Begin ("class=\"MCH_NUM_QST\"");
|
|
switch (Match->Status.Showing)
|
|
{
|
|
case Mch_START: // Not started
|
|
HTM_Txt (Txt_MATCH_Start);
|
|
break;
|
|
case Mch_END: // Match over
|
|
HTM_Txt (Txt_MATCH_End);
|
|
break;
|
|
default:
|
|
HTM_TxtF ("%u/%u",Match->Status.QstInd,NumQsts);
|
|
break;
|
|
}
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/********************** Put buttons to control a match ***********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_PutMatchControlButtons (const struct Mch_Match *Match)
|
|
{
|
|
extern const char *Txt_Go_back;
|
|
extern const char *Txt_Go_forward;
|
|
extern const char *Txt_Pause;
|
|
extern const char *Txt_Start;
|
|
extern const char *Txt_Resume;
|
|
|
|
/***** Begin buttons container *****/
|
|
HTM_DIV_Begin ("class=\"MCH_BUTTONS_CONT\"");
|
|
|
|
/***** Left button *****/
|
|
HTM_DIV_Begin ("class=\"MCH_BUTTON_LEFT_CONT\"");
|
|
switch (Match->Status.Showing)
|
|
{
|
|
case Mch_START:
|
|
/* Put button to close browser tab */
|
|
Mch_PutBigButtonClose ();
|
|
break;
|
|
default:
|
|
/* Put button to go back */
|
|
Mch_PutBigButton (ActBckMch,"backward",Match->MchCod,
|
|
Mch_ICON_PREVIOUS,Txt_Go_back);
|
|
break;
|
|
}
|
|
HTM_DIV_End ();
|
|
|
|
/***** Center button *****/
|
|
HTM_DIV_Begin ("class=\"MCH_BUTTON_CENTER_CONT\"");
|
|
if (Match->Status.Playing) // Match is being played
|
|
/* Put button to pause match */
|
|
Mch_PutBigButton (ActPlyPauMch,"play_pause",Match->MchCod,
|
|
Mch_ICON_PAUSE,Txt_Pause);
|
|
else // Match is paused, not being played
|
|
{
|
|
switch (Match->Status.Showing)
|
|
{
|
|
case Mch_START: // Match just started, before first question
|
|
/* Put button to start playing match */
|
|
Mch_PutBigButton (ActPlyPauMch,"play_pause",Match->MchCod,
|
|
Mch_ICON_PLAY,Txt_Start);
|
|
break;
|
|
case Mch_END: // Match over
|
|
/* Put disabled button to play match */
|
|
Mch_PutBigButtonHidden (Mch_ICON_PLAY);
|
|
break;
|
|
default:
|
|
/* Put button to resume match */
|
|
Mch_PutBigButton (ActPlyPauMch,"play_pause",Match->MchCod,
|
|
Mch_ICON_PLAY,Txt_Resume);
|
|
}
|
|
}
|
|
HTM_DIV_End ();
|
|
|
|
/***** Right button *****/
|
|
HTM_DIV_Begin ("class=\"MCH_BUTTON_RIGHT_CONT\"");
|
|
if (Match->Status.Showing == Mch_END) // Match over
|
|
/* Put button to close browser tab */
|
|
Mch_PutBigButtonClose ();
|
|
else // Match not over
|
|
/* Put button to show answers */
|
|
Mch_PutBigButton (ActFwdMch,"forward",Match->MchCod,
|
|
Mch_ICON_NEXT,Txt_Go_forward);
|
|
HTM_DIV_End ();
|
|
|
|
/***** End buttons container *****/
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/** Show form to choice whether to show answers in one column or two columns */
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ShowFormColumns (const struct Mch_Match *Match)
|
|
{
|
|
extern const char *Txt_column;
|
|
extern const char *Txt_columns;
|
|
char *Title;
|
|
unsigned NumCols;
|
|
static const char *NumColsIcon[1 + Mch_MAX_COLS] =
|
|
{
|
|
"", // Not used
|
|
"1col.png", // 1 column
|
|
"2col.png", // 2 columns
|
|
"3col.png", // 3 columns
|
|
"4col.png", // 4 columns
|
|
};
|
|
|
|
/***** Begin selector *****/
|
|
Set_BeginOneSettingSelector ();
|
|
|
|
for (NumCols = 1;
|
|
NumCols <= Mch_MAX_COLS;
|
|
NumCols++)
|
|
{
|
|
/* Begin container for this option */
|
|
HTM_DIV_Begin ("class=\"%s\"",
|
|
(Match->Status.NumCols == NumCols) ? "MCH_NUM_COL_ON" :
|
|
"MCH_NUM_COL_OFF");
|
|
|
|
/* Begin form */
|
|
Frm_BeginForm (ActChgNumColMch);
|
|
ParCod_PutPar (ParCod_Mch,Match->MchCod); // Current match being played
|
|
Mch_PutParNumCols (NumCols); // Number of columns
|
|
|
|
/* Number of columns */
|
|
if (asprintf (&Title,"%u %s",NumCols,
|
|
NumCols == 1 ? Txt_column :
|
|
Txt_columns) < 0)
|
|
Err_NotEnoughMemoryExit ();
|
|
Ico_PutSettingIconLink (NumColsIcon[NumCols],Ico_BLACK,Title);
|
|
free (Title);
|
|
|
|
/* End form */
|
|
Frm_EndForm ();
|
|
|
|
/* End container for this option */
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/***** End selector *****/
|
|
Set_EndOneSettingSelector ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******** Write parameter with number of columns in answers of match *********/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_PutParNumCols (unsigned NumCols) // Number of columns
|
|
{
|
|
Par_PutParUnsigned (NULL,"NumCols",NumCols);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Put checkbox to select if show results ********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_PutCheckboxResult (const struct Mch_Match *Match)
|
|
{
|
|
extern const char *Txt_View_results;
|
|
|
|
/***** Begin container *****/
|
|
HTM_DIV_Begin ("class=\"MCH_SHOW_RESULTS DAT_%s\"",
|
|
The_GetSuffix ());
|
|
|
|
/***** Begin form *****/
|
|
Frm_BeginForm (ActChgVisResMchQst);
|
|
ParCod_PutPar (ParCod_Mch,Match->MchCod); // Current match being played
|
|
|
|
/***** Put icon with link *****/
|
|
HTM_BUTTON_Submit_Begin (Txt_View_results,
|
|
"class=\"BT_LINK ICO_HIGHLIGHT\"");
|
|
HTM_TxtF ("<i class=\"%s\"></i>",
|
|
Match->Status.ShowQstResults ? "fas fa-toggle-on" :
|
|
"fas fa-toggle-off");
|
|
HTM_TxtF (" %s",Txt_View_results);
|
|
HTM_BUTTON_End ();
|
|
|
|
/***** End form *****/
|
|
Frm_EndForm ();
|
|
|
|
/***** End container *****/
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Put checkbox to select if show results ********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_PutIfAnswered (const struct Mch_Match *Match,bool Answered)
|
|
{
|
|
extern const char *Txt_View_my_answer;
|
|
extern const char *Txt_MATCH_QUESTION_Answered;
|
|
extern const char *Txt_MATCH_QUESTION_Unanswered;
|
|
|
|
/***** Begin container *****/
|
|
HTM_DIV_Begin ("class=\"MCH_SHOW_ANSWERED\"");
|
|
|
|
/***** Put icon with link *****/
|
|
if (Match->Status.Playing && // Match is being played
|
|
Match->Status.Showing == Mch_ANSWERS && // Teacher's screen is showing question answers
|
|
Answered) // I have answered this question
|
|
{
|
|
/* Begin form */
|
|
Frm_BeginForm (ActSeeMchAnsQstStd);
|
|
ParCod_PutPar (ParCod_Mch,Match->MchCod); // Current match being played
|
|
|
|
HTM_BUTTON_Submit_Begin (Txt_View_my_answer,
|
|
"class=\"BT_LINK DAT_SMALL_GREEN_%s\""
|
|
" onmousedown=\"this.form.submit();return false;\"",
|
|
The_GetSuffix ());
|
|
HTM_TxtF ("<i class=\"%s\"></i>","fas fa-check-circle");
|
|
HTM_TxtF (" %s",Txt_MATCH_QUESTION_Answered);
|
|
HTM_BUTTON_End ();
|
|
|
|
/* End form */
|
|
Frm_EndForm ();
|
|
}
|
|
else
|
|
{
|
|
HTM_DIV_Begin ("class=\"%s_%s\"",Answered ? "DAT_SMALL_GREEN" :
|
|
"DAT_SMALL_RED",
|
|
The_GetSuffix ());
|
|
HTM_TxtF ("<i class=\"%s\" title=\"%s\"></i>",
|
|
Answered ? "fas fa-check-circle" :
|
|
"fas fa-exclamation-circle",
|
|
Answered ? Txt_MATCH_QUESTION_Answered :
|
|
Txt_MATCH_QUESTION_Unanswered);
|
|
HTM_TxtF (" %s",Answered ? Txt_MATCH_QUESTION_Answered :
|
|
Txt_MATCH_QUESTION_Unanswered);
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/***** End container *****/
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************** Put checkbox to select if show results ********************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_PutIconToRemoveMyAnswer (const struct Mch_Match *Match)
|
|
{
|
|
extern const char *Txt_Delete_my_answer;
|
|
|
|
/***** Begin container *****/
|
|
HTM_DIV_Begin ("class=\"MCH_REM_MY_ANS\"");
|
|
|
|
/***** Begin form *****/
|
|
Frm_BeginForm (ActRemMchAnsQstStd);
|
|
ParCod_PutPar (ParCod_Mch,Match->MchCod); // Current match being played
|
|
Gam_PutParQstInd (Match->Status.QstInd); // Current question index shown
|
|
|
|
/***** Put icon with link *****/
|
|
HTM_DIV_Begin ("class=\"MCH_BIGBUTTON_CONT\"");
|
|
HTM_BUTTON_Submit_Begin (Txt_Delete_my_answer,
|
|
"BT_LINK MCH_BUTTON_ON ICO_DARKRED\""
|
|
" onmousedown=\"this.form.submit();return false;\"");
|
|
HTM_Txt ("<i class=\"fas fa-trash\"></i>");
|
|
HTM_BUTTON_End ();
|
|
HTM_DIV_End ();
|
|
|
|
/***** End form *****/
|
|
Frm_EndForm ();
|
|
|
|
/***** End container *****/
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***************************** Show match title ******************************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ShowMatchTitleTch (const struct Mch_Match *Match)
|
|
{
|
|
/***** Match title *****/
|
|
HTM_DIV_Begin ("class=\"MCH_TOP LT\"");
|
|
HTM_Txt (Match->Title);
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
static void Mch_ShowMatchTitleStd (const struct Mch_Match *Match)
|
|
{
|
|
/***** Match title *****/
|
|
HTM_DIV_Begin ("class=\"MCH_TOP CT\"");
|
|
HTM_Txt (Match->Title);
|
|
HTM_DIV_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***** Show question and its answers when playing a match (as a teacher) *****/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ShowQuestionAndAnswersTch (const struct Mch_Match *Match)
|
|
{
|
|
extern const char *Txt_MATCH_Paused;
|
|
extern const char *Txt_Question_removed;
|
|
struct Qst_Question Question;
|
|
|
|
/***** Create test question *****/
|
|
Qst_QstConstructor (&Question);
|
|
Question.QstCod = Match->Status.QstCod;
|
|
|
|
/***** Trivial check: do not show anything on match start and end *****/
|
|
switch (Match->Status.Showing)
|
|
{
|
|
case Mch_START:
|
|
case Mch_END:
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/***** Get data of question from database *****/
|
|
if (Qst_GetQstDataByCod (&Question))
|
|
{
|
|
/***** Show question *****/
|
|
/* Check answer type */
|
|
if (Question.Answer.Type != Qst_ANS_UNIQUE_CHOICE)
|
|
Err_WrongAnswerExit ();
|
|
|
|
/* Begin container */
|
|
HTM_DIV_Begin ("class=\"MCH_BOTTOM\""); // Bottom
|
|
|
|
/* Write stem */
|
|
Qst_WriteQstStem (Question.Stem,"MCH_TCH_STEM",
|
|
true); // Visible
|
|
|
|
/* Show media */
|
|
Med_ShowMedia (&Question.Media,
|
|
"Tst_MED_EDIT_LIST_CONT",
|
|
"Tst_MED_EDIT_LIST");
|
|
|
|
/***** Write answers? *****/
|
|
switch (Match->Status.Showing)
|
|
{
|
|
case Mch_ANSWERS:
|
|
if (Match->Status.Playing) // Match is being played
|
|
/* Write answers */
|
|
Mch_WriteAnswersMatchResult (Match,
|
|
&Question,
|
|
"MCH_TCH_ANS",
|
|
false); // Don't show result
|
|
else // Match is paused, not being played
|
|
Mch_ShowWaitImage (Txt_MATCH_Paused);
|
|
break;
|
|
case Mch_RESULTS:
|
|
/* Write answers with results */
|
|
Mch_WriteAnswersMatchResult (Match,
|
|
&Question,
|
|
"MCH_TCH_ANS",
|
|
true); // Show result
|
|
break;
|
|
default:
|
|
/* Don't write anything */
|
|
break;
|
|
}
|
|
|
|
/* End container */
|
|
HTM_DIV_End (); // Bottom
|
|
}
|
|
else
|
|
Ale_ShowAlert (Ale_WARNING,Txt_Question_removed);
|
|
|
|
/***** Destroy test question *****/
|
|
Qst_QstDestructor (&Question);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/************* Write answers of a question when seeing a match ***************/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_WriteAnswersMatchResult (const struct Mch_Match *Match,
|
|
struct Qst_Question *Question,
|
|
const char *Class,bool ShowResult)
|
|
{
|
|
/***** Write answer depending on type *****/
|
|
if (Question->Answer.Type == Qst_ANS_UNIQUE_CHOICE)
|
|
Mch_WriteChoiceAnsViewMatch (Match,
|
|
Question,
|
|
Class,ShowResult);
|
|
else
|
|
Err_WrongAnswerTypeExit ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/******** Write single or multiple choice answer when seeing a match *********/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_WriteChoiceAnsViewMatch (const struct Mch_Match *Match,
|
|
struct Qst_Question *Question,
|
|
const char *Class,bool ShowResult)
|
|
{
|
|
unsigned NumOpt;
|
|
bool RowIsOpen = false;
|
|
unsigned NumRespondersQst;
|
|
unsigned NumRespondersAns;
|
|
unsigned Indexes[Qst_MAX_OPTIONS_PER_QUESTION]; // Indexes of all answers of this question
|
|
|
|
/***** Get number of users who have answered this question from database *****/
|
|
NumRespondersQst = Mch_DB_GetNumUsrsWhoAnsweredQst (Match->MchCod,Match->Status.QstInd);
|
|
|
|
/***** Change format of answers text *****/
|
|
Qst_ChangeFormatAnswersText (Question);
|
|
|
|
/***** Get indexes for this question in match *****/
|
|
Mch_GetIndexes (Match->MchCod,Match->Status.QstInd,Indexes);
|
|
|
|
/***** Begin table *****/
|
|
HTM_TABLE_BeginWidePadding (0);
|
|
|
|
/***** Show options distributed in columns *****/
|
|
for (NumOpt = 0;
|
|
NumOpt < Question->Answer.NumOptions;
|
|
NumOpt++)
|
|
{
|
|
/***** Begin row? *****/
|
|
if (NumOpt % Match->Status.NumCols == 0)
|
|
{
|
|
HTM_TR_Begin (NULL);
|
|
RowIsOpen = true;
|
|
}
|
|
|
|
/***** Write letter for this option *****/
|
|
HTM_TD_Begin ("class=\"MCH_TCH_BUTTON_TD\"");
|
|
HTM_DIV_Begin ("class=\"MCH_TCH_BUTTON BT_%c\"",'A' + (char) NumOpt);
|
|
HTM_TxtF ("%c",'a' + (char) NumOpt);
|
|
HTM_DIV_End ();
|
|
HTM_TD_End ();
|
|
|
|
/***** Write the option text and the result *****/
|
|
HTM_TD_Begin ("class=\"LT\"");
|
|
HTM_LABEL_Begin ("for=\"Ans%06u_%u\" class=\"%s\"",Match->Status.QstInd,NumOpt,Class);
|
|
HTM_Txt (Question->Answer.Options[Indexes[NumOpt]].Text);
|
|
HTM_LABEL_End ();
|
|
Med_ShowMedia (&Question->Answer.Options[Indexes[NumOpt]].Media,
|
|
"Tst_MED_SHOW_CONT",
|
|
"Tst_MED_SHOW");
|
|
|
|
/* Show result (number of users who answered? */
|
|
if (ShowResult)
|
|
{
|
|
/* Get number of users who selected this answer */
|
|
NumRespondersAns = Mch_DB_GetNumUsrsWhoHaveChosenAns (Match->MchCod,Match->Status.QstInd,Indexes[NumOpt]);
|
|
|
|
/* Draw proportional bar for this answer */
|
|
Mch_DrawBarNumUsrs (NumRespondersAns,NumRespondersQst,
|
|
Question->Answer.Options[Indexes[NumOpt]].Correct);
|
|
}
|
|
else
|
|
/* Draw empty bar for this answer
|
|
in order to show the same layout that the one shown with results */
|
|
Mch_DrawBarNumUsrs (0,0,
|
|
false); // Not used when length of bar is 0
|
|
|
|
HTM_TD_End ();
|
|
|
|
/***** End row? *****/
|
|
if (NumOpt % Match->Status.NumCols == Match->Status.NumCols - 1)
|
|
{
|
|
HTM_TR_End ();
|
|
RowIsOpen = false;
|
|
}
|
|
}
|
|
|
|
/***** End row? *****/
|
|
if (RowIsOpen)
|
|
HTM_TR_End ();
|
|
|
|
/***** End table *****/
|
|
HTM_TABLE_End ();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/***** Show question and its answers when playing a match (as a student) *****/
|
|
/*****************************************************************************/
|
|
|
|
static void Mch_ShowQuestionAndAnswersStd (const struct Mch_Match *Match,
|
|
const struct Mch_UsrAnswer *UsrAnswer,
|
|
Mch_Update_t Update)
|
|
{
|
|
unsigned NumOptions;
|