diff --git a/css/swad19.39.css b/css/swad19.40.css similarity index 99% rename from css/swad19.39.css rename to css/swad19.40.css index a799a409b..617a51450 100644 --- a/css/swad19.39.css +++ b/css/swad19.40.css @@ -2804,56 +2804,30 @@ a:hover img.CENTRE_PHOTO_SHOW width:25%; } -.MATCH_PODIUM +.MATCH_SCORE_SCO { - display:table; - box-sizing:border-box; - width:100%; - text-align:center; - } -.MATCH_PODIUM_POSITION - { - color:white; - font-size:32pt; + width:5%; + text-align:right; + vertical-align:middle; + height:12px; + font-size:7pt; font-weight:bold; } -.MATCH_PODIUM_SCORE +.MATCH_SCORE_NUM { - color:white; - font-size:24pt; + width:95%; + text-align:left; + vertical-align:middle; + background:white; + height:12px; + font-size:7pt; font-weight:bold; } -.MATCH_PODIUM_1 +.MATCH_SCORE_BAR { - display:table-cell; - box-sizing:border-box; - float:left; - width:33%; - margin-top:200px; - height:200px; - margin-top:200px; - background:grey; - } -.MATCH_PODIUM_0 - { - display:table-cell; - box-sizing:border-box; - float:left; - width:34%; - margin-top:100px; - height:300px; - margin-top:100px; - background:grey; - } -.MATCH_PODIUM_2 - { - display:table-cell; - box-sizing:border-box; - float:left; - width:33%; - height:100px; - margin-top:300px; - background:grey; + display:inline-block; + height:10px; + vertical-align:middle; } .MATCH_TCH_BUTTON_TD diff --git a/sql/cambios.sql b/sql/cambios.sql index 95d70c67f..474dedb48 100644 --- a/sql/cambios.sql +++ b/sql/cambios.sql @@ -13094,7 +13094,7 @@ SELECT projects.PrjCod,COUNT(prj_usr.UsrCod) AS NumStds FROM projects LEFT JOIN - +SELECT COUNT(*) FROM tst_answers,gam_questions WHERE gam_questions.GamCod=6 AND gam_questions.QstCod=tst_answers.QstCod; diff --git a/swad_changelog.h b/swad_changelog.h index f6a01e044..4c62f5bad 100644 --- a/swad_changelog.h +++ b/swad_changelog.h @@ -487,13 +487,14 @@ enscript -2 --landscape --color --file-align=2 --highlight --line-numbers -o - * En OpenSWAD: ps2pdf source.ps destination.pdf */ -#define Log_PLATFORM_VERSION "SWAD 19.39.2 (2019-10-18)" -#define CSS_FILE "swad19.39.css" +#define Log_PLATFORM_VERSION "SWAD 19.40 (2019-10-21)" +#define CSS_FILE "swad19.40.css" #define JS_FILE "swad19.39.js" /* // TODO: Perico: poner un candado de bloqueo de creación/edición de proyectos (por ejemplo en asignaturas obsoletas) // TODO: Hacer un nuevo rol en los TFG: tutor externo (profesor de áreas no vinculadas con el centro, profesionales de empresas, etc.) + Version 19.40: Oct 21, 2019 Match podium has been replaced by score table. (245741 lines) Version 19.39.2: Oct 18, 2019 Fixed HTML bug in listing of registration requests. (245659 lines) Version 19.39.1: Oct 18, 2019 Changes in layout and behaviour of matches. (245656 lines) Version 19.39: Oct 17, 2019 Keyboard/presenter is allowed for playing matches. (245657 lines) diff --git a/swad_game.c b/swad_game.c index 18bbb16c7..4cbcc05c2 100644 --- a/swad_game.c +++ b/swad_game.c @@ -2545,3 +2545,45 @@ static long Gam_GetParamCurrentGamCod (void) { return Gam_CurrentGamCod; } + +/*****************************************************************************/ +/*************** Get maximum score of a game from database *******************/ +/*****************************************************************************/ + +void Gam_GetScoreRange (long GamCod,double *MinScore,double *MaxScore) + { + MYSQL_RES *mysql_res; + MYSQL_ROW row; + unsigned NumRows; + unsigned NumRow; + unsigned NumAnswers; + + /***** Get maximum score of a game from database *****/ + NumRows = (unsigned) + DB_QuerySELECT (&mysql_res,"can not get data of a question", + "SELECT COUNT(tst_answers.AnsInd) AS N" + " FROM tst_answers,gam_questions" + " WHERE gam_questions.GamCod=%ld" + " AND gam_questions.QstCod=tst_answers.QstCod" + " GROUP BY tst_answers.QstCod", + GamCod); + for (NumRow = 0, *MinScore = *MaxScore = 0.0; + NumRow < NumRows; + NumRow++) + { + row = mysql_fetch_row (mysql_res); + + /* Get min answers (row[0]) */ + if (sscanf (row[0],"%u",&NumAnswers) != 1) + NumAnswers = 0; + + /* Accumulate minimum and maximum score */ + if (NumAnswers < 2) + Lay_ShowErrorAndExit ("Wrong number of answers."); + *MinScore += -1.0 / (double) (NumAnswers - 1); + *MaxScore += 1.0; + } + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } diff --git a/swad_game.h b/swad_game.h index c20867610..2bbbb7e1a 100644 --- a/swad_game.h +++ b/swad_game.h @@ -130,4 +130,6 @@ void Gam_ShowTstTagsPresentInAGame (long GamCod); void Gam_SetParamCurrentGamCod (long GamCod); +void Gam_GetScoreRange (long GamCod,double *MinScore,double *MaxScore); + #endif diff --git a/swad_match.c b/swad_match.c index be77b68e7..98c388df3 100644 --- a/swad_match.c +++ b/swad_match.c @@ -160,7 +160,10 @@ static void Mch_PutCheckboxResult (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_ShowMatchPodium (struct Match *Match); + +static void Mch_ShowMatchScore (struct Match *Match); +static void Mch_DrawEmptyRowScore (unsigned NumRow,double MinScore,double MaxScore); +static void Mch_DrawScoreRow (double Score,unsigned MaxUsrs,unsigned NumUsrs); static void Mch_PutParamNumOpt (unsigned NumOpt); static unsigned Mch_GetParamNumOpt (void); @@ -2134,7 +2137,7 @@ static void Mch_ShowRightColumnTch (struct Match *Match) if (Match->Status.QstInd < Mch_AFTER_LAST_QUESTION) // Not finished Mch_ShowQuestionAndAnswersTch (Match); else // Finished - Mch_ShowMatchPodium (Match); + Mch_ShowMatchScore (Match); /***** End right container *****/ fprintf (Gbl.F.Out,""); @@ -2482,91 +2485,173 @@ static void Mch_ShowQuestionAndAnswersStd (struct Match *Match) } /*****************************************************************************/ -/***************************** Show match podium *****************************/ +/***************************** Show match scores *****************************/ /*****************************************************************************/ -static void Mch_ShowMatchPodium (struct Match *Match) +#define Mch_NUM_ROWS_SCORE 41 + +static void Mch_ShowMatchScore (struct Match *Match) { MYSQL_RES *mysql_res; MYSQL_ROW row; - unsigned NumRows; + unsigned NumScores; + unsigned NumScore; + double MinScore; + double MaxScore; + double Range; + double NumRowsPerScorePoint; + double Score; + unsigned MaxUsrs = 0; + unsigned NumUsrs; + unsigned NumRowForThisScore; unsigned NumRow; - struct - { - double Score; - unsigned NumUsrs; - } Podium[3]; - unsigned i; - static const unsigned Position[3] = - { - 1, // 2nd position - 0, // 1st position - 2 // 3rd position - }; - /***** Get podium from database *****/ - NumRows = (unsigned) - DB_QuerySELECT (&mysql_res,"can not get data of a question", - "SELECT Score," // row[0] - "COUNT(*) AS NumUsrs" // row[1] - " FROM mch_results" - " WHERE MchCod=%ld" - " GROUP BY Score" - " ORDER BY Score DESC LIMIT 3", - Match->MchCod); + /***** Get minimum and maximum scores *****/ + Gam_GetScoreRange (Match->GamCod,&MinScore,&MaxScore); + Range = MaxScore - MinScore; + if (Range == 0.0) + return; + NumRowsPerScorePoint = (double) Mch_NUM_ROWS_SCORE / Range; - /* Get podium */ - for (NumRow = 0; - NumRow < NumRows; - NumRow++) + /***** Get maximum number of users *****/ + if (DB_QuerySELECT (&mysql_res,"can not get max users", + "SELECT MAX(NumUsrs)" + " FROM " + "(SELECT COUNT(*) AS NumUsrs" // row[1] + " FROM mch_results" + " WHERE MchCod=%ld" + " GROUP BY Score" + " ORDER BY Score) AS Scores", + Match->MchCod)) { row = mysql_fetch_row (mysql_res); + /* Get maximum number of users (row[0]) *****/ + if (row) + if (row[0]) + if (sscanf (row[0],"%u",&MaxUsrs) != 1) + MaxUsrs = 0; + } + + /* Free structure that stores the query result */ + DB_FreeMySQLResult (&mysql_res); + + /***** Get scores from database *****/ + NumScores = (unsigned) + DB_QuerySELECT (&mysql_res,"can not get scores", + "SELECT Score," // row[0] + "COUNT(*) AS NumUsrs" // row[1] + " FROM mch_results" + " WHERE MchCod=%ld" + " GROUP BY Score" + " ORDER BY Score DESC", + Match->MchCod); + + /***** Begin table ****/ + Tbl_TABLE_BeginWide (); + + /***** Get and draw scores *****/ + for (NumScore = 0, NumRow = 0; + NumScore < NumScores; + NumScore++) + { + /***** Get score and number of users from database *****/ + row = mysql_fetch_row (mysql_res); + /* Get score (row[0]) */ - Str_SetDecimalPointToUS (); // To get the decimal point as a dot - if (sscanf (row[0],"%lf",&(Podium[NumRow].Score)) != 1) - Podium[NumRow].Score = 0.0; + Str_SetDecimalPointToUS (); // To get the decimal point as a dot + if (sscanf (row[0],"%lf",&Score) != 1) + Score = 0.0; Str_SetDecimalPointToLocal (); // Return to local system /* Get number of users (row[1]) *****/ - if (sscanf (row[1],"%u",&(Podium[NumRow].NumUsrs)) != 1) - Podium[NumRow].NumUsrs = 0; + if (sscanf (row[1],"%u",&NumUsrs) != 1) + NumUsrs = 0; + + /***** Draw empty rows until reaching the adequate row *****/ + NumRowForThisScore = (unsigned) ((MaxScore - Score) * NumRowsPerScorePoint); + for (; + NumRow < NumRowForThisScore; + NumRow++) + Mch_DrawEmptyRowScore (NumRow,MinScore,MaxScore); + + /***** Draw row for this score *****/ + Mch_DrawScoreRow (Score,MaxUsrs,NumUsrs); + NumRow++; } - /* Reset remaining positions */ + /***** Draw final empty rows *****/ for (; - NumRow < 3; + NumRow < Mch_NUM_ROWS_SCORE; NumRow++) + Mch_DrawEmptyRowScore (NumRow,MinScore,MaxScore); + + /***** End table *****/ + Tbl_TABLE_End (); + + /***** Free structure that stores the query result *****/ + DB_FreeMySQLResult (&mysql_res); + } + +static void Mch_DrawEmptyRowScore (unsigned NumRow,double MinScore,double MaxScore) + { + /***** Draw row *****/ + Tbl_TR_Begin (NULL); + + /* Write score */ + Tbl_TD_Begin ("class=\"MATCH_SCORE_SCO\""); + if (NumRow == 0) { - /* Score */ - Podium[NumRow].Score = 0.0; - - /* Number of users */ - Podium[NumRow].NumUsrs = 0; + Str_WriteFloatNumToFile (Gbl.F.Out,MaxScore); + fprintf (Gbl.F.Out," "); } - - /***** Show podium *****/ - fprintf (Gbl.F.Out,"
"); // Bottom - - fprintf (Gbl.F.Out,"
"); // Podium - - for (i = 0; - i < 3; - i++) + else if (NumRow == Mch_NUM_ROWS_SCORE - 1) { - fprintf (Gbl.F.Out,"
",Position[i]); - if (Podium[Position[i]].NumUsrs) - fprintf (Gbl.F.Out,"
%u
" - "
%.2lf (%u)
", - Position[i] + 1, - Podium[Position[i]].Score, - Podium[Position[i]].NumUsrs); - fprintf (Gbl.F.Out,"
"); + Str_WriteFloatNumToFile (Gbl.F.Out,MinScore); + fprintf (Gbl.F.Out," "); } + Tbl_TD_End (); - fprintf (Gbl.F.Out,"
"); // Podium + /* Empty column with bar and number of users */ + Tbl_TD_Begin ("class=\"MATCH_SCORE_NUM\""); + Tbl_TD_End (); - fprintf (Gbl.F.Out,"
"); // Bottom + Tbl_TR_End (); + } + +static void Mch_DrawScoreRow (double Score,unsigned MaxUsrs,unsigned NumUsrs) + { + unsigned BarWidth; + + /***** Compute bar width *****/ + if (MaxUsrs > 0) + { + BarWidth = (unsigned) (((NumUsrs * 95.0) / MaxUsrs) + 0.5); + if (BarWidth == 0) + BarWidth = 1; + } + else + BarWidth = 0; + + /***** Draw row *****/ + Tbl_TR_Begin (NULL); + + /* Write score */ + Tbl_TD_Begin ("class=\"MATCH_SCORE_SCO\""); + Str_WriteFloatNumToFile (Gbl.F.Out,Score); + fprintf (Gbl.F.Out," "); + Tbl_TD_End (); + + /* Draw bar and write number of users for this score */ + Tbl_TD_Begin ("class=\"MATCH_SCORE_NUM\""); + fprintf (Gbl.F.Out,"\"\"" + " %u", + Cfg_URL_ICON_PUBLIC,BarWidth,NumUsrs); + Tbl_TD_End (); + + Tbl_TR_End (); } /*****************************************************************************/ diff --git a/swad_string.c b/swad_string.c index c8da869d5..fc7e3dad2 100644 --- a/swad_string.c +++ b/swad_string.c @@ -908,7 +908,7 @@ void Str_FloatNumToStr (char **Str,float Number) double FractionaryPart; char *Format; - FractionaryPart = modf ((double) Number,&IntegerPart); + FractionaryPart = fabs (modf ((double) Number,&IntegerPart)); if (FractionaryPart == 0.0) {