package fic import ( "fmt" "path" "strings" ) // HintCoefficient is the current coefficient applied on its cost lost per showing var HintCoefficient = 1.0 // EHint represents a challenge hint. type EHint struct { Id int64 `json:"id"` // IdExercice is the identifier of the underlying challenge IdExercice int64 `json:"idExercice"` // Title is the hint name displayed to players Title string `json:"title"` // Content is the actual content of small text hints (mutually exclusive with File field) // When File is filled, Content contains the hexadecimal file's hash. Content string `json:"content"` // File is path, relative to FilesDir where the file hint is stored (mutually exclusive with Content field) File string `json:"file"` // Cost is the amount of points the player will loose if it unlocks the hint Cost int64 `json:"cost"` } // treatHintContent reads Content to detect if this is a hint file in order to convert to such hint. func treatHintContent(h *EHint) { if strings.HasPrefix(h.Content, "$FILES") { fpath := strings.TrimPrefix(h.Content, "$FILES") h.Content = fpath[:128] fpath = fpath[128:] h.File = path.Join(FilesDir, fpath) } } // GetHint retrieves the hint with the given id. func GetHint(id int64) (*EHint, error) { h := &EHint{} if err := DBQueryRow("SELECT id_hint, id_exercice, title, content, cost FROM exercice_hints WHERE id_hint = ?", id).Scan(&h.Id, &h.IdExercice, &h.Title, &h.Content, &h.Cost); err != nil { return nil, err } treatHintContent(h) return h, nil } // GetHintByTitle retrieves the hint with the given id. func (e *Exercice) GetHintByTitle(id int64) (*EHint, error) { h := &EHint{} if err := DBQueryRow("SELECT id_hint, id_exercice, title, content, cost FROM exercice_hints WHERE title = ? AND id_exercice = ?", id, e.Id).Scan(&h.Id, &h.IdExercice, &h.Title, &h.Content, &h.Cost); err != nil { return nil, err } treatHintContent(h) return h, nil } // GetHints returns a list of hints comming with the challenge. func (e *Exercice) GetHints() ([]*EHint, error) { if rows, err := DBQuery("SELECT id_hint, title, content, cost FROM exercice_hints WHERE id_exercice = ?", e.Id); err != nil { return nil, err } else { defer rows.Close() var hints []*EHint for rows.Next() { h := &EHint{} h.IdExercice = e.Id if err := rows.Scan(&h.Id, &h.Title, &h.Content, &h.Cost); err != nil { return nil, err } treatHintContent(h) hints = append(hints, h) } if err := rows.Err(); err != nil { return nil, err } return hints, nil } } // AddHint creates and fills a new struct EHint and registers it into the database. func (e *Exercice) AddHint(title string, content string, cost int64) (*EHint, error) { if res, err := DBExec("INSERT INTO exercice_hints (id_exercice, title, content, cost) VALUES (?, ?, ?, ?)", e.Id, title, content, cost); err != nil { return nil, err } else if hid, err := res.LastInsertId(); err != nil { return nil, err } else { return &EHint{hid, e.Id, title, content, "", cost}, nil } } // Update applies modifications back to the database. func (h *EHint) Update() (int64, error) { if res, err := DBExec("UPDATE exercice_hints SET id_exercice = ?, title = ?, content = ?, cost = ? WHERE id_hint = ?", h.IdExercice, h.Title, h.Content, h.Cost, h.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err } else { return nb, err } } // Delete the hint from the database. func (h *EHint) Delete() (int64, error) { if res, err := DBExec("DELETE FROM exercice_hints WHERE id_hint = ?", h.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err } else { return nb, err } } // WipeHints deletes (only in the database, not on disk) hints coming with the challenge. func (e *Exercice) WipeHints() (int64, error) { 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 } 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 } else if res, err := DBExec("DELETE FROM exercice_hints WHERE id_exercice = ?", e.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err } else { return nb, err } } // AddDepend insert a new dependency to a given flag. func (h *EHint) AddDepend(f Flag) (err error) { if d, ok := f.(*FlagKey); ok { _, err = DBExec("INSERT INTO exercice_hints_okey_deps (id_hint, id_flag_dep) VALUES (?, ?)", h.Id, d.Id) } else if d, ok := f.(*MCQ); ok { _, err = DBExec("INSERT INTO exercice_hints_omcq_deps (id_hint, id_mcq_dep) VALUES (?, ?)", h.Id, d.Id) } else { err = fmt.Errorf("dependancy type for key (%T) not implemented for this flag", f) } return } // GetDepends retrieve the flag's dependency list. func (h *EHint) GetDepends() ([]Flag, error) { var deps []Flag if rows, err := DBQuery("SELECT id_flag_dep FROM exercice_hints_okey_deps WHERE id_hint = ?", h.Id); err != nil { return nil, err } else { defer rows.Close() for rows.Next() { var d int if err := rows.Scan(&d); err != nil { return nil, err } deps = append(deps, &FlagKey{Id: d, IdExercice: h.IdExercice}) } if err := rows.Err(); err != nil { return nil, err } } if rows, err := DBQuery("SELECT id_mcq_dep FROM exercice_hints_omcq_deps WHERE id_hint = ?", h.Id); err != nil { return nil, err } else { defer rows.Close() for rows.Next() { var d int if err := rows.Scan(&d); err != nil { return nil, err } deps = append(deps, &MCQ{Id: d, IdExercice: h.IdExercice}) } if err := rows.Err(); err != nil { return nil, err } } return deps, nil } // GetExercice returns the parent Exercice where this hint can be found. func (h *EHint) GetExercice() (*Exercice, error) { var eid int64 if err := DBQueryRow("SELECT id_exercice FROM exercice_hints WHERE id_hint = ?", h.Id).Scan(&eid); err != nil { return nil, err } return GetExercice(eid) }