Index Projects Blog
AOC Leaderboard

AOC Leaderboard

2022-12-01

For Advent of Code 2022 I created a leaderboard website that pulled from the AOC API and showed people's progress. That leaderboard was then shown prominently at my school. It encouraged participation in Advent of Code, with about ten people who were otherwise not intending to take part doing so. The source code is available at github/Alex-Programs/aoc-leaderboard. I continuously worked on the scoring system to try to discourage getting up particularly early (AOC increments at 5AM in the UK) without being unfair or removing all forms of differentiation and competition.

The scoring went through some changes over time, settling on a combination of:

  • The time it took you to complete the first and second stars, measured from release time.
  • The time between completing the first star and the second star, compared to that of peers.
  • How far through the month the star released - later days awarded more points.

The time curve was created to minimise the advantage of waking up early (It opened at 4AM, and I didn't want to encourage people to lose sleep over it), while maximising the advantage of doing it quickly during the bus journey, break times, and lunch times. This was done through a linear curve that transitioned into a square-root one. I tuned it using a Python script and Desmos.

Initially, there were also different curves for day and all-time.

# Day curve
def score(star_time_hours):
    if star_time_hours < 9.081 * 2:
        return ((-(1 / 2) * star_time_hours) + 35) * 10
    return (100 / math.sqrt(star_time_hours)) * 11.005
# All-time curve
def score(star_time_hours):
    if star_time_hours <= 24:
        return (((-(1/4)) * star_time_hours) + 100)
    return ((1 / math.sqrt(star_time_hours)) * 460)

The processing using your delta times took into account other people who were in the "top group":

# Control time is the median of the top group
# (before the jump to the next group > 2.5x the existing median)
topGroup = []
for index, element in enumerate(sorted(deltas)):
    if index == 0:
        topGroup.append(element)
        continue

    median = topGroup[math.floor(len(topGroup) / 2)]
    if element > median * 2.5:
        break

    topGroup.append(element)

if len(topGroup) > 1:
    controlTime = topGroup[math.floor(len(topGroup) / 2)]
elif len(topGroup) == 1:
    controlTime = topGroup[0]
else:
    return round(total_unadjusted / 50)

log("CONTROL TIME: " + str(controlTime))
log("DELTA TIME: " + str(deltaTime))

deltaTimeSegment = deltaTime / controlTime

deltaTimeSegment = score_func(deltaTimeSegment * 24) * 150

# Shouldn't be needed
deltaTimeSegment = max(0, deltaTimeSegment)

# Delta segment is your delta time divided by the control time
log(name)
log("Delta time segment: " + str(deltaTimeSegment))
normalSegment = total_unadjusted
log("Normal segment:     " + str(normalSegment))
log("_------_")
total_adjusted = normalSegment + deltaTimeSegment

The control time is limited to the top group to prevent people who take a long break before doing the second star (which happened quite frequently) from skewing the results.

I successfully reused it in December 2023. My Christmas-y theme had unintentionally looked more like a stereotypical script-kiddy green-text-black-background one, so I instead opted to mimick an old DOS CRT, complete with flickering effects and slight RGB offsets.