server/libfic/team_my.go

429 lines
13 KiB
Go

package fic
import (
"encoding/hex"
"encoding/json"
"fmt"
"log"
"os"
"path"
"sort"
"strconv"
"strings"
"time"
)
// DisplayAllFlags doesn't respect the predefined constraint existing between flags.
var DisplayAllFlags bool
// DisplayMCQBadCount activates the report of MCQ bad responses counter.
var DisplayMCQBadCount bool
// HideCaseSensitivity never tells the user if the flag is case sensitive or not.
var HideCaseSensitivity bool
// UnlockedStandaloneExercices unlock this number of standalone exercice.
var UnlockedStandaloneExercices int
// UnlockedStandaloneExercicesByThemeStepValidation unlock this number of standalone exercice for each theme step validated.
var UnlockedStandaloneExercicesByThemeStepValidation float64
// UnlockedStandaloneExercicesByStandaloneExerciceValidation unlock this number of standalone exercice for each standalone exercice validated.
var UnlockedStandaloneExercicesByStandaloneExerciceValidation float64
type myTeamFile struct {
Path string `json:"path"`
Name string `json:"name"`
Checksum string `json:"checksum"`
Compressed bool `json:"compressed,omitempty"`
Size int64 `json:"size"`
}
type myTeamHint struct {
HintId int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content,omitempty"`
File string `json:"file,omitempty"`
Cost int64 `json:"cost"`
}
type myTeamFlag struct {
Id int `json:"id,omitempty"`
Label string `json:"label"`
Type string `json:"type,omitempty"`
Placeholder string `json:"placeholder,omitempty"`
Help string `json:"help,omitempty"`
Unit string `json:"unit,omitempty"`
Separator string `json:"separator,omitempty"`
NbLines uint64 `json:"nb_lines,omitempty"`
IgnoreOrder bool `json:"ignore_order,omitempty"`
IgnoreCase bool `json:"ignore_case,omitempty"`
Multiline bool `json:"multiline,omitempty"`
CaptureRe *string `json:"capture_regexp,omitempty"`
Solved *time.Time `json:"found,omitempty"`
PSolved *time.Time `json:"part_solved,omitempty"`
Soluce string `json:"soluce,omitempty"`
Justify bool `json:"justify,omitempty"`
BonusGain int64 `json:"bonus_gain,omitempty"`
Choices map[string]interface{} `json:"choices,omitempty"`
ChoicesCost int64 `json:"choices_cost,omitempty"`
Variant string `json:"variant,omitempty"`
Min *float64 `json:"min,omitempty"`
Max *float64 `json:"max,omitempty"`
Step *float64 `json:"step,omitempty"`
order int8
}
type myTeamMCQJustifiedChoice struct {
Label string `json:"label"`
Value bool `json:"value,omitempty"`
Justification myTeamFlag `json:"justification,omitempty"`
}
type myTeamExercice struct {
ThemeId int64 `json:"theme_id,omitempty"`
Disabled bool `json:"disabled,omitempty"`
WIP bool `json:"wip,omitempty"`
Statement string `json:"statement"`
Overview string `json:"overview,omitempty"`
Finished string `json:"finished,omitempty"`
Hints []myTeamHint `json:"hints,omitempty"`
Gain int `json:"gain"`
Files []myTeamFile `json:"files,omitempty"`
Flags []myTeamFlag `json:"flags,omitempty"`
NbFlags int `json:"nb_flags,omitempty"`
SolveDist int64 `json:"solve_dist,omitempty"`
SolvedTime *time.Time `json:"solved_time,omitempty"`
SolvedRank int64 `json:"solved_rank,omitempty"`
Tries int64 `json:"tries,omitempty"`
TotalTries int64 `json:"total_tries,omitempty"`
VideoURI string `json:"video_uri,omitempty"`
Resolution string `json:"resolution,omitempty"`
Issue string `json:"issue,omitempty"`
IssueKind string `json:"issuekind,omitempty"`
}
type MyTeam struct {
Id int64 `json:"team_id"`
Name string `json:"name"`
Points int64 `json:"score"`
Points100 int64 `json:"score100"`
Members []*Member `json:"members,omitempty"`
Exercices map[string]myTeamExercice `json:"exercices"`
}
type ByOrder []myTeamFlag
func (a ByOrder) Len() int { return len(a) }
func (a ByOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByOrder) Less(i, j int) bool { return a[i].order < a[j].order }
func MyJSONTeam(t *Team, started bool) (interface{}, error) {
ret := MyTeam{}
// Fill information about the team
if t == nil {
ret.Id = 0
} else {
ret.Name = t.Name
ret.Id = t.Id
points, _ := t.GetPoints()
ret.Points = int64(float64(points) * GlobalScoreCoefficient)
ret.Points100 = int64(float64(points) * GlobalScoreCoefficient * 100)
if members, err := t.GetMembers(); err == nil {
ret.Members = members
}
}
// Retrieve themes
themes, err := GetThemes()
if err != nil {
return ret, err
}
mapthemes := map[int64]*Theme{}
for _, theme := range themes {
mapthemes[theme.Id] = theme
}
// Fill exercices, only if the challenge is started
ret.Exercices = map[string]myTeamExercice{}
if exos, err := GetDiscountedExercices(); err != nil {
return ret, err
} else if started {
for _, e := range exos {
if t == nil || ((!e.Disabled || (e.IdTheme != nil && !mapthemes[*e.IdTheme].Locked)) && t.HasAccess(e)) {
exercice := myTeamExercice{}
exercice.Disabled = e.Disabled
exercice.WIP = e.WIP
if e.IdTheme != nil {
exercice.ThemeId = *e.IdTheme
}
exercice.Statement = strings.Replace(e.Statement, "$FILES$", FilesDir, -1)
if len(e.Issue) > 0 {
exercice.Issue = e.Issue
exercice.IssueKind = e.IssueKind
}
if t == nil {
exercice.Overview = strings.Replace(e.Overview, "$FILES$", FilesDir, -1)
exercice.Finished = strings.Replace(e.Finished, "$FILES$", FilesDir, -1)
exercice.VideoURI = strings.Replace(e.VideoURI, "$FILES$", FilesDir, -1)
exercice.Resolution = strings.Replace(e.Resolution, "$FILES$", FilesDir, -1)
exercice.TotalTries = e.TriedCount()
exercice.Gain = int(float64(e.Gain) * e.Coefficient * GlobalScoreCoefficient)
} else {
stime := t.HasSolved(e)
exercice.SolvedTime = stime
if stime != nil {
exercice.SolvedRank, _ = t.GetSolvedRank(e)
exercice.Finished = e.Finished
exercice.Tries, _ = t.CountTries(e)
} else {
var ttime *time.Time
exercice.Tries, ttime = t.CountTries(e)
exercice.SolvedTime = ttime
if DisplayMCQBadCount && exercice.Tries > 0 {
exercice.SolveDist = t.LastTryDist(e)
}
}
if exercice.SolvedTime != nil && exercice.SolvedTime.Equal(time.Unix(0, 0)) {
exercice.SolvedTime = nil
}
if gain, err := e.EstimateGain(t, stime != nil); err == nil {
exercice.Gain = int(gain * GlobalScoreCoefficient)
} else {
log.Printf("ERROR during gain estimation (tid=%d ; eid=%d): %s", t.Id, e.Id, err.Error())
exercice.Gain = int(float64(e.Gain) * GlobalScoreCoefficient)
}
}
// Expose exercice files
exercice.Files = []myTeamFile{}
if files, err := e.GetFiles(); err != nil {
return nil, err
} else {
for _, f := range files {
if t == nil || t.CanDownload(f) {
cksum := f.Checksum
compressed := false
if len(f.ChecksumShown) > 0 {
cksum = f.ChecksumShown
compressed = true
}
exercice.Files = append(exercice.Files, myTeamFile{path.Join(FilesDir, f.Path), f.Name, hex.EncodeToString(cksum), compressed, f.Size})
}
}
}
// Expose exercice hints
exercice.Hints = []myTeamHint{}
if hints, err := e.GetHints(); err != nil {
return nil, err
} else {
for _, h := range hints {
if t == nil || HintCoefficient < 0 || t.HasHint(h) {
exercice.Hints = append(exercice.Hints, myTeamHint{h.Id, h.Title, h.Content, h.File, int64(float64(h.Cost) * GlobalScoreCoefficient)})
} else if t.CanSeeHint(h) {
exercice.Hints = append(exercice.Hints, myTeamHint{h.Id, h.Title, "", "", int64(float64(h.Cost) * GlobalScoreCoefficient)})
}
}
}
// Expose exercice flags
if !e.Disabled {
justifiedMCQ := map[int]myTeamFlag{}
if labels, err := e.GetFlagLabels(); err != nil {
return nil, err
} else {
for _, l := range labels {
if !DisplayAllFlags && t != nil && !t.CanSeeFlag(l) {
// Dependancy missing, skip the flag for now
continue
}
exercice.Flags = append(exercice.Flags, myTeamFlag{
order: l.Order,
Label: l.Label,
Variant: l.Variant,
})
}
}
if flags, err := e.GetFlagKeys(); err != nil {
return nil, err
} else {
for _, k := range flags {
if !strings.HasPrefix(k.Type, "label") {
exercice.NbFlags += 1
}
if !DisplayAllFlags && t != nil && !t.CanSeeFlag(k) {
// Dependancy missing, skip the flag for now
continue
}
flag := myTeamFlag{
Id: k.Id,
Type: k.Type,
order: k.Order,
Help: k.Help,
Unit: k.Unit,
}
if strings.HasPrefix(flag.Type, "number") {
flag.Min, flag.Max, flag.Step, err = AnalyzeNumberFlag(flag.Type)
flag.Type = "number"
if err != nil {
return nil, err
}
}
// Retrieve solved state or solution for public iface
if t == nil {
flag.IgnoreCase = k.IgnoreCase
flag.CaptureRe = k.CaptureRegexp
flag.Soluce = hex.EncodeToString(k.Checksum)
} else {
if PartialValidation {
flag.Solved = t.HasPartiallySolved(k)
}
if !HideCaseSensitivity {
flag.IgnoreCase = k.IgnoreCase
}
}
flag.Multiline = k.Multiline
flag.BonusGain = int64(float64(k.BonusGain) * GlobalScoreCoefficient)
var fl FlagMCQLabel
if fl, err = k.GetMCQJustification(); err == nil {
k.Label = fl.Label
}
// Treat array flags
flag.Label, flag.Separator, flag.IgnoreOrder, flag.NbLines = k.AnalyzeFlagLabel()
// Fill more information if the flag is displaied
if flag.Solved == nil {
flag.Placeholder = k.Placeholder
if choices, err := k.GetChoices(); err != nil {
return nil, err
} else if t == nil || WChoiceCoefficient < 0 || k.ChoicesCost == 0 || t.SeeChoices(k) {
flag.Choices = map[string]interface{}{}
for _, c := range choices {
flag.Choices[c.Value] = c.Label
}
} else {
flag.ChoicesCost = int64(float64(k.ChoicesCost) * GlobalScoreCoefficient)
}
}
// Append to corresponding flags' map
if fl.IdChoice != 0 {
justifiedMCQ[fl.IdChoice] = flag
} else {
exercice.Flags = append(exercice.Flags, flag)
}
}
}
if mcqs, err := e.GetMCQ(); err != nil {
return nil, err
} else {
for _, mcq := range mcqs {
exercice.NbFlags += 1
if !DisplayAllFlags && t != nil && !t.CanSeeFlag(mcq) {
// Dependancy missing, skip the flag for now
continue
}
m := myTeamFlag{
Id: mcq.Id,
Type: "mcq",
order: mcq.Order,
Label: mcq.Title,
Choices: map[string]interface{}{},
}
soluce := ""
fullySolved := true
if t != nil {
m.PSolved = t.HasPartiallySolved(mcq)
}
for _, e := range mcq.Entries {
if e.Response {
soluce += "t"
} else {
soluce += "f"
}
if v, ok := justifiedMCQ[e.Id]; ok {
m.Justify = true
if m.PSolved != nil || v.Solved != nil {
jc := myTeamMCQJustifiedChoice{
Label: e.Label,
Value: v.Solved != nil,
Justification: v,
}
if v.Solved == nil {
fullySolved = false
}
if PartialValidation && m.PSolved != nil {
jc.Value = e.Response
}
m.Choices[strconv.Itoa(e.Id)] = jc
} else {
m.Choices[strconv.Itoa(e.Id)] = e.Label
}
} else {
m.Choices[strconv.Itoa(e.Id)] = e.Label
}
}
if t == nil {
h, _ := ComputeHashedFlag([]byte(soluce), false, false, nil, false)
m.Soluce = hex.EncodeToString(h[:])
}
if fullySolved {
m.Solved = m.PSolved
m.PSolved = nil
}
exercice.Flags = append(exercice.Flags, m)
}
}
// Sort flags by order
sort.Sort(ByOrder(exercice.Flags))
}
// Hash table ordered by exercice Id
ret.Exercices[fmt.Sprintf("%d", e.Id)] = exercice
}
}
}
return ret, nil
}
func ReadMyJSON(fd *os.File) (my MyTeam, err error) {
jdec := json.NewDecoder(fd)
err = jdec.Decode(&my)
return
}