New setting: introduce a decote/discount to exercice's gain

This commit is contained in:
nemunaire 2023-04-01 17:11:40 +02:00
parent 838ce2fb3f
commit 4451e41285
11 changed files with 123 additions and 24 deletions

View file

@ -407,22 +407,34 @@ type exerciceStats struct {
SolvedCount int64 `json:"solved_count"` SolvedCount int64 `json:"solved_count"`
FlagSolved []int64 `json:"flag_solved"` FlagSolved []int64 `json:"flag_solved"`
MCQSolved []int64 `json:"mcq_solved"` MCQSolved []int64 `json:"mcq_solved"`
CurrentGain int64 `json:"current_gain"`
} }
func getExerciceStats(c *gin.Context) { func getExerciceStats(c *gin.Context) {
e := c.MustGet("exercice").(*fic.Exercice) e := c.MustGet("exercice").(*fic.Exercice)
current_gain := e.Gain
if fic.DiscountedFactor > 0 {
decoted_exercice, err := fic.GetDiscountedExercice(e.Id)
if err == nil {
current_gain = decoted_exercice.Gain
} else {
log.Println("Unable to fetch decotedExercice:", err.Error())
}
}
c.JSON(http.StatusOK, exerciceStats{ c.JSON(http.StatusOK, exerciceStats{
TeamTries: e.TriedTeamCount(), TeamTries: e.TriedTeamCount(),
TotalTries: e.TriedCount(), TotalTries: e.TriedCount(),
SolvedCount: e.SolvedCount(), SolvedCount: e.SolvedCount(),
FlagSolved: e.FlagSolved(), FlagSolved: e.FlagSolved(),
MCQSolved: e.MCQSolved(), MCQSolved: e.MCQSolved(),
CurrentGain: current_gain,
}) })
} }
func getExercicesStats(c *gin.Context) { func getExercicesStats(c *gin.Context) {
exercices, err := fic.GetExercices() exercices, err := fic.GetDiscountedExercices()
if err != nil { if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
} }
@ -436,6 +448,7 @@ func getExercicesStats(c *gin.Context) {
SolvedCount: e.SolvedCount(), SolvedCount: e.SolvedCount(),
FlagSolved: e.FlagSolved(), FlagSolved: e.FlagSolved(),
MCQSolved: e.MCQSolved(), MCQSolved: e.MCQSolved(),
CurrentGain: e.Gain,
}) })
} }

View file

