-
- Gain
-
+ {#if $settings.discountedFactor > 0 && $my && $my.exercices[$current_exercice.id]}
+
+ Cote
+
+ {:else}
+
+ Gain
+
+ {/if}
- {$current_exercice.gain} {$current_exercice.gain==1?"point":"points"}
+ {#if $settings.discountedFactor && $current_exercice.solved}
+
+ {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}
{#if $settings.firstBlood && $current_exercice.solved < 1}
+{$settings.firstBlood * 100}% (prem's)
+ {:else if $settings.discountedFactor > 0 && $my && $my.exercices[$current_exercice.id]}
+
+ initialement {$current_exercice.gain} {$current_exercice.gain==1?"point":"points"}
+
{/if}
{#if $current_exercice.curcoeff != 1.0 || $settings.exerciceCurrentCoefficient != 1.0}
diff --git a/libfic/db.go b/libfic/db.go
index 2df0ddb0..9fe40337 100644
--- a/libfic/db.go
+++ b/libfic/db.go
@@ -3,6 +3,7 @@ package fic
import (
"database/sql"
"errors"
+ "fmt"
"log"
"os"
"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 {
return err
}
+ if err := DBRecreateDiscountedView(); err != nil {
+ return err
+ }
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
func DBClose() error {
return db.Close()
diff --git a/libfic/exercice.go b/libfic/exercice.go
index 96d94bc5..b61dd317 100644
--- a/libfic/exercice.go
+++ b/libfic/exercice.go
@@ -3,6 +3,7 @@ package fic
import (
"errors"
"fmt"
+ "math"
"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
- 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
}
+ e.Gain = int64(math.Trunc(tmpgain))
e.AnalyzeTitle()
return &e, nil
@@ -81,27 +84,36 @@ func getExercice(condition string, args ...interface{}) (*Exercice, error) {
// GetExercice retrieves the challenge with the given id.
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.
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.
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.
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.
-func 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 ORDER BY path ASC"); err != nil {
+// 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) {
+ 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
} else {
defer rows.Close()
@@ -109,9 +121,11 @@ func GetExercices() ([]*Exercice, error) {
exos := []*Exercice{}
for rows.Next() {
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
}
+ e.Gain = int64(math.Trunc(tmpgain))
e.AnalyzeTitle()
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.
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 {
diff --git a/libfic/stats.go b/libfic/stats.go
index 5cab0822..b1ae2314 100644
--- a/libfic/stats.go
+++ b/libfic/stats.go
@@ -20,6 +20,9 @@ var SubmissionUniqueness = false
// CountOnlyNotGoodTries don't count as a try when one good response is given at least.
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 {
tries_table := "exercice_tries"
if SubmissionUniqueness {
@@ -30,10 +33,15 @@ func exoptsQuery(whereExo string) string {
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 (
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
- ) 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 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`
}
diff --git a/libfic/team_my.go b/libfic/team_my.go
index 6387ce82..197d5743 100644
--- a/libfic/team_my.go
+++ b/libfic/team_my.go
@@ -116,7 +116,7 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) {
// Fill exercices, only if the challenge is started
ret.Exercices = map[string]myTeamExercice{}
- if exos, err := GetExercices(); err != nil {
+ if exos, err := GetDiscountedExercices(); err != nil {
return ret, err
} else if started {
for _, e := range exos {
diff --git a/settings/settings.go b/settings/settings.go
index 122968e8..9c586105 100644
--- a/settings/settings.go
+++ b/settings/settings.go
@@ -46,6 +46,8 @@ type Settings struct {
WChoiceCurCoefficient float64 `json:"wchoiceCurrentCoefficient"`
// GlobalScoreCoefficient is a coefficient to apply on display scores, not considered bonus.
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 bool `json:"allowRegistration,omitempty"`