2016-12-27 20:12:17 +00:00
package fic
import (
"database/sql"
2022-06-07 14:05:41 +00:00
"encoding/json"
2016-12-30 11:45:14 +00:00
"fmt"
2022-06-07 14:05:41 +00:00
"os"
2016-12-27 20:12:17 +00:00
"time"
)
2018-03-09 18:07:08 +00:00
// FirstBlood is the coefficient added to the challenge coefficient when a Team is the first to solve a challenge.
2016-12-30 11:45:14 +00:00
var FirstBlood = 0.12
2018-03-09 18:07:08 +00:00
// SubmissionCostBase is the basis amount of point lost per submission
2016-12-30 11:45:14 +00:00
var SubmissionCostBase = 0.5
2018-12-06 21:18:08 +00:00
// SubmissionUniqueness don't count multiple times identical tries.
var SubmissionUniqueness = false
2021-09-08 01:34:48 +00:00
// CountOnlyNotGoodTries don't count as a try when one good response is given at least.
var CountOnlyNotGoodTries = false
2023-04-01 15:11:40 +00:00
// DiscountedFactor stores the percentage of the exercice's gain lost on each validation.
var DiscountedFactor = 0.0
2017-01-16 11:08:15 +00:00
func exoptsQuery ( whereExo string ) string {
2018-12-06 21:18:08 +00:00
tries_table := "exercice_tries"
if SubmissionUniqueness {
tries_table = "exercice_distinct_tries"
}
2021-09-08 01:34:48 +00:00
if CountOnlyNotGoodTries {
tries_table += "_notgood"
}
2023-04-01 15:11:40 +00:00
exercices_table := "exercices"
if DiscountedFactor > 0 {
exercices_table = "exercices_discounted"
}
2023-04-02 14:49:10 +00:00
query := ` SELECT S . id_team , S . time , E . gain AS points , coeff , S . reason , S . id_exercice FROM (
2023-04-02 16:45:35 +00:00
SELECT id_team , id_exercice , time , ` + fmt.Sprintf("%f", FirstBlood) + ` AS coeff , "First blood" AS reason FROM exercice_solved JOIN ( SELECT id_exercice , MIN ( time ) time FROM exercice_solved GROUP BY id_exercice ) d1 USING ( id_exercice , time ) UNION
2018-12-08 02:30:56 +00:00
SELECT id_team , id_exercice , time , coefficient AS coeff , "Validation" AS reason FROM exercice_solved
2023-04-02 14:49:10 +00:00
) S INNER JOIN ` + exercices_table + ` E ON S . id_exercice = E . id_exercice UNION ALL
2022-05-31 20:03:51 +00:00
SELECT B . id_team , B . time , F . bonus_gain AS points , 1 AS coeff , "Bonus flag" AS reason , F . id_exercice FROM flag_found B INNER JOIN exercice_flags F ON F . id_flag = B . id_flag WHERE F . bonus_gain != 0 HAVING points != 0 UNION ALL
2023-04-02 14:49:10 +00:00
SELECT id_team , MAX ( time ) AS time , ( FLOOR ( COUNT ( * ) / 10 - 1 ) * ( FLOOR ( COUNT ( * ) / 10 ) ) ) / 0.2 + ( FLOOR ( COUNT ( * ) / 10 ) * ( COUNT ( * ) % 10 ) ) AS points , ` + fmt.Sprintf("%f", SubmissionCostBase*-1) + ` AS coeff , "Tries" AS reason , id_exercice FROM ` + tries_table + ` S GROUP BY id_exercice , id_team `
if whereExo != "" {
query = "SELECT W.* FROM (" + query + ") W " + whereExo
}
return query
2018-12-08 02:30:56 +00:00
}
func teamptsQuery ( ) string {
return exoptsQuery ( "" ) + ` UNION ALL
2019-01-17 11:03:18 +00:00
SELECT D . id_team , D . time , H . cost AS points , D . coefficient * - 1 AS coeff , "Hint" AS reason , H . id_exercice FROM team_hints D INNER JOIN exercice_hints H ON H . id_hint = D . id_hint HAVING points != 0 UNION ALL
SELECT W . id_team , W . time , F . choices_cost AS points , W . coefficient * - 1 AS coeff , "Display choices" AS reason , F . id_exercice FROM team_wchoices W INNER JOIN exercice_flags F ON F . id_flag = W . id_flag HAVING points != 0 `
2017-01-16 11:08:15 +00:00
}
func rankQuery ( whereTeam string ) string {
2018-12-06 20:47:48 +00:00
return ` SELECT A . id_team , SUM ( A . points * A . coeff ) AS score , MAX ( A . time ) AS time FROM (
2018-12-08 02:30:56 +00:00
` + teamptsQuery() + `
2018-12-06 20:47:48 +00:00
) A ` + whereTeam + ` GROUP BY A . id_team ORDER BY score DESC , time ASC `
2017-01-16 11:08:15 +00:00
}
2022-06-07 14:05:41 +00:00
type ScoreGridRow struct {
Reason string ` json:"reason" `
IdExercice int64 ` json:"id_exercice" `
Time time . Time ` json:"time" `
Points float64 ` json:"points" `
Coeff float64 ` json:"coeff" `
}
func ( t * Team ) ScoreGrid ( ) ( grid [ ] ScoreGridRow , err error ) {
2018-12-08 02:30:56 +00:00
q := "SELECT G.reason, G.id_exercice, G.time, G.points, G.coeff FROM (" + teamptsQuery ( ) + ") AS G WHERE G.id_team = ? AND G.points != 0"
if rows , err := DBQuery ( q , t . Id ) ; err != nil {
return nil , err
} else {
defer rows . Close ( )
for rows . Next ( ) {
2022-06-07 14:05:41 +00:00
var sgr ScoreGridRow
2018-12-08 02:30:56 +00:00
2022-06-07 14:05:41 +00:00
if err := rows . Scan ( & sgr . Reason , & sgr . IdExercice , & sgr . Time , & sgr . Points , & sgr . Coeff ) ; err != nil {
2018-12-08 02:30:56 +00:00
return nil , err
}
2022-06-07 14:05:41 +00:00
grid = append ( grid , sgr )
2018-12-08 02:30:56 +00:00
}
}
return
}
2022-06-07 14:05:41 +00:00
func ReadScoreGrid ( fd * os . File ) ( grid [ ] ScoreGridRow , err error ) {
jdec := json . NewDecoder ( fd )
err = jdec . Decode ( & grid )
return
}
2016-12-27 20:12:17 +00:00
// Points
2018-03-09 18:07:08 +00:00
// EstimateGain calculates the amount of point the Team has (or could have, if not already solved) won.
2023-04-02 14:49:10 +00:00
func ( e * Exercice ) EstimateGain ( t * Team , solved bool ) ( pts float64 , err error ) {
2017-01-16 11:08:15 +00:00
if solved {
2023-04-02 14:49:10 +00:00
err = DBQueryRow ( "SELECT SUM(A.points * A.coeff) AS score FROM (" + exoptsQuery ( "WHERE W.id_team = ? AND W.id_exercice = ?" ) + ") A GROUP BY id_team" , t . Id , e . Id ) . Scan ( & pts )
return
2017-01-16 11:08:15 +00:00
}
2023-04-02 14:49:10 +00:00
pts += float64 ( e . Gain ) * e . Coefficient
if e . SolvedCount ( ) <= 0 {
pts += float64 ( e . Gain ) * FirstBlood
}
return
2017-01-16 11:08:15 +00:00
}
2018-03-09 18:07:08 +00:00
// GetPoints returns the score for the Team.
2021-11-22 14:35:07 +00:00
func ( t * Team ) GetPoints ( ) ( float64 , error ) {
2017-01-16 11:08:15 +00:00
var tid * int64
2017-01-12 10:52:51 +00:00
var nb * float64
2017-01-16 11:08:15 +00:00
var tzzz * time . Time
err := DBQueryRow ( rankQuery ( "WHERE A.id_team = ?" ) , t . Id ) . Scan ( & tid , & nb , & tzzz )
2016-12-27 20:12:17 +00:00
if nb != nil {
return * nb , err
} else {
return 0 , err
}
}
2018-03-09 18:07:08 +00:00
// GetRank returns a map which associates team ID their rank.
2016-12-27 20:12:17 +00:00
func GetRank ( ) ( map [ int64 ] int , error ) {
2017-01-16 11:08:15 +00:00
if rows , err := DBQuery ( rankQuery ( "" ) ) ; err != nil {
2016-12-27 20:12:17 +00:00
return nil , err
} else {
defer rows . Close ( )
rank := map [ int64 ] int { }
nteam := 0
for rows . Next ( ) {
nteam += 1
var tid int64
2017-01-12 10:52:51 +00:00
var score float64
2016-12-27 20:12:17 +00:00
var tzzz time . Time
if err := rows . Scan ( & tid , & score , & tzzz ) ; err != nil {
return nil , err
}
rank [ tid ] = nteam
}
if err := rows . Err ( ) ; err != nil {
return nil , err
}
return rank , nil
}
}
2018-03-09 18:07:08 +00:00
// Attempts
2016-12-27 20:12:17 +00:00
2018-03-09 18:07:08 +00:00
// GetTries retrieves all attempts made by the matching Team or challenge (both can be nil to not filter).
func GetTries ( t * Team , e * Exercice ) ( times [ ] time . Time , err error ) {
2016-12-27 20:12:17 +00:00
var rows * sql . Rows
if t == nil {
if e == nil {
rows , err = DBQuery ( "SELECT time FROM exercice_tries ORDER BY time ASC" )
} else {
rows , err = DBQuery ( "SELECT time FROM exercice_tries WHERE id_exercice = ? ORDER BY time ASC" , e . Id )
}
} else {
if e == nil {
rows , err = DBQuery ( "SELECT time FROM exercice_tries WHERE id_team = ? ORDER BY time ASC" , t . Id )
} else {
rows , err = DBQuery ( "SELECT time FROM exercice_tries WHERE id_team = ? AND id_exercice = ? ORDER BY time ASC" , t . Id , e . Id )
}
}
if err != nil {
2018-03-09 18:07:08 +00:00
return
2016-12-27 20:12:17 +00:00
} else {
defer rows . Close ( )
for rows . Next ( ) {
var tm time . Time
2018-03-09 18:07:08 +00:00
if err = rows . Scan ( & tm ) ; err != nil {
return
2016-12-27 20:12:17 +00:00
}
times = append ( times , tm )
}
2018-03-09 18:07:08 +00:00
err = rows . Err ( )
return
2016-12-27 20:12:17 +00:00
}
}
2022-06-08 01:40:03 +00:00
// GetValidations retrieves all flag validation made by the matching Team or challenge (both can be nil to not filter).
func GetValidations ( t * Team , e * Exercice ) ( times [ ] time . Time , err error ) {
var rows * sql . Rows
if t == nil {
if e == nil {
rows , err = DBQuery ( "SELECT time FROM flag_found UNION SELECT time FROM mcq_found ORDER BY time ASC" )
} else {
rows , err = DBQuery ( "SELECT time FROM flag_found WHERE id_exercice = ? UNION SELECT time FROM mcq_found WHERE id_exercice = ? ORDER BY time ASC" , e . Id , e . Id )
}
} else {
if e == nil {
rows , err = DBQuery ( "SELECT time FROM flag_found WHERE id_team = ? UNION SELECT time FROM mcq_found WHERE id_team = ? ORDER BY time ASC" , t . Id , t . Id )
} else {
rows , err = DBQuery ( "SELECT time FROM flag_found WHERE id_team = ? AND id_exercice = ? UNION SELECT time FROM mcq_found WHERE id_team = ? AND id_exercice = ? ORDER BY time ASC" , t . Id , e . Id , t . Id , e . Id )
}
}
if err != nil {
return
} else {
defer rows . Close ( )
for rows . Next ( ) {
var tm time . Time
if err = rows . Scan ( & tm ) ; err != nil {
return
}
times = append ( times , tm )
}
err = rows . Err ( )
return
}
}
2018-03-09 18:07:08 +00:00
// GetTryRank generates a special rank based on number of attempts
2016-12-27 20:12:17 +00:00
func GetTryRank ( ) ( [ ] int64 , error ) {
if rows , err := DBQuery ( "SELECT id_team, COUNT(*) AS score FROM exercice_tries GROUP BY id_team HAVING score > 0 ORDER BY score DESC" ) ; err != nil {
return nil , err
} else {
defer rows . Close ( )
rank := make ( [ ] int64 , 0 )
for rows . Next ( ) {
var tid int64
var score int64
if err := rows . Scan ( & tid , & score ) ; err != nil {
return nil , err
}
rank = append ( rank , tid )
}
if err := rows . Err ( ) ; err != nil {
return nil , err
}
return rank , nil
}
}
2024-03-19 12:58:37 +00:00
func ReverseTriesPoints ( points int64 ) int64 {
var i int64 = 1
for ( i + 1 ) * i * 5 < points {
i += 1
}
i = i * 10
for ( i / 10 - 1 ) * i / 10 * 5 + i / 10 * ( i % 10 ) < points {
i += 1
}
return i
}
func TermTriesSeq ( i int64 ) float64 {
if i % 10 == 0 {
return float64 ( ( i - 1 ) / 10 )
}
return float64 ( i / 10 )
}