Skip to content

Commit

Permalink
Implement time-based points scaling
Browse files Browse the repository at this point in the history
  • Loading branch information
diamondburned committed Mar 18, 2024
1 parent 247f3d4 commit 195e033
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 34 deletions.
10 changes: 6 additions & 4 deletions server/frontend/pages/problem.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ <h2>Output</h2>
{{ else }}
<strong class="primary">You've solved both parts!</strong>
{{ end }}
<!--
This nets you a total of
<strong class="primary">
{{ if and .SolvedPart1 (not .SolvedPart2) }}
{{ .PointsPerPart }}
{{ else }}
{{ mul .PointsPerPart 2 }}
{{ end }}
{{ .PointsPerPart }}
{{ else }}
{{ mul .PointsPerPart 2 }}
{{ end }}
</strong>
points.
-->
</p>
{{ end }}

Expand Down
24 changes: 0 additions & 24 deletions server/problem/cooldown.go

This file was deleted.

4 changes: 0 additions & 4 deletions server/problem/problem.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ import (
badgeropts "github.com/dgraph-io/badger/v4/options"
)

// PointsPerPart is the number of points awarded for solving a part of a
// problem.
const PointsPerPart = 100

// Problem is a problem that can be solved.
type Problem struct {
// ID returns the unique ID of the problem.
Expand Down
12 changes: 12 additions & 0 deletions server/problem/problemset.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ func (p *ProblemSet) Problem(i int) *Problem {
return &p.problems[i]
}

// ProblemStartTime calculates the time at which the problem at the given index
// was released. If the problem set does not have a release schedule, it returns
// the zero time.
func (p *ProblemSet) ProblemStartTime(i int) time.Time {
if p.schedule == nil {
return time.Time{}
}
start := p.schedule.StartReleaseAt
delta := time.Duration(i) * p.schedule.ReleaseEvery
return start.Add(delta)
}

// TotalProblems returns the total number of problems in the set.
func (p *ProblemSet) TotalProblems() int {
return len(p.problems)
Expand Down
53 changes: 53 additions & 0 deletions server/problem/scoring.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package problem

import (
"math"
"time"
)

// CalculateCooldownEnd calculates the end of the cooldown period for a problem
// given the total number of attempts, the time of the last submission and the
// current time. If there is no cooldown, the returned timestamp is before the
// current time, which may or may not be zero.
func CalculateCooldownEnd(totalAttempts int, lastSubmitted, now time.Time) time.Time {
const cooldownThreshold = 2
const cooldownMultiplier = 2
const cooldownMax = 5 * time.Minute
const cooldown = 30 * time.Second

if totalAttempts < cooldownThreshold {
return time.Time{}
}

n := totalAttempts - cooldownThreshold + 1

return lastSubmitted.Add(min(
cooldown*time.Duration(cooldownMultiplier*n),
cooldownMax))
}

const (
// PointsPerPart is the number of points awarded for solving a part of a
// problem.
PointsPerPart = 100
// MaxHour is the maximum hour before people get the lowest points.
MaxHour = 24
)

// ScalePoints scales the points for a problem's part based on the time the
// problem was started and the time the part was solved.
func ScalePoints(t, startedAt time.Time) float64 {
h := t.Sub(startedAt).Hours()
return scoreScalingFn(clamp(h/MaxHour, 0, 1)) * PointsPerPart
}

func scoreScalingFn(x float64) float64 {
// https://www.desmos.com/calculator/22el44ng3r
f := func(x float64) float64 { return (math.Atan(-math.Pi*x+math.Pi/2) / 4) + 0.75 }
g := func(x float64) float64 { return f(x) + (1 - f(0)) }
return g(x)
}

func clamp(x, minX, maxX float64) float64 {
return math.Max(minX, math.Min(maxX, x))
}
4 changes: 2 additions & 2 deletions server/r_problems.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type problemPageData struct {
frontend.ComponentContext
Problem *problem.Problem
Day problemDay
PointsPerPart int
PointsPerPart float64
SolvedPart1 bool
SolvedPart2 bool
}
Expand Down Expand Up @@ -221,7 +221,7 @@ func (s *Server) submitProblem(w http.ResponseWriter, r *http.Request) {
if correct {
_, err = s.database.AddPoints(ctx, db.AddPointsParams{
TeamName: u.TeamName,
Points: problem.PointsPerPart,
Points: problem.ScalePoints(now, s.problems.ProblemStartTime(day.index())),
Reason: "week of code",
})
if err != nil {
Expand Down

0 comments on commit 195e033

Please sign in to comment.