From 9d4d1da3ecb8d869c4c1f66524fc250adf748dbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Ca=C3=B1as=20Vargas?= Date: Sat, 14 Sep 2019 12:59:34 +0200 Subject: [PATCH] Version19.1 --- Makefile | 4 +- swad_action.c | 29 +- swad_changelog.h | 3 +- swad_game.c | 2391 +--------------------------------------------- swad_game.h | 38 +- swad_match.c | 2297 ++++++++++++++++++++++++++++++++++++++++++++ swad_match.h | 68 ++ swad_test.c | 3 +- 8 files changed, 2416 insertions(+), 2417 deletions(-) create mode 100644 swad_match.c create mode 100644 swad_match.h diff --git a/Makefile b/Makefile index 3af99ffd..9d3349f5 100644 --- a/Makefile +++ b/Makefile @@ -42,8 +42,8 @@ OBJS = swad_account.o swad_action.o swad_agenda.o swad_alert.o \ swad_help.o swad_hierarchy.o swad_holiday.o \ swad_icon.o swad_ID.o swad_indicator.o swad_info.o swad_institution.o \ swad_language.o swad_layout.o swad_link.o swad_logo.o \ - swad_mail.o swad_main.o swad_mark.o swad_media.o swad_menu.o \ - swad_message.o swad_MFU.o \ + swad_mail.o swad_main.o swad_mark.o swad_match.o swad_media.o \ + swad_menu.o swad_message.o swad_MFU.o \ swad_network.o swad_nickname.o swad_notice.o swad_notification.o \ swad_pagination.o swad_parameter.o swad_password.o swad_photo.o \ swad_place.o swad_plugin.o swad_privacy.o swad_profile.o swad_project.o \ diff --git a/swad_action.c b/swad_action.c index 8be1911a..927c7fb3 100644 --- a/swad_action.c +++ b/swad_action.c @@ -56,6 +56,7 @@ #include "swad_language.h" #include "swad_mail.h" #include "swad_mark.h" +#include "swad_match.h" #include "swad_MFU.h" #include "swad_network.h" #include "swad_nickname.h" @@ -2152,22 +2153,22 @@ struct Act_Actions Act_Actions[Act_NUM_ACTIONS] = /* ActSeeGam */{1650,-1,TabUnk,ActSeeAllGam ,0x238,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Gam_SeeOneGame ,NULL}, - /* ActReqRemMchTch */{1783,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Gam_RequestRemoveMatchTch ,NULL}, - /* ActRemMchTch */{1784,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Gam_RemoveMatchTch ,NULL}, + /* ActReqRemMchTch */{1783,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Mch_RequestRemoveMatchTch ,NULL}, + /* ActRemMchTch */{1784,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Mch_RemoveMatchTch ,NULL}, /* ActReqNewMchTch */{1670,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Gam_RequestNewMatchTch ,NULL}, - /* ActNewMchTch */{1671,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_NEW_TAB,Gam_CreateNewMatchTch ,Gam_RequestStartResumeMatchTch ,NULL}, - /* ActResMchTch */{1785,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_NEW_TAB,Gam_GetMatchBeingPlayed ,Gam_RequestStartResumeMatchTch ,NULL}, - /* ActPauMchTch */{1791,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Gam_GetMatchBeingPlayed ,Gam_PauseMatchTch ,NULL}, - /* ActPlyMchTch */{1789,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Gam_GetMatchBeingPlayed ,Gam_ResumeMatchTch ,NULL}, - /* ActBckMchTch */{1790,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Gam_GetMatchBeingPlayed ,Gam_BackMatchTch ,NULL}, - /* ActFwdMchTch */{1672,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Gam_GetMatchBeingPlayed ,Gam_ForwardMatchTch ,NULL}, - /* ActShoResMchTch */{1795,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Gam_GetMatchBeingPlayed ,Gam_ShowResultsQstMatchTch ,NULL}, - /* ActRefMchTch */{1788,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_AJAX_RFRESH,Gam_GetMatchBeingPlayed ,Gam_RefreshMatchTch ,NULL}, - /* ActShoMchTch */{1786,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Gam_ShowFinishedMatchResults ,NULL}, + /* ActNewMchTch */{1671,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_NEW_TAB,Mch_CreateNewMatchTch ,Mch_RequestStartResumeMatchTch ,NULL}, + /* ActResMchTch */{1785,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_NEW_TAB,Mch_GetMatchBeingPlayed ,Mch_RequestStartResumeMatchTch ,NULL}, + /* ActPauMchTch */{1791,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Mch_GetMatchBeingPlayed ,Mch_PauseMatchTch ,NULL}, + /* ActPlyMchTch */{1789,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Mch_GetMatchBeingPlayed ,Mch_ResumeMatchTch ,NULL}, + /* ActBckMchTch */{1790,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Mch_GetMatchBeingPlayed ,Mch_BackMatchTch ,NULL}, + /* ActFwdMchTch */{1672,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Mch_GetMatchBeingPlayed ,Mch_ForwardMatchTch ,NULL}, + /* ActShoResMchTch */{1795,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Mch_GetMatchBeingPlayed ,Mch_ShowResultsQstMatchTch ,NULL}, + /* ActRefMchTch */{1788,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_AJAX_RFRESH,Mch_GetMatchBeingPlayed ,Mch_RefreshMatchTch ,NULL}, + /* ActShoMchTch */{1786,-1,TabUnk,ActSeeAllGam ,0x230,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Mch_ShowFinishedMatchResults ,NULL}, - /* ActPlyMchStd */{1780,-1,TabUnk,ActSeeAllGam ,0x008, 0, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_NEW_TAB,Gam_GetMatchBeingPlayed ,Gam_ShowMatchToMeAsStd ,NULL}, - /* ActRefMchStd */{1782,-1,TabUnk,ActSeeAllGam ,0x008, 0, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_AJAX_RFRESH,Gam_GetMatchBeingPlayed ,Gam_RefreshMatchStd ,NULL}, - /* ActAnsMchQstStd */{1651,-1,TabUnk,ActSeeAllGam ,0x238,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Gam_GetMatchBeingPlayed ,Gam_ReceiveQstAnsFromStd ,NULL}, + /* ActPlyMchStd */{1780,-1,TabUnk,ActSeeAllGam ,0x008, 0, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_NEW_TAB,Mch_GetMatchBeingPlayed ,Mch_ShowMatchToMeAsStd ,NULL}, + /* ActRefMchStd */{1782,-1,TabUnk,ActSeeAllGam ,0x008, 0, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_AJAX_RFRESH,Mch_GetMatchBeingPlayed ,Mch_RefreshMatchStd ,NULL}, + /* ActAnsMchQstStd */{1651,-1,TabUnk,ActSeeAllGam ,0x238,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_2ND_TAB,Mch_GetMatchBeingPlayed ,Mch_ReceiveQstAnsFromStd ,NULL}, /* ActFrmNewGam */{1652,-1,TabUnk,ActSeeAllGam ,0x238,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Gam_RequestCreatOrEditGame ,NULL}, /* ActEdiOneGam */{1653,-1,TabUnk,ActSeeAllGam ,0x238,0x200, 0, 0, 0, 0, 0,Act_CONT_NORM,Act_BRW_1ST_TAB,NULL ,Gam_RequestCreatOrEditGame ,NULL}, diff --git a/swad_changelog.h b/swad_changelog.h index fff2727e..46436e84 100644 --- a/swad_changelog.h +++ b/swad_changelog.h @@ -460,10 +460,11 @@ En OpenSWAD: ps2pdf source.ps destination.pdf */ -#define Log_PLATFORM_VERSION "SWAD 19.0.1 (2019-09-13)" +#define Log_PLATFORM_VERSION "SWAD 19.1 (2019-09-14)" #define CSS_FILE "swad18.138.css" #define JS_FILE "swad18.130.2.js" /* + Version 19.1: Sep 14, 2019 Game module splitted into game and match modules. (244572 lines) Version 19.0.1: Sep 13, 2019 Show results of questions in matches. (244583 lines) Version 19.0: Sep 12, 2019 Changes in behaviour of matches. (244585 lines) 3 changes necessary in database: diff --git a/swad_game.c b/swad_game.c index f8acced8..eab96be0 100644 --- a/swad_game.c +++ b/swad_game.c @@ -39,6 +39,7 @@ #include "swad_game.h" #include "swad_global.h" #include "swad_group.h" +#include "swad_match.h" #include "swad_pagination.h" #include "swad_parameter.h" #include "swad_role.h" @@ -72,62 +73,15 @@ const char *Gam_StrAnswerTypesDB[Gam_NUM_ANS_TYPES] = #define Gam_MAX_SELECTED_QUESTIONS 1000 #define Gam_MAX_BYTES_LIST_SELECTED_QUESTIONS (Gam_MAX_SELECTED_QUESTIONS * (1 + 10 + 1)) -#define Gam_NEW_MATCH_SECTION_ID "new_match" - -#define Gam_AFTER_LAST_QUESTION ((unsigned)((1UL << 31) - 1)) // 2^31 - 1, don't change this number because it is used in database to indicate that a match is finished - -#define Gam_ICON_CLOSE "fas fa-times" -#define Gam_ICON_PLAY "fas fa-play" -#define Gam_ICON_PAUSE "fas fa-pause" -#define Gam_ICON_PREVIOUS "fas fa-step-backward" -#define Gam_ICON_NEXT "fas fa-step-forward" -#define Gam_ICON_RESULTS "fas fa-chart-bar" - /*****************************************************************************/ /******************************* Private types *******************************/ /*****************************************************************************/ -#define Gam_NUM_SHOWING 4 -typedef enum - { - Gam_WORDING, // Showing only the question wording - Gam_ANSWERS, // Showing the question wording and the answers - Gam_REQUEST, // Requesting confirmation to show the results - Gam_RESULTS, // Showing the results - } Gam_Showing_t; -#define Gam_SHOWING_DEFAULT Gam_WORDING -const char *Gam_ShowingStringsDB[Gam_NUM_SHOWING] = - { - "wording", - "answers", - "request", - "results", - }; - -struct Match - { - long MchCod; - long GamCod; - long UsrCod; - time_t TimeUTC[2]; - char Title[Gam_MAX_BYTES_TITLE + 1]; - struct - { - unsigned QstInd; // 0 means that the game has not started. First question has index 1. - long QstCod; - time_t QstStartTimeUTC; - Gam_Showing_t Showing; - bool BeingPlayed; - unsigned NumPlayers; - } Status; - }; - /*****************************************************************************/ /***************************** Private variables *****************************/ /*****************************************************************************/ long Gam_CurrentGamCod = -1L; // Used as parameter in contextual links -long Gam_CurrentMchCod = -1L; // Used as parameter in contextual links unsigned Gam_CurrentQstInd = 0; // Used as parameter in contextual links /*****************************************************************************/ @@ -140,46 +94,23 @@ static void Gam_PutIconsListGames (void); static void Gam_PutIconToCreateNewGame (void); static void Gam_PutButtonToCreateNewGame (void); static void Gam_PutParamsToCreateNewGame (void); -static void Gam_ShowOneGame (long GamCod, - bool ShowOnlyThisGame, - bool ListGameQuestions, - bool PutFormNewMatch); static void Gam_WriteAuthor (struct Game *Game); static void Gam_PutFormsToRemEditOneGame (const struct Game *Game, const char *Anchor); -static void Gam_PutParams (void); -static void Gam_PutParamCurrentMchCod (void); static void Gam_PutHiddenParamOrder (void); static void Gam_GetParamOrder (void); static void Gam_GetGameTxtFromDB (long GamCod,char Txt[Cns_MAX_BYTES_TEXT + 1]); -static void Gam_PutParamMatchCod (long MchCod); -static long Gam_GetParamMatchCod (void); - static bool Gam_CheckIfSimilarGameExists (struct Game *Game); -static void Gam_ShowLstGrpsToCreateMatch (void); static void Gam_CreateGame (struct Game *Game,const char *Txt); static void Gam_UpdateGame (struct Game *Game,const char *Txt); -static void Gam_CreateGrps (long MchCod); -static void Gam_GetAndWriteNamesOfGrpsAssociatedToMatch (struct Match *Match); -static bool Gam_CheckIfIPlayThisMatchBasedOnGrps (long GamCod); -static unsigned Gam_GetNumQstsGame (long GamCod); -static void Gam_PutParamQstInd (unsigned QstInd); -static unsigned Gam_GetParamQstInd (void); -static void Gam_PutParamAnswer (unsigned AnsInd); -static unsigned Gam_GetParamAnswer (void); -static unsigned Gam_GetQstIndFromStr (const char *UnsignedStr); -static Gam_Showing_t Gam_GetShowingFromStr (const char *Str); static void Gam_RemAnswersOfAQuestion (long GamCod,unsigned QstInd); -static long Gam_GetQstCodFromQstInd (long GamCod,unsigned QstInd); static unsigned Gam_GetMaxQuestionIndexInGame (long GamCod); -static unsigned Gam_GetPrevQuestionIndexInGame (long GamCod,unsigned QstInd); -static unsigned Gam_GetNextQuestionIndexInGame (long GamCod,unsigned QstInd); static void Gam_ListGameQuestions (struct Game *Game); static void Gam_ListOneOrMoreQuestionsForEdition (long GamCod,unsigned NumQsts, MYSQL_RES *mysql_res); @@ -190,68 +121,11 @@ static void Gam_AllocateListSelectedQuestions (void); static void Gam_FreeListsSelectedQuestions (void); static unsigned Gam_CountNumQuestionsInList (void); -static unsigned Gam_GetNumAnswerers (struct Match *Match); -static unsigned Gam_GetNumUsrsWhoAnswered (long MchCod,unsigned QstInd,unsigned AnsInd); -static void Gam_DrawBarNumUsrs (unsigned NumUsrs,unsigned MaxUsrs); - static void Gam_PutParamsOneQst (void); static void Gam_ExchangeQuestions (long GamCod, unsigned QstIndTop,unsigned QstIndBottom); -static void Gam_ListMatches (struct Game *Game,bool PutFormNewMatch); -static void Gam_PutIconToPlayNewMatch (void); -static void Gam_ListOneOrMoreMatches (struct Game *Game, - unsigned NumMatches, - MYSQL_RES *mysql_res); -static void Gam_GetMatchDataFromRow (MYSQL_RES *mysql_res, - struct Match *Match); -static void Gam_PutButtonNewMatch (long GamCod); - -static void Gam_PutFormNewMatch (struct Game *Game); - -static long Gam_CreateMatch (long GamCod,char Title[Gam_MAX_BYTES_TITLE + 1]); -static void Gam_UpdateMatchStatusInDB (struct Match *Match); - -static void Gam_UpdateElapsedTimeInQuestion (struct Match *Match); -static void Gam_GetElapsedTimeInQuestion (struct Match *Match, - struct Time *Time); -static void Gam_GetElapsedTimeInMatch (struct Match *Match, - struct Time *Time); -static void Gam_GetElapsedTime (unsigned NumRows,MYSQL_RES *mysql_res, - struct Time *Time); - -static void Gam_SetMatchStatusToPrev (struct Match *Match); -static void Gam_SetMatchStatusToNext (struct Match *Match); -static void Gam_ShowMatchStatusForTch (struct Match *Match); -static void Gam_ShowMatchStatusForStd (struct Match *Match); -static void Gam_ShowLeftColumnTch (struct Match *Match); -static void Gam_ShowLeftColumnStd (struct Match *Match); -static void Gam_PutMatchControlButtons (struct Match *Match); -// static void Gam_PutCheckboxResult (struct Match *Match); -static void Gam_ShowNumQstInGame (struct Match *Match); -static void Gam_ShowNumPlayers (struct Match *Match); -static void Gam_ShowMatchTitle (struct Match *Match); -static void Gam_ShowQuestionAndAnswersTch (struct Match *Match); -static void Gam_ShowQuestionAndAnswersStd (struct Match *Match); - -static void Gam_PutBigButton (Act_Action_t NextAction,long MchCod, - const char *Icon,const char *Txt); -static void Gam_PutBigButtonOff (const char *Icon); -static void Gam_PutBigButtonClose (void); - -static void Gam_ShowWaitImage (const char *Txt); - -static void Gam_RemoveOldPlayers (void); -static void Gam_UpdateMatchAsBeingPlayed (long MchCod); -static void Gam_SetMatchAsNotBeingPlayed (long MchCod); -static bool Gam_GetIfMatchIsBeingPlayed (long MchCod); -static void Gam_RegisterMeAsPlayerInMatch (long MchCod); -static void Gam_GetNumPlayers (struct Match *Match); - -static void Gam_ShowMatchStatusForStd (struct Match *Match); -static int Gam_GetQstAnsFromDB (long MchCod,unsigned QstInd); - /*****************************************************************************/ /*************************** List all the games ******************************/ /*****************************************************************************/ @@ -467,10 +341,10 @@ void Gam_SeeOneGame (void) /******************************* Show one game *******************************/ /*****************************************************************************/ -static void Gam_ShowOneGame (long GamCod, - bool ShowOnlyThisGame, - bool ListGameQuestions, - bool PutFormNewMatch) +void Gam_ShowOneGame (long GamCod, + bool ShowOnlyThisGame, + bool ListGameQuestions, + bool PutFormNewMatch) { extern const char *Hlp_ASSESSMENT_Games; extern const char *Txt_Game; @@ -516,7 +390,7 @@ static void Gam_ShowOneGame (long GamCod, if (ShowOnlyThisGame) /* Icon to start a new match */ - Lay_PutContextualLinkOnlyIcon (ActReqNewMchTch,Gam_NEW_MATCH_SECTION_ID, + Lay_PutContextualLinkOnlyIcon (ActReqNewMchTch,Mch_NEW_MATCH_SECTION_ID, Gam_PutParams, "play.svg", Txt_New_match); @@ -630,7 +504,7 @@ static void Gam_ShowOneGame (long GamCod, if (ShowOnlyThisGame) { /***** List matches *****/ - Gam_ListMatches (&Game,PutFormNewMatch); + Mch_ListMatches (&Game,PutFormNewMatch); /***** Write questions of this game *****/ if (ListGameQuestions) @@ -690,7 +564,7 @@ static void Gam_PutFormsToRemEditOneGame (const struct Game *Game, /******************** Params used to edit/play a game ************************/ /*****************************************************************************/ -static void Gam_PutParams (void) +void Gam_PutParams (void) { if (Gam_CurrentGamCod > 0) Gam_PutParamGameCod (Gam_CurrentGamCod); @@ -699,12 +573,6 @@ static void Gam_PutParams (void) Pag_PutHiddenParamPagNum (Pag_GAMES,Gbl.Games.CurrentPage); } -static void Gam_PutParamCurrentMchCod (void) - { - if (Gam_CurrentMchCod > 0) - Gam_PutParamMatchCod (Gam_CurrentMchCod); - } - /*****************************************************************************/ /****** Put a hidden parameter with the type of order in list of games *******/ /*****************************************************************************/ @@ -975,25 +843,6 @@ long Gam_GetParamGameCod (void) return Par_GetParToLong ("GamCod"); } -/*****************************************************************************/ -/******************** Write parameter with code of match **********************/ -/*****************************************************************************/ - -static void Gam_PutParamMatchCod (long MchCod) - { - Par_PutHiddenParamLong ("MchCod",MchCod); - } - -/*****************************************************************************/ -/********************* Get parameter with code of match **********************/ -/*****************************************************************************/ - -static long Gam_GetParamMatchCod (void) - { - /***** Get code of match *****/ - return Par_GetParToLong ("MchCod"); - } - /*****************************************************************************/ /*************** Ask for confirmation of removing of a game ******************/ /*****************************************************************************/ @@ -1332,66 +1181,6 @@ void Gam_RequestCreatOrEditGame (void) Gam_ListGameQuestions (&Game); } -/*****************************************************************************/ -/***************** Show list of groups to create a new match *****************/ -/*****************************************************************************/ - -static void Gam_ShowLstGrpsToCreateMatch (void) - { - extern const char *The_ClassFormInBox[The_NUM_THEMES]; - 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.Num) - { - /***** Start box and table *****/ - fprintf (Gbl.F.Out,"" - "" - "%s:" - "" - "", - The_ClassFormInBox[Gbl.Prefs.Theme], - Txt_Groups); - Box_StartBoxTable ("95%",NULL,NULL, - NULL,Box_NOT_CLOSABLE,0); - - /***** First row: checkbox to select the whole course *****/ - fprintf (Gbl.F.Out,"" - "" - "" - "" - "", - Txt_The_whole_course,Gbl.Hierarchy.Crs.ShrtName); - - /***** List the groups for each group type *****/ - for (NumGrpTyp = 0; - NumGrpTyp < Gbl.Crs.Grps.GrpTypes.Num; - NumGrpTyp++) - if (Gbl.Crs.Grps.GrpTypes.LstGrpTypes[NumGrpTyp].NumGrps) - Grp_ListGrpsToEditAsgAttSvyMch (&Gbl.Crs.Grps.GrpTypes.LstGrpTypes[NumGrpTyp], - -1L, // -1 means "New match" - Grp_MATCH); - - /***** End table and box *****/ - Box_EndBoxTable (); - fprintf (Gbl.F.Out,"" - ""); - } - - /***** Free list of groups types and groups in this course *****/ - Grp_FreeListGrpTypesAndGrps (); - } - /*****************************************************************************/ /********************* Receive form to create a new game *******************/ /*****************************************************************************/ @@ -1548,91 +1337,6 @@ void Gam_RemoveGroupsOfType (long GrpTypCod) GrpTypCod); } -/*****************************************************************************/ -/******************* Create groups associated to a match *********************/ -/*****************************************************************************/ - -static void Gam_CreateGrps (long MchCod) - { - unsigned NumGrpSel; - - /***** Create groups associated to the match *****/ - for (NumGrpSel = 0; - NumGrpSel < Gbl.Crs.Grps.LstGrpsSel.NumGrps; - NumGrpSel++) - /* Create group */ - DB_QueryINSERT ("can not associate a group to a match", - "INSERT INTO gam_grp" - " (MchCod,GrpCod)" - " VALUES" - " (%ld,%ld)", - MchCod,Gbl.Crs.Grps.LstGrpsSel.GrpCods[NumGrpSel]); - } - -/*****************************************************************************/ -/************* Get and write the names of the groups of a match **************/ -/*****************************************************************************/ - -static void Gam_GetAndWriteNamesOfGrpsAssociatedToMatch (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 gam_grp,crs_grp,crs_grp_types" - " WHERE gam_grp.MchCod=%ld" - " AND gam_grp.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 *****/ - 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); - - fprintf (Gbl.F.Out,"
"); - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - } - /*****************************************************************************/ /************* Remove all the games of a place on the hierarchy **************/ /************* (country, institution, centre, degree or course) **************/ @@ -1674,28 +1378,11 @@ void Gam_RemoveGames (Hie_Level_t Scope,long Cod) Sco_GetDBStrFromScope (Scope),Cod); } -/*****************************************************************************/ -/************ Check if I belong to any of the groups of a match **************/ -/*****************************************************************************/ - -static bool Gam_CheckIfIPlayThisMatchBasedOnGrps (long MchCod) - { - /***** Get if I can play a match from database *****/ - return (DB_QueryCOUNT ("can not check if I can play a match", - "SELECT COUNT(*) FROM gam_matches" - " WHERE MchCod=%ld" - " AND (MchCod NOT IN (SELECT MchCod FROM gam_grp) OR" - " MchCod IN (SELECT gam_grp.MchCod FROM gam_grp,crs_grp_usr" - " WHERE crs_grp_usr.UsrCod=%ld" - " AND gam_grp.GrpCod=crs_grp_usr.GrpCod))", - MchCod,Gbl.Usrs.Me.UsrDat.UsrCod) != 0); - } - /*****************************************************************************/ /******************* Get number of questions of a game *********************/ /*****************************************************************************/ -static unsigned Gam_GetNumQstsGame (long GamCod) +unsigned Gam_GetNumQstsGame (long GamCod) { /***** Get nuumber of questions in a game from database *****/ return @@ -1736,7 +1423,7 @@ void Gam_RequestNewQuestion (void) /****************** Write parameter with index of question *******************/ /*****************************************************************************/ -static void Gam_PutParamQstInd (unsigned QstInd) +void Gam_PutParamQstInd (unsigned QstInd) { Par_PutHiddenParamUnsigned ("QstInd",QstInd); } @@ -1745,7 +1432,7 @@ static void Gam_PutParamQstInd (unsigned QstInd) /******************* Get parameter with index of question ********************/ /*****************************************************************************/ -static unsigned Gam_GetParamQstInd (void) +unsigned Gam_GetParamQstInd (void) { long LongNum; @@ -1756,35 +1443,11 @@ static unsigned Gam_GetParamQstInd (void) return (unsigned) LongNum; } -/*****************************************************************************/ -/******************* Write parameter with student's answer *******************/ -/*****************************************************************************/ - -static void Gam_PutParamAnswer (unsigned AnsInd) - { - Par_PutHiddenParamUnsigned ("Ans",AnsInd); - } - -/*****************************************************************************/ -/******************* Get parameter with student's answer *********************/ -/*****************************************************************************/ - -static unsigned Gam_GetParamAnswer (void) - { - long LongNum; - - LongNum = Par_GetParToLong ("Ans"); - if (LongNum < 0) - Lay_ShowErrorAndExit ("Wrong answer index."); - - return (unsigned) LongNum; - } - /*****************************************************************************/ /******************* Get parameter with index of question ********************/ /*****************************************************************************/ -static unsigned Gam_GetQstIndFromStr (const char *UnsignedStr) +unsigned Gam_GetQstIndFromStr (const char *UnsignedStr) { long LongNum; @@ -1793,23 +1456,6 @@ static unsigned Gam_GetQstIndFromStr (const char *UnsignedStr) 0; } -/*****************************************************************************/ -/****************** Get parameter with what is being shown *******************/ -/*****************************************************************************/ - -static Gam_Showing_t Gam_GetShowingFromStr (const char *Str) - { - Gam_Showing_t Showing; - - for (Showing = (Gam_Showing_t) 0; - Showing <= (Gam_Showing_t) (Gam_NUM_SHOWING - 1); - Showing++) - if (!strcmp (Str,Gam_ShowingStringsDB[Showing])) - return Showing; - - return (Gam_Showing_t) Gam_SHOWING_DEFAULT; - } - /*****************************************************************************/ /********************** Remove answers of a game question ********************/ /*****************************************************************************/ @@ -1827,7 +1473,7 @@ static void Gam_RemAnswersOfAQuestion (long GamCod,unsigned QstInd) /************ Get question code given game and index of question *************/ /*****************************************************************************/ -static long Gam_GetQstCodFromQstInd (long GamCod,unsigned QstInd) +long Gam_GetQstCodFromQstInd (long GamCod,unsigned QstInd) { MYSQL_RES *mysql_res; MYSQL_ROW row; @@ -1884,7 +1530,7 @@ static unsigned Gam_GetMaxQuestionIndexInGame (long GamCod) // Input question index can be 1, 2, 3... n-1 // Return question index will be 1, 2, 3... n if previous question exists, or 0 if no previous question -static unsigned Gam_GetPrevQuestionIndexInGame (long GamCod,unsigned QstInd) +unsigned Gam_GetPrevQuestionIndexInGame (long GamCod,unsigned QstInd) { MYSQL_RES *mysql_res; MYSQL_ROW row; @@ -1917,11 +1563,11 @@ static unsigned Gam_GetPrevQuestionIndexInGame (long GamCod,unsigned QstInd) // Input question index can be 0, 1, 2, 3... n-1 // Return question index will be 1, 2, 3... n if next question exists, or 0 if no next question -static unsigned Gam_GetNextQuestionIndexInGame (long GamCod,unsigned QstInd) +unsigned Gam_GetNextQuestionIndexInGame (long GamCod,unsigned QstInd) { MYSQL_RES *mysql_res; MYSQL_ROW row; - unsigned NextQstInd = Gam_AFTER_LAST_QUESTION; // End of questions has been reached + unsigned NextQstInd = Mch_AFTER_LAST_QUESTION; // End of questions has been reached /***** Get next question index in a game from database *****/ // Although indexes are always continuous... @@ -2340,95 +1986,6 @@ static unsigned Gam_CountNumQuestionsInList (void) return NumQuestions; } -/*****************************************************************************/ -/*** Get number of users who selected this answer and draw proportional bar **/ -/*****************************************************************************/ - -void Gam_GetAndDrawBarNumUsrsWhoAnswered (long MchCod,unsigned QstInd,unsigned AnsInd,unsigned NumUsrs) - { - unsigned NumUsrsThisAnswer; - - /***** Get number of users who selected this answer *****/ - NumUsrsThisAnswer = Gam_GetNumUsrsWhoAnswered (MchCod,QstInd,AnsInd); - - /***** Show stats of this answer *****/ - Gam_DrawBarNumUsrs (NumUsrsThisAnswer,NumUsrs); - } - -/*****************************************************************************/ -/***** Get number of users who have answered current question in a match *****/ -/*****************************************************************************/ - -static unsigned Gam_GetNumAnswerers (struct Match *Match) - { - /***** Get number of users who have answered the current question in a match from database *****/ - return - (unsigned) DB_QueryCOUNT ("can not get number of questions of a game", - "SELECT COUNT(*) FROM gam_answers" - " WHERE MchCod=%ld AND QstInd=%u", - Match->MchCod,Match->Status.QstInd); - } - -/*****************************************************************************/ -/**** Get number of users who selected a given answer of a game question *****/ -/*****************************************************************************/ - -static unsigned Gam_GetNumUsrsWhoAnswered (long MchCod,unsigned QstInd,unsigned AnsInd) - { - /***** Get number of users who have chosen - an answer of a question from database *****/ - return (unsigned) DB_QueryCOUNT ("can not get number of users who answered", - "SELECT COUNT(*)" - " FROM gam_answers" - " WHERE MchCod=%ld" - " AND QstInd=%u" - " AND AnsInd=%u", - MchCod,QstInd,AnsInd); - } - -/*****************************************************************************/ -/***************** Draw a bar with the percentage of answers *****************/ -/*****************************************************************************/ - -#define Gam_MAX_BAR_WIDTH 125 - -static void Gam_DrawBarNumUsrs (unsigned NumUsrs,unsigned MaxUsrs) - { - extern const char *Txt_of_PART_OF_A_TOTAL; - unsigned BarWidth = 0; - - /***** String with the number of users *****/ - if (MaxUsrs) - snprintf (Gbl.Title,sizeof (Gbl.Title), - "%u (%u%% %s %u)", - NumUsrs, - (unsigned) ((((float) NumUsrs * 100.0) / (float) MaxUsrs) + 0.5), - Txt_of_PART_OF_A_TOTAL,MaxUsrs); - else - snprintf (Gbl.Title,sizeof (Gbl.Title), - "0 (0%% %s %u)", - Txt_of_PART_OF_A_TOTAL,MaxUsrs); - - /***** Draw bar with a with proportional to the number of clicks *****/ - if (NumUsrs && MaxUsrs) - BarWidth = (unsigned) ((((float) NumUsrs * (float) Gam_MAX_BAR_WIDTH) / - (float) MaxUsrs) + 0.5); - if (BarWidth < 2) - BarWidth = 2; - fprintf (Gbl.F.Out,"\"%s\"" - " ", - Cfg_URL_ICON_PUBLIC, - Gbl.Title, - Gbl.Title, - BarWidth); - - /***** Write the number of users *****/ - fprintf (Gbl.F.Out,"%s",Gbl.Title); - } - /*****************************************************************************/ /**************** Put parameter to move/remove one question ******************/ /*****************************************************************************/ @@ -2673,522 +2230,7 @@ static void Gam_ExchangeQuestions (long GamCod, } /*****************************************************************************/ -/************************* List the matches of a game ************************/ -/*****************************************************************************/ - -static void Gam_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; - - /***** Get data of matches from database *****/ - /* Fill subquery for game */ - if (Gbl.Crs.Grps.WhichGrps == Grp_ONLY_MY_GROUPS) - { - if (asprintf (&SubQuery," AND" - "(MchCod NOT IN" - " (SELECT MchCod FROM gam_grp)" - " OR" - " MchCod IN" - " (SELECT gam_grp.MchCod" - " FROM gam_grp,crs_grp_usr" - " WHERE crs_grp_usr.UsrCod=%ld" - " AND gam_grp.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] - "UNIX_TIMESTAMP(QstStartTime)," // row[8] - "Showing" // row[9] - " FROM gam_matches" - " WHERE GamCod=%ld%s" - " ORDER BY MchCod", - Game->GamCod, - SubQuery); - - /* Free allocated memory for subquery */ - free ((void *) SubQuery); - - /***** Start box *****/ - Gam_CurrentGamCod = Game->GamCod; - Box_StartBox (NULL,Txt_Matches,Gam_PutIconToPlayNewMatch, - Hlp_ASSESSMENT_Games_matches,Box_NOT_CLOSABLE); - - if (NumMatches) - /***** Show the table with the matches *****/ - Gam_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) - Gam_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_EndBox (); - } - -/*****************************************************************************/ -/********************** Get match data using its code ************************/ -/*****************************************************************************/ - -void Gam_GetDataOfMatchByCod (struct Match *Match) - { - MYSQL_RES *mysql_res; - unsigned long NumRows; - - /***** 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] - "UNIX_TIMESTAMP(QstStartTime)," // row[8] - "Showing" // row[9] - " FROM gam_matches" - " WHERE MchCod=%ld" - " AND GamCod IN" // Extra check - " (SELECT GamCod FROM games" - " WHERE CrsCod='%ld')", - Match->MchCod, - Gbl.Hierarchy.Crs.CrsCod); - if (NumRows) // Match found... - /***** Get match data from row *****/ - Gam_GetMatchDataFromRow (mysql_res,Match); - else - { - /* Initialize to empty match */ - Match->MchCod = -1L; - Match->GamCod = -1L; - Match->UsrCod = -1L; - Match->TimeUTC[Gam_START_TIME] = - Match->TimeUTC[Gam_END_TIME ] = (time_t) 0; - Match->Title[0] = '\0'; - Match->Status.QstInd = 0; - Match->Status.QstCod = -1L; - Match->Status.QstStartTimeUTC = (time_t) 0; - Match->Status.Showing = Gam_WORDING; - Match->Status.BeingPlayed = false; - } - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - } - -/*****************************************************************************/ -/***************** Put icon to add a new questions to game *******************/ -/*****************************************************************************/ - -static void Gam_PutIconToPlayNewMatch (void) - { - extern const char *Txt_New_match; - - /***** Put form to create a new question *****/ - Ico_PutContextualIconToAdd (ActReqNewMchTch,Gam_NEW_MATCH_SECTION_ID,Gam_PutParams, - Txt_New_match); - } - -/*****************************************************************************/ -/*********************** List game matches for edition ***********************/ -/*****************************************************************************/ - -static void Gam_ListOneOrMoreMatches (struct Game *Game, - unsigned NumMatches, - MYSQL_RES *mysql_res) - { - extern const char *Txt_No_INDEX; - 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_Status; - extern const char *Txt_Play; - extern const char *Txt_Resume; - extern const char *Txt_Today; - unsigned NumMatch; - unsigned UniqueId; - struct Match Match; - - /***** Write the heading *****/ - Tbl_StartTableWideMargin (2); - fprintf (Gbl.F.Out,"" - "" - "" - "%s" - "" - "" - "%s" - "" - "" - "%s" - "" - "" - "%s" - "" - "" - "%s" - "" - "" - "%s" - "" - "", - Txt_No_INDEX, - Txt_ROLES_SINGUL_Abc[Rol_TCH][Usr_SEX_UNKNOWN], - Txt_START_END_TIME[Gam_ORDER_BY_START_DATE], - Txt_START_END_TIME[Gam_ORDER_BY_END_DATE], - Txt_Match, - Txt_Status); - - /***** Write rows *****/ - for (NumMatch = 0, UniqueId = 1; - NumMatch < NumMatches; - NumMatch++, UniqueId++) - { - Gbl.RowEvenOdd = NumMatch % 2; - - /***** Get match data from row *****/ - Gam_GetMatchDataFromRow (mysql_res,&Match); - - /***** Icons *****/ - fprintf (Gbl.F.Out,"" - "",Gbl.RowEvenOdd); - - /* Put icon to remove the match */ - Frm_StartForm (ActReqRemMchTch); - Gam_PutParamMatchCod (Match.MchCod); - Ico_PutIconRemove (); - Frm_EndForm (); - - fprintf (Gbl.F.Out,""); - - /***** Number of match ******/ - fprintf (Gbl.F.Out,"%u", - Gbl.RowEvenOdd,NumMatch + 1); - - /***** Match player *****/ - fprintf (Gbl.F.Out,"", - Gbl.RowEvenOdd); - Usr_WriteAuthor1Line (Match.UsrCod,false); - fprintf (Gbl.F.Out,""); - - /***** Start date/time *****/ - fprintf (Gbl.F.Out,"", - UniqueId, - Match.Status.QstInd >= Gam_AFTER_LAST_QUESTION ? "DATE_RED" : - "DATE_GREEN", - Gbl.RowEvenOdd); - fprintf (Gbl.F.Out,"" - "", - UniqueId,Match.TimeUTC[Gam_START_TIME], - (unsigned) Gbl.Prefs.DateFormat,Txt_Today); - - /***** End date/time *****/ - fprintf (Gbl.F.Out,"", - UniqueId, - Match.Status.QstInd >= Gam_AFTER_LAST_QUESTION ? "DATE_RED" : - "DATE_GREEN", - Gbl.RowEvenOdd); - fprintf (Gbl.F.Out,"\">" - "" - "", - UniqueId,Match.TimeUTC[Gam_END_TIME], - (unsigned) Gbl.Prefs.DateFormat,Txt_Today); - - /***** Title and groups *****/ - fprintf (Gbl.F.Out,"",Gbl.RowEvenOdd); - - /* Title */ - fprintf (Gbl.F.Out,"%s",Match.Title); - - /* Groups whose students can answer this match */ - if (Gbl.Crs.Grps.NumGrps) - Gam_GetAndWriteNamesOfGrpsAssociatedToMatch (&Match); - - fprintf (Gbl.F.Out,""); - - /***** Match status ******/ - fprintf (Gbl.F.Out,"",Gbl.RowEvenOdd); - - if (Match.Status.QstInd < Gam_AFTER_LAST_QUESTION) // Unfinished match - /* Current question index / total of questions */ - fprintf (Gbl.F.Out,"
%u/%u
", - Match.Status.QstInd,Game->NumQsts); - - switch (Gbl.Usrs.Me.Role.Logged) - { - case Rol_STD: - /* Icon to play as student */ - Gam_CurrentMchCod = Match.MchCod; - Lay_PutContextualLinkOnlyIcon (ActPlyMchStd,NULL, - Gam_PutParamCurrentMchCod, - Match.Status.QstInd < Gam_AFTER_LAST_QUESTION ? "play.svg" : - "flag-checkered.svg", - Txt_Play); - break; - case Rol_NET: - case Rol_TCH: - case Rol_DEG_ADM: - case Rol_CTR_ADM: - case Rol_INS_ADM: - case Rol_SYS_ADM: - /* Icon to resume */ - Gam_CurrentMchCod = Match.MchCod; - Lay_PutContextualLinkOnlyIcon (ActResMchTch,NULL, - Gam_PutParamCurrentMchCod, - Match.Status.QstInd < Gam_AFTER_LAST_QUESTION ? "play.svg" : - "flag-checkered.svg", - Txt_Resume); - break; - default: - break; - } - - fprintf (Gbl.F.Out,""); - - fprintf (Gbl.F.Out,""); - } - - /***** End table *****/ - Tbl_EndTable (); - } - -/*****************************************************************************/ -/******************** Get game data from a database row **********************/ -/*****************************************************************************/ - -static void Gam_GetMatchDataFromRow (MYSQL_RES *mysql_res, - struct Match *Match) - { - MYSQL_ROW row; - - /***** 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 date (row[3] holds the start UTC time) */ - Match->TimeUTC[Gam_START_TIME] = Dat_GetUNIXTimeFromStr (row[3]); - - /* Get end date (row[4] holds the end UTC time) */ - Match->TimeUTC[Gam_END_TIME ] = Dat_GetUNIXTimeFromStr (row[4]); - - /* 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] UNIX_TIMESTAMP(QstStartTime) - row[9] Showing - */ - /* 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 question start date (row[8] holds the start UTC time) */ - Match->Status.QstStartTimeUTC = Dat_GetUNIXTimeFromStr (row[8]); - - /* Get what to show (stem, answers, results) (row(9)) */ - Match->Status.Showing = Gam_GetShowingFromStr (row[9]); - - /***** Get whether the match is being played or not *****/ - if (Match->Status.QstInd >= Gam_AFTER_LAST_QUESTION) // Finished - Match->Status.BeingPlayed = false; - else // Unfinished - Match->Status.BeingPlayed = Gam_GetIfMatchIsBeingPlayed (Match->MchCod); - } - -/*****************************************************************************/ -/************** Request the removal of a match (game instance) ***************/ -/*****************************************************************************/ - -void Gam_RequestRemoveMatchTch (void) - { - extern const char *Txt_Do_you_really_want_to_remove_the_match_X; - extern const char *Txt_Remove_match; - struct Match Match; - - /***** Get parameters *****/ - /* Get match code */ - if ((Match.MchCod = Gam_GetParamMatchCod ()) == -1L) - Lay_ShowErrorAndExit ("Code of match is missing."); - - /***** Get data of the match from database *****/ - Gam_GetDataOfMatchByCod (&Match); - - /***** Show question and button to remove question *****/ - Gam_CurrentMchCod = Match.MchCod; - Ale_ShowAlertAndButton (ActRemMchTch,NULL,NULL,Gam_PutParamCurrentMchCod, - 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 Gam_RemoveMatchTch (void) - { - extern const char *Txt_Match_X_removed; - struct Match Match; - - /***** Get parameters *****/ - /* Get match code */ - if ((Match.MchCod = Gam_GetParamMatchCod ()) == -1L) - Lay_ShowErrorAndExit ("Code of match is missing."); - - /***** Get data of the match from database *****/ - Gam_GetDataOfMatchByCod (&Match); - - /***** Remove the match from all the tables *****/ - /* Remove match players */ - DB_QueryDELETE ("can not remove match players", - "DELETE FROM gam_players" - " USING gam_players,gam_matches,games" - " WHERE gam_players.MchCod=%ld" - " AND gam_players.MchCod=gam_matches.MchCod" - " AND gam_matches.GamCod=games.GamCod" - " AND games.CrsCod=%ld", // Extra check - Match.MchCod,Gbl.Hierarchy.Crs.CrsCod); - - /* Remove match from list of matches being played */ - DB_QueryDELETE ("can not remove match from matches being played", - "DELETE FROM gam_mch_being_played" - " USING gam_mch_being_played,gam_matches,games" - " WHERE gam_mch_being_played.MchCod=%ld" - " AND gam_mch_being_played.MchCod=gam_matches.MchCod" - " AND gam_matches.GamCod=games.GamCod" - " AND games.CrsCod=%ld", // Extra check - Match.MchCod,Gbl.Hierarchy.Crs.CrsCod); - - /* Remove students' answers to match */ - DB_QueryDELETE ("can not remove students' answers associated to a match", - "DELETE FROM gam_answers" - " USING gam_answers,gam_matches,games" - " WHERE gam_answers.MchCod=%ld" - " AND gam_answers.MchCod=gam_matches.MchCod" - " AND gam_matches.GamCod=games.GamCod" - " AND games.CrsCod=%ld", // Extra check - Match.MchCod,Gbl.Hierarchy.Crs.CrsCod); - - /* Remove groups associated to the match */ - DB_QueryDELETE ("can not remove the groups associated to a match", - "DELETE FROM gam_grp" - " USING gam_grp,gam_matches,games" - " WHERE gam_grp.MchCod=%ld" - " AND gam_grp.MchCod=gam_matches.MchCod" - " AND gam_matches.GamCod=games.GamCod" - " AND games.CrsCod=%ld", // Extra check - Match.MchCod,Gbl.Hierarchy.Crs.CrsCod); - - /* Remove the match itself */ - DB_QueryDELETE ("can not remove a match", - "DELETE FROM gam_matches" - " USING gam_matches,games" - " WHERE gam_matches.MchCod=%ld" - " AND gam_matches.GamCod=games.GamCod" - " AND games.CrsCod=%ld", // Extra check - Match.MchCod,Gbl.Hierarchy.Crs.CrsCod); - if (!mysql_affected_rows (&Gbl.mysql)) - Lay_ShowErrorAndExit ("The match to be removed does not exist."); - - /***** 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 - } - -/*****************************************************************************/ -/********************* Put button to create a new match **********************/ -/*****************************************************************************/ - -static void Gam_PutButtonNewMatch (long GamCod) - { - extern const char *Txt_New_match; - - Frm_StartFormAnchor (ActReqNewMchTch,Gam_NEW_MATCH_SECTION_ID); - Gam_PutParamGameCod (GamCod); - Btn_PutConfirmButton (Txt_New_match); - Frm_EndForm (); - } - -/*****************************************************************************/ -/******************* Start playing a game as a teacher ***********************/ +/************* Request the creation of a new match as a teacher **************/ /*****************************************************************************/ void Gam_RequestNewMatchTch (void) @@ -3211,1405 +2253,6 @@ void Gam_RequestNewMatchTch (void) true); // Put form to start new match } -/*****************************************************************************/ -/****** Put a big button to play match (start a new match) as a teacher ******/ -/*****************************************************************************/ - -static void Gam_PutFormNewMatch (struct Game *Game) - { - extern const char *Hlp_ASSESSMENT_Games_new_match; - 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 *****/ - Lay_StartSection (Gam_NEW_MATCH_SECTION_ID); - - /***** Start form *****/ - Frm_StartForm (ActNewMchTch); - Gam_PutParamGameCod (Game->GamCod); - Gam_PutParamQstInd (0); // Start by first question in game - - /***** Start box and table *****/ - Box_StartBoxTable (NULL,Txt_New_match,NULL, - Hlp_ASSESSMENT_Games_new_match,Box_NOT_CLOSABLE,2); - - /***** Match title *****/ - fprintf (Gbl.F.Out,"" - "" - "" - "" - "" - "" - "" - "", - The_ClassFormInBox[Gbl.Prefs.Theme], - Txt_Title, - Gam_MAX_CHARS_TITLE,Game->Title); - - /***** Groups *****/ - Gam_ShowLstGrpsToCreateMatch (); - - /***** End table *****/ - Tbl_EndTable (); - - /***** Put icon with link *****/ - Frm_LinkFormSubmit (Txt_Play,NULL,NULL); - fprintf (Gbl.F.Out,"\"%s\"", - Cfg_URL_ICON_PUBLIC,Txt_Play,Txt_Play); - fprintf (Gbl.F.Out,""); - - /***** End box *****/ - Box_EndBox (); - - /***** End form *****/ - Frm_EndForm (); - - /***** End section for a new match *****/ - Lay_EndSection (); - } - -/*****************************************************************************/ -/********************* Create a new match (by a teacher) *********************/ -/*****************************************************************************/ - -void Gam_CreateNewMatchTch (void) - { - long GamCod; - char Title[Gam_MAX_BYTES_TITLE + 1]; - - /***** Get form parameters *****/ - /* Get match code */ - if ((GamCod = Gam_GetParamGameCod ()) == -1L) - Lay_ShowErrorAndExit ("Code of game is missing."); - - /* Get match title */ - Par_GetParToText ("Title",Title,Gam_MAX_BYTES_TITLE); - - /* Get groups for this games */ - Grp_GetParCodsSeveralGrps (); - - /***** Create a new match *****/ - Gbl.Games.MchCodBeingPlayed = Gam_CreateMatch (GamCod,Title); - - /***** Free memory for list of selected groups *****/ - Grp_FreeListCodSelectedGrps (); - } - -/*****************************************************************************/ -/******* Show button to actually start / resume a match (by a teacher) *******/ -/*****************************************************************************/ - -void Gam_RequestStartResumeMatchTch (void) - { - struct Match Match; - - /***** Remove old players. - This function must be called before getting match status. *****/ - Gam_RemoveOldPlayers (); - - /***** Get data of the match from database *****/ - Match.MchCod = Gbl.Games.MchCodBeingPlayed; - Gam_GetDataOfMatchByCod (&Match); - - /***** Update match status in database *****/ - Gam_UpdateMatchStatusInDB (&Match); - - /***** Show current match status *****/ - fprintf (Gbl.F.Out,"
"); - Gam_ShowMatchStatusForTch (&Match); - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/********************** Create a new match in a game *************************/ -/*****************************************************************************/ - -static long Gam_CreateMatch (long GamCod,char Title[Gam_MAX_BYTES_TITLE + 1]) - { - long MchCod; - - /***** Insert this new match into database *****/ - MchCod = DB_QueryINSERTandReturnCode ("can not create match", - "INSERT gam_matches" - " (GamCod,UsrCod,StartTime,EndTime,Title,ShowResults," - "QstInd,QstCod,QstStartTime,Showing)" - " VALUES" - " (%ld," // GamCod - "%ld," // UsrCod - "NOW()," // StartTime - "NOW()," // EndTime - "'%s'," // Title - "'N'," // ShowResults: Don't show results initially - "0," // QstInd: Match has not started, so not the first question yet - "-1," // QstCod: Non-existent question - "NOW()," // QstStartTime - "'%s'", // What is being shown - GamCod, - Gbl.Usrs.Me.UsrDat.UsrCod, // Game creator - Title, - Gam_ShowingStringsDB[Gam_SHOWING_DEFAULT]); - - /***** Create groups associated to the match *****/ - if (Gbl.Crs.Grps.LstGrpsSel.NumGrps) - Gam_CreateGrps (MchCod); - - return MchCod; - } - -/*****************************************************************************/ -/***************** Insert/update a game match being played *******************/ -/*****************************************************************************/ - -static void Gam_UpdateMatchStatusInDB (struct Match *Match) - { - /***** Update match status in database *****/ - DB_QueryUPDATE ("can not update match being played", - "UPDATE gam_matches,games" - " SET gam_matches.EndTime=NOW()," - "gam_matches.QstInd=%u," - "gam_matches.QstCod=%ld," - "gam_matches.QstStartTime=NOW()," - "gam_matches.Showing='%s'" - " WHERE gam_matches.MchCod=%ld" - " AND gam_matches.GamCod=games.GamCod" - " AND games.CrsCod=%ld", // Extra check - Match->Status.QstInd,Match->Status.QstCod, - Gam_ShowingStringsDB[Match->Status.Showing], - Match->MchCod,Gbl.Hierarchy.Crs.CrsCod); - - if (Match->Status.BeingPlayed) - /* Update match as being played */ - Gam_UpdateMatchAsBeingPlayed (Match->MchCod); - else - /* Update match as not being played */ - Gam_SetMatchAsNotBeingPlayed (Match->MchCod); - } - -/*****************************************************************************/ -/********** Update elapsed time in current question (by a teacher) ***********/ -/*****************************************************************************/ - -static void Gam_UpdateElapsedTimeInQuestion (struct Match *Match) - { - /***** Update elapsed time in current question in database *****/ - if (Match->Status.BeingPlayed && - Match->Status.QstInd > 0 && - Match->Status.QstInd < Gam_AFTER_LAST_QUESTION) - DB_QueryINSERT ("can not update elapsed time in question", - "INSERT INTO gam_time (MchCod,QstInd,ElapsedTime)" - " VALUES (%ld,%u,SEC_TO_TIME(%u))" - " ON DUPLICATE KEY" - " UPDATE ElapsedTime=ADDTIME(ElapsedTime,SEC_TO_TIME(%u))", - Match->MchCod,Match->Status.QstInd, - Cfg_SECONDS_TO_REFRESH_GAME, - Cfg_SECONDS_TO_REFRESH_GAME); - } - -/*****************************************************************************/ -/******************* Get elapsed time in a match question ********************/ -/*****************************************************************************/ - -static void Gam_GetElapsedTimeInQuestion (struct Match *Match, - struct Time *Time) - { - MYSQL_RES *mysql_res; - unsigned NumRows; - - /***** Query database *****/ - NumRows = (unsigned) DB_QuerySELECT (&mysql_res,"can not get elapsed time", - "SELECT ElapsedTime" - " FROM gam_time" - " WHERE MchCod=%ld AND QstInd=%u", - Match->MchCod,Match->Status.QstInd); - - /***** Get elapsed time from query result *****/ - Gam_GetElapsedTime (NumRows,mysql_res,Time); - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - } - -/*****************************************************************************/ -/*********************** Get elapsed time in a match *************************/ -/*****************************************************************************/ - -static void Gam_GetElapsedTimeInMatch (struct Match *Match, - struct Time *Time) - { - MYSQL_RES *mysql_res; - unsigned NumRows; - - /***** Query database *****/ - NumRows = (unsigned) DB_QuerySELECT (&mysql_res,"can not get elapsed time", - "SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(ElapsedTime)))" - " FROM gam_time WHERE MchCod=%ld", - Match->MchCod); - - /***** Get elapsed time from query result *****/ - Gam_GetElapsedTime (NumRows,mysql_res,Time); - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - } - -/*****************************************************************************/ -/*********************** Get elapsed time in a match *************************/ -/*****************************************************************************/ - -static void Gam_GetElapsedTime (unsigned NumRows,MYSQL_RES *mysql_res, - struct 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; - } - -/*****************************************************************************/ -/********************* Pause current match (by a teacher) ********************/ -/*****************************************************************************/ - -void Gam_PauseMatchTch (void) - { - struct Match Match; - - /***** Remove old players. - This function must be called before getting match status. *****/ - Gam_RemoveOldPlayers (); - - /***** Get data of the match from database *****/ - Match.MchCod = Gbl.Games.MchCodBeingPlayed; - Gam_GetDataOfMatchByCod (&Match); - - /***** Update status *****/ - Match.Status.BeingPlayed = false; // Resume match - - /***** Update match status in database *****/ - Gam_UpdateMatchStatusInDB (&Match); - - /***** Show current match status *****/ - fprintf (Gbl.F.Out,"
"); - Gam_ShowMatchStatusForTch (&Match); - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/** Show current match status (current question, answers...) (by a teacher) **/ -/*****************************************************************************/ - -void Gam_ResumeMatchTch (void) - { - struct Match Match; - - /***** Remove old players. - This function must be called before getting match status. *****/ - Gam_RemoveOldPlayers (); - - /***** Get data of the match from database *****/ - Match.MchCod = Gbl.Games.MchCodBeingPlayed; - Gam_GetDataOfMatchByCod (&Match); - - /***** If not yet finished, update status *****/ - if (Match.Status.QstInd < Gam_AFTER_LAST_QUESTION) // Unfinished - { - if (Match.Status.QstInd == 0) // Match has been created, but it has not started - Gam_SetMatchStatusToNext (&Match); - Match.Status.BeingPlayed = true; // Start/resume match - } - - /***** Update match status in database *****/ - Gam_UpdateMatchStatusInDB (&Match); - - /***** Show current match status *****/ - fprintf (Gbl.F.Out,"
"); - Gam_ShowMatchStatusForTch (&Match); - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/** Show stem, hiding answers, of current question in a match (by a teacher) */ -/*****************************************************************************/ -/* -void Gam_ShowStemQstMatchTch (void) - { - struct Match Match; - - ***** Remove old players. - This function must be called before getting match status. ***** - Gam_RemoveOldPlayers (); - - ***** Get data of the match from database ***** - Match.MchCod = Gbl.Games.MchCodBeingPlayed; - Gam_GetDataOfMatchByCod (&Match); - - ***** Update status ***** - Match.Status.Showing = Gam_WORDING; // Show only the stem - - ***** Update match status in database ***** - Gam_UpdateMatchStatusInDB (&Match); - - ***** Show current match status ***** - fprintf (Gbl.F.Out,"
"); - Gam_ShowMatchStatusForTch (&Match); - fprintf (Gbl.F.Out,"
"); - } -*/ -/*****************************************************************************/ -/**** Show stem and answers of current question in a match (by a teacher) ****/ -/*****************************************************************************/ -/* -void Gam_ShowAnssQstMatchTch (void) - { - struct Match Match; - - ***** Remove old players. - This function must be called before getting match status. ***** - Gam_RemoveOldPlayers (); - - ***** Get data of the match from database ***** - Match.MchCod = Gbl.Games.MchCodBeingPlayed; - Gam_GetDataOfMatchByCod (&Match); - - ***** Update status ***** - Match.Status.Showing = Gam_ANSWERS; // Show answers - - ***** Update match status in database ***** - Gam_UpdateMatchStatusInDB (&Match); - - ***** Show current match status ***** - fprintf (Gbl.F.Out,"
"); - Gam_ShowMatchStatusForTch (&Match); - fprintf (Gbl.F.Out,"
"); - } -*/ -/*****************************************************************************/ -/******** Show results of current question in a match (by a teacher) *********/ -/*****************************************************************************/ - -void Gam_ShowResultsQstMatchTch (void) - { - struct Match Match; - - /***** Remove old players. - This function must be called before getting match status. *****/ - Gam_RemoveOldPlayers (); - - /***** Get data of the match from database *****/ - Match.MchCod = Gbl.Games.MchCodBeingPlayed; - Gam_GetDataOfMatchByCod (&Match); - - /***** Update status *****/ - Match.Status.Showing = Gam_RESULTS; // Show results - - /***** Update match status in database *****/ - Gam_UpdateMatchStatusInDB (&Match); - - /***** Show current match status *****/ - fprintf (Gbl.F.Out,"
"); - Gam_ShowMatchStatusForTch (&Match); - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/************* Show previous question in a match (by a teacher) **************/ -/*****************************************************************************/ - -void Gam_BackMatchTch (void) - { - struct Match Match; - - /***** Remove old players. - This function must be called before getting match status. *****/ - Gam_RemoveOldPlayers (); - - /***** Get data of the match from database *****/ - Match.MchCod = Gbl.Games.MchCodBeingPlayed; - Gam_GetDataOfMatchByCod (&Match); - - /***** Update status *****/ - Gam_SetMatchStatusToPrev (&Match); - - /***** Update match status in database *****/ - Gam_UpdateMatchStatusInDB (&Match); - - /***** Show current match status *****/ - fprintf (Gbl.F.Out,"
"); - Gam_ShowMatchStatusForTch (&Match); - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/*************** Show next question in a match (by a teacher) ****************/ -/*****************************************************************************/ - -void Gam_ForwardMatchTch (void) - { - struct Match Match; - - /***** Remove old players. - This function must be called before getting match status. *****/ - Gam_RemoveOldPlayers (); - - /***** Get data of the match from database *****/ - Match.MchCod = Gbl.Games.MchCodBeingPlayed; - Gam_GetDataOfMatchByCod (&Match); - - /***** Update status *****/ - Gam_SetMatchStatusToNext (&Match); - - /***** Update match status in database *****/ - Gam_UpdateMatchStatusInDB (&Match); - - /***** Show current match status *****/ - fprintf (Gbl.F.Out,"
"); - Gam_ShowMatchStatusForTch (&Match); - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/************** Show current question in a match (by a teacher) **************/ -/*****************************************************************************/ -/* -void Gam_CurrQstMatchTch (void) - { - struct Match Match; - - ***** Remove old players. - This function must be called before getting match status. ***** - Gam_RemoveOldPlayers (); - - ***** Get data of the match from database ***** - Match.MchCod = Gbl.Games.MchCodBeingPlayed; - Gam_GetDataOfMatchByCod (&Match); - - ***** Toggle display of results ***** - Match.Status.ShowResults = !Match.Status.ShowResults; - - ***** Update match status in database ***** - Gam_UpdateMatchStatusInDB (&Match); - - ***** Show current match status ***** - fprintf (Gbl.F.Out,"
"); - Gam_ShowMatchStatusForTch (&Match); - fprintf (Gbl.F.Out,"
"); - } -*/ -/*****************************************************************************/ -/************** Set match status to previous (backward) status ***************/ -/*****************************************************************************/ - -static void Gam_SetMatchStatusToPrev (struct Match *Match) - { - /***** What to show *****/ - switch (Match->Status.Showing) - { - case Gam_WORDING: - Match->Status.Showing = Gam_REQUEST; - - /***** Get index of the previous question *****/ - Match->Status.QstInd = Gam_GetPrevQuestionIndexInGame (Match->GamCod, - Match->Status.QstInd); - if (Match->Status.QstInd == 0) // Start of questions has been reached - { - Match->Status.QstCod = -1L; // No previous questions - Match->Status.BeingPlayed = false; // Match is not being played - } - else - Match->Status.QstCod = Gam_GetQstCodFromQstInd (Match->GamCod, - Match->Status.QstInd); - break; - case Gam_ANSWERS: - Match->Status.Showing = Gam_WORDING; - break; - case Gam_REQUEST: - Match->Status.Showing = Gam_ANSWERS; - break; - case Gam_RESULTS: - Match->Status.Showing = Gam_REQUEST; - break; - } - } - -/*****************************************************************************/ -/**************** Set match status to next (forward) status ******************/ -/*****************************************************************************/ - -static void Gam_SetMatchStatusToNext (struct Match *Match) - { - /***** What to show *****/ - switch (Match->Status.Showing) - { - case Gam_WORDING: - Match->Status.Showing = Gam_ANSWERS; - break; - case Gam_ANSWERS: - Match->Status.Showing = Gam_REQUEST; - break; - case Gam_REQUEST: - case Gam_RESULTS: - Match->Status.Showing = Gam_WORDING; - - /***** Get index of the next question *****/ - Match->Status.QstInd = Gam_GetNextQuestionIndexInGame (Match->GamCod, - Match->Status.QstInd); - if (Match->Status.QstInd < Gam_AFTER_LAST_QUESTION) // Unfinished - Match->Status.QstCod = Gam_GetQstCodFromQstInd (Match->GamCod, - Match->Status.QstInd); - else // Finished - { - Match->Status.QstCod = -1L; // No more questions - Match->Status.BeingPlayed = false; - } - break; - } - } - -/*****************************************************************************/ -/******* Show current match status (number, question, answers, button) *******/ -/*****************************************************************************/ - -static void Gam_ShowMatchStatusForTch (struct Match *Match) - { - /***** Get current number of players *****/ - Gam_GetNumPlayers (Match); - - /***** Left column *****/ - Gam_ShowLeftColumnTch (Match); - - /***** Right column *****/ - /* Start right container */ - fprintf (Gbl.F.Out,"
"); - - /* Top row: match title */ - Gam_ShowMatchTitle (Match); - - /* Bottom row: current question and possible answers */ - Gam_ShowQuestionAndAnswersTch (Match); - - /* End right container */ - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/************ Show current question being played for a student ***************/ -/*****************************************************************************/ - -static void Gam_ShowMatchStatusForStd (struct Match *Match) - { - extern const char *Txt_Please_wait_; - bool IBelongToGroups; - - /***** Do I belong to valid groups to play this match? *****/ - IBelongToGroups = Gbl.Usrs.Me.IBelongToCurrentCrs && - Gam_CheckIfIPlayThisMatchBasedOnGrps (Match->MchCod); - if (!IBelongToGroups) - Lay_ShowErrorAndExit ("You can not play this match!"); - - /***** Get current number of players *****/ - Gam_GetNumPlayers (Match); - - /***** Left column *****/ - Gam_ShowLeftColumnStd (Match); - - /***** Right column *****/ - /* Start right container */ - fprintf (Gbl.F.Out,"
"); - - /***** Top row *****/ - Gam_ShowMatchTitle (Match); - - /***** Bottom row *****/ - if (Match->Status.QstInd < Gam_AFTER_LAST_QUESTION) // Unfinished - { - fprintf (Gbl.F.Out,"
"); - - /***** Update players ******/ - Gam_RegisterMeAsPlayerInMatch (Match->MchCod); - - if (Match->Status.BeingPlayed) - /* Show current question and possible answers */ - Gam_ShowQuestionAndAnswersStd (Match); - else // Not being played - Gam_ShowWaitImage (Txt_Please_wait_); - - fprintf (Gbl.F.Out,"
"); - } - - /* End right container */ - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/******** Show left botton column when playing a match (as a teacher) ********/ -/*****************************************************************************/ - -static void Gam_ShowLeftColumnTch (struct Match *Match) - { - extern const char *Txt_MATCH_respond; - struct Time Time; - unsigned NumAnswerers; - - /***** Start left container *****/ - fprintf (Gbl.F.Out,"
"); - - /***** Top *****/ - fprintf (Gbl.F.Out,"
"); - - /* Write elapsed time in match */ - Gam_GetElapsedTimeInMatch (Match,&Time); - Dat_WriteHoursMinutesSeconds (&Time); - - fprintf (Gbl.F.Out,"
"); - - /***** Write number of question *****/ - Gam_ShowNumQstInGame (Match); - - /***** Write elapsed time in question *****/ - fprintf (Gbl.F.Out,"
"); - if (Match->Status.QstInd > 0 && - Match->Status.QstInd < Gam_AFTER_LAST_QUESTION) - { - Gam_GetElapsedTimeInQuestion (Match,&Time); - Dat_WriteHoursMinutesSeconds (&Time); - } - else - fprintf (Gbl.F.Out,"-"); - fprintf (Gbl.F.Out,"
"); - - /***** Buttons *****/ - Gam_PutMatchControlButtons (Match); - - /***** Checkbox to show results *****/ - // Gam_PutCheckboxResult (Match); - - /***** Number of players *****/ - Gam_ShowNumPlayers (Match); - - /***** Number of users who have answered *****/ - if (Match->Status.BeingPlayed) - { - NumAnswerers = Gam_GetNumAnswerers (Match); - fprintf (Gbl.F.Out,"
" - "%s
" - "%u/%u" - "
", - Txt_MATCH_respond, - NumAnswerers,Match->Status.NumPlayers); - } - - /***** End left container *****/ - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/******** Show left botton column when playing a match (as a student) ********/ -/*****************************************************************************/ - -static void Gam_ShowLeftColumnStd (struct Match *Match) - { - /***** Start left container *****/ - fprintf (Gbl.F.Out,"
"); - - /***** Top *****/ - fprintf (Gbl.F.Out,"
"); - - /***** Write number of question *****/ - Gam_ShowNumQstInGame (Match); - - /***** End left container *****/ - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/********************** Put buttons to control a match ***********************/ -/*****************************************************************************/ - -static void Gam_PutMatchControlButtons (struct 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; - - /***** Start buttons container *****/ - fprintf (Gbl.F.Out,"
"); - - /***** Left button *****/ - fprintf (Gbl.F.Out,"
"); - if (Match->Status.QstInd == 0) - /* Put button to close browser tab */ - Gam_PutBigButtonClose (); - else - /* Put button to go back */ - Gam_PutBigButton (ActBckMchTch,Match->MchCod, - Gam_ICON_PREVIOUS,Txt_Go_back); - fprintf (Gbl.F.Out,"
"); - - /***** Center button *****/ - fprintf (Gbl.F.Out,"
"); - if (Match->Status.BeingPlayed) // Being played - /* Put button to pause match */ - Gam_PutBigButton (ActPauMchTch, - Match->MchCod, - Gam_ICON_PAUSE,Txt_Pause); - else // Paused - { - if (Match->Status.QstInd < Gam_AFTER_LAST_QUESTION) // Not finished - /* Put button to play match */ - Gam_PutBigButton (ActPlyMchTch, - Match->MchCod, - Gam_ICON_PLAY,Match->Status.QstInd == 0 ? Txt_Start : - Txt_Resume); - else // Finished - /* Put disabled button to play match */ - Gam_PutBigButtonOff (Gam_ICON_PLAY); - } - fprintf (Gbl.F.Out,"
"); - - /***** Right button *****/ - fprintf (Gbl.F.Out,"
"); - if (Match->Status.QstInd >= Gam_AFTER_LAST_QUESTION) // Finished - /* Put button to close browser tab */ - Gam_PutBigButtonClose (); - else - /* Put button to show answers */ - Gam_PutBigButton (ActFwdMchTch,Match->MchCod, - Gam_ICON_NEXT,Txt_Go_forward); - fprintf (Gbl.F.Out,"
"); - - /***** End buttons container *****/ - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/********************* Show number of question in game ***********************/ -/*****************************************************************************/ - -static void Gam_ShowNumQstInGame (struct Match *Match) - { - extern const char *Txt_MATCH_Start; - extern const char *Txt_MATCH_End; - unsigned NumQsts = Gam_GetNumQstsGame (Match->GamCod); - - fprintf (Gbl.F.Out,"
"); - if (Match->Status.QstInd == 0) // Not started - fprintf (Gbl.F.Out,"%s",Txt_MATCH_Start); - else if (Match->Status.QstInd >= Gam_AFTER_LAST_QUESTION) // Finished - fprintf (Gbl.F.Out,"%s",Txt_MATCH_End); - else - fprintf (Gbl.F.Out,"%u/%u",Match->Status.QstInd,NumQsts); - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/************************** Show number of players ***************************/ -/*****************************************************************************/ - -static void Gam_ShowNumPlayers (struct Match *Match) - { - extern const char *Txt_Players; - - fprintf (Gbl.F.Out,"
" - "%s
" - "%u" - "
", - Txt_Players,Match->Status.NumPlayers); - } - -/*****************************************************************************/ -/***************************** Show match title ******************************/ -/*****************************************************************************/ - -static void Gam_ShowMatchTitle (struct Match *Match) - { - /***** Match title *****/ - fprintf (Gbl.F.Out,"
%s
",Match->Title); - } - -/*****************************************************************************/ -/***** Show question and its answers when playing a match (as a teacher) *****/ -/*****************************************************************************/ - -static void Gam_ShowQuestionAndAnswersTch (struct Match *Match) - { - extern const char *Txt_MATCH_Paused; - extern const char *Txt_View_results; - MYSQL_RES *mysql_res; - MYSQL_ROW row; - - /***** Trivial check: question index should be correct *****/ - if (Match->Status.QstInd == 0 || - Match->Status.QstInd >= Gam_AFTER_LAST_QUESTION) - return; - - /***** Get data of question from database *****/ - if (!DB_QuerySELECT (&mysql_res,"can not get data of a question", - "SELECT AnsType," // row[0] - "Stem," // row[1] - "MedCod" // row[2] - " FROM tst_questions" - " WHERE QstCod=%ld", - Match->Status.QstCod)) - Ale_ShowAlert (Ale_ERROR,"Question doesn't exist."); - row = mysql_fetch_row (mysql_res); - - /***** Show question *****/ - /* Get answer type (row[0]) */ - Gbl.Test.AnswerType = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[0]); - // TODO: Check that answer type is correct (unique choice) - - fprintf (Gbl.F.Out,"
"); - - /* Write stem (row[1]) */ - Tst_WriteQstStem (row[1],"MATCH_TCH_QST"); - - /* Get media (row[2]) */ - Gbl.Test.Media.MedCod = Str_ConvertStrCodToLongCod (row[2]); - Med_GetMediaDataByCod (&Gbl.Test.Media); - - /* Show media */ - Med_ShowMedia (&Gbl.Test.Media, - "TEST_MED_EDIT_LIST_STEM_CONTAINER", - "TEST_MED_EDIT_LIST_STEM"); - - /* Write answers? */ - switch (Match->Status.Showing) - { - case Gam_WORDING: - /* Don't write anything */ - break; - case Gam_ANSWERS: - if (Match->Status.BeingPlayed) - /* Write answers */ - Tst_WriteAnswersMatchResult (Match->MchCod, - Match->Status.QstInd, - Match->Status.QstCod, - "MATCH_TCH_QST",false); // Don't show result - else // Not being played - Gam_ShowWaitImage (Txt_MATCH_Paused); - break; - case Gam_REQUEST: - /* Write button to request viewing results */ - Gam_PutBigButton (ActShoResMchTch,Match->MchCod, - Gam_ICON_RESULTS,Txt_View_results); - break; - case Gam_RESULTS: - /* Write answers with results */ - Tst_WriteAnswersMatchResult (Match->MchCod, - Match->Status.QstInd, - Match->Status.QstCod, - "MATCH_TCH_QST",true); // Show result - break; - } - - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/************************ Put button to show results *************************/ -/*****************************************************************************/ -/* -static void Gam_PutCheckboxResult (struct Match *Match) - { - extern const char *Txt_View_results; - - ***** Start container ***** - fprintf (Gbl.F.Out,"
"); - - ***** Start form ***** - Frm_StartForm (ActChgShoResMchTch); - Gam_PutParamMatchCod (Match->MchCod); // Current match being played - - ***** Put icon with link ***** - * Submitting onmousedown instead of default onclick - is necessary in order to be fast - and not lose clicks due to refresh * - fprintf (Gbl.F.Out,"", - Txt_View_results, - Gbl.Form.Id, - Match->Status.ShowResults ? "fas fa-toggle-on" : - "fas fa-toggle-off", - Txt_View_results); - - ***** End form ***** - Frm_EndForm (); - - ***** End container ***** - fprintf (Gbl.F.Out,"
"); - } -*/ - -/*****************************************************************************/ -/***** Show question and its answers when playing a match (as a student) *****/ -/*****************************************************************************/ - -static void Gam_ShowQuestionAndAnswersStd (struct Match *Match) - { - bool Shuffle = false; // TODO: Read shuffle from question - MYSQL_RES *mysql_res; - MYSQL_ROW row; - int StdAnsInd; - unsigned NumOptions; - unsigned NumOpt; - unsigned Index; - bool ErrorInIndex = false; - - /***** Show question *****/ - /* Write buttons for answers? */ - if (Match->Status.Showing == Gam_ANSWERS) - { - if (Tst_CheckIfQuestionIsValidForGame (Match->Status.QstCod)) - { - /***** Get student's answer to this question - (<0 ==> no answer) *****/ - StdAnsInd = Gam_GetQstAnsFromDB (Match->MchCod, - Match->Status.QstInd); - - /***** Get number of options in this question *****/ - NumOptions = Tst_GetNumAnswersQst (Match->Status.QstCod); - - /***** Get answers of question from database *****/ - Shuffle = false; - NumOptions = Tst_GetAnswersQst (Match->Status.QstCod,&mysql_res,Shuffle); - /* - row[0] AnsInd - row[1] Answer - row[2] Feedback - row[3] MedCod - row[4] Correct - */ - - /***** Start table *****/ - Tbl_StartTableWide (8); - - for (NumOpt = 0; - NumOpt < NumOptions; - NumOpt++) - { - /***** Get next answer *****/ - row = mysql_fetch_row (mysql_res); - - /***** Assign index (row[0]). - Index is 0,1,2,3... if no shuffle - or 1,3,0,2... (example) if shuffle *****/ - if (sscanf (row[0],"%u",&Index) == 1) - { - if (Index >= Tst_MAX_OPTIONS_PER_QUESTION) - ErrorInIndex = true; - } - else - ErrorInIndex = true; - if (ErrorInIndex) - Lay_ShowErrorAndExit ("Wrong index of answer when showing a test."); - - /***** Start row *****/ - // if (NumOpt % 2 == 0) - fprintf (Gbl.F.Out,""); - - /***** Write letter for this option *****/ - /* Start table cell */ - fprintf (Gbl.F.Out,""); - - /* Form with button. - Sumitting onmousedown instead of default onclick - is necessary in order to be fast - and not lose clicks due to refresh */ - Frm_StartForm (ActAnsMchQstStd); - Gam_PutParamMatchCod (Match->MchCod); // Current match being played - Gam_PutParamQstInd (Match->Status.QstInd); // Current question index shown - Gam_PutParamAnswer (Index); // Index for this option - fprintf (Gbl.F.Out,"", - 'A' + (char) NumOpt, - 'a' + (char) NumOpt); - Frm_EndForm (); - - /* End table cell */ - fprintf (Gbl.F.Out,""); - - /***** End row *****/ - // if (NumOpt % 2 == 1) - fprintf (Gbl.F.Out,""); - } - - /***** End table *****/ - Tbl_EndTable (); - } - else - Ale_ShowAlert (Ale_ERROR,"Type of answer not valid in a game."); - } - } - -/*****************************************************************************/ -/*********************** Put a big button to do action ***********************/ -/*****************************************************************************/ - -static void Gam_PutBigButton (Act_Action_t NextAction,long MchCod, - const char *Icon,const char *Txt) - { - /***** Start form *****/ - Frm_StartForm (NextAction); - Gam_PutParamMatchCod (MchCod); - - /***** Put icon with link *****/ - /* Submitting onmousedown instead of default onclick - is necessary in order to be fast - and not lose clicks due to refresh */ - fprintf (Gbl.F.Out,"
" - "" - "" - "" - "
", - Txt, - Gbl.Form.Id, - Icon); - - /***** End form *****/ - Frm_EndForm (); - } - -static void Gam_PutBigButtonOff (const char *Icon) - { - /***** Put inactive icon *****/ - fprintf (Gbl.F.Out,"
" - "
" - "" - "
" - "
", - Icon); - } - -static void Gam_PutBigButtonClose (void) - { - extern const char *Txt_Close; - - /***** Put icon with link *****/ - /* onmousedown instead of default onclick - is necessary in order to be fast - and not lose clicks due to refresh */ - fprintf (Gbl.F.Out,"
" - "" - "" - "" - "
", - Txt_Close,Gam_ICON_CLOSE); - } - -/*****************************************************************************/ -/****************************** Show wait image ******************************/ -/*****************************************************************************/ - -static void Gam_ShowWaitImage (const char *Txt) - { - fprintf (Gbl.F.Out,"
" - "\"%s\"" - "
", - Cfg_URL_ICON_PUBLIC, - Txt, - Txt); - } - -/*****************************************************************************/ -/**************************** Remove old players *****************************/ -/*****************************************************************************/ - -static void Gam_RemoveOldPlayers (void) - { - /***** Delete matches not being played *****/ - DB_QueryDELETE ("can not update matches as not being played", - "DELETE FROM gam_mch_being_played" - " WHERE TSStatus.NumPlayers = (unsigned) DB_QueryCOUNT ("can not get number of players", - "SELECT COUNT(*) FROM gam_players" - " WHERE MchCod=%ld", - Match->MchCod); - } - -/*****************************************************************************/ -/******************* Show the results of a finished match ********************/ -/*****************************************************************************/ - -void Gam_ShowFinishedMatchResults (void) - { - Ale_ShowAlert (Ale_INFO,"To be implemented..."); - } - -/*****************************************************************************/ -/********************** Get code of match being played ***********************/ -/*****************************************************************************/ - -void Gam_GetMatchBeingPlayed (void) - { - /***** Get match code ****/ - if ((Gbl.Games.MchCodBeingPlayed = Gam_GetParamMatchCod ()) == -1L) - Lay_ShowErrorAndExit ("Code of match is missing."); - } - -/*****************************************************************************/ -/********* Show game being played to me as student in a new window ***********/ -/*****************************************************************************/ - -void Gam_ShowMatchToMeAsStd (void) - { - struct Match Match; - - /***** Remove old players. - This function must be called before getting match status. *****/ - Gam_RemoveOldPlayers (); - - /***** Get data of the match from database *****/ - Match.MchCod = Gbl.Games.MchCodBeingPlayed; - Gam_GetDataOfMatchByCod (&Match); - - /***** Show current match status *****/ - fprintf (Gbl.F.Out,"
"); - Gam_ShowMatchStatusForStd (&Match); - fprintf (Gbl.F.Out,"
"); - } - -/*****************************************************************************/ -/****************** Refresh match for a teacher via AJAX *********************/ -/*****************************************************************************/ - -void Gam_RefreshMatchTch (void) - { - struct Match Match; - - if (!Gbl.Session.IsOpen) // If session has been closed, do not write anything - return; - - /***** Remove old players. - This function must be called before getting match status. *****/ - Gam_RemoveOldPlayers (); - - /***** Get data of the match from database *****/ - Match.MchCod = Gbl.Games.MchCodBeingPlayed; - Gam_GetDataOfMatchByCod (&Match); - - /***** Update match status in database *****/ - Gam_UpdateMatchStatusInDB (&Match); - - /***** Update elapsed time in this question *****/ - Gam_UpdateElapsedTimeInQuestion (&Match); - - /***** Show current match status *****/ - Gam_ShowMatchStatusForTch (&Match); - } - -/*****************************************************************************/ -/*************** Refresh current game for a student via AJAX *****************/ -/*****************************************************************************/ - -void Gam_RefreshMatchStd (void) - { - struct Match Match; - - if (!Gbl.Session.IsOpen) // If session has been closed, do not write anything - return; - - /***** Remove old players. - This function must be called before getting match status. *****/ - Gam_RemoveOldPlayers (); - - /***** Get data of the match from database *****/ - Match.MchCod = Gbl.Games.MchCodBeingPlayed; - Gam_GetDataOfMatchByCod (&Match); - - /***** Show current match status *****/ - Gam_ShowMatchStatusForStd (&Match); - } - -/*****************************************************************************/ -/**** Receive previous question answer in a match question from database *****/ -/*****************************************************************************/ - -static int Gam_GetQstAnsFromDB (long MchCod,unsigned QstInd) - { - MYSQL_RES *mysql_res; - MYSQL_ROW row; - unsigned NumRows; - int StdAnsInd = -1; // <0 ==> no answer selected - - /***** Get student's answer *****/ - NumRows = (unsigned) DB_QuerySELECT (&mysql_res,"can not get student's answer to a match question", - "SELECT AnsInd FROM gam_answers" - " WHERE MchCod=%ld AND UsrCod=%ld AND QstInd=%u", - MchCod, - Gbl.Usrs.Me.UsrDat.UsrCod, - QstInd); - if (NumRows) // Answer found... - { - /***** Get answer index *****/ - row = mysql_fetch_row (mysql_res); - if (sscanf (row[0],"%d",&StdAnsInd) != 1) - Lay_ShowErrorAndExit ("Error when getting student's answer to a match question."); - } - - /***** Free structure that stores the query result *****/ - DB_FreeMySQLResult (&mysql_res); - - return StdAnsInd; - } - -/*****************************************************************************/ -/********* Receive question answer from student when playing a match *********/ -/*****************************************************************************/ - -void Gam_ReceiveQstAnsFromStd (void) - { - struct Match Match; - unsigned QstInd; - unsigned StdAnsInd; - int PreviousStdAnsInd; - - /***** Remove old players. - This function must be called before getting match status. *****/ - Gam_RemoveOldPlayers (); - - /***** Get data of the match from database *****/ - Match.MchCod = Gbl.Games.MchCodBeingPlayed; - Gam_GetDataOfMatchByCod (&Match); - - /***** Get question index from form *****/ - QstInd = Gam_GetParamQstInd (); - - /***** Check that question index is the current one being played *****/ - if (QstInd == Match.Status.QstInd) // Receiving an answer - // to the current question being played - { - /***** Get answer index *****/ - /*-------+--------------+ - | Button | Answer index | - +--------+--------------+ - | a | 0 | - | b | 1 | - | c | 2 | - | d | 3 | - | ... | ... | - +--------+-------------*/ - StdAnsInd = Gam_GetParamAnswer (); - - /***** Get previous student's answer to this question - (<0 ==> no answer) *****/ - PreviousStdAnsInd = Gam_GetQstAnsFromDB (Match.MchCod,QstInd); - - /***** Store student's answer *****/ - if (PreviousStdAnsInd == (int) StdAnsInd) - DB_QueryDELETE ("can not register your answer to the match question", - "DELETE FROM gam_answers" - " WHERE MchCod=%ld AND UsrCod=%ld AND QstInd=%u", - Match.MchCod,Gbl.Usrs.Me.UsrDat.UsrCod,QstInd); - else - DB_QueryREPLACE ("can not register your answer to the match question", - "REPLACE gam_answers" - " (MchCod,UsrCod,QstInd,AnsInd)" - " VALUES" - " (%ld,%ld,%u,%u)", - Match.MchCod,Gbl.Usrs.Me.UsrDat.UsrCod,QstInd,StdAnsInd); - } - - /***** Show current match status *****/ - fprintf (Gbl.F.Out,"
"); - Gam_ShowMatchStatusForStd (&Match); - fprintf (Gbl.F.Out,"
"); - } - /*****************************************************************************/ /********************* Get number of courses with games **********************/ /*****************************************************************************/ diff --git a/swad_game.h b/swad_game.h index 4552060a..d54df598 100644 --- a/swad_game.h +++ b/swad_game.h @@ -81,8 +81,13 @@ typedef enum void Gam_SeeAllGames (void); void Gam_SeeOneGame (void); +void Gam_ShowOneGame (long GamCod, + bool ShowOnlyThisGame, + bool ListGameQuestions, + bool PutFormNewMatch); void Gam_PutHiddenParamGameOrder (void); void Gam_RequestCreatOrEditGame (void); +void Gam_PutParams (void); void Gam_GetListGames (void); void Gam_GetDataOfGameByCod (struct Game *Gam); void Gam_GetDataOfGameByFolder (struct Game *Gam); @@ -102,9 +107,15 @@ void Gam_RemoveGroup (long GrpCod); void Gam_RemoveGroupsOfType (long GrpTypCod); void Gam_RemoveGames (Hie_Level_t Scope,long Cod); -void Gam_RequestNewQuestion (void); +unsigned Gam_GetNumQstsGame (long GamCod); -void Gam_GetAndDrawBarNumUsrsWhoAnswered (long MchCod,unsigned QstInd,unsigned AnsInd,unsigned NumUsrs); +void Gam_RequestNewQuestion (void); +void Gam_PutParamQstInd (unsigned QstInd); +unsigned Gam_GetParamQstInd (void); +unsigned Gam_GetQstIndFromStr (const char *UnsignedStr); +long Gam_GetQstCodFromQstInd (long GamCod,unsigned QstInd); +unsigned Gam_GetPrevQuestionIndexInGame (long GamCod,unsigned QstInd); +unsigned Gam_GetNextQuestionIndexInGame (long GamCod,unsigned QstInd); void Gam_AddTstQuestionsToGame (void); @@ -114,31 +125,8 @@ void Gam_RemoveQst (void); void Gam_MoveUpQst (void); void Gam_MoveDownQst (void); -void Gam_RequestRemoveMatchTch (void); -void Gam_RemoveMatchTch (void); - void Gam_RequestNewMatchTch (void); -void Gam_CreateNewMatchTch (void); -void Gam_RequestStartResumeMatchTch (void); -void Gam_PauseMatchTch (void); -void Gam_ResumeMatchTch (void); -// void Gam_ShowStemQstMatchTch (void); -// void Gam_ShowAnssQstMatchTch (void); -void Gam_ShowResultsQstMatchTch (void); -void Gam_BackMatchTch (void); -void Gam_ForwardMatchTch (void); -// void Gam_CurrQstMatchTch (void); - -void Gam_ShowFinishedMatchResults (void); - -void Gam_GetMatchBeingPlayed (void); -void Gam_ShowMatchToMeAsStd (void); -void Gam_RefreshMatchTch (void); -void Gam_RefreshMatchStd (void); - -void Gam_ReceiveQstAnsFromStd (void); - unsigned Gam_GetNumCoursesWithGames (Hie_Level_t Scope); unsigned Gam_GetNumGames (Hie_Level_t Scope); float Gam_GetNumQstsPerCrsGame (Hie_Level_t Scope); diff --git a/swad_match.c b/swad_match.c new file mode 100644 index 00000000..455843a6 --- /dev/null +++ b/swad_match.c @@ -0,0 +1,2297 @@ +// 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_alert.h" +#include "swad_box.h" +#include "swad_database.h" +#include "swad_form.h" +#include "swad_game.h" +#include "swad_global.h" +#include "swad_group.h" +#include "swad_match.h" +#include "swad_pagination.h" +#include "swad_parameter.h" +#include "swad_role.h" +#include "swad_setting.h" +#include "swad_table.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 *******************************/ +/*****************************************************************************/ + +#define Mch_NUM_SHOWING 4 +typedef enum + { + Mch_WORDING, // Showing only the question wording + Mch_ANSWERS, // Showing the question wording and the answers + Mch_REQUEST, // Requesting confirmation to show the results + Mch_RESULTS, // Showing the results + } Mch_Showing_t; +#define Mch_SHOWING_DEFAULT Mch_WORDING + +struct Match + { + long MchCod; + long GamCod; + long UsrCod; + time_t TimeUTC[2]; + char Title[Gam_MAX_BYTES_TITLE + 1]; + struct + { + unsigned QstInd; // 0 means that the game has not started. First question has index 1. + long QstCod; + time_t QstStartTimeUTC; + Mch_Showing_t Showing; + bool BeingPlayed; + unsigned NumPlayers; + } Status; + }; + +/*****************************************************************************/ +/***************************** Private constants *****************************/ +/*****************************************************************************/ + +const char *Mch_ShowingStringsDB[Mch_NUM_SHOWING] = + { + "wording", + "answers", + "request", + "results", + }; + +/*****************************************************************************/ +/***************************** Private variables *****************************/ +/*****************************************************************************/ + +long Mch_CurrentMchCod = -1L; // Used as parameter in contextual links + +/*****************************************************************************/ +/***************************** Private prototypes ****************************/ +/*****************************************************************************/ + +static void Mch_GetDataOfMatchByCod (struct Match *Match); + +static void Mch_PutIconToPlayNewMatch (void); +static void Mch_ListOneOrMoreMatches (struct Game *Game, + unsigned NumMatches, + MYSQL_RES *mysql_res); +static void Mch_GetAndWriteNamesOfGrpsAssociatedToMatch (struct Match *Match); +static void Mch_GetMatchDataFromRow (MYSQL_RES *mysql_res, + struct Match *Match); +static Mch_Showing_t Mch_GetShowingFromStr (const char *Str); + +static void Mch_PutParamCurrentMchCod (void); +static void Mch_PutParamMatchCod (long MchCod); +static long Mch_GetParamMatchCod (void); + +static void Mch_PutButtonNewMatch (long GamCod); + +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_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_SetMatchStatusToNext (struct Match *Match); +static void Mch_ShowMatchStatusForTch (struct Match *Match); +static void Mch_ShowMatchStatusForStd (struct Match *Match); +static bool Mch_CheckIfIPlayThisMatchBasedOnGrps (long MchCod); +static void Mch_ShowLeftColumnTch (struct Match *Match); +static void Mch_ShowLeftColumnStd (struct Match *Match); +static void Mch_PutMatchControlButtons (struct Match *Match); +static void Mch_ShowNumQstInMatch (struct Match *Match); +static void Mch_ShowNumPlayers (struct Match *Match); +static void Mch_ShowMatchTitle (struct Match *Match); +static void Mch_ShowQuestionAndAnswersTch (struct Match *Match); +static void Mch_ShowQuestionAndAnswersStd (struct Match *Match); + +static void Mch_PutParamAnswer (unsigned AnsInd); +static unsigned Mch_GetParamAnswer (void); + +static void Mch_PutBigButton (Act_Action_t NextAction,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 int Mch_GetQstAnsFromDB (long MchCod,unsigned QstInd); + +static unsigned Mch_GetNumAnswerers (struct Match *Match); +static unsigned Mch_GetNumUsrsWhoAnswered (long MchCod,unsigned QstInd,unsigned AnsInd); +static void Mch_DrawBarNumUsrs (unsigned NumUsrs,unsigned MaxUsrs); + +/*****************************************************************************/ +/************************* 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; + extern long Gam_CurrentGamCod; // Used as parameter in contextual links; + char *SubQuery; + MYSQL_RES *mysql_res; + unsigned NumMatches; + + /***** Get data of matches from database *****/ + /* Fill subquery for game */ + if (Gbl.Crs.Grps.WhichGrps == Grp_ONLY_MY_GROUPS) + { + if (asprintf (&SubQuery," AND" + "(MchCod NOT IN" + " (SELECT MchCod FROM gam_grp)" + " OR" + " MchCod IN" + " (SELECT gam_grp.MchCod" + " FROM gam_grp,crs_grp_usr" + " WHERE crs_grp_usr.UsrCod=%ld" + " AND gam_grp.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] + "UNIX_TIMESTAMP(QstStartTime)," // row[8] + "Showing" // row[9] + " FROM gam_matches" + " WHERE GamCod=%ld%s" + " ORDER BY MchCod", + Game->GamCod, + SubQuery); + + /* Free allocated memory for subquery */ + free ((void *) SubQuery); + + /***** Start box *****/ + Gam_CurrentGamCod = Game->GamCod; + Box_StartBox (NULL,Txt_Matches,Mch_PutIconToPlayNewMatch, + Hlp_ASSESSMENT_Games_matches,Box_NOT_CLOSABLE); + + 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 + Mch_PutButtonNewMatch (Game->GamCod); // Button to create a new match + break; + default: + break; + } + + /***** End box *****/ + Box_EndBox (); + } + +/*****************************************************************************/ +/********************** Get match data using its code ************************/ +/*****************************************************************************/ + +static void Mch_GetDataOfMatchByCod (struct Match *Match) + { + MYSQL_RES *mysql_res; + unsigned long NumRows; + + /***** 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] + "UNIX_TIMESTAMP(QstStartTime)," // row[8] + "Showing" // row[9] + " FROM gam_matches" + " WHERE MchCod=%ld" + " AND GamCod IN" // Extra check + " (SELECT GamCod FROM 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; + Match->TimeUTC[Gam_START_TIME] = + Match->TimeUTC[Gam_END_TIME ] = (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_WORDING; + Match->Status.BeingPlayed = false; + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/***************** Put icon to add a new questions to game *******************/ +/*****************************************************************************/ + +static void Mch_PutIconToPlayNewMatch (void) + { + extern const char *Txt_New_match; + + /***** Put form to create a new question *****/ + Ico_PutContextualIconToAdd (ActReqNewMchTch,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) + { + extern const char *Txt_No_INDEX; + 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_Status; + extern const char *Txt_Play; + extern const char *Txt_Resume; + extern const char *Txt_Today; + unsigned NumMatch; + unsigned UniqueId; + struct Match Match; + + /***** Write the heading *****/ + Tbl_StartTableWideMargin (2); + fprintf (Gbl.F.Out,"" + "" + "" + "%s" + "" + "" + "%s" + "" + "" + "%s" + "" + "" + "%s" + "" + "" + "%s" + "" + "" + "%s" + "" + "", + Txt_No_INDEX, + Txt_ROLES_SINGUL_Abc[Rol_TCH][Usr_SEX_UNKNOWN], + Txt_START_END_TIME[Gam_ORDER_BY_START_DATE], + Txt_START_END_TIME[Gam_ORDER_BY_END_DATE], + Txt_Match, + Txt_Status); + + /***** 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); + + /***** Icons *****/ + fprintf (Gbl.F.Out,"" + "",Gbl.RowEvenOdd); + + /* Put icon to remove the match */ + Frm_StartForm (ActReqRemMchTch); + Mch_PutParamMatchCod (Match.MchCod); + Ico_PutIconRemove (); + Frm_EndForm (); + + fprintf (Gbl.F.Out,""); + + /***** Number of match ******/ + fprintf (Gbl.F.Out,"%u", + Gbl.RowEvenOdd,NumMatch + 1); + + /***** Match player *****/ + fprintf (Gbl.F.Out,"", + Gbl.RowEvenOdd); + Usr_WriteAuthor1Line (Match.UsrCod,false); + fprintf (Gbl.F.Out,""); + + /***** Start date/time *****/ + fprintf (Gbl.F.Out,"", + UniqueId, + Match.Status.QstInd >= Mch_AFTER_LAST_QUESTION ? "DATE_RED" : + "DATE_GREEN", + Gbl.RowEvenOdd); + fprintf (Gbl.F.Out,"" + "", + UniqueId,Match.TimeUTC[Gam_START_TIME], + (unsigned) Gbl.Prefs.DateFormat,Txt_Today); + + /***** End date/time *****/ + fprintf (Gbl.F.Out,"", + UniqueId, + Match.Status.QstInd >= Mch_AFTER_LAST_QUESTION ? "DATE_RED" : + "DATE_GREEN", + Gbl.RowEvenOdd); + fprintf (Gbl.F.Out,"\">" + "" + "", + UniqueId,Match.TimeUTC[Gam_END_TIME], + (unsigned) Gbl.Prefs.DateFormat,Txt_Today); + + /***** Title and groups *****/ + fprintf (Gbl.F.Out,"",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); + + fprintf (Gbl.F.Out,""); + + /***** Match status ******/ + fprintf (Gbl.F.Out,"",Gbl.RowEvenOdd); + + if (Match.Status.QstInd < Mch_AFTER_LAST_QUESTION) // Unfinished match + /* Current question index / total of questions */ + fprintf (Gbl.F.Out,"
%u/%u
", + Match.Status.QstInd,Game->NumQsts); + + switch (Gbl.Usrs.Me.Role.Logged) + { + case Rol_STD: + /* Icon to play as student */ + Mch_CurrentMchCod = Match.MchCod; + Lay_PutContextualLinkOnlyIcon (ActPlyMchStd,NULL, + Mch_PutParamCurrentMchCod, + Match.Status.QstInd < Mch_AFTER_LAST_QUESTION ? "play.svg" : + "flag-checkered.svg", + Txt_Play); + break; + case Rol_NET: + case Rol_TCH: + case Rol_DEG_ADM: + case Rol_CTR_ADM: + case Rol_INS_ADM: + case Rol_SYS_ADM: + /* Icon to resume */ + Mch_CurrentMchCod = Match.MchCod; + Lay_PutContextualLinkOnlyIcon (ActResMchTch,NULL, + Mch_PutParamCurrentMchCod, + Match.Status.QstInd < Mch_AFTER_LAST_QUESTION ? "play.svg" : + "flag-checkered.svg", + Txt_Resume); + break; + default: + break; + } + + fprintf (Gbl.F.Out,""); + + fprintf (Gbl.F.Out,""); + } + + /***** End table *****/ + Tbl_EndTable (); + } + +/*****************************************************************************/ +/************* Get and write the names of the groups of a match **************/ +/*****************************************************************************/ + +static void Mch_GetAndWriteNamesOfGrpsAssociatedToMatch (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 gam_grp,crs_grp,crs_grp_types" + " WHERE gam_grp.MchCod=%ld" + " AND gam_grp.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 *****/ + 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); + + fprintf (Gbl.F.Out,"
"); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +/*****************************************************************************/ +/******************** Get game data from a database row **********************/ +/*****************************************************************************/ + +static void Mch_GetMatchDataFromRow (MYSQL_RES *mysql_res, + struct Match *Match) + { + MYSQL_ROW row; + + /***** 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 date (row[3] holds the start UTC time) */ + Match->TimeUTC[Gam_START_TIME] = Dat_GetUNIXTimeFromStr (row[3]); + + /* Get end date (row[4] holds the end UTC time) */ + Match->TimeUTC[Gam_END_TIME ] = Dat_GetUNIXTimeFromStr (row[4]); + + /* 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] UNIX_TIMESTAMP(QstStartTime) + row[9] Showing + */ + /* 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 question start date (row[8] holds the start UTC time) */ + Match->Status.QstStartTimeUTC = Dat_GetUNIXTimeFromStr (row[8]); + + /* Get what to show (stem, answers, results) (row(9)) */ + Match->Status.Showing = Mch_GetShowingFromStr (row[9]); + + /***** Get whether the match is being played or not *****/ + if (Match->Status.QstInd >= Mch_AFTER_LAST_QUESTION) // Finished + Match->Status.BeingPlayed = false; + else // Unfinished + Match->Status.BeingPlayed = 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_RequestRemoveMatchTch (void) + { + extern const char *Txt_Do_you_really_want_to_remove_the_match_X; + extern const char *Txt_Remove_match; + struct Match Match; + + /***** Get parameters *****/ + /* Get match code */ + if ((Match.MchCod = Mch_GetParamMatchCod ()) == -1L) + Lay_ShowErrorAndExit ("Code of match is missing."); + + /***** Get data of the match from database *****/ + Mch_GetDataOfMatchByCod (&Match); + + /***** Show question and button to remove question *****/ + Mch_CurrentMchCod = Match.MchCod; + Ale_ShowAlertAndButton (ActRemMchTch,NULL,NULL,Mch_PutParamCurrentMchCod, + 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_RemoveMatchTch (void) + { + extern const char *Txt_Match_X_removed; + struct Match Match; + + /***** Get parameters *****/ + /* Get match code */ + if ((Match.MchCod = Mch_GetParamMatchCod ()) == -1L) + Lay_ShowErrorAndExit ("Code of match is missing."); + + /***** Get data of the match from database *****/ + Mch_GetDataOfMatchByCod (&Match); + + /***** Remove the match from all the tables *****/ + /* Remove match players */ + DB_QueryDELETE ("can not remove match players", + "DELETE FROM gam_players" + " USING gam_players,gam_matches,games" + " WHERE gam_players.MchCod=%ld" + " AND gam_players.MchCod=gam_matches.MchCod" + " AND gam_matches.GamCod=games.GamCod" + " AND games.CrsCod=%ld", // Extra check + Match.MchCod,Gbl.Hierarchy.Crs.CrsCod); + + /* Remove match from list of matches being played */ + DB_QueryDELETE ("can not remove match from matches being played", + "DELETE FROM gam_mch_being_played" + " USING gam_mch_being_played,gam_matches,games" + " WHERE gam_mch_being_played.MchCod=%ld" + " AND gam_mch_being_played.MchCod=gam_matches.MchCod" + " AND gam_matches.GamCod=games.GamCod" + " AND games.CrsCod=%ld", // Extra check + Match.MchCod,Gbl.Hierarchy.Crs.CrsCod); + + /* Remove students' answers to match */ + DB_QueryDELETE ("can not remove students' answers associated to a match", + "DELETE FROM gam_answers" + " USING gam_answers,gam_matches,games" + " WHERE gam_answers.MchCod=%ld" + " AND gam_answers.MchCod=gam_matches.MchCod" + " AND gam_matches.GamCod=games.GamCod" + " AND games.CrsCod=%ld", // Extra check + Match.MchCod,Gbl.Hierarchy.Crs.CrsCod); + + /* Remove groups associated to the match */ + DB_QueryDELETE ("can not remove the groups associated to a match", + "DELETE FROM gam_grp" + " USING gam_grp,gam_matches,games" + " WHERE gam_grp.MchCod=%ld" + " AND gam_grp.MchCod=gam_matches.MchCod" + " AND gam_matches.GamCod=games.GamCod" + " AND games.CrsCod=%ld", // Extra check + Match.MchCod,Gbl.Hierarchy.Crs.CrsCod); + + /* Remove the match itself */ + DB_QueryDELETE ("can not remove a match", + "DELETE FROM gam_matches" + " USING gam_matches,games" + " WHERE gam_matches.MchCod=%ld" + " AND gam_matches.GamCod=games.GamCod" + " AND games.CrsCod=%ld", // Extra check + Match.MchCod,Gbl.Hierarchy.Crs.CrsCod); + if (!mysql_affected_rows (&Gbl.mysql)) + Lay_ShowErrorAndExit ("The match to be removed does not exist."); + + /***** 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 + } + +/*****************************************************************************/ +/***************** Put parameter with current match code *********************/ +/*****************************************************************************/ + +static void Mch_PutParamCurrentMchCod (void) + { + if (Mch_CurrentMchCod > 0) + Mch_PutParamMatchCod (Mch_CurrentMchCod); + } + +/*****************************************************************************/ +/******************** Write parameter with code of match **********************/ +/*****************************************************************************/ + +static void Mch_PutParamMatchCod (long MchCod) + { + Par_PutHiddenParamLong ("MchCod",MchCod); + } + +/*****************************************************************************/ +/********************* Get parameter with code of match **********************/ +/*****************************************************************************/ + +static long Mch_GetParamMatchCod (void) + { + /***** Get code of match *****/ + return Par_GetParToLong ("MchCod"); + } + +/*****************************************************************************/ +/********************* Put button to create a new match **********************/ +/*****************************************************************************/ + +static void Mch_PutButtonNewMatch (long GamCod) + { + extern const char *Txt_New_match; + + Frm_StartFormAnchor (ActReqNewMchTch,Mch_NEW_MATCH_SECTION_ID); + Gam_PutParamGameCod (GamCod); + Btn_PutConfirmButton (Txt_New_match); + Frm_EndForm (); + } + +/*****************************************************************************/ +/****** 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_new_match; + 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 *****/ + Lay_StartSection (Mch_NEW_MATCH_SECTION_ID); + + /***** Start form *****/ + Frm_StartForm (ActNewMchTch); + Gam_PutParamGameCod (Game->GamCod); + Gam_PutParamQstInd (0); // Start by first question in game + + /***** Start box and table *****/ + Box_StartBoxTable (NULL,Txt_New_match,NULL, + Hlp_ASSESSMENT_Games_new_match,Box_NOT_CLOSABLE,2); + + /***** Match title *****/ + fprintf (Gbl.F.Out,"" + "" + "" + "" + "" + "" + "" + "", + The_ClassFormInBox[Gbl.Prefs.Theme], + Txt_Title, + Gam_MAX_CHARS_TITLE,Game->Title); + + /***** Groups *****/ + Mch_ShowLstGrpsToCreateMatch (); + + /***** End table *****/ + Tbl_EndTable (); + + /***** Put icon with link *****/ + Frm_LinkFormSubmit (Txt_Play,NULL,NULL); + fprintf (Gbl.F.Out,"\"%s\"", + Cfg_URL_ICON_PUBLIC,Txt_Play,Txt_Play); + fprintf (Gbl.F.Out,""); + + /***** End box *****/ + Box_EndBox (); + + /***** End form *****/ + Frm_EndForm (); + + /***** End section for a new match *****/ + Lay_EndSection (); + } + +/*****************************************************************************/ +/***************** Show list of groups to create a new match *****************/ +/*****************************************************************************/ + +static void Mch_ShowLstGrpsToCreateMatch (void) + { + extern const char *The_ClassFormInBox[The_NUM_THEMES]; + 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.Num) + { + /***** Start box and table *****/ + fprintf (Gbl.F.Out,"" + "" + "%s:" + "" + "", + The_ClassFormInBox[Gbl.Prefs.Theme], + Txt_Groups); + Box_StartBoxTable ("95%",NULL,NULL, + NULL,Box_NOT_CLOSABLE,0); + + /***** First row: checkbox to select the whole course *****/ + fprintf (Gbl.F.Out,"" + "" + "" + "" + "", + Txt_The_whole_course,Gbl.Hierarchy.Crs.ShrtName); + + /***** List the groups for each group type *****/ + for (NumGrpTyp = 0; + NumGrpTyp < Gbl.Crs.Grps.GrpTypes.Num; + NumGrpTyp++) + if (Gbl.Crs.Grps.GrpTypes.LstGrpTypes[NumGrpTyp].NumGrps) + Grp_ListGrpsToEditAsgAttSvyMch (&Gbl.Crs.Grps.GrpTypes.LstGrpTypes[NumGrpTyp], + -1L, // -1 means "New match" + Grp_MATCH); + + /***** End table and box *****/ + Box_EndBoxTable (); + fprintf (Gbl.F.Out,"" + ""); + } + + /***** Free list of groups types and groups in this course *****/ + Grp_FreeListGrpTypesAndGrps (); + } + +/*****************************************************************************/ +/********************* Create a new match (by a teacher) *********************/ +/*****************************************************************************/ + +void Mch_CreateNewMatchTch (void) + { + long GamCod; + char Title[Gam_MAX_BYTES_TITLE + 1]; + + /***** Get form parameters *****/ + /* Get match code */ + if ((GamCod = Gam_GetParamGameCod ()) == -1L) + Lay_ShowErrorAndExit ("Code of game is missing."); + + /* Get match title */ + Par_GetParToText ("Title",Title,Gam_MAX_BYTES_TITLE); + + /* Get groups for this games */ + Grp_GetParCodsSeveralGrps (); + + /***** Create a new match *****/ + Gbl.Games.MchCodBeingPlayed = Mch_CreateMatch (GamCod,Title); + + /***** Free memory for list of selected groups *****/ + Grp_FreeListCodSelectedGrps (); + } + +/*****************************************************************************/ +/******* Show button to actually start / resume a match (by a teacher) *******/ +/*****************************************************************************/ + +void Mch_RequestStartResumeMatchTch (void) + { + struct Match Match; + + /***** Remove old players. + This function must be called before getting match status. *****/ + Mch_RemoveOldPlayers (); + + /***** Get data of the match from database *****/ + Match.MchCod = Gbl.Games.MchCodBeingPlayed; + Mch_GetDataOfMatchByCod (&Match); + + /***** Update match status in database *****/ + Mch_UpdateMatchStatusInDB (&Match); + + /***** Show current match status *****/ + fprintf (Gbl.F.Out,"
"); + Mch_ShowMatchStatusForTch (&Match); + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/********************** Create a new match in a game *************************/ +/*****************************************************************************/ + +static long Mch_CreateMatch (long GamCod,char Title[Gam_MAX_BYTES_TITLE + 1]) + { + long MchCod; + + /***** Insert this new match into database *****/ + MchCod = DB_QueryINSERTandReturnCode ("can not create match", + "INSERT gam_matches" + " (GamCod,UsrCod,StartTime,EndTime,Title,ShowResults," + "QstInd,QstCod,QstStartTime,Showing)" + " VALUES" + " (%ld," // GamCod + "%ld," // UsrCod + "NOW()," // StartTime + "NOW()," // EndTime + "'%s'," // Title + "'N'," // ShowResults: Don't show results initially + "0," // QstInd: Match has not started, so not the first question yet + "-1," // QstCod: Non-existent question + "NOW()," // QstStartTime + "'%s'", // What is being shown + GamCod, + Gbl.Usrs.Me.UsrDat.UsrCod, // Game creator + Title, + Mch_ShowingStringsDB[Mch_SHOWING_DEFAULT]); + + /***** Create groups associated to the match *****/ + if (Gbl.Crs.Grps.LstGrpsSel.NumGrps) + Mch_CreateGrps (MchCod); + + return MchCod; + } + +/*****************************************************************************/ +/******************* Create groups associated to a match *********************/ +/*****************************************************************************/ + +static void Mch_CreateGrps (long MchCod) + { + unsigned NumGrpSel; + + /***** Create groups associated to the match *****/ + for (NumGrpSel = 0; + NumGrpSel < Gbl.Crs.Grps.LstGrpsSel.NumGrps; + NumGrpSel++) + /* Create group */ + DB_QueryINSERT ("can not associate a group to a match", + "INSERT INTO gam_grp" + " (MchCod,GrpCod)" + " VALUES" + " (%ld,%ld)", + MchCod,Gbl.Crs.Grps.LstGrpsSel.GrpCods[NumGrpSel]); + } + +/*****************************************************************************/ +/***************** Insert/update a game match being played *******************/ +/*****************************************************************************/ + +static void Mch_UpdateMatchStatusInDB (struct Match *Match) + { + /***** Update match status in database *****/ + DB_QueryUPDATE ("can not update match being played", + "UPDATE gam_matches,games" + " SET gam_matches.EndTime=NOW()," + "gam_matches.QstInd=%u," + "gam_matches.QstCod=%ld," + "gam_matches.QstStartTime=NOW()," + "gam_matches.Showing='%s'" + " WHERE gam_matches.MchCod=%ld" + " AND gam_matches.GamCod=games.GamCod" + " AND games.CrsCod=%ld", // Extra check + Match->Status.QstInd,Match->Status.QstCod, + Mch_ShowingStringsDB[Match->Status.Showing], + Match->MchCod,Gbl.Hierarchy.Crs.CrsCod); + + if (Match->Status.BeingPlayed) + /* Update match as being played */ + Mch_UpdateMatchAsBeingPlayed (Match->MchCod); + else + /* Update match as not being played */ + Mch_SetMatchAsNotBeingPlayed (Match->MchCod); + } + +/*****************************************************************************/ +/********** Update elapsed time in current question (by a teacher) ***********/ +/*****************************************************************************/ + +static void Mch_UpdateElapsedTimeInQuestion (struct Match *Match) + { + /***** Update elapsed time in current question in database *****/ + if (Match->Status.BeingPlayed && + Match->Status.QstInd > 0 && + Match->Status.QstInd < Mch_AFTER_LAST_QUESTION) + DB_QueryINSERT ("can not update elapsed time in question", + "INSERT INTO gam_time (MchCod,QstInd,ElapsedTime)" + " VALUES (%ld,%u,SEC_TO_TIME(%u))" + " ON DUPLICATE KEY" + " UPDATE ElapsedTime=ADDTIME(ElapsedTime,SEC_TO_TIME(%u))", + Match->MchCod,Match->Status.QstInd, + Cfg_SECONDS_TO_REFRESH_GAME, + Cfg_SECONDS_TO_REFRESH_GAME); + } + +/*****************************************************************************/ +/******************* Get elapsed time in a match question ********************/ +/*****************************************************************************/ + +static void Mch_GetElapsedTimeInQuestion (struct Match *Match, + struct Time *Time) + { + MYSQL_RES *mysql_res; + unsigned NumRows; + + /***** Query database *****/ + NumRows = (unsigned) DB_QuerySELECT (&mysql_res,"can not get elapsed time", + "SELECT ElapsedTime" + " FROM gam_time" + " WHERE MchCod=%ld AND QstInd=%u", + 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 (struct Match *Match, + struct Time *Time) + { + MYSQL_RES *mysql_res; + unsigned NumRows; + + /***** Query database *****/ + NumRows = (unsigned) DB_QuerySELECT (&mysql_res,"can not get elapsed time", + "SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(ElapsedTime)))" + " FROM gam_time WHERE MchCod=%ld", + 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 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; + } + +/*****************************************************************************/ +/********************* Pause current match (by a teacher) ********************/ +/*****************************************************************************/ + +void Mch_PauseMatchTch (void) + { + struct Match Match; + + /***** Remove old players. + This function must be called before getting match status. *****/ + Mch_RemoveOldPlayers (); + + /***** Get data of the match from database *****/ + Match.MchCod = Gbl.Games.MchCodBeingPlayed; + Mch_GetDataOfMatchByCod (&Match); + + /***** Update status *****/ + Match.Status.BeingPlayed = false; // Resume match + + /***** Update match status in database *****/ + Mch_UpdateMatchStatusInDB (&Match); + + /***** Show current match status *****/ + fprintf (Gbl.F.Out,"
"); + Mch_ShowMatchStatusForTch (&Match); + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/** Show current match status (current question, answers...) (by a teacher) **/ +/*****************************************************************************/ + +void Mch_ResumeMatchTch (void) + { + struct Match Match; + + /***** Remove old players. + This function must be called before getting match status. *****/ + Mch_RemoveOldPlayers (); + + /***** Get data of the match from database *****/ + Match.MchCod = Gbl.Games.MchCodBeingPlayed; + Mch_GetDataOfMatchByCod (&Match); + + /***** If not yet finished, update status *****/ + if (Match.Status.QstInd < Mch_AFTER_LAST_QUESTION) // Unfinished + { + if (Match.Status.QstInd == 0) // Match has been created, but it has not started + Mch_SetMatchStatusToNext (&Match); + Match.Status.BeingPlayed = true; // Start/resume match + } + + /***** Update match status in database *****/ + Mch_UpdateMatchStatusInDB (&Match); + + /***** Show current match status *****/ + fprintf (Gbl.F.Out,"
"); + Mch_ShowMatchStatusForTch (&Match); + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/******** Show results of current question in a match (by a teacher) *********/ +/*****************************************************************************/ + +void Mch_ShowResultsQstMatchTch (void) + { + struct Match Match; + + /***** Remove old players. + This function must be called before getting match status. *****/ + Mch_RemoveOldPlayers (); + + /***** Get data of the match from database *****/ + Match.MchCod = Gbl.Games.MchCodBeingPlayed; + Mch_GetDataOfMatchByCod (&Match); + + /***** Update status *****/ + Match.Status.Showing = Mch_RESULTS; // Show results + + /***** Update match status in database *****/ + Mch_UpdateMatchStatusInDB (&Match); + + /***** Show current match status *****/ + fprintf (Gbl.F.Out,"
"); + Mch_ShowMatchStatusForTch (&Match); + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/************* Show previous question in a match (by a teacher) **************/ +/*****************************************************************************/ + +void Mch_BackMatchTch (void) + { + struct Match Match; + + /***** Remove old players. + This function must be called before getting match status. *****/ + Mch_RemoveOldPlayers (); + + /***** Get data of the match from database *****/ + Match.MchCod = Gbl.Games.MchCodBeingPlayed; + Mch_GetDataOfMatchByCod (&Match); + + /***** Update status *****/ + Mch_SetMatchStatusToPrev (&Match); + + /***** Update match status in database *****/ + Mch_UpdateMatchStatusInDB (&Match); + + /***** Show current match status *****/ + fprintf (Gbl.F.Out,"
"); + Mch_ShowMatchStatusForTch (&Match); + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/*************** Show next question in a match (by a teacher) ****************/ +/*****************************************************************************/ + +void Mch_ForwardMatchTch (void) + { + struct Match Match; + + /***** Remove old players. + This function must be called before getting match status. *****/ + Mch_RemoveOldPlayers (); + + /***** Get data of the match from database *****/ + Match.MchCod = Gbl.Games.MchCodBeingPlayed; + Mch_GetDataOfMatchByCod (&Match); + + /***** Update status *****/ + Mch_SetMatchStatusToNext (&Match); + + /***** Update match status in database *****/ + Mch_UpdateMatchStatusInDB (&Match); + + /***** Show current match status *****/ + fprintf (Gbl.F.Out,"
"); + Mch_ShowMatchStatusForTch (&Match); + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/************** Set match status to previous (backward) status ***************/ +/*****************************************************************************/ + +static void Mch_SetMatchStatusToPrev (struct Match *Match) + { + /***** What to show *****/ + switch (Match->Status.Showing) + { + case Mch_WORDING: + Match->Status.Showing = Mch_REQUEST; + + /***** Get index of the previous question *****/ + Match->Status.QstInd = Gam_GetPrevQuestionIndexInGame (Match->GamCod, + Match->Status.QstInd); + if (Match->Status.QstInd == 0) // Start of questions has been reached + { + Match->Status.QstCod = -1L; // No previous questions + Match->Status.BeingPlayed = false; // Match is not being played + } + else + Match->Status.QstCod = Gam_GetQstCodFromQstInd (Match->GamCod, + Match->Status.QstInd); + break; + case Mch_ANSWERS: + Match->Status.Showing = Mch_WORDING; + break; + case Mch_REQUEST: + Match->Status.Showing = Mch_ANSWERS; + break; + case Mch_RESULTS: + Match->Status.Showing = Mch_REQUEST; + break; + } + } + +/*****************************************************************************/ +/**************** Set match status to next (forward) status ******************/ +/*****************************************************************************/ + +static void Mch_SetMatchStatusToNext (struct Match *Match) + { + /***** What to show *****/ + switch (Match->Status.Showing) + { + case Mch_WORDING: + Match->Status.Showing = Mch_ANSWERS; + break; + case Mch_ANSWERS: + Match->Status.Showing = Mch_REQUEST; + break; + case Mch_REQUEST: + case Mch_RESULTS: + Match->Status.Showing = Mch_WORDING; + + /***** Get index of the next question *****/ + Match->Status.QstInd = Gam_GetNextQuestionIndexInGame (Match->GamCod, + Match->Status.QstInd); + if (Match->Status.QstInd < Mch_AFTER_LAST_QUESTION) // Unfinished + Match->Status.QstCod = Gam_GetQstCodFromQstInd (Match->GamCod, + Match->Status.QstInd); + else // Finished + { + Match->Status.QstCod = -1L; // No more questions + Match->Status.BeingPlayed = false; + } + break; + } + } + +/*****************************************************************************/ +/******* Show current match status (number, question, answers, button) *******/ +/*****************************************************************************/ + +static void Mch_ShowMatchStatusForTch (struct Match *Match) + { + /***** Get current number of players *****/ + Mch_GetNumPlayers (Match); + + /***** Left column *****/ + Mch_ShowLeftColumnTch (Match); + + /***** Right column *****/ + /* Start right container */ + fprintf (Gbl.F.Out,"
"); + + /* Top row: match title */ + Mch_ShowMatchTitle (Match); + + /* Bottom row: current question and possible answers */ + Mch_ShowQuestionAndAnswersTch (Match); + + /* End right container */ + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/************ Show current question being played for a student ***************/ +/*****************************************************************************/ + +static void Mch_ShowMatchStatusForStd (struct Match *Match) + { + extern const char *Txt_Please_wait_; + bool IBelongToGroups; + + /***** Do I belong to valid groups to play this match? *****/ + IBelongToGroups = Gbl.Usrs.Me.IBelongToCurrentCrs && + Mch_CheckIfIPlayThisMatchBasedOnGrps (Match->MchCod); + if (!IBelongToGroups) + Lay_ShowErrorAndExit ("You can not play this match!"); + + /***** Get current number of players *****/ + Mch_GetNumPlayers (Match); + + /***** Left column *****/ + Mch_ShowLeftColumnStd (Match); + + /***** Right column *****/ + /* Start right container */ + fprintf (Gbl.F.Out,"
"); + + /***** Top row *****/ + Mch_ShowMatchTitle (Match); + + /***** Bottom row *****/ + if (Match->Status.QstInd < Mch_AFTER_LAST_QUESTION) // Unfinished + { + fprintf (Gbl.F.Out,"
"); + + /***** Update players ******/ + Mch_RegisterMeAsPlayerInMatch (Match->MchCod); + + if (Match->Status.BeingPlayed) + /* Show current question and possible answers */ + Mch_ShowQuestionAndAnswersStd (Match); + else // Not being played + Mch_ShowWaitImage (Txt_Please_wait_); + + fprintf (Gbl.F.Out,"
"); + } + + /* End right container */ + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/************ Check if I belong to any of the groups of a match **************/ +/*****************************************************************************/ + +static bool Mch_CheckIfIPlayThisMatchBasedOnGrps (long MchCod) + { + /***** Get if I can play a match from database *****/ + return (DB_QueryCOUNT ("can not check if I can play a match", + "SELECT COUNT(*) FROM gam_matches" + " WHERE MchCod=%ld" + " AND (MchCod NOT IN (SELECT MchCod FROM gam_grp) OR" + " MchCod IN (SELECT gam_grp.MchCod FROM gam_grp,crs_grp_usr" + " WHERE crs_grp_usr.UsrCod=%ld" + " AND gam_grp.GrpCod=crs_grp_usr.GrpCod))", + MchCod,Gbl.Usrs.Me.UsrDat.UsrCod) != 0); + } + +/*****************************************************************************/ +/******** Show left botton column when playing a match (as a teacher) ********/ +/*****************************************************************************/ + +static void Mch_ShowLeftColumnTch (struct Match *Match) + { + extern const char *Txt_MATCH_respond; + struct Time Time; + unsigned NumAnswerers; + + /***** Start left container *****/ + fprintf (Gbl.F.Out,"
"); + + /***** Top *****/ + fprintf (Gbl.F.Out,"
"); + + /* Write elapsed time in match */ + Mch_GetElapsedTimeInMatch (Match,&Time); + Dat_WriteHoursMinutesSeconds (&Time); + + fprintf (Gbl.F.Out,"
"); + + /***** Write number of question *****/ + Mch_ShowNumQstInMatch (Match); + + /***** Write elapsed time in question *****/ + fprintf (Gbl.F.Out,"
"); + if (Match->Status.QstInd > 0 && + Match->Status.QstInd < Mch_AFTER_LAST_QUESTION) + { + Mch_GetElapsedTimeInQuestion (Match,&Time); + Dat_WriteHoursMinutesSeconds (&Time); + } + else + fprintf (Gbl.F.Out,"-"); + fprintf (Gbl.F.Out,"
"); + + /***** Buttons *****/ + Mch_PutMatchControlButtons (Match); + + /***** Number of players *****/ + Mch_ShowNumPlayers (Match); + + /***** Number of users who have answered *****/ + if (Match->Status.BeingPlayed) + { + NumAnswerers = Mch_GetNumAnswerers (Match); + fprintf (Gbl.F.Out,"
" + "%s
" + "%u/%u" + "
", + Txt_MATCH_respond, + NumAnswerers,Match->Status.NumPlayers); + } + + /***** End left container *****/ + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/******** Show left botton column when playing a match (as a student) ********/ +/*****************************************************************************/ + +static void Mch_ShowLeftColumnStd (struct Match *Match) + { + /***** Start left container *****/ + fprintf (Gbl.F.Out,"
"); + + /***** Top *****/ + fprintf (Gbl.F.Out,"
"); + + /***** Write number of question *****/ + Mch_ShowNumQstInMatch (Match); + + /***** End left container *****/ + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/********************** Put buttons to control a match ***********************/ +/*****************************************************************************/ + +static void Mch_PutMatchControlButtons (struct 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; + + /***** Start buttons container *****/ + fprintf (Gbl.F.Out,"
"); + + /***** Left button *****/ + fprintf (Gbl.F.Out,"
"); + if (Match->Status.QstInd == 0) + /* Put button to close browser tab */ + Mch_PutBigButtonClose (); + else + /* Put button to go back */ + Mch_PutBigButton (ActBckMchTch,Match->MchCod, + Mch_ICON_PREVIOUS,Txt_Go_back); + fprintf (Gbl.F.Out,"
"); + + /***** Center button *****/ + fprintf (Gbl.F.Out,"
"); + if (Match->Status.BeingPlayed) // Being played + /* Put button to pause match */ + Mch_PutBigButton (ActPauMchTch, + Match->MchCod, + Mch_ICON_PAUSE,Txt_Pause); + else // Paused + { + if (Match->Status.QstInd < Mch_AFTER_LAST_QUESTION) // Not finished + /* Put button to play match */ + Mch_PutBigButton (ActPlyMchTch, + Match->MchCod, + Mch_ICON_PLAY,Match->Status.QstInd == 0 ? Txt_Start : + Txt_Resume); + else // Finished + /* Put disabled button to play match */ + Mch_PutBigButtonOff (Mch_ICON_PLAY); + } + fprintf (Gbl.F.Out,"
"); + + /***** Right button *****/ + fprintf (Gbl.F.Out,"
"); + if (Match->Status.QstInd >= Mch_AFTER_LAST_QUESTION) // Finished + /* Put button to close browser tab */ + Mch_PutBigButtonClose (); + else + /* Put button to show answers */ + Mch_PutBigButton (ActFwdMchTch,Match->MchCod, + Mch_ICON_NEXT,Txt_Go_forward); + fprintf (Gbl.F.Out,"
"); + + /***** End buttons container *****/ + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/********************* Show number of question in game ***********************/ +/*****************************************************************************/ + +static void Mch_ShowNumQstInMatch (struct Match *Match) + { + extern const char *Txt_MATCH_Start; + extern const char *Txt_MATCH_End; + unsigned NumQsts = Gam_GetNumQstsGame (Match->GamCod); + + fprintf (Gbl.F.Out,"
"); + if (Match->Status.QstInd == 0) // Not started + fprintf (Gbl.F.Out,"%s",Txt_MATCH_Start); + else if (Match->Status.QstInd >= Mch_AFTER_LAST_QUESTION) // Finished + fprintf (Gbl.F.Out,"%s",Txt_MATCH_End); + else + fprintf (Gbl.F.Out,"%u/%u",Match->Status.QstInd,NumQsts); + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/************************** Show number of players ***************************/ +/*****************************************************************************/ + +static void Mch_ShowNumPlayers (struct Match *Match) + { + extern const char *Txt_Players; + + fprintf (Gbl.F.Out,"
" + "%s
" + "%u" + "
", + Txt_Players,Match->Status.NumPlayers); + } + +/*****************************************************************************/ +/***************************** Show match title ******************************/ +/*****************************************************************************/ + +static void Mch_ShowMatchTitle (struct Match *Match) + { + /***** Match title *****/ + fprintf (Gbl.F.Out,"
%s
",Match->Title); + } + +/*****************************************************************************/ +/***** Show question and its answers when playing a match (as a teacher) *****/ +/*****************************************************************************/ + +static void Mch_ShowQuestionAndAnswersTch (struct Match *Match) + { + extern const char *Txt_MATCH_Paused; + extern const char *Txt_View_results; + MYSQL_RES *mysql_res; + MYSQL_ROW row; + + /***** Trivial check: question index should be correct *****/ + if (Match->Status.QstInd == 0 || + Match->Status.QstInd >= Mch_AFTER_LAST_QUESTION) + return; + + /***** Get data of question from database *****/ + if (!DB_QuerySELECT (&mysql_res,"can not get data of a question", + "SELECT AnsType," // row[0] + "Stem," // row[1] + "MedCod" // row[2] + " FROM tst_questions" + " WHERE QstCod=%ld", + Match->Status.QstCod)) + Ale_ShowAlert (Ale_ERROR,"Question doesn't exist."); + row = mysql_fetch_row (mysql_res); + + /***** Show question *****/ + /* Get answer type (row[0]) */ + Gbl.Test.AnswerType = Tst_ConvertFromStrAnsTypDBToAnsTyp (row[0]); + // TODO: Check that answer type is correct (unique choice) + + fprintf (Gbl.F.Out,"
"); + + /* Write stem (row[1]) */ + Tst_WriteQstStem (row[1],"MATCH_TCH_QST"); + + /* Get media (row[2]) */ + Gbl.Test.Media.MedCod = Str_ConvertStrCodToLongCod (row[2]); + Med_GetMediaDataByCod (&Gbl.Test.Media); + + /* Show media */ + Med_ShowMedia (&Gbl.Test.Media, + "TEST_MED_EDIT_LIST_STEM_CONTAINER", + "TEST_MED_EDIT_LIST_STEM"); + + /* Write answers? */ + switch (Match->Status.Showing) + { + case Mch_WORDING: + /* Don't write anything */ + break; + case Mch_ANSWERS: + if (Match->Status.BeingPlayed) + /* Write answers */ + Tst_WriteAnswersMatchResult (Match->MchCod, + Match->Status.QstInd, + Match->Status.QstCod, + "MATCH_TCH_QST",false); // Don't show result + else // Not being played + Mch_ShowWaitImage (Txt_MATCH_Paused); + break; + case Mch_REQUEST: + /* Write button to request viewing results */ + Mch_PutBigButton (ActShoResMchTch,Match->MchCod, + Mch_ICON_RESULTS,Txt_View_results); + break; + case Mch_RESULTS: + /* Write answers with results */ + Tst_WriteAnswersMatchResult (Match->MchCod, + Match->Status.QstInd, + Match->Status.QstCod, + "MATCH_TCH_QST",true); // Show result + break; + } + + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/***** Show question and its answers when playing a match (as a student) *****/ +/*****************************************************************************/ + +static void Mch_ShowQuestionAndAnswersStd (struct Match *Match) + { + bool Shuffle = false; // TODO: Read shuffle from question + MYSQL_RES *mysql_res; + MYSQL_ROW row; + int StdAnsInd; + unsigned NumOptions; + unsigned NumOpt; + unsigned Index; + bool ErrorInIndex = false; + + /***** Show question *****/ + /* Write buttons for answers? */ + if (Match->Status.Showing == Mch_ANSWERS) + { + if (Tst_CheckIfQuestionIsValidForGame (Match->Status.QstCod)) + { + /***** Get student's answer to this question + (<0 ==> no answer) *****/ + StdAnsInd = Mch_GetQstAnsFromDB (Match->MchCod, + Match->Status.QstInd); + + /***** Get number of options in this question *****/ + NumOptions = Tst_GetNumAnswersQst (Match->Status.QstCod); + + /***** Get answers of question from database *****/ + Shuffle = false; + NumOptions = Tst_GetAnswersQst (Match->Status.QstCod,&mysql_res,Shuffle); + /* + row[0] AnsInd + row[1] Answer + row[2] Feedback + row[3] MedCod + row[4] Correct + */ + + /***** Start table *****/ + Tbl_StartTableWide (8); + + for (NumOpt = 0; + NumOpt < NumOptions; + NumOpt++) + { + /***** Get next answer *****/ + row = mysql_fetch_row (mysql_res); + + /***** Assign index (row[0]). + Index is 0,1,2,3... if no shuffle + or 1,3,0,2... (example) if shuffle *****/ + if (sscanf (row[0],"%u",&Index) == 1) + { + if (Index >= Tst_MAX_OPTIONS_PER_QUESTION) + ErrorInIndex = true; + } + else + ErrorInIndex = true; + if (ErrorInIndex) + Lay_ShowErrorAndExit ("Wrong index of answer when showing a test."); + + /***** Start row *****/ + // if (NumOpt % 2 == 0) + fprintf (Gbl.F.Out,""); + + /***** Write letter for this option *****/ + /* Start table cell */ + fprintf (Gbl.F.Out,""); + + /* Form with button. + Sumitting onmousedown instead of default onclick + is necessary in order to be fast + and not lose clicks due to refresh */ + Frm_StartForm (ActAnsMchQstStd); + Mch_PutParamMatchCod (Match->MchCod); // Current match being played + Gam_PutParamQstInd (Match->Status.QstInd); // Current question index shown + Mch_PutParamAnswer (Index); // Index for this option + fprintf (Gbl.F.Out,"", + 'A' + (char) NumOpt, + 'a' + (char) NumOpt); + Frm_EndForm (); + + /* End table cell */ + fprintf (Gbl.F.Out,""); + + /***** End row *****/ + // if (NumOpt % 2 == 1) + fprintf (Gbl.F.Out,""); + } + + /***** End table *****/ + Tbl_EndTable (); + } + else + Ale_ShowAlert (Ale_ERROR,"Type of answer not valid in a game."); + } + } + + +/*****************************************************************************/ +/******************* Write parameter with student's answer *******************/ +/*****************************************************************************/ + +static void Mch_PutParamAnswer (unsigned AnsInd) + { + Par_PutHiddenParamUnsigned ("Ans",AnsInd); + } + +/*****************************************************************************/ +/******************* Get parameter with student's answer *********************/ +/*****************************************************************************/ + +static unsigned Mch_GetParamAnswer (void) + { + long LongNum; + + LongNum = Par_GetParToLong ("Ans"); + if (LongNum < 0) + Lay_ShowErrorAndExit ("Wrong answer index."); + + return (unsigned) LongNum; + } + +/*****************************************************************************/ +/*********************** Put a big button to do action ***********************/ +/*****************************************************************************/ + +static void Mch_PutBigButton (Act_Action_t NextAction,long MchCod, + const char *Icon,const char *Txt) + { + /***** Start form *****/ + Frm_StartForm (NextAction); + Mch_PutParamMatchCod (MchCod); + + /***** Put icon with link *****/ + /* Submitting onmousedown instead of default onclick + is necessary in order to be fast + and not lose clicks due to refresh */ + fprintf (Gbl.F.Out,"
" + "" + "" + "" + "
", + Txt, + Gbl.Form.Id, + Icon); + + /***** End form *****/ + Frm_EndForm (); + } + +static void Mch_PutBigButtonOff (const char *Icon) + { + /***** Put inactive icon *****/ + fprintf (Gbl.F.Out,"
" + "
" + "" + "
" + "
", + Icon); + } + +static void Mch_PutBigButtonClose (void) + { + extern const char *Txt_Close; + + /***** Put icon with link *****/ + /* onmousedown instead of default onclick + is necessary in order to be fast + and not lose clicks due to refresh */ + fprintf (Gbl.F.Out,"
" + "" + "" + "" + "
", + Txt_Close,Mch_ICON_CLOSE); + } + +/*****************************************************************************/ +/****************************** Show wait image ******************************/ +/*****************************************************************************/ + +static void Mch_ShowWaitImage (const char *Txt) + { + fprintf (Gbl.F.Out,"
" + "\"%s\"" + "
", + Cfg_URL_ICON_PUBLIC, + Txt, + Txt); + } + +/*****************************************************************************/ +/**************************** Remove old players *****************************/ +/*****************************************************************************/ + +static void Mch_RemoveOldPlayers (void) + { + /***** Delete matches not being played *****/ + DB_QueryDELETE ("can not update matches as not being played", + "DELETE FROM gam_mch_being_played" + " WHERE TSStatus.NumPlayers = (unsigned) DB_QueryCOUNT ("can not get number of players", + "SELECT COUNT(*) FROM gam_players" + " WHERE MchCod=%ld", + Match->MchCod); + } + +/*****************************************************************************/ +/******************* Show the results of a finished match ********************/ +/*****************************************************************************/ + +void Mch_ShowFinishedMatchResults (void) + { + Ale_ShowAlert (Ale_INFO,"To be implemented..."); + } + +/*****************************************************************************/ +/********************** Get code of match being played ***********************/ +/*****************************************************************************/ + +void Mch_GetMatchBeingPlayed (void) + { + /***** Get match code ****/ + if ((Gbl.Games.MchCodBeingPlayed = Mch_GetParamMatchCod ()) == -1L) + Lay_ShowErrorAndExit ("Code of match is missing."); + } + +/*****************************************************************************/ +/********* Show game being played to me as student in a new window ***********/ +/*****************************************************************************/ + +void Mch_ShowMatchToMeAsStd (void) + { + struct Match Match; + + /***** Remove old players. + This function must be called before getting match status. *****/ + Mch_RemoveOldPlayers (); + + /***** Get data of the match from database *****/ + Match.MchCod = Gbl.Games.MchCodBeingPlayed; + Mch_GetDataOfMatchByCod (&Match); + + /***** Show current match status *****/ + fprintf (Gbl.F.Out,"
"); + Mch_ShowMatchStatusForStd (&Match); + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/****************** Refresh match for a teacher via AJAX *********************/ +/*****************************************************************************/ + +void Mch_RefreshMatchTch (void) + { + struct Match Match; + + if (!Gbl.Session.IsOpen) // If session has been closed, do not write anything + return; + + /***** Remove old players. + This function must be called before getting match status. *****/ + Mch_RemoveOldPlayers (); + + /***** Get data of the match from database *****/ + Match.MchCod = Gbl.Games.MchCodBeingPlayed; + Mch_GetDataOfMatchByCod (&Match); + + /***** Update match status in database *****/ + Mch_UpdateMatchStatusInDB (&Match); + + /***** Update elapsed time in this question *****/ + Mch_UpdateElapsedTimeInQuestion (&Match); + + /***** Show current match status *****/ + Mch_ShowMatchStatusForTch (&Match); + } + +/*****************************************************************************/ +/*************** Refresh current game for a student via AJAX *****************/ +/*****************************************************************************/ + +void Mch_RefreshMatchStd (void) + { + struct Match Match; + + if (!Gbl.Session.IsOpen) // If session has been closed, do not write anything + return; + + /***** Remove old players. + This function must be called before getting match status. *****/ + Mch_RemoveOldPlayers (); + + /***** Get data of the match from database *****/ + Match.MchCod = Gbl.Games.MchCodBeingPlayed; + Mch_GetDataOfMatchByCod (&Match); + + /***** Show current match status *****/ + Mch_ShowMatchStatusForStd (&Match); + } + +/*****************************************************************************/ +/**** Receive previous question answer in a match question from database *****/ +/*****************************************************************************/ + +static int Mch_GetQstAnsFromDB (long MchCod,unsigned QstInd) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumRows; + int StdAnsInd = -1; // <0 ==> no answer selected + + /***** Get student's answer *****/ + NumRows = (unsigned) DB_QuerySELECT (&mysql_res,"can not get student's answer to a match question", + "SELECT AnsInd FROM gam_answers" + " WHERE MchCod=%ld AND UsrCod=%ld AND QstInd=%u", + MchCod, + Gbl.Usrs.Me.UsrDat.UsrCod, + QstInd); + if (NumRows) // Answer found... + { + /***** Get answer index *****/ + row = mysql_fetch_row (mysql_res); + if (sscanf (row[0],"%d",&StdAnsInd) != 1) + Lay_ShowErrorAndExit ("Error when getting student's answer to a match question."); + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + + return StdAnsInd; + } + +/*****************************************************************************/ +/********* Receive question answer from student when playing a match *********/ +/*****************************************************************************/ + +void Mch_ReceiveQstAnsFromStd (void) + { + struct Match Match; + unsigned QstInd; + unsigned StdAnsInd; + int PreviousStdAnsInd; + + /***** Remove old players. + This function must be called before getting match status. *****/ + Mch_RemoveOldPlayers (); + + /***** Get data of the match from database *****/ + Match.MchCod = Gbl.Games.MchCodBeingPlayed; + Mch_GetDataOfMatchByCod (&Match); + + /***** Get question index from form *****/ + QstInd = Gam_GetParamQstInd (); + + /***** Check that question index is the current one being played *****/ + if (QstInd == Match.Status.QstInd) // Receiving an answer + // to the current question being played + { + /***** Get answer index *****/ + /*-------+--------------+ + | Button | Answer index | + +--------+--------------+ + | a | 0 | + | b | 1 | + | c | 2 | + | d | 3 | + | ... | ... | + +--------+-------------*/ + StdAnsInd = Mch_GetParamAnswer (); + + /***** Get previous student's answer to this question + (<0 ==> no answer) *****/ + PreviousStdAnsInd = Mch_GetQstAnsFromDB (Match.MchCod,QstInd); + + /***** Store student's answer *****/ + if (PreviousStdAnsInd == (int) StdAnsInd) + DB_QueryDELETE ("can not register your answer to the match question", + "DELETE FROM gam_answers" + " WHERE MchCod=%ld AND UsrCod=%ld AND QstInd=%u", + Match.MchCod,Gbl.Usrs.Me.UsrDat.UsrCod,QstInd); + else + DB_QueryREPLACE ("can not register your answer to the match question", + "REPLACE gam_answers" + " (MchCod,UsrCod,QstInd,AnsInd)" + " VALUES" + " (%ld,%ld,%u,%u)", + Match.MchCod,Gbl.Usrs.Me.UsrDat.UsrCod,QstInd,StdAnsInd); + } + + /***** Show current match status *****/ + fprintf (Gbl.F.Out,"
"); + Mch_ShowMatchStatusForStd (&Match); + fprintf (Gbl.F.Out,"
"); + } + +/*****************************************************************************/ +/***** Get number of users who have answered current question in a match *****/ +/*****************************************************************************/ + +static unsigned Mch_GetNumAnswerers (struct Match *Match) + { + /***** Get number of users who have answered the current question in a match from database *****/ + return + (unsigned) DB_QueryCOUNT ("can not get number of questions of a game", + "SELECT COUNT(*) FROM gam_answers" + " WHERE MchCod=%ld AND QstInd=%u", + Match->MchCod, + Match->Status.QstInd); + } + +/*****************************************************************************/ +/*** Get number of users who selected this answer and draw proportional bar **/ +/*****************************************************************************/ + +void Mch_GetAndDrawBarNumUsrsWhoAnswered (long MchCod,unsigned QstInd,unsigned AnsInd,unsigned NumUsrs) + { + unsigned NumUsrsThisAnswer; + + /***** Get number of users who selected this answer *****/ + NumUsrsThisAnswer = Mch_GetNumUsrsWhoAnswered (MchCod,QstInd,AnsInd); + + /***** Show stats of this answer *****/ + Mch_DrawBarNumUsrs (NumUsrsThisAnswer,NumUsrs); + } + +/*****************************************************************************/ +/**** Get number of users who selected a given answer of a game question *****/ +/*****************************************************************************/ + +static unsigned Mch_GetNumUsrsWhoAnswered (long MchCod,unsigned QstInd,unsigned AnsInd) + { + /***** Get number of users who have chosen + an answer of a question from database *****/ + return (unsigned) DB_QueryCOUNT ("can not get number of users who answered", + "SELECT COUNT(*)" + " FROM gam_answers" + " WHERE MchCod=%ld" + " AND QstInd=%u" + " AND AnsInd=%u", + MchCod,QstInd,AnsInd); + } + +/*****************************************************************************/ +/***************** Draw a bar with the percentage of answers *****************/ +/*****************************************************************************/ + +#define Gam_MAX_BAR_WIDTH 125 + +static void Mch_DrawBarNumUsrs (unsigned NumUsrs,unsigned MaxUsrs) + { + extern const char *Txt_of_PART_OF_A_TOTAL; + unsigned BarWidth = 0; + + /***** String with the number of users *****/ + if (MaxUsrs) + snprintf (Gbl.Title,sizeof (Gbl.Title), + "%u (%u%% %s %u)", + NumUsrs, + (unsigned) ((((float) NumUsrs * 100.0) / (float) MaxUsrs) + 0.5), + Txt_of_PART_OF_A_TOTAL,MaxUsrs); + else + snprintf (Gbl.Title,sizeof (Gbl.Title), + "0 (0%% %s %u)", + Txt_of_PART_OF_A_TOTAL,MaxUsrs); + + /***** Draw bar with a with proportional to the number of clicks *****/ + if (NumUsrs && MaxUsrs) + BarWidth = (unsigned) ((((float) NumUsrs * (float) Gam_MAX_BAR_WIDTH) / + (float) MaxUsrs) + 0.5); + if (BarWidth < 2) + BarWidth = 2; + fprintf (Gbl.F.Out,"\"%s\"" + " ", + Cfg_URL_ICON_PUBLIC, + Gbl.Title, + Gbl.Title, + BarWidth); + + /***** Write the number of users *****/ + fprintf (Gbl.F.Out,"%s",Gbl.Title); + } diff --git a/swad_match.h b/swad_match.h new file mode 100644 index 00000000..d37f08ff --- /dev/null +++ b/swad_match.h @@ -0,0 +1,68 @@ +// swad_match.h: matches in games using remote control + +#ifndef _SWAD_MCH +#define _SWAD_MCH +/* + SWAD (Shared Workspace At a Distance in Spanish), + 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 ***********************************/ +/*****************************************************************************/ + +#include "swad_scope.h" + +/*****************************************************************************/ +/************************** Public types and constants ***********************/ +/*****************************************************************************/ + +#define Mch_NEW_MATCH_SECTION_ID "new_match" + +#define Mch_AFTER_LAST_QUESTION ((unsigned)((1UL << 31) - 1)) // 2^31 - 1, don't change this number because it is used in database to indicate that a match is finished + +/*****************************************************************************/ +/***************************** Public prototypes *****************************/ +/*****************************************************************************/ + +void Mch_ListMatches (struct Game *Game,bool PutFormNewMatch); + +void Mch_RequestRemoveMatchTch (void); +void Mch_RemoveMatchTch (void); + +void Mch_CreateNewMatchTch (void); +void Mch_RequestStartResumeMatchTch (void); +void Mch_PauseMatchTch (void); +void Mch_ResumeMatchTch (void); +void Mch_ShowResultsQstMatchTch (void); +void Mch_BackMatchTch (void); +void Mch_ForwardMatchTch (void); + +void Mch_ShowFinishedMatchResults (void); + +void Mch_GetMatchBeingPlayed (void); +void Mch_ShowMatchToMeAsStd (void); +void Mch_RefreshMatchTch (void); +void Mch_RefreshMatchStd (void); + +void Mch_ReceiveQstAnsFromStd (void); + +void Mch_GetAndDrawBarNumUsrsWhoAnswered (long MchCod,unsigned QstInd,unsigned AnsInd,unsigned NumUsrs); + +#endif diff --git a/swad_test.c b/swad_test.c index 5a4571b4..8e9de988 100644 --- a/swad_test.c +++ b/swad_test.c @@ -43,6 +43,7 @@ #include "swad_global.h" #include "swad_ID.h" #include "swad_language.h" +#include "swad_match.h" #include "swad_media.h" #include "swad_parameter.h" #include "swad_table.h" @@ -4142,7 +4143,7 @@ static void Tst_WriteChoiceAnsViewGame (long MchCod,unsigned QstInd,long QstCod, ""); /* Get number of users who selected this answer and draw proportional bar */ - Gam_GetAndDrawBarNumUsrsWhoAnswered (MchCod,QstInd,AnsInd, + Mch_GetAndDrawBarNumUsrsWhoAnswered (MchCod,QstInd,AnsInd, 0); // TODO: NumUsrs fprintf (Gbl.F.Out,"" "");