// 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-2019 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 . */ /*****************************************************************************/ /********************************* Headers ***********************************/ /*****************************************************************************/ #define _GNU_SOURCE // For asprintf #include // For PATH_MAX #include // For NULL #include // For asprintf #include // For calloc #include // For string functions #include "swad_database.h" #include "swad_date.h" #include "swad_form.h" #include "swad_game.h" #include "swad_global.h" #include "swad_HTML.h" #include "swad_match.h" #include "swad_match_result.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" /*****************************************************************************/ /******************************* Private types *******************************/ /*****************************************************************************/ /*****************************************************************************/ /***************************** Private constants *****************************/ /*****************************************************************************/ const char *Mch_ShowingStringsDB[Mch_NUM_SHOWING] = { "nothing", "stem", "answers", "results", }; #define Mch_MAX_COLS 4 #define Mch_NUM_COLS_DEFAULT 1 /*****************************************************************************/ /***************************** Private variables *****************************/ /*****************************************************************************/ long Mch_CurrentMchCod = -1L; // Used as parameter in contextual links /*****************************************************************************/ /***************************** Private prototypes ****************************/ /*****************************************************************************/ static void Mch_PutIconToCreateNewMatch (void); static void Mch_ListOneOrMoreMatches (struct Game *Game, unsigned NumMatches, MYSQL_RES *mysql_res); static void Mch_ListOneOrMoreMatchesHeading (bool ICanEditMatches); static bool Mch_CheckIfICanEditMatches (void); static bool Mch_CheckIfICanEditThisMatch (const struct Match *Match); static void Mch_ListOneOrMoreMatchesIcons (const struct Match *Match); static void Mch_ListOneOrMoreMatchesAuthor (const struct Match *Match); static void Mch_ListOneOrMoreMatchesTimes (const struct Match *Match,unsigned UniqueId); static void Mch_ListOneOrMoreMatchesTitleGrps (const struct Match *Match); static void Mch_GetAndWriteNamesOfGrpsAssociatedToMatch (const struct Match *Match); static void Mch_ListOneOrMoreMatchesNumPlayers (const struct Match *Match); static void Mch_ListOneOrMoreMatchesStatus (const struct Match *Match,unsigned NumQsts, bool ICanPlayThisMatchBasedOnGrps); static void Mch_ListOneOrMoreMatchesResult (const struct Match *Match, bool ICanPlayThisMatchBasedOnGrps); static void Mch_GetMatchDataFromRow (MYSQL_RES *mysql_res, struct Match *Match); static Mch_Showing_t Mch_GetShowingFromStr (const char *Str); static void Mch_RemoveMatchFromAllTables (long MchCod); static void Mch_RemoveMatchFromTable (long MchCod,const char *TableName); static void Mch_RemoveMatchesInGameFromTable (long GamCod,const char *TableName); static void Mch_RemoveMatchInCourseFromTable (long CrsCod,const char *TableName); static void Mch_RemoveUsrMchResultsInCrs (long UsrCod,long CrsCod,const char *TableName); static void Mch_PutParamsPlay (void); static void Mch_PutParamMchCod (long MchCod); static void Mch_PutFormNewMatch (struct Game *Game); static void Mch_ShowLstGrpsToCreateMatch (void); static long Mch_CreateMatch (long GamCod,char Title[Gam_MAX_BYTES_TITLE + 1]); static void Mch_CreateIndexes (long GamCod,long MchCod); static void Mch_ReorderAnswer (long MchCod,unsigned QstInd, long QstCod,bool Shuffle); static void Mch_CreateGrps (long MchCod); static void Mch_UpdateMatchStatusInDB (struct Match *Match); static void Mch_UpdateElapsedTimeInQuestion (struct Match *Match); static void Mch_GetElapsedTimeInQuestion (struct Match *Match, struct Time *Time); static void Mch_GetElapsedTimeInMatch (struct Match *Match, struct Time *Time); static void Mch_GetElapsedTime (unsigned NumRows,MYSQL_RES *mysql_res, struct Time *Time); static void Mch_SetMatchStatusToPrev (struct Match *Match); static void Mch_SetMatchStatusToPrevQst (struct Match *Match); static void Mch_SetMatchStatusToStart (struct Match *Match); static void Mch_SetMatchStatusToNext (struct Match *Match); static void Mch_SetMatchStatusToNextQst (struct Match *Match); static void Mch_SetMatchStatusToEnd (struct Match *Match); static void Mch_ShowMatchStatusForTch (struct Match *Match); static void Mch_ShowMatchStatusForStd (struct Match *Match); static void Mch_ShowLeftColumnTch (struct Match *Match); static void Mch_ShowRefreshablePartTch (struct Match *Match); static void Mch_ShowRightColumnTch (struct Match *Match); static void Mch_ShowLeftColumnStd (struct Match *Match); static void Mch_ShowRightColumnStd (struct Match *Match); static void Mch_ShowNumQstInMatch (struct Match *Match); static void Mch_PutMatchControlButtons (struct Match *Match); static void Mch_ShowFormColumns (struct Match *Match); static void Mch_PutParamNumCols (unsigned NumCols); static void Mch_ShowMatchTitle (struct Match *Match); static void Mch_PutCheckboxResult (struct Match *Match); static void Mch_ShowQuestionAndAnswersTch (struct Match *Match); static void Mch_WriteAnswersMatchResult (struct Match *Match, const char *Class,bool ShowResult); static void Mch_ShowQuestionAndAnswersStd (struct Match *Match); static void Mch_ShowMatchScore (struct Match *Match); static void Mch_DrawEmptyRowScore (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_PutParamNumOpt (unsigned NumOpt); static unsigned Mch_GetParamNumOpt (void); static void Mch_PutBigButton (Act_Action_t NextAction,const char *Id, long MchCod,const char *Icon,const char *Txt); static void Mch_PutBigButtonOff (const char *Icon); static void Mch_PutBigButtonClose (void); static void Mch_ShowWaitImage (const char *Txt); static void Mch_RemoveOldPlayers (void); static void Mch_UpdateMatchAsBeingPlayed (long MchCod); static void Mch_SetMatchAsNotBeingPlayed (long MchCod); static bool Mch_GetIfMatchIsBeingPlayed (long MchCod); static void Mch_RegisterMeAsPlayerInMatch (long MchCod); static void Mch_GetNumPlayers (struct Match *Match); static double Mch_ComputeScore (unsigned NumQsts); static unsigned Mch_GetNumUsrsWhoHaveChosenAns (long MchCod,unsigned QstInd,unsigned AnsInd); static unsigned Mch_GetNumUsrsWhoHaveAnswerMch (long MchCod); static void Mch_DrawBarNumUsrs (unsigned NumAnswerersAns,unsigned NumAnswerersQst,bool Correct); static long Mch_GetParamCurrentMchCod (void); /*****************************************************************************/ /************************* List the matches of a game ************************/ /*****************************************************************************/ void Mch_ListMatches (struct Game *Game,bool PutFormNewMatch) { extern const char *Hlp_ASSESSMENT_Games_matches; extern const char *Txt_Matches; char *SubQuery; MYSQL_RES *mysql_res; unsigned NumMatches; bool ICanEditMatches = Mch_CheckIfICanEditMatches (); /***** Get data of matches from database *****/ /* Fill subquery for game */ if (Gbl.Crs.Grps.WhichGrps == Grp_MY_GROUPS) { if (asprintf (&SubQuery," AND" "(MchCod NOT IN" " (SELECT MchCod FROM mch_groups)" " OR" " MchCod IN" " (SELECT mch_groups.MchCod" " FROM mch_groups,crs_grp_usr" " WHERE crs_grp_usr.UsrCod=%ld" " AND mch_groups.GrpCod=crs_grp_usr.GrpCod))", Gbl.Usrs.Me.UsrDat.UsrCod) < 0) Lay_NotEnoughMemoryExit (); } else // Gbl.Crs.Grps.WhichGrps == Grp_ALL_GROUPS if (asprintf (&SubQuery,"%s","") < 0) Lay_NotEnoughMemoryExit (); /* Make query */ NumMatches = (unsigned) DB_QuerySELECT (&mysql_res,"can not get matches", "SELECT MchCod," // row[ 0] "GamCod," // row[ 1] "UsrCod," // row[ 2] "UNIX_TIMESTAMP(StartTime)," // row[ 3] "UNIX_TIMESTAMP(EndTime)," // row[ 4] "Title," // row[ 5] "QstInd," // row[ 6] "QstCod," // row[ 7] "Showing," // row[ 8] "NumCols," // row[ 9] "ShowQstResults," // row[10] "ShowUsrResults" // row[11] " FROM mch_matches" " WHERE GamCod=%ld%s" " ORDER BY MchCod", Game->GamCod, SubQuery); /* Free allocated memory for subquery */ free ((void *) SubQuery); /***** Begin box *****/ Gam_SetParamCurrentGamCod (Game->GamCod); // Used to pass parameter Box_BoxBegin (NULL,Txt_Matches,ICanEditMatches ? Mch_PutIconToCreateNewMatch : NULL, Hlp_ASSESSMENT_Games_matches,Box_NOT_CLOSABLE); /***** Select whether show only my groups or all groups *****/ if (Gbl.Crs.Grps.NumGrps) { Set_StartSettingsHead (); Grp_ShowFormToSelWhichGrps (ActSeeGam,Gam_PutParams); Set_EndSettingsHead (); } if (NumMatches) /***** Show the table with the matches *****/ Mch_ListOneOrMoreMatches (Game,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 (Game); // Form to fill in data and start playing a new match else Gam_PutButtonNewMatch (Game->GamCod); // Button to create a new match break; default: break; } /***** End box *****/ Box_BoxEnd (); } /*****************************************************************************/ /********************** Get match data using its code ************************/ /*****************************************************************************/ void Mch_GetDataOfMatchByCod (struct Match *Match) { MYSQL_RES *mysql_res; unsigned long NumRows; Dat_StartEndTime_t StartEndTime; /***** Get data of match from database *****/ NumRows = (unsigned) DB_QuerySELECT (&mysql_res,"can not get matches", "SELECT MchCod," // row[ 0] "GamCod," // row[ 1] "UsrCod," // row[ 2] "UNIX_TIMESTAMP(StartTime)," // row[ 3] "UNIX_TIMESTAMP(EndTime)," // row[ 4] "Title," // row[ 5] "QstInd," // row[ 6] "QstCod," // row[ 7] "Showing," // row[ 8] "NumCols," // row[ 9] "ShowQstResults," // row[10] "ShowUsrResults" // row[11] " FROM mch_matches" " WHERE MchCod=%ld" " AND GamCod IN" // Extra check " (SELECT GamCod FROM gam_games" " WHERE CrsCod='%ld')", Match->MchCod, Gbl.Hierarchy.Crs.CrsCod); if (NumRows) // Match found... /***** Get match data from row *****/ Mch_GetMatchDataFromRow (mysql_res,Match); else { /* 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_STEM; Match->Status.Playing = false; Match->Status.NumPlayers = 0; Match->Status.NumCols = Mch_NUM_COLS_DEFAULT; Match->Status.ShowQstResults = false; Match->Status.ShowUsrResults = false; } /***** Free structure that stores the query result *****/ DB_FreeMySQLResult (&mysql_res); } /*****************************************************************************/ /********************* Put icon to create a new match ************************/ /*****************************************************************************/ static void Mch_PutIconToCreateNewMatch (void) { extern const char *Txt_New_match; /***** Put form to create a new match *****/ Ico_PutContextualIconToAdd (ActReqNewMch,Mch_NEW_MATCH_SECTION_ID,Gam_PutParams, Txt_New_match); } /*****************************************************************************/ /*********************** List game matches for edition ***********************/ /*****************************************************************************/ static void Mch_ListOneOrMoreMatches (struct Game *Game, unsigned NumMatches, MYSQL_RES *mysql_res) { unsigned NumMatch; unsigned UniqueId; struct Match Match; bool ICanPlayThisMatchBasedOnGrps; bool ICanEditMatches = Mch_CheckIfICanEditMatches (); /***** Write the heading *****/ HTM_TABLE_BeginWideMarginPadding (2); Mch_ListOneOrMoreMatchesHeading (ICanEditMatches); /***** Write rows *****/ for (NumMatch = 0, UniqueId = 1; NumMatch < NumMatches; NumMatch++, UniqueId++) { Gbl.RowEvenOdd = NumMatch % 2; /***** Get match data from row *****/ Mch_GetMatchDataFromRow (mysql_res,&Match); ICanPlayThisMatchBasedOnGrps = Mch_CheckIfICanPlayThisMatchBasedOnGrps (Match.MchCod); /***** Write row for this match ****/ HTM_TR_Begin (NULL); /* Icons */ if (ICanEditMatches) Mch_ListOneOrMoreMatchesIcons (&Match); /* Match player */ Mch_ListOneOrMoreMatchesAuthor (&Match); /* Start/end date/time */ Mch_ListOneOrMoreMatchesTimes (&Match,UniqueId); /* Title and groups */ Mch_ListOneOrMoreMatchesTitleGrps (&Match); /* Number of players who have answered any question in the match */ Mch_ListOneOrMoreMatchesNumPlayers (&Match); /* Match status */ Mch_ListOneOrMoreMatchesStatus (&Match,Game->NumQsts, ICanPlayThisMatchBasedOnGrps); /* Match result visible? */ Mch_ListOneOrMoreMatchesResult (&Match, ICanPlayThisMatchBasedOnGrps); HTM_TR_End (); } /***** 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_Result; /***** Start row *****/ HTM_TR_Begin (NULL); /***** Column for icons *****/ if (ICanEditMatches) HTM_TH_Empty (1); /***** The rest of columns *****/ HTM_TH (1,1,"LT",Txt_ROLES_SINGUL_Abc[Rol_TCH][Usr_SEX_UNKNOWN]); HTM_TH (1,1,"LT",Txt_START_END_TIME[Gam_ORDER_BY_START_DATE]); HTM_TH (1,1,"LT",Txt_START_END_TIME[Gam_ORDER_BY_END_DATE]); HTM_TH (1,1,"LT",Txt_Match); HTM_TH (1,1,"RT",Txt_Players); HTM_TH (1,1,"CT",Txt_Status); HTM_TH (1,1,"CT",Txt_Result); /***** End row *****/ HTM_TR_End (); } /*****************************************************************************/ /*********************** Check if I can edit matches *************************/ /*****************************************************************************/ static bool Mch_CheckIfICanEditMatches (void) { switch (Gbl.Usrs.Me.Role.Logged) { case Rol_NET: case Rol_TCH: case Rol_SYS_ADM: return true; default: return false; } } /*****************************************************************************/ /***************** Check if I can edit (remove/resume) a match ***************/ /*****************************************************************************/ static bool Mch_CheckIfICanEditThisMatch (const struct 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; } } /*****************************************************************************/ /************************* Put a column for icons ****************************/ /*****************************************************************************/ static void Mch_ListOneOrMoreMatchesIcons (const struct Match *Match) { HTM_TD_Begin ("class=\"BT%u\"",Gbl.RowEvenOdd); /***** Put icon to remove the match *****/ if (Mch_CheckIfICanEditThisMatch (Match)) { Gam_SetParamCurrentGamCod (Match->GamCod); // Used to pass parameter Mch_SetParamCurrentMchCod (Match->MchCod); // Used to pass parameter Frm_StartForm (ActReqRemMch); Mch_PutParamsEdit (); Ico_PutIconRemove (); Frm_EndForm (); } else Ico_PutIconRemovalNotAllowed (); HTM_TD_End (); } /*****************************************************************************/ /************* Put a column for teacher who created the match ****************/ /*****************************************************************************/ static void Mch_ListOneOrMoreMatchesAuthor (const struct Match *Match) { /***** Match author (teacher) *****/ HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd); Usr_WriteAuthor1Line (Match->UsrCod,false); HTM_TD_End (); } /*****************************************************************************/ /***************** Put a column for match start and end times ****************/ /*****************************************************************************/ static void Mch_ListOneOrMoreMatchesTimes (const struct 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) Lay_NotEnoughMemoryExit (); HTM_TD_Begin ("id=\"%s\" class=\"%s LT COLOR%u\"", Id, Match->Status.QstInd >= Mch_AFTER_LAST_QUESTION ? "DATE_RED" : "DATE_GREEN", Gbl.RowEvenOdd); Dat_WriteLocalDateHMSFromUTC (Id,Match->TimeUTC[StartEndTime], Gbl.Prefs.DateFormat,Dat_SEPARATOR_BREAK, true,true,true,0x7); HTM_TD_End (); free ((void *) Id); } } /*****************************************************************************/ /***************** Put a column for match title and grous ********************/ /*****************************************************************************/ static void Mch_ListOneOrMoreMatchesTitleGrps (const struct Match *Match) { HTM_TD_Begin ("class=\"LT COLOR%u\"",Gbl.RowEvenOdd); /***** Title *****/ fprintf (Gbl.F.Out,"%s",Match->Title); /***** Groups whose students can answer this match *****/ if (Gbl.Crs.Grps.NumGrps) Mch_GetAndWriteNamesOfGrpsAssociatedToMatch (Match); HTM_TD_End (); } /*****************************************************************************/ /************* Get and write the names of the groups of a match **************/ /*****************************************************************************/ static void Mch_GetAndWriteNamesOfGrpsAssociatedToMatch (const struct 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 long NumRow; unsigned long NumRows; /***** Get groups associated to a match from database *****/ NumRows = DB_QuerySELECT (&mysql_res,"can not get groups of a match", "SELECT crs_grp_types.GrpTypName,crs_grp.GrpName" " FROM mch_groups,crs_grp,crs_grp_types" " WHERE mch_groups.MchCod=%ld" " AND mch_groups.GrpCod=crs_grp.GrpCod" " AND crs_grp.GrpTypCod=crs_grp_types.GrpTypCod" " ORDER BY crs_grp_types.GrpTypName,crs_grp.GrpName", Match->MchCod); /***** Write heading *****/ HTM_DIV_Begin ("class=\"ASG_GRP\""); fprintf (Gbl.F.Out,"%s: ",NumRows == 1 ? Txt_Group : Txt_Groups); /***** Write groups *****/ if (NumRows) // Groups found... { /* Get and write the group types and names */ for (NumRow = 0; NumRow < NumRows; NumRow++) { /* Get next group */ row = mysql_fetch_row (mysql_res); /* Write group type name and group name */ fprintf (Gbl.F.Out,"%s %s",row[0],row[1]); if (NumRows >= 2) { if (NumRow == NumRows-2) fprintf (Gbl.F.Out," %s ",Txt_and); if (NumRows >= 3) if (NumRow < NumRows-2) fprintf (Gbl.F.Out,", "); } } } else fprintf (Gbl.F.Out,"%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 **********************/ /*****************************************************************************/ static void Mch_ListOneOrMoreMatchesNumPlayers (const struct Match *Match) { /***** Number of players who have answered any question in the match ******/ HTM_TD_Begin ("class=\"DAT RT COLOR%u\"",Gbl.RowEvenOdd); fprintf (Gbl.F.Out,"%u",Mch_GetNumUsrsWhoHaveAnswerMch (Match->MchCod)); HTM_TD_End (); } /*****************************************************************************/ /********************** Put a column for match status ************************/ /*****************************************************************************/ static void Mch_ListOneOrMoreMatchesStatus (const struct Match *Match,unsigned NumQsts, bool ICanPlayThisMatchBasedOnGrps) { extern const char *Txt_Play; extern const char *Txt_Resume; HTM_TD_Begin ("class=\"DAT CT COLOR%u\"",Gbl.RowEvenOdd); if (Match->Status.QstInd < Mch_AFTER_LAST_QUESTION) // Unfinished match { /* Current question index / total of questions */ HTM_DIV_Begin ("class=\"DAT\""); fprintf (Gbl.F.Out,"%u/%u",Match->Status.QstInd,NumQsts); HTM_DIV_End (); } switch (Gbl.Usrs.Me.Role.Logged) { case Rol_STD: if (ICanPlayThisMatchBasedOnGrps) { /* Icon to play as student */ Mch_SetParamCurrentMchCod (Match->MchCod); // Used to pass parameter Lay_PutContextualLinkOnlyIcon (ActJoiMch,NULL, Mch_PutParamsPlay, Match->Status.QstInd < Mch_AFTER_LAST_QUESTION ? "play.svg" : "flag-checkered.svg", Txt_Play); } break; case Rol_NET: case Rol_TCH: case Rol_SYS_ADM: /* Icon to resume */ if (Mch_CheckIfICanEditThisMatch (Match)) { Mch_SetParamCurrentMchCod (Match->MchCod); // Used to pass parameter Lay_PutContextualLinkOnlyIcon (ActResMch,NULL, Mch_PutParamsPlay, Match->Status.QstInd < Mch_AFTER_LAST_QUESTION ? "play.svg" : "flag-checkered.svg", Txt_Resume); } break; default: break; } HTM_TD_End (); } /*****************************************************************************/ /**************** Put a column for visibility of match result ****************/ /*****************************************************************************/ static void Mch_ListOneOrMoreMatchesResult (const struct Match *Match, bool ICanPlayThisMatchBasedOnGrps) { extern const char *Txt_Match_result; extern const char *Txt_Hidden_result; extern const char *Txt_Visible_result; HTM_TD_Begin ("class=\"DAT CT COLOR%u\"",Gbl.RowEvenOdd); switch (Gbl.Usrs.Me.Role.Logged) { case Rol_STD: if (ICanPlayThisMatchBasedOnGrps) { /* Match result visible or hidden? */ if (Match->Status.ShowUsrResults) { /* Result is visible by me */ Gam_SetParamCurrentGamCod (Match->GamCod); // Used to pass parameter Mch_SetParamCurrentMchCod (Match->MchCod); // Used to pass parameter Frm_StartForm (ActSeeOneMchResMe); Mch_PutParamsEdit (); Ico_PutIconLink ("tasks.svg",Txt_Match_result); Frm_EndForm (); } else /* Result is forbidden to me */ Ico_PutIconOff ("eye-slash.svg",Txt_Hidden_result); } break; case Rol_NET: case Rol_TCH: case Rol_SYS_ADM: /* Match result visible or hidden? */ if (Mch_CheckIfICanEditThisMatch (Match)) { /* I can edit visibility */ Gam_SetParamCurrentGamCod (Match->GamCod); // Used to pass parameter Mch_SetParamCurrentMchCod (Match->MchCod); // Used to pass parameter Lay_PutContextualLinkOnlyIcon (ActChgVisResMchUsr,NULL, Mch_PutParamsEdit, Match->Status.ShowUsrResults ? "eye.svg" : "eye-slash.svg", Match->Status.ShowUsrResults ? Txt_Visible_result : Txt_Hidden_result); } else /* I can not edit visibility */ Ico_PutIconOff (Match->Status.ShowUsrResults ? "eye.svg" : "eye-slash.svg", Match->Status.ShowUsrResults ? Txt_Visible_result : Txt_Hidden_result); break; default: break; } HTM_TD_End (); } /*****************************************************************************/ /******************** Toggle visibility of match results *********************/ /*****************************************************************************/ void Mch_ToggleVisibilResultsMchUsr (void) { struct Game Game; struct Match Match; /***** Get and check parameters *****/ Mch_GetAndCheckParameters (&Game,&Match); /***** Check if I have permission to change visibility *****/ if (!Mch_CheckIfICanEditThisMatch (&Match)) Lay_NoPermissionExit (); /***** Toggle visibility of match results *****/ Match.Status.ShowUsrResults = !Match.Status.ShowUsrResults; DB_QueryUPDATE ("can not toggle visibility of match results", "UPDATE mch_matches" " SET ShowUsrResults='%c'" " WHERE MchCod=%ld", Match.Status.ShowUsrResults ? 'Y' : 'N', Match.MchCod); /***** Show current game *****/ Gam_ShowOneGame (Match.GamCod, true, // Show only this game 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 Match *Match) { MYSQL_ROW row; Dat_StartEndTime_t StartEndTime; long LongNum; /***** Get match data *****/ 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) Lay_ShowErrorAndExit ("Wrong code of match."); /* Code of the game (row[1]) */ if ((Match->GamCod = Str_ConvertStrCodToLongCod (row[1])) <= 0) Lay_ShowErrorAndExit ("Wrong code of game."); /* 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 game (row[5]) */ if (row[5]) Str_Copy (Match->Title,row[5], Gam_MAX_BYTES_TITLE); else Match->Title[0] = '\0'; /***** Get current match status *****/ /* row[ 6] QstInd row[ 7] QstCod row[ 8] Showing row[ 9] NumCols row[10] ShowQstResults row[11] ShowUsrResults */ /* Current question index (row[6]) */ Match->Status.QstInd = Gam_GetQstIndFromStr (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_GetShowingFromStr (row[8]); /* Get number of columns (row[9]) */ LongNum = Str_ConvertStrCodToLongCod (row[9]); Match->Status.NumCols = (LongNum <= 1 ) ? 1 : ((LongNum >= Mch_MAX_COLS) ? Mch_MAX_COLS : (unsigned) LongNum); /* Get whether to show question results or not (row(10)) */ Match->Status.ShowQstResults = (row[10][0] == 'Y'); /* Get whether to show user results or not (row(11)) */ Match->Status.ShowUsrResults = (row[11][0] == 'Y'); /***** Get whether the match is being played or not *****/ if (Match->Status.QstInd >= Mch_AFTER_LAST_QUESTION) // Finished Match->Status.Playing = false; else // Unfinished Match->Status.Playing = Mch_GetIfMatchIsBeingPlayed (Match->MchCod); } /*****************************************************************************/ /****************** Get parameter with what is being shown *******************/ /*****************************************************************************/ static Mch_Showing_t Mch_GetShowingFromStr (const char *Str) { Mch_Showing_t Showing; for (Showing = (Mch_Showing_t) 0; Showing <= (Mch_Showing_t) (Mch_NUM_SHOWING - 1); Showing++) if (!strcmp (Str,Mch_ShowingStringsDB[Showing])) return Showing; return (Mch_Showing_t) Mch_SHOWING_DEFAULT; } /*****************************************************************************/ /************** Request the removal of a match (game instance) ***************/ /*****************************************************************************/ void Mch_RequestRemoveMatch (void) { extern const char *Txt_Do_you_really_want_to_remove_the_match_X; extern const char *Txt_Remove_match; struct Game Game; struct Match Match; /***** Get and check parameters *****/ Mch_GetAndCheckParameters (&Game,&Match); /***** Show question and button to remove question *****/ Gam_SetParamCurrentGamCod (Match.GamCod); // Used to pass parameter Mch_SetParamCurrentMchCod (Match.MchCod); // Used to pass parameter Ale_ShowAlertAndButton (ActRemMch,NULL,NULL,Mch_PutParamsEdit, Btn_REMOVE_BUTTON,Txt_Remove_match, Ale_QUESTION,Txt_Do_you_really_want_to_remove_the_match_X, Match.Title); /***** Show current game *****/ Gam_ShowOneGame (Match.GamCod, true, // Show only this game 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 Game Game; struct Match Match; /***** Get and check parameters *****/ Mch_GetAndCheckParameters (&Game,&Match); /***** Check if I can remove this match *****/ if (!Mch_CheckIfICanEditThisMatch (&Match)) Lay_NoPermissionExit (); /***** Remove the match from all database tables *****/ Mch_RemoveMatchFromAllTables (Match.MchCod); /***** Write message *****/ Ale_ShowAlert (Ale_SUCCESS,Txt_Match_X_removed, Match.Title); /***** Show current game *****/ Gam_ShowOneGame (Match.GamCod, true, // Show only this game false, // Do not list game questions false); // Do not put form to start new match } /*****************************************************************************/ /********************** Remove match from all tables *************************/ /*****************************************************************************/ /* mysql> SELECT table_name FROM information_schema.tables WHERE table_name LIKE 'mch%'; */ static void Mch_RemoveMatchFromAllTables (long MchCod) { /***** Remove match from secondary tables *****/ Mch_RemoveMatchFromTable (MchCod,"mch_players"); Mch_RemoveMatchFromTable (MchCod,"mch_playing"); Mch_RemoveMatchFromTable (MchCod,"mch_results"); Mch_RemoveMatchFromTable (MchCod,"mch_answers"); Mch_RemoveMatchFromTable (MchCod,"mch_times"); Mch_RemoveMatchFromTable (MchCod,"mch_groups"); Mch_RemoveMatchFromTable (MchCod,"mch_indexes"); /***** Remove match from main table *****/ DB_QueryDELETE ("can not remove match", "DELETE FROM mch_matches WHERE MchCod=%ld", MchCod); } static void Mch_RemoveMatchFromTable (long MchCod,const char *TableName) { /***** Remove match from secondary table *****/ DB_QueryDELETE ("can not remove match from table", "DELETE FROM %s WHERE MchCod=%ld", TableName, MchCod); } /*****************************************************************************/ /******************** Remove match in game from all tables *******************/ /*****************************************************************************/ void Mch_RemoveMatchesInGameFromAllTables (long GamCod) { /***** Remove matches from secondary tables *****/ Mch_RemoveMatchesInGameFromTable (GamCod,"mch_players"); Mch_RemoveMatchesInGameFromTable (GamCod,"mch_playing"); Mch_RemoveMatchesInGameFromTable (GamCod,"mch_results"); Mch_RemoveMatchesInGameFromTable (GamCod,"mch_answers"); Mch_RemoveMatchesInGameFromTable (GamCod,"mch_times"); Mch_RemoveMatchesInGameFromTable (GamCod,"mch_groups"); Mch_RemoveMatchesInGameFromTable (GamCod,"mch_indexes"); /***** Remove matches from main table *****/ DB_QueryDELETE ("can not remove matches of a game", "DELETE FROM mch_matches WHERE GamCod=%ld", GamCod); } static void Mch_RemoveMatchesInGameFromTable (long GamCod,const char *TableName) { /***** Remove matches in game from secondary table *****/ DB_QueryDELETE ("can not remove matches of a game from table", "DELETE FROM %s" " USING mch_matches,%s" " WHERE mch_matches.GamCod=%ld" " AND mch_matches.MchCod=%s.MchCod", TableName, TableName, GamCod, TableName); } /*****************************************************************************/ /******************* Remove match in course from all tables ******************/ /*****************************************************************************/ void Mch_RemoveMatchInCourseFromAllTables (long CrsCod) { /***** Remove matches from secondary tables *****/ Mch_RemoveMatchInCourseFromTable (CrsCod,"mch_players"); Mch_RemoveMatchInCourseFromTable (CrsCod,"mch_playing"); Mch_RemoveMatchInCourseFromTable (CrsCod,"mch_results"); Mch_RemoveMatchInCourseFromTable (CrsCod,"mch_answers"); Mch_RemoveMatchInCourseFromTable (CrsCod,"mch_times"); Mch_RemoveMatchInCourseFromTable (CrsCod,"mch_groups"); Mch_RemoveMatchInCourseFromTable (CrsCod,"mch_indexes"); /***** Remove matches from main table *****/ DB_QueryDELETE ("can not remove matches of a course", "DELETE FROM mch_matches" " USING gam_games,mch_matches" " WHERE gam_games.CrsCod=%ld" " AND gam_games.GamCod=mch_matches.GamCod", CrsCod); } static void Mch_RemoveMatchInCourseFromTable (long CrsCod,const char *TableName) { /***** Remove matches in course from secondary table *****/ DB_QueryDELETE ("can not remove matches of a course from table", "DELETE FROM %s" " USING gam_games,mch_matches,%s" " WHERE gam_games.CrsCod=%ld" " AND gam_games.GamCod=mch_matches.GamCod" " AND mch_matches.MchCod=%s.MchCod", TableName, TableName, CrsCod, TableName); } /*****************************************************************************/ /***************** Remove user from secondary match tables *******************/ /*****************************************************************************/ void Mch_RemoveUsrFromMatchTablesInCrs (long UsrCod,long CrsCod) { /***** Remove student from secondary tables *****/ Mch_RemoveUsrMchResultsInCrs (UsrCod,CrsCod,"mch_players"); Mch_RemoveUsrMchResultsInCrs (UsrCod,CrsCod,"mch_results"); Mch_RemoveUsrMchResultsInCrs (UsrCod,CrsCod,"mch_answers"); } static void Mch_RemoveUsrMchResultsInCrs (long UsrCod,long CrsCod,const char *TableName) { /***** Remove matches in course from secondary table *****/ DB_QueryDELETE ("can not remove matches of a user from table", "DELETE FROM %s" " USING gam_games,mch_matches,%s" " WHERE gam_games.CrsCod=%ld" " AND gam_games.GamCod=mch_matches.GamCod" " AND mch_matches.MchCod=%s.MchCod" " AND %s.UsrCod=%ld", TableName, TableName, CrsCod, TableName, TableName, UsrCod); } /*****************************************************************************/ /*********************** Params used to edit a match *************************/ /*****************************************************************************/ void Mch_PutParamsEdit (void) { Gam_PutParams (); Mch_PutParamsPlay (); } /*****************************************************************************/ /*********************** Params used to edit a match *************************/ /*****************************************************************************/ static void Mch_PutParamsPlay (void) { long CurrentMchCod = Mch_GetParamCurrentMchCod (); if (CurrentMchCod > 0) Mch_PutParamMchCod (CurrentMchCod); } /*****************************************************************************/ /******************** Write parameter with code of match **********************/ /*****************************************************************************/ static void Mch_PutParamMchCod (long MchCod) { Par_PutHiddenParamLong ("MchCod",MchCod); } /*****************************************************************************/ /************************** Get and check parameters *************************/ /*****************************************************************************/ void Mch_GetAndCheckParameters (struct Game *Game,struct Match *Match) { /***** Get parameters *****/ /* Get parameters of game */ if ((Game->GamCod = Gam_GetParams ()) == -1L) Lay_ShowErrorAndExit ("Code of game is missing."); Gam_GetDataOfGameByCod (Game); /* Get match code */ if ((Match->MchCod = Mch_GetParamMchCod ()) == -1L) Lay_ShowErrorAndExit ("Code of match is missing."); Mch_GetDataOfMatchByCod (Match); /***** Ensure parameters are correct *****/ if (Game->GamCod != Match->GamCod) Lay_ShowErrorAndExit ("Wrong game code."); if (Game->CrsCod != Gbl.Hierarchy.Crs.CrsCod) Lay_ShowErrorAndExit ("Match does not belong to this course."); } /*****************************************************************************/ /********************* Get parameter with code of match **********************/ /*****************************************************************************/ long Mch_GetParamMchCod (void) { /***** Get code of match *****/ return Par_GetParToLong ("MchCod"); } /*****************************************************************************/ /****** Put a big button to play match (start a new match) as a teacher ******/ /*****************************************************************************/ static void Mch_PutFormNewMatch (struct Game *Game) { extern const char *Hlp_ASSESSMENT_Games_matches; extern const char *The_ClassFormInBox[The_NUM_THEMES]; extern const char *Txt_New_match; extern const char *Txt_Title; extern const char *Txt_Play; /***** Start section for a new match *****/ HTM_SECTION_Begin (Mch_NEW_MATCH_SECTION_ID); /***** Begin form *****/ Frm_StartForm (ActNewMch); Gam_PutParamGameCod (Game->GamCod); Gam_PutParamQstInd (0); // Start by first question in game /***** Begin box and table *****/ Box_StartBoxTable (NULL,Txt_New_match,NULL, Hlp_ASSESSMENT_Games_matches,Box_NOT_CLOSABLE,2); /***** Match title *****/ HTM_TR_Begin (NULL); HTM_TD_Begin ("class=\"RM\""); fprintf (Gbl.F.Out,"