2016-01-13 19:25:25 +00:00
package fic
2016-01-07 17:43:02 +00:00
import (
2016-01-15 11:57:35 +00:00
"errors"
2018-12-05 04:02:27 +00:00
"fmt"
2023-04-01 15:11:40 +00:00
"math"
2016-01-07 17:43:02 +00:00
"time"
)
2018-03-09 18:07:08 +00:00
// PartialValidation allows a Team to validate an Exercice at a given point if
// it has ever found all classical flags, even if the last validation is not
// complete or contains mistakes on previously validated flags.
2016-12-04 18:08:46 +00:00
var PartialValidation bool
2018-03-09 18:07:08 +00:00
// PartialMCQValidation allows a Team to validate each MCQ independently.
// Otherwise, all MCQ has to be correct for being validated.
2017-12-16 00:16:30 +00:00
var PartialMCQValidation bool
2016-12-04 18:08:46 +00:00
2019-01-17 12:26:49 +00:00
// ExerciceCurrentCoefficient is the current coefficient applied on solved exercices
var ExerciceCurrentCoefficient = 1.0
2018-03-09 18:07:08 +00:00
// Exercice represents a challenge inside a Theme.
2016-01-07 17:43:02 +00:00
type Exercice struct {
2024-03-18 10:26:01 +00:00
Id int64 ` json:"id" `
IdTheme * int64 ` json:"id_theme" `
Language string ` json:"lang,omitempty" `
Title string ` json:"title" `
Authors string ` json:"authors" `
Image string ` json:"image" `
BackgroundColor uint32 ` json:"background_color,omitempty" `
2023-03-20 10:23:03 +00:00
// Disabled indicates if the exercice is available to players now or not
Disabled bool ` json:"disabled" `
2022-11-05 14:26:57 +00:00
// WIP indicates if the exercice is in development or not
WIP bool ` json:"wip" `
2018-03-09 18:07:08 +00:00
// URLid is used to reference the challenge from the URL path
2019-07-05 20:28:56 +00:00
URLId string ` json:"urlid" `
2018-03-09 18:07:08 +00:00
// Path is the relative import location where find challenge data
2019-07-05 20:28:56 +00:00
Path string ` json:"path" `
2018-03-09 18:07:08 +00:00
// Statement is the challenge description shown to players
2019-07-05 20:28:56 +00:00
Statement string ` json:"statement" `
2018-03-09 18:07:08 +00:00
// Overview is the challenge description shown to public
2019-07-05 20:28:56 +00:00
Overview string ` json:"overview" `
2018-12-02 10:43:24 +00:00
// Headline is the challenge headline to fill in small part
2019-07-05 20:28:56 +00:00
Headline string ` json:"headline" `
2018-12-02 16:53:26 +00:00
// Finished is the text shown when the exercice is solved
2019-07-05 20:28:56 +00:00
Finished string ` json:"finished" `
2018-11-21 01:20:37 +00:00
// Issue is an optional text describing an issue with the exercice
2019-07-05 20:28:56 +00:00
Issue string ` json:"issue" `
2018-11-21 01:20:37 +00:00
// IssueKind is the criticity level of the previous issue
2019-07-05 20:28:56 +00:00
IssueKind string ` json:"issuekind" `
Depend * int64 ` json:"depend" `
2018-03-09 18:07:08 +00:00
// Gain is the basis amount of points player will earn if it solve the challenge
// If this value fluctuate during the challenge, players' scores will follow changes.
// Use Coefficient value to create temporary bonus/malus.
2019-07-05 20:28:56 +00:00
Gain int64 ` json:"gain" `
2018-03-09 18:07:08 +00:00
// Coefficient is a temporary bonus/malus applied on Gain when the challenge is solved.
// This value is saved along with solved informations: changing it doesn't affect Team that already solved the challenge.
2017-01-15 22:40:58 +00:00
Coefficient float64 ` json:"coefficient" `
2018-03-09 18:07:08 +00:00
// VideoURI is the link to the resolution video
2019-07-05 20:28:56 +00:00
VideoURI string ` json:"videoURI" `
2021-12-10 17:55:47 +00:00
// Resolution is a write-up (in HTML)
Resolution string ` json:"resolution" `
// SeeAlso is a collection of links (HTML formated)
SeeAlso string ` json:"seealso" `
2016-01-07 17:43:02 +00:00
}
2022-11-05 14:26:57 +00:00
func ( e * Exercice ) AnalyzeTitle ( ) {
if len ( e . Title ) > 0 && e . Title [ 0 ] == '%' {
e . WIP = true
e . Title = e . Title [ 1 : ]
} else {
e . WIP = false
}
}
2023-04-01 15:11:40 +00:00
func getExercice ( table , condition string , args ... interface { } ) ( * Exercice , error ) {
2016-01-13 12:07:45 +00:00
var e Exercice
2023-04-01 15:11:40 +00:00
var tmpgain float64
2024-03-18 10:26:01 +00:00
if err := DBQueryRow ( "SELECT id_exercice, id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM " + table + " " + condition , args ... ) . Scan ( & e . Id , & e . IdTheme , & e . Title , & e . Authors , & e . Image , & e . BackgroundColor , & e . Disabled , & e . URLId , & e . Path , & e . Statement , & e . Overview , & e . Headline , & e . Issue , & e . IssueKind , & e . Depend , & tmpgain , & e . Coefficient , & e . VideoURI , & e . Resolution , & e . SeeAlso , & e . Finished ) ; err != nil {
2021-11-22 14:35:07 +00:00
return nil , err
2016-01-13 12:07:45 +00:00
}
2023-04-01 15:11:40 +00:00
e . Gain = int64 ( math . Trunc ( tmpgain ) )
2022-11-05 14:26:57 +00:00
e . AnalyzeTitle ( )
2016-01-13 12:07:45 +00:00
2021-11-22 14:35:07 +00:00
return & e , nil
2016-01-13 12:07:45 +00:00
}
2018-03-09 18:07:08 +00:00
// GetExercice retrieves the challenge with the given id.
2022-01-20 16:56:39 +00:00
func GetExercice ( id int64 ) ( * Exercice , error ) {
2023-04-01 15:11:40 +00:00
return getExercice ( "exercices" , "WHERE id_exercice = ?" , id )
2022-01-20 16:56:39 +00:00
}
2016-01-13 12:07:45 +00:00
2022-01-20 16:56:39 +00:00
// GetExercice retrieves the challenge with the given id.
func ( t * Theme ) GetExercice ( id int ) ( * Exercice , error ) {
2024-03-15 16:46:50 +00:00
query := "WHERE id_exercice = ? AND id_theme = ?"
args := [ ] interface { } { id }
if t . GetId ( ) == nil {
query = "WHERE id_exercice = ? AND id_theme IS NULL"
} else {
args = append ( args , t . GetId ( ) )
}
return getExercice ( "exercices" , query , args ... )
2016-01-13 12:07:45 +00:00
}
2018-03-09 18:07:08 +00:00
// GetExerciceByTitle retrieves the challenge with the given title.
2021-11-22 14:35:07 +00:00
func ( t * Theme ) GetExerciceByTitle ( title string ) ( * Exercice , error ) {
2024-03-15 16:46:50 +00:00
query := "WHERE title = ? AND id_theme = ?"
args := [ ] interface { } { title }
if t . GetId ( ) == nil {
query = "WHERE title = ? AND id_theme IS NULL"
} else {
args = append ( args , t . GetId ( ) )
}
return getExercice ( "exercices" , query , args ... )
2022-01-20 16:56:39 +00:00
}
2017-12-08 23:53:08 +00:00
2022-01-20 16:56:39 +00:00
// GetExerciceByPath retrieves the challenge with the given path.
func ( t * Theme ) GetExerciceByPath ( epath string ) ( * Exercice , error ) {
2024-03-15 16:46:50 +00:00
query := "WHERE path = ? AND id_theme = ?"
args := [ ] interface { } { epath }
if t . GetId ( ) == nil {
query = "WHERE path = ? AND id_theme IS NULL"
} else {
args = append ( args , t . GetId ( ) )
}
return getExercice ( "exercices" , query , args ... )
2017-12-08 23:53:08 +00:00
}
2023-04-01 15:11:40 +00:00
// GetDiscountedExercice retrieves the challenge with the given id.
func GetDiscountedExercice ( id int64 ) ( * Exercice , error ) {
table := "exercices"
if DiscountedFactor > 0 {
table = "exercices_discounted"
}
return getExercice ( table , "WHERE id_exercice = ?" , id )
}
// getExercices returns the list of all challenges present in the database.
func getExercices ( table string ) ( [ ] * Exercice , error ) {
2024-03-18 10:26:01 +00:00
if rows , err := DBQuery ( "SELECT id_exercice, id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM " + table + " ORDER BY path ASC" ) ; err != nil {
2016-01-18 17:25:06 +00:00
return nil , err
} else {
defer rows . Close ( )
2021-11-22 14:35:07 +00:00
exos := [ ] * Exercice { }
2016-01-18 17:25:06 +00:00
for rows . Next ( ) {
2021-11-22 14:35:07 +00:00
e := & Exercice { }
2023-04-01 15:11:40 +00:00
var tmpgain float64
2024-03-18 10:26:01 +00:00
if err := rows . Scan ( & e . Id , & e . IdTheme , & e . Title , & e . Authors , & e . Image , & e . BackgroundColor , & e . Disabled , & e . URLId , & e . Path , & e . Statement , & e . Overview , & e . Headline , & e . Issue , & e . IssueKind , & e . Depend , & tmpgain , & e . Coefficient , & e . VideoURI , & e . Resolution , & e . SeeAlso , & e . Finished ) ; err != nil {
2016-01-18 17:25:06 +00:00
return nil , err
}
2023-04-01 15:11:40 +00:00
e . Gain = int64 ( math . Trunc ( tmpgain ) )
2022-11-05 14:26:57 +00:00
e . AnalyzeTitle ( )
2016-01-18 17:25:06 +00:00
exos = append ( exos , e )
}
if err := rows . Err ( ) ; err != nil {
return nil , err
}
return exos , nil
}
}
2023-04-01 15:11:40 +00:00
func GetExercices ( ) ( [ ] * Exercice , error ) {
return getExercices ( "exercices" )
}
func GetDiscountedExercices ( ) ( [ ] * Exercice , error ) {
table := "exercices"
if DiscountedFactor > 0 {
table = "exercices_discounted"
}
return getExercices ( table )
}
2018-03-09 18:07:08 +00:00
// GetExercices returns the list of all challenges in the Theme.
2021-11-22 14:35:07 +00:00
func ( t * Theme ) GetExercices ( ) ( [ ] * Exercice , error ) {
2024-03-18 10:26:01 +00:00
query := "SELECT id_exercice, id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM exercices WHERE id_theme IS NULL ORDER BY path ASC"
2024-03-15 16:46:50 +00:00
args := [ ] interface { } { }
if t . GetId ( ) != nil {
2024-03-18 10:26:01 +00:00
query = "SELECT id_exercice, id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM exercices WHERE id_theme = ? ORDER BY path ASC"
2024-03-15 16:46:50 +00:00
args = append ( args , t . GetId ( ) )
}
if rows , err := DBQuery ( query , args ... ) ; err != nil {
2016-01-07 17:43:02 +00:00
return nil , err
} else {
defer rows . Close ( )
2021-11-22 14:35:07 +00:00
exos := [ ] * Exercice { }
2016-01-07 17:43:02 +00:00
for rows . Next ( ) {
2021-11-22 14:35:07 +00:00
e := & Exercice { }
2024-03-18 10:26:01 +00:00
if err := rows . Scan ( & e . Id , & e . IdTheme , & e . Title , & e . Authors , & e . Image , & e . BackgroundColor , & e . Disabled , & e . URLId , & e . Path , & e . Statement , & e . Overview , & e . Headline , & e . Issue , & e . IssueKind , & e . Depend , & e . Gain , & e . Coefficient , & e . VideoURI , & e . Resolution , & e . SeeAlso , & e . Finished ) ; err != nil {
2016-01-07 17:43:02 +00:00
return nil , err
}
2022-11-05 14:26:57 +00:00
e . AnalyzeTitle ( )
2016-01-07 17:43:02 +00:00
exos = append ( exos , e )
}
if err := rows . Err ( ) ; err != nil {
return nil , err
}
return exos , nil
}
}
2018-12-05 04:02:27 +00:00
// SaveNamedExercice looks for an exercice with the same title to update it, or create it if it doesn't exists yet.
2021-11-22 14:35:07 +00:00
func ( t * Theme ) SaveNamedExercice ( e * Exercice ) ( err error ) {
var search * Exercice
2022-01-20 16:56:39 +00:00
// Search same title
search , _ = t . GetExerciceByTitle ( e . Title )
// Search on same path
if search == nil && len ( e . Path ) > 0 {
search , err = t . GetExerciceByPath ( e . Path )
}
if search == nil {
err = t . addExercice ( e )
} else {
2018-12-05 04:02:27 +00:00
// Force ID
e . Id = search . Id
// Don't expect those values
if e . Coefficient == 0 {
e . Coefficient = search . Coefficient
}
if len ( e . Issue ) == 0 {
e . Issue = search . Issue
}
if len ( e . IssueKind ) == 0 {
e . IssueKind = search . IssueKind
}
_ , err = e . Update ( )
2016-01-13 12:07:45 +00:00
}
2018-12-05 04:02:27 +00:00
return
}
2021-11-22 14:35:07 +00:00
func ( t * Theme ) addExercice ( e * Exercice ) ( err error ) {
2018-12-05 04:02:27 +00:00
var ik = "DEFAULT"
if len ( e . IssueKind ) > 0 {
ik = fmt . Sprintf ( "%q" , e . IssueKind )
}
var cc = "DEFAULT"
if e . Coefficient != 0 {
cc = fmt . Sprintf ( "%f" , e . Coefficient )
}
2022-11-05 14:26:57 +00:00
wip := ""
if e . WIP {
wip = "%"
}
2024-03-23 11:57:15 +00:00
if res , err := DBExec ( "INSERT INTO exercices (id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, finished, headline, issue, depend, gain, video_uri, resolution, seealso, issue_kind, coefficient_cur) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + ik + ", " + cc + ")" , t . GetId ( ) , wip + e . Title , e . Authors , e . Image , e . BackgroundColor , e . Disabled , e . URLId , e . Path , e . Statement , e . Overview , e . Finished , e . Headline , e . Issue , e . Depend , e . Gain , e . VideoURI , e . Resolution , e . SeeAlso ) ; err != nil {
2018-12-05 04:02:27 +00:00
return err
2016-01-07 17:43:02 +00:00
} else if eid , err := res . LastInsertId ( ) ; err != nil {
2018-12-05 04:02:27 +00:00
return err
2016-01-07 17:43:02 +00:00
} else {
2018-12-05 04:02:27 +00:00
e . Id = eid
return nil
}
}
// AddExercice creates and fills a new struct Exercice and registers it into the database.
2024-03-18 10:26:01 +00:00
func ( t * Theme ) AddExercice ( title string , authors string , image string , backgroundcolor uint32 , wip bool , urlId string , path string , statement string , overview string , headline string , depend * Exercice , gain int64 , videoURI string , resolution string , seealso string , finished string ) ( e * Exercice , err error ) {
2018-12-05 04:02:27 +00:00
var dpd * int64 = nil
if depend != nil {
dpd = & depend . Id
}
2021-11-22 14:35:07 +00:00
e = & Exercice {
2024-03-18 10:26:01 +00:00
Title : title ,
Authors : authors ,
Image : image ,
BackgroundColor : backgroundcolor ,
Disabled : false ,
WIP : wip ,
URLId : urlId ,
Path : path ,
Statement : statement ,
Overview : overview ,
Headline : headline ,
Depend : dpd ,
Finished : finished ,
Gain : gain ,
VideoURI : videoURI ,
Resolution : resolution ,
SeeAlso : seealso ,
2016-01-07 17:43:02 +00:00
}
2018-12-05 04:02:27 +00:00
2021-11-22 14:35:07 +00:00
err = t . addExercice ( e )
2018-12-05 04:02:27 +00:00
return
2016-01-07 17:43:02 +00:00
}
2018-03-09 18:07:08 +00:00
// Update applies modifications back to the database.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) Update ( ) ( int64 , error ) {
2022-11-05 14:26:57 +00:00
wip := ""
if e . WIP {
wip = "%"
}
2024-03-18 10:26:01 +00:00
if res , err := DBExec ( "UPDATE exercices SET title = ?, authors = ?, image = ?, background_color = ?, disabled = ?, url_id = ?, path = ?, statement = ?, overview = ?, headline = ?, issue = ?, issue_kind = ?, depend = ?, gain = ?, coefficient_cur = ?, video_uri = ?, resolution = ?, seealso = ?, finished = ? WHERE id_exercice = ?" , wip + e . Title , e . Authors , e . Image , e . BackgroundColor , e . Disabled , e . URLId , e . Path , e . Statement , e . Overview , e . Headline , e . Issue , e . IssueKind , e . Depend , e . Gain , e . Coefficient , e . VideoURI , e . Resolution , e . SeeAlso , e . Finished , e . Id ) ; err != nil {
2016-01-07 17:43:02 +00:00
return 0 , err
} else if nb , err := res . RowsAffected ( ) ; err != nil {
return 0 , err
} else {
return nb , err
}
}
2018-03-09 18:07:08 +00:00
// FixURLId generates a valid URLid from the challenge Title.
// It returns true if something has been generated.
2018-01-23 00:00:24 +00:00
func ( e * Exercice ) FixURLId ( ) bool {
if e . URLId == "" {
e . URLId = ToURLid ( e . Title )
return true
}
return false
}
2018-03-09 18:07:08 +00:00
// Delete the challenge from the database.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) Delete ( ) ( int64 , error ) {
2016-01-13 00:20:21 +00:00
if res , err := DBExec ( "DELETE FROM exercices WHERE id_exercice = ?" , e . Id ) ; err != nil {
2016-01-07 17:43:02 +00:00
return 0 , err
} else if nb , err := res . RowsAffected ( ) ; err != nil {
return 0 , err
} else {
return nb , err
}
}
2018-12-04 22:11:45 +00:00
// DeleteCascade the challenge from the database, including inner content but not player content.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) DeleteCascade ( ) ( int64 , error ) {
2020-09-06 10:26:19 +00:00
if _ , err := DBExec ( "UPDATE exercices SET depend = NULL WHERE depend = ?" , e . Id ) ; err != nil {
2018-12-04 22:11:45 +00:00
return 0 , err
2020-05-16 01:49:27 +00:00
} else if _ , err := DBExec ( "DELETE FROM exercice_files_okey_deps WHERE id_file IN (SELECT id_file FROM exercice_files WHERE id_exercice = ?)" , e . Id ) ; err != nil {
return 0 , err
} else if _ , err := DBExec ( "DELETE FROM exercice_files_omcq_deps WHERE id_file IN (SELECT id_file FROM exercice_files WHERE id_exercice = ?)" , e . Id ) ; err != nil {
2018-12-04 22:11:45 +00:00
return 0 , err
} else if _ , err := DBExec ( "DELETE FROM exercice_files WHERE id_exercice = ?" , e . Id ) ; err != nil {
return 0 , err
2020-09-06 10:26:19 +00:00
} else if _ , err := DBExec ( "DELETE FROM exercice_hints_okey_deps WHERE id_hint IN (SELECT id_hint FROM exercice_hints WHERE id_exercice = ?)" , e . Id ) ; err != nil {
return 0 , err
2021-09-02 00:25:18 +00:00
} else if _ , err := DBExec ( "DELETE FROM exercice_hints_omcq_deps WHERE id_hint IN (SELECT id_hint FROM exercice_hints WHERE id_exercice = ?)" , e . Id ) ; err != nil {
return 0 , err
2018-12-04 22:11:45 +00:00
} else if _ , err := DBExec ( "DELETE FROM exercice_hints WHERE id_exercice = ?" , e . Id ) ; err != nil {
return 0 , err
} else if _ , err := DBExec ( "DELETE FROM exercice_flags_deps WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)" , e . Id ) ; err != nil {
return 0 , err
2020-09-06 10:26:19 +00:00
} else if _ , err := DBExec ( "DELETE FROM flag_choices WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)" , e . Id ) ; err != nil {
2018-12-04 22:11:45 +00:00
return 0 , err
} else if _ , err := DBExec ( "DELETE FROM exercice_flags WHERE id_exercice = ?" , e . Id ) ; err != nil {
return 0 , err
} else if _ , err := DBExec ( "DELETE FROM mcq_entries WHERE id_mcq IN (SELECT id_mcq FROM exercice_flags WHERE id_exercice = ?)" , e . Id ) ; err != nil {
return 0 , err
} else if _ , err := DBExec ( "DELETE FROM exercice_mcq WHERE id_exercice = ?" , e . Id ) ; err != nil {
return 0 , err
} else if _ , err := DBExec ( "DELETE FROM exercice_tags WHERE id_exercice = ?" , e . Id ) ; err != nil {
return 0 , err
2020-12-11 18:38:57 +00:00
} else if _ , err := DBExec ( "DELETE FROM teams_qa_todo WHERE id_exercice = ?" , e . Id ) ; err != nil {
return 0 , err
} else if _ , err := DBExec ( "DELETE FROM teams_qa_view WHERE id_exercice = ?" , e . Id ) ; err != nil {
return 0 , err
2018-12-04 22:11:45 +00:00
} else {
return e . Delete ( )
}
}
2021-11-13 00:39:58 +00:00
// DeleteCascadePlayer delete player content related to this challenge.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) DeleteCascadePlayer ( ) ( int64 , error ) {
2018-12-04 22:11:45 +00:00
if _ , err := DBExec ( "DELETE FROM mcq_found WHERE id_mcq IN (SELECT id_mcq FROM exercice_mcq WHERE id_exercice = ?)" , e . Id ) ; err != nil {
return 0 , err
} else if _ , err := DBExec ( "DELETE FROM flag_found WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)" , e . Id ) ; err != nil {
return 0 , err
} else if _ , err := DBExec ( "DELETE FROM team_hints WHERE id_hint IN (SELECT id_hint FROM exercice_hints WHERE id_exercice = ?)" , e . Id ) ; err != nil {
return 0 , err
} else if _ , err := DBExec ( "DELETE FROM team_wchoices WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)" , e . Id ) ; err != nil {
return 0 , err
} else if _ , err := DBExec ( "DELETE FROM exercice_solved WHERE id_exercice = ?" , e . Id ) ; err != nil {
return 0 , err
} else if _ , err := DBExec ( "DELETE FROM exercice_tries WHERE id_exercice = ?" , e . Id ) ; err != nil {
return 0 , err
2021-11-13 00:39:58 +00:00
} else {
return 1 , nil
}
}
// DeleteDeep the challenge from the database, including player content.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) DeleteDeep ( ) ( int64 , error ) {
2021-11-13 00:39:58 +00:00
if _ , err := e . DeleteCascadePlayer ( ) ; err != nil {
return 0 , err
2018-12-04 22:11:45 +00:00
} else {
return e . DeleteCascade ( )
}
}
2018-03-09 18:07:08 +00:00
// GetLevel returns the number of dependancy challenges.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) GetLevel ( ) ( int , error ) {
2016-01-25 02:09:22 +00:00
dep := e . Depend
nb := 1
for dep != nil {
nb += 1
2020-01-30 02:54:18 +00:00
if nb > 10 || * dep == e . Id {
2021-11-22 14:35:07 +00:00
return nb , errors . New ( "exceed number of levels" )
2020-01-30 02:54:18 +00:00
} else if edep , err := GetExercice ( * dep ) ; err != nil {
2016-01-25 02:09:22 +00:00
return nb , err
} else {
dep = edep . Depend
}
}
return nb , nil
}
2024-03-16 10:28:59 +00:00
// GetOrdinal returns the position of the exercice in list (usefull for standalone exercices).
func ( e * Exercice ) GetOrdinal ( ) ( int , error ) {
theme := & Theme { Id : 0 }
if e . IdTheme != nil {
theme . Id = * e . IdTheme
}
exercices , err := theme . GetExercices ( )
if err != nil {
return 0 , err
}
for n , exercice := range exercices {
if exercice . Id == e . Id {
return n , nil
}
}
return 0 , fmt . Errorf ( "exercice not found in theme" )
}
2018-03-09 18:07:08 +00:00
// NewTry registers a solving attempt for the given Team.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) NewTry ( t * Team , cksum [ ] byte ) error {
2018-11-21 02:22:42 +00:00
if _ , err := DBExec ( "INSERT INTO exercice_tries (id_exercice, id_team, time, cksum) VALUES (?, ?, ?, ?)" , e . Id , t . Id , time . Now ( ) , cksum ) ; err != nil {
2016-01-07 17:43:02 +00:00
return err
} else {
return nil
}
}
2017-12-17 01:48:02 +00:00
2018-03-09 18:07:08 +00:00
// UpdateTry applies modifications to the latest try registered for the given Team.
// Updated values are time and the given nbdiff.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) UpdateTry ( t * Team , nbdiff int , oneGood bool ) error {
2021-09-08 01:34:48 +00:00
if _ , err := DBExec ( "UPDATE exercice_tries SET nbdiff = ?, onegood = ?, time = ? WHERE id_exercice = ? AND id_team = ? ORDER BY time DESC LIMIT 1" , nbdiff , oneGood , time . Now ( ) , e . Id , t . Id ) ; err != nil {
2017-12-17 01:48:02 +00:00
return err
} else {
return nil
}
}
2016-01-07 17:43:02 +00:00
2018-03-09 18:07:08 +00:00
// Solved registers that the given Team solves the challenge.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) Solved ( t * Team ) error {
2019-07-05 20:28:56 +00:00
if _ , err := DBExec ( "INSERT INTO exercice_solved (id_exercice, id_team, time, coefficient) VALUES (?, ?, ?, ?)" , e . Id , t . Id , time . Now ( ) , e . Coefficient * ExerciceCurrentCoefficient ) ; err != nil {
2016-01-07 17:43:02 +00:00
return err
} else {
return nil
}
}
2016-01-15 11:57:35 +00:00
2018-03-09 18:07:08 +00:00
// SolvedCount returns the number of Team that already have solved the challenge.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) SolvedCount ( ) int64 {
2016-01-21 00:37:33 +00:00
var nb int64
if err := DBQueryRow ( "SELECT COUNT(id_exercice) FROM exercice_solved WHERE id_exercice = ?" , e . Id ) . Scan ( & nb ) ; err != nil {
return 0
} else {
return nb
}
}
2018-03-09 18:07:08 +00:00
// TriedTeamCount returns the number of Team that attempted to solve the exercice.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) TriedTeamCount ( ) int64 {
2018-12-06 21:18:08 +00:00
tries_table := "exercice_tries"
if SubmissionUniqueness {
tries_table = "exercice_distinct_tries"
}
2016-01-25 02:05:32 +00:00
var nb int64
2019-07-05 20:28:56 +00:00
if err := DBQueryRow ( "SELECT COUNT(DISTINCT id_team) FROM " + tries_table + " WHERE id_exercice = ?" , e . Id ) . Scan ( & nb ) ; err != nil {
2016-01-25 02:05:32 +00:00
return 0
} else {
return nb
}
}
2018-03-09 18:07:08 +00:00
// TriedCount returns the number of cumulative attempts, all Team combined, for the exercice.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) TriedCount ( ) int64 {
2018-12-06 21:18:08 +00:00
tries_table := "exercice_tries"
if SubmissionUniqueness {
tries_table = "exercice_distinct_tries"
}
2016-01-25 02:05:32 +00:00
var nb int64
2019-07-05 20:28:56 +00:00
if err := DBQueryRow ( "SELECT COUNT(id_team) FROM " + tries_table + " WHERE id_exercice = ?" , e . Id ) . Scan ( & nb ) ; err != nil {
2016-01-25 02:05:32 +00:00
return 0
} else {
return nb
}
}
2020-01-29 14:57:34 +00:00
// FlagSolved returns the list of flags solved.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) FlagSolved ( ) ( res [ ] int64 ) {
2020-01-29 14:57:34 +00:00
if rows , err := DBQuery ( "SELECT F.id_flag FROM flag_found F INNER JOIN exercice_flags E ON E.id_flag = F.id_flag WHERE E.id_exercice = ? GROUP BY id_flag" , e . Id ) ; err != nil {
return
} else {
defer rows . Close ( )
for rows . Next ( ) {
var n int64
if err := rows . Scan ( & n ) ; err != nil {
return
}
res = append ( res , n )
}
return
}
}
// MCQSolved returns the list of mcqs solved.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) MCQSolved ( ) ( res [ ] int64 ) {
2020-01-29 14:57:34 +00:00
if rows , err := DBQuery ( "SELECT F.id_mcq FROM mcq_found F INNER JOIN exercice_mcq E ON E.id_mcq = F.id_mcq WHERE E.id_exercice = ? GROUP BY id_mcq" , e . Id ) ; err != nil {
return
} else {
defer rows . Close ( )
for rows . Next ( ) {
var n int64
if err := rows . Scan ( & n ) ; err != nil {
return
}
res = append ( res , n )
}
return
}
}
2018-03-09 18:07:08 +00:00
// CheckResponse, given both flags and MCQ responses, figures out if thoses are correct (or if they are previously solved).
// In the meanwhile, CheckResponse registers good answers given (but it does not mark the challenge as solved at the end).
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) CheckResponse ( cksum [ ] byte , respflags map [ int ] string , respmcq map [ int ] bool , t * Team ) ( bool , error ) {
2018-11-21 02:22:42 +00:00
if err := e . NewTry ( t , cksum ) ; err != nil {
2016-02-26 00:27:08 +00:00
return false , err
2019-01-02 20:51:09 +00:00
} else if flags , err := e . GetFlagKeys ( ) ; err != nil {
2016-02-26 00:27:08 +00:00
return false , err
2017-12-16 00:16:30 +00:00
} else if mcqs , err := e . GetMCQ ( ) ; err != nil {
return false , err
2018-09-24 08:00:17 +00:00
} else if len ( flags ) < 1 && len ( mcqs ) < 1 {
return true , errors . New ( "Exercice with no flag registered" )
2016-01-15 11:57:35 +00:00
} else {
2017-12-16 00:16:30 +00:00
valid := true
diff := 0
2021-09-08 01:34:48 +00:00
goodResponses := 0
2017-12-16 00:16:30 +00:00
// Check MCQs
for _ , mcq := range mcqs {
if d := mcq . Check ( respmcq ) ; d > 0 {
2019-01-02 20:51:09 +00:00
if ! PartialValidation || t . HasPartiallySolved ( mcq ) == nil {
2017-12-16 00:16:30 +00:00
valid = false
diff += d
}
2019-01-02 20:51:09 +00:00
} else if ! PartialMCQValidation && d == 0 {
2017-12-16 00:16:30 +00:00
mcq . FoundBy ( t )
2021-09-08 01:34:48 +00:00
goodResponses += 1
2017-12-16 00:16:30 +00:00
}
2016-01-15 11:57:35 +00:00
}
2017-12-16 00:16:30 +00:00
// Validate MCQs if no error
if valid {
for _ , mcq := range mcqs {
2022-06-08 14:38:00 +00:00
if mcq . FoundBy ( t ) == nil {
goodResponses += 1
}
2017-12-16 00:16:30 +00:00
}
}
2018-09-24 08:00:17 +00:00
// Check flags
for _ , flag := range flags {
2018-11-28 05:39:38 +00:00
if res , ok := respflags [ flag . Id ] ; ! ok && ( ! PartialValidation || t . HasPartiallySolved ( flag ) == nil ) {
2022-05-31 20:03:51 +00:00
valid = valid && flag . IsOptionnal ( )
2019-01-02 20:51:09 +00:00
} else if flag . Check ( [ ] byte ( res ) ) != 0 {
2018-09-24 08:00:17 +00:00
if ! PartialValidation || t . HasPartiallySolved ( flag ) == nil {
2022-05-31 20:03:51 +00:00
valid = valid && flag . IsOptionnal ( )
2016-12-04 18:08:46 +00:00
}
} else {
2024-03-27 20:30:20 +00:00
err := flag . FoundBy ( t )
if err == nil {
// err is unicity issue, probably flag already found
goodResponses += 1
}
2016-01-15 11:57:35 +00:00
}
}
2021-09-08 01:34:48 +00:00
if diff > 0 || goodResponses > 0 {
e . UpdateTry ( t , diff , goodResponses > 0 )
2017-12-16 00:16:30 +00:00
}
2016-02-26 00:27:08 +00:00
return valid , nil
2016-01-15 11:57:35 +00:00
}
}
2018-03-09 18:07:08 +00:00
// IsSolved returns the number of time this challenge has been solved and the time of the first solve occurence.
2021-11-22 14:35:07 +00:00
func ( e * Exercice ) IsSolved ( ) ( int , * time . Time ) {
2018-03-09 18:07:08 +00:00
var nb * int
var tm * time . Time
if DBQueryRow ( "SELECT COUNT(id_exercice), MIN(time) FROM exercice_solved WHERE id_exercice = ?" , e . Id ) . Scan ( & nb , & tm ) ; nb == nil || tm == nil {
2021-11-22 14:35:07 +00:00
return 0 , nil
2018-03-09 18:07:08 +00:00
} else {
2021-11-22 14:35:07 +00:00
return * nb , tm
2018-03-09 18:07:08 +00:00
}
}