@ -307,6 +307,13 @@ func ApplySettings(config *settings.Settings) {
fic.SubmissionCostBase = config.SubmissionCostBase fic.SubmissionCostBase = config.SubmissionCostBase
fic.SubmissionUniqueness = config.SubmissionUniqueness fic.SubmissionUniqueness = config.SubmissionUniqueness
fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries
if config.DiscountedFactor != fic.DiscountedFactor {
fic.DiscountedFactor = config.DiscountedFactor
if err := fic.DBRecreateDiscountedView(); err != nil {
log.Println("Unable to recreate exercices_discounted view:", err.Error())
}
}
} }
func ResetSettings() error { func ResetSettings() error {
@ -318,6 +325,7 @@ func ResetSettings() error {
HintCurCoefficient: 1, HintCurCoefficient: 1,
WChoiceCurCoefficient: 1, WChoiceCurCoefficient: 1,
GlobalScoreCoefficient: 1, GlobalScoreCoefficient: 1,
DiscountedFactor: 0,
AllowRegistration: false, AllowRegistration: false,
CanJoinTeam: false, CanJoinTeam: false,
DenyTeamCreation: false, DenyTeamCreation: false,

View file

@ -62,6 +62,9 @@
</div> </div>
<div class="collapse show" id="collapseStats" aria-labelledby="headingStats" data-parent="#accordionExercice"> <div class="collapse show" id="collapseStats" aria-labelledby="headingStats" data-parent="#accordionExercice">
<dl class="row mt-2 ml-2"> <dl class="row mt-2 ml-2">
<dt class="col-sm-6 text-truncate" title="Nombre de points actuel">Points actuels</dt>
<dd class="col-sm-6"><ng-pluralize count="stats.current_gain" when="{'0': '0 points', 'one': '{} point', 'other': '{} points'}"></ng-pluralize></dd>
<dt class="col-sm-6 text-truncate" title="Nombre d'équipes">Défi tenté par</dt> <dt class="col-sm-6 text-truncate" title="Nombre d'équipes">Défi tenté par</dt>
<dd class="col-sm-6"><ng-pluralize count="stats.team_tries" when="{'0': 'aucune équipe', 'one': '{} équipe', 'other': '{} équipes'}"></ng-pluralize></dd> <dd class="col-sm-6"><ng-pluralize count="stats.team_tries" when="{'0': 'aucune équipe', 'one': '{} équipe', 'other': '{} équipes'}"></ng-pluralize></dd>

View file

@ -88,13 +88,23 @@
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="firstBlood" class="col-sm-3 col-form-label col-form-label-sm" ng-class="{'text-primary font-weight-bold': config.firstBlood != dist_config.firstBlood}">Bonus premier sang</label> <div class="col-sm row">
<div class="col-sm-2"> <label for="firstBlood" class="col-sm-8 col-form-label col-form-label-sm" ng-class="{'text-primary font-weight-bold': config.firstBlood != dist_config.firstBlood}">Bonus premier sang</label>
<input type="text" class="form-control form-control-sm" id="firstBlood" ng-model="config.firstBlood" float ng-class="{'border-primary': config.firstBlood != dist_config.firstBlood}"> <div class="col-sm-4">
<input type="text" class="form-control form-control-sm" id="firstBlood" ng-model="config.firstBlood" float ng-class="{'border-primary': config.firstBlood != dist_config.firstBlood}">
</div>
</div> </div>
<label for="submissionCostBase" class="offset-sm-1 col-sm-4 col-form-label col-form-label-sm text-right" ng-class="{'text-primary font-weight-bold': config.submissionCostBase != dist_config.submissionCostBase}">Coût de base d'une tentative</label> <div class="col-sm row">
<div class="col-sm-2"> <label for="discountFactor" class="col-sm-8 col-form-label col-form-label-sm" ng-class="{'text-primary font-weight-bold': config.discountedFactor != dist_config.discountedFactor}">Décote des exercices</label>
<input type="text" class="form-control form-control-sm" id="submissionCostBase" ng-model="config.submissionCostBase" float ng-class="{'border-primary': config.submissionCostBase != dist_config.submissionCostBase}"> <div class="col-sm-4">
<input type="text" class="form-control form-control-sm" id="discountFactor" ng-model="config.discountedFactor" float ng-class="{'border-primary': config.discountedFactor != dist_config.discountedFactor}">
</div>
</div>
<div class="col-sm row">
<label for="submissionCostBase" class="col-sm-8 col-form-label col-form-label-sm text-right" ng-class="{'text-primary font-weight-bold': config.submissionCostBase != dist_config.submissionCostBase}">Coût de base tentative</label>
<div class="col-sm-4">
<input type="text" class="form-control form-control-sm" id="submissionCostBase" ng-model="config.submissionCostBase" float ng-class="{'border-primary': config.submissionCostBase != dist_config.submissionCostBase}">
</div>
</div> </div>
</div> </div>

View file

@ -75,7 +75,7 @@ func reloadSettings(config *settings.Settings) {
fic.WChoiceCoefficient = config.WChoiceCurCoefficient fic.WChoiceCoefficient = config.WChoiceCurCoefficient
fic.ExerciceCurrentCoefficient = config.ExerciceCurCoefficient fic.ExerciceCurrentCoefficient = config.ExerciceCurCoefficient
ChStarted = config.Start.Unix() > 0 && time.Since(config.Start) >= 0 ChStarted = config.Start.Unix() > 0 && time.Since(config.Start) >= 0
if lastRegeneration != config.Generation || fic.PartialValidation != config.PartialValidation || fic.UnlockedChallengeDepth != config.UnlockedChallengeDepth || fic.UnlockedChallengeUpTo != config.UnlockedChallengeUpTo || fic.DisplayAllFlags != config.DisplayAllFlags || fic.FirstBlood != config.FirstBlood || fic.SubmissionCostBase != config.SubmissionCostBase || fic.SubmissionUniqueness != config.SubmissionUniqueness { if lastRegeneration != config.Generation || fic.PartialValidation != config.PartialValidation || fic.UnlockedChallengeDepth != config.UnlockedChallengeDepth || fic.UnlockedChallengeUpTo != config.UnlockedChallengeUpTo || fic.DisplayAllFlags != config.DisplayAllFlags || fic.FirstBlood != config.FirstBlood || fic.SubmissionCostBase != config.SubmissionCostBase || fic.SubmissionUniqueness != config.SubmissionUniqueness || fic.DiscountedFactor != config.DiscountedFactor {
fic.PartialValidation = config.PartialValidation fic.PartialValidation = config.PartialValidation
fic.UnlockedChallengeDepth = config.UnlockedChallengeDepth fic.UnlockedChallengeDepth = config.UnlockedChallengeDepth
fic.UnlockedChallengeUpTo = config.UnlockedChallengeUpTo fic.UnlockedChallengeUpTo = config.UnlockedChallengeUpTo
@ -86,6 +86,7 @@ func reloadSettings(config *settings.Settings) {
fic.SubmissionUniqueness = config.SubmissionUniqueness fic.SubmissionUniqueness = config.SubmissionUniqueness
fic.GlobalScoreCoefficient = config.GlobalScoreCoefficient fic.GlobalScoreCoefficient = config.GlobalScoreCoefficient
fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries
fic.DiscountedFactor = config.DiscountedFactor
if !skipInitialGeneration { if !skipInitialGeneration {
log.Println("Generating files...") log.Println("Generating files...")

View file

@ -61,16 +61,31 @@
<Row class="level" cols={{xs:2, md:3, xl:4}}> <Row class="level" cols={{xs:2, md:3, xl:4}}>
<Col> <Col>
<div class="level-item"> <div class="level-item">
<div class="heading"> {#if $settings.discountedFactor > 0 && $my && $my.exercices[$current_exercice.id]}
Gain <div class="heading">
</div> Cote
</div>
{:else}
<div class="heading">
Gain
</div>
{/if}
<div class="value"> <div class="value">
{$current_exercice.gain} {$current_exercice.gain==1?"point":"points"} {#if $settings.discountedFactor && $current_exercice.solved}
<!-- This display the number of points gained by the team if it validates the exercice (current teams have more points than that) -->
{Math.trunc($current_exercice.gain * (1-$settings.discountedFactor*$current_exercice.solved)*10)/10} {$current_exercice.gain==1?"point":"points"}
{:else}
{$current_exercice.gain} {$current_exercice.gain==1?"point":"points"}
{/if}
</div> </div>
{#if $settings.firstBlood && $current_exercice.solved < 1} {#if $settings.firstBlood && $current_exercice.solved < 1}
<div class="text-muted"> <div class="text-muted">
<em>+{$settings.firstBlood * 100}% (prem's)</em> <em>+{$settings.firstBlood * 100}% (prem's)</em>
</div> </div>
{:else if $settings.discountedFactor > 0 && $my && $my.exercices[$current_exercice.id]}
<div class="text-muted">
<em>initialement {$current_exercice.gain} {$current_exercice.gain==1?"point":"points"}</em>
</div>
{/if} {/if}
{#if $current_exercice.curcoeff != 1.0 || $settings.exerciceCurrentCoefficient != 1.0} {#if $current_exercice.curcoeff != 1.0 || $settings.exerciceCurrentCoefficient != 1.0}
<div class="text-muted"> <div class="text-muted">

View file

@ -3,6 +3,7 @@ package fic
import ( import (
"database/sql" "database/sql"
"errors" "errors"
"fmt"
"log" "log"
"os" "os"
"strings" "strings"
@ -548,10 +549,22 @@ CREATE TABLE IF NOT EXISTS teams_qa_view(
if _, err := db.Exec("CREATE OR REPLACE VIEW exercice_distinct_tries_notgood AS SELECT id_exercice, id_team, MAX(time) AS time, cksum, nbdiff, MAX(onegood) AS onegood FROM exercice_tries_notgood GROUP BY id_team, id_exercice, cksum;"); err != nil { if _, err := db.Exec("CREATE OR REPLACE VIEW exercice_distinct_tries_notgood AS SELECT id_exercice, id_team, MAX(time) AS time, cksum, nbdiff, MAX(onegood) AS onegood FROM exercice_tries_notgood GROUP BY id_team, id_exercice, cksum;"); err != nil {
return err return err
} }
if err := DBRecreateDiscountedView(); err != nil {
return err
}
return nil return nil
} }
func DBRecreateDiscountedView() (err error) {
if db == nil {
return
}
_, err = db.Exec("CREATE OR REPLACE VIEW exercices_discounted AS SELECT E.id_exercice, E.id_theme, E.title, E.disabled, E.headline, E.url_id, E.path, E.statement, E.overview, E.issue, E.issue_kind, E.depend, E.gain - " + fmt.Sprintf("%f", DiscountedFactor) + " * E.gain * (COUNT(*) - 1) AS gain, E.coefficient_cur, E.finished, E.video_uri, E.resolution, E.seealso FROM exercices E LEFT OUTER JOIN exercice_solved S ON S.id_exercice = E.id_exercice GROUP BY E.id_exercice;")
return
}
// DBClose closes the connection to the database // DBClose closes the connection to the database
func DBClose() error { func DBClose() error {
return db.Close() return db.Close()

View file

@ -3,6 +3,7 @@ package fic
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
"time" "time"
) )
@ -69,11 +70,13 @@ func (e *Exercice) AnalyzeTitle() {
} }
} }
func getExercice(condition string, args ...interface{}) (*Exercice, error) { func getExercice(table, condition string, args ...interface{}) (*Exercice, error) {
var e Exercice var e Exercice
if err := DBQueryRow("SELECT id_exercice, id_theme, title, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM exercices "+condition, args...).Scan(&e.Id, &e.IdTheme, &e.Title, &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 { var tmpgain float64
if err := DBQueryRow("SELECT id_exercice, id_theme, title, 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.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 {
return nil, err return nil, err
} }
e.Gain = int64(math.Trunc(tmpgain))
e.AnalyzeTitle() e.AnalyzeTitle()
return &e, nil return &e, nil
@ -81,27 +84,36 @@ func getExercice(condition string, args ...interface{}) (*Exercice, error) {
// GetExercice retrieves the challenge with the given id. // GetExercice retrieves the challenge with the given id.
func GetExercice(id int64) (*Exercice, error) { func GetExercice(id int64) (*Exercice, error) {
return getExercice("WHERE id_exercice = ?", id) return getExercice("exercices", "WHERE id_exercice = ?", id)
} }
// GetExercice retrieves the challenge with the given id. // GetExercice retrieves the challenge with the given id.
func (t *Theme) GetExercice(id int) (*Exercice, error) { func (t *Theme) GetExercice(id int) (*Exercice, error) {
return getExercice("WHERE id_theme = ? AND id_exercice = ?", t.Id, id) return getExercice("exercices", "WHERE id_theme = ? AND id_exercice = ?", t.Id, id)
} }
// GetExerciceByTitle retrieves the challenge with the given title. // GetExerciceByTitle retrieves the challenge with the given title.
func (t *Theme) GetExerciceByTitle(title string) (*Exercice, error) { func (t *Theme) GetExerciceByTitle(title string) (*Exercice, error) {
return getExercice("WHERE id_theme = ? AND title = ?", t.Id, title) return getExercice("exercices", "WHERE id_theme = ? AND title = ?", t.Id, title)
} }
// GetExerciceByPath retrieves the challenge with the given path. // GetExerciceByPath retrieves the challenge with the given path.
func (t *Theme) GetExerciceByPath(epath string) (*Exercice, error) { func (t *Theme) GetExerciceByPath(epath string) (*Exercice, error) {
return getExercice("WHERE id_theme = ? AND path = ?", t.Id, epath) return getExercice("exercices", "WHERE id_theme = ? AND path = ?", t.Id, epath)
} }
// GetExercices returns the list of all challenges present in the database. // GetDiscountedExercice retrieves the challenge with the given id.
func GetExercices() ([]*Exercice, error) { func GetDiscountedExercice(id int64) (*Exercice, error) {
if rows, err := DBQuery("SELECT id_exercice, id_theme, title, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM exercices ORDER BY path ASC"); err != nil { 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) {
if rows, err := DBQuery("SELECT id_exercice, id_theme, title, 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 {
return nil, err return nil, err
} else { } else {
defer rows.Close() defer rows.Close()
@ -109,9 +121,11 @@ func GetExercices() ([]*Exercice, error) {
exos := []*Exercice{} exos := []*Exercice{}
for rows.Next() { for rows.Next() {
e := &Exercice{} e := &Exercice{}
if err := rows.Scan(&e.Id, &e.IdTheme, &e.Title, &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 { var tmpgain float64
if err := rows.Scan(&e.Id, &e.IdTheme, &e.Title, &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 {
return nil, err return nil, err
} }
e.Gain = int64(math.Trunc(tmpgain))
e.AnalyzeTitle() e.AnalyzeTitle()
exos = append(exos, e) exos = append(exos, e)
} }
@ -123,6 +137,18 @@ func GetExercices() ([]*Exercice, error) {
} }
} }
func GetExercices() ([]*Exercice, error) {
return getExercices("exercices")
}
func GetDiscountedExercices() ([]*Exercice, error) {
table := "exercices"
if DiscountedFactor > 0 {
table = "exercices_discounted"
}
return getExercices(table)
}
// GetExercices returns the list of all challenges in the Theme. // GetExercices returns the list of all challenges in the Theme.
func (t *Theme) GetExercices() ([]*Exercice, error) { func (t *Theme) GetExercices() ([]*Exercice, error) {
if rows, err := DBQuery("SELECT id_exercice, id_theme, title, 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", t.Id); err != nil { if rows, err := DBQuery("SELECT id_exercice, id_theme, title, 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", t.Id); err != nil {

View file

@ -20,6 +20,9 @@ var SubmissionUniqueness = false
// CountOnlyNotGoodTries don't count as a try when one good response is given at least. // CountOnlyNotGoodTries don't count as a try when one good response is given at least.
var CountOnlyNotGoodTries = false var CountOnlyNotGoodTries = false
// DiscountedFactor stores the percentage of the exercice's gain lost on each validation.
var DiscountedFactor = 0.0
func exoptsQuery(whereExo string) string { func exoptsQuery(whereExo string) string {
tries_table := "exercice_tries" tries_table := "exercice_tries"
if SubmissionUniqueness { if SubmissionUniqueness {
@ -30,10 +33,15 @@ func exoptsQuery(whereExo string) string {
tries_table += "_notgood" tries_table += "_notgood"
} }
exercices_table := "exercices"
if DiscountedFactor > 0 {
exercices_table = "exercices_discounted"
}
return `SELECT S.id_team, S.time, E.gain AS points, coeff, S.reason, S.id_exercice FROM ( return `SELECT S.id_team, S.time, E.gain AS points, coeff, S.reason, S.id_exercice FROM (
SELECT id_team, id_exercice, MIN(time) AS time, ` + fmt.Sprintf("%f", FirstBlood) + ` AS coeff, "First blood" AS reason FROM exercice_solved GROUP BY id_exercice UNION SELECT id_team, id_exercice, MIN(time) AS time, ` + fmt.Sprintf("%f", FirstBlood) + ` AS coeff, "First blood" AS reason FROM exercice_solved GROUP BY id_exercice UNION
SELECT id_team, id_exercice, time, coefficient AS coeff, "Validation" AS reason FROM exercice_solved SELECT id_team, id_exercice, time, coefficient AS coeff, "Validation" AS reason FROM exercice_solved
) S INNER JOIN exercices E ON S.id_exercice = E.id_exercice ` + whereExo + ` UNION ALL ) S INNER JOIN ` + exercices_table + ` E ON S.id_exercice = E.id_exercice ` + whereExo + ` UNION ALL
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 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
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 ` + whereExo + ` GROUP BY id_exercice, id_team` 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 ` + whereExo + ` GROUP BY id_exercice, id_team`
} }

View file

@ -116,7 +116,7 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) {
// Fill exercices, only if the challenge is started // Fill exercices, only if the challenge is started
ret.Exercices = map[string]myTeamExercice{} ret.Exercices = map[string]myTeamExercice{}
if exos, err := GetExercices(); err != nil { if exos, err := GetDiscountedExercices(); err != nil {
return ret, err return ret, err
} else if started { } else if started {
for _, e := range exos { for _, e := range exos {

View file

@ -46,6 +46,8 @@ type Settings struct {
WChoiceCurCoefficient float64 `json:"wchoiceCurrentCoefficient"` WChoiceCurCoefficient float64 `json:"wchoiceCurrentCoefficient"`
// GlobalScoreCoefficient is a coefficient to apply on display scores, not considered bonus. // GlobalScoreCoefficient is a coefficient to apply on display scores, not considered bonus.
GlobalScoreCoefficient float64 `json:"globalScoreCoefficient"` GlobalScoreCoefficient float64 `json:"globalScoreCoefficient"`
// DiscountedFactor stores the percentage of the exercice's gain lost on each validation.
DiscountedFactor float64 `json:"discountedFactor,omitempty"`
// AllowRegistration permits unregistered Team to register themselves. // AllowRegistration permits unregistered Team to register themselves.
AllowRegistration bool `json:"allowRegistration,omitempty"` AllowRegistration bool `json:"allowRegistration,omitempty"`