Scoring
EU/EFTA teams and guest teams will have seperate scoreboards, but will use the same platforms.
Jeopardy
Jeopardy challenges will using dynamic scoring.
Parameters
solved_by_teams
is the number of teams that have solved a challenge.
total_teams
is the total number of teams.
max_score
is the maximum score for the challenge. In ECSC 2023 this is set 1000 points.
min_score
is the minimum score for the challenge. In ECSC 2023 this is set 400 points.
If only one teams solves a challenge, the challenge will be worth the maximum score.
If 75% or more teams solves a challenge, it will be worth the minimum score.
If there are between 1 and 75% of teams that have solved a challenge, the points for that challenge is calculated by a simple linear interpolation between these two endpoints.
Reference implementation
# ECSC 2023 jeopardy dynamic scoring algorithm
def dynamic_score(solved_by_teams: int, total_teams: int, max_score: int, min_score: int):
# Calculate the ratio of solved teams to total teams
ratio = solved_by_teams / total_teams
# Ensure that if only one team solves the challenge, they'll get the max_score
fraction_high_score = 1 / total_teams
# If more than 3/4 of teams solves a challenge it drops to min_score
# "+ fraction_high_score" is there make sure this also works correctly when total_teams
# is an integer multiple of 4
fraction_low_score = 3/4 + fraction_high_score
match ratio:
# Set to the maximum score if less than the fraction for highest score have solved challenge
case ratio if ratio <= fraction_high_score:
score = max_score
# Set to the minimum score if more than the fraction for lowest score have solved challenge
case ratio if ratio >= fraction_low_score:
score = min_score
# All other score must be between max_score and min_score
# Here we use a simple linear interpolation between min_score and max_score when the fraction
# of teams that have solved a challenge is between fraction_hig_score and fraction_low_score
case _:
score = max_score - (max_score - min_score) * (ratio - fraction_high_score) / (fraction_low_score - fraction_high_score)
return round(score)
# Simple tests showing score for a challenge based on number of solves
if __name__ == '__main__':
MAX_SCORE = 1000
MIN_SCORE = 400
TOTAL_TEAMS = 28
print(f"\nECSC EU/EFTA with {TOTAL_TEAMS} teams\n"
f"Solves\tPoints\tPercent drop in score\n{'-' * 80}")
number_of_teams = TOTAL_TEAMS
for i in range(number_of_teams + 1):
score = dynamic_score(i, number_of_teams, MAX_SCORE, MIN_SCORE)
print(f"{i:2}\t\t{score:4}\t{1-(score/MAX_SCORE):>6.2%}")
TOTAL_TEAMS = 7
print(f"\nECSC Guests with {TOTAL_TEAMS} teams\n"
f"Solves\tPoints\tPercent drop in score\n{'-' * 80}")
number_of_teams = TOTAL_TEAMS
for i in range(number_of_teams + 1):
score = dynamic_score(i, number_of_teams, MAX_SCORE, MIN_SCORE)
print(f"{i:2}\t\t{score:4}\t{1-(score/MAX_SCORE):>6.2%}")
Attack/Defense
Tick score per service
where:
is the service id.
is the teams' total score in a tick for a service.
is the defense score in a tick for a service.
is the SLA score in a tick for a service.
is the attack teams' id.
is the total number of teams.
is the score for a successful attack on team 's service in a tick.
Attack
where:
is the score reduction for attacking down, and is calculated with:
and:
is a the attack score for a successful attack on a vitcim in a tick.
is the number of teams that have captured a flag from the victimin a tick.
is the rank of attacker in tick flag was lost captured from victim.
is the rank of the victim in tick flag was lost to attacker.
is the total number of teams.
Attack score is only calculated if a flag was captured.
SLA
where:
is a the SLA score for a keeping the service up in a tick.
A service is considered up if the service checker returns the state UP.
A service is considered recovering if the service checker returns the state RECOVERING.
A service is considered down if service cheke returns any other state.
Defense
where:
is a the defense score for successfully defending a service in a tick from all attackers.
is the number of teams that did not loose flags for service in tick.
Full scoring equations
The full form of scoring equations for A/D are documented in this document.
Overall ranking
The overall rank will be based on normalized scores from each day.
Jeopardy will count 50% of the overall score and Attack/Defense will count 50% of the overall score.
Day 1 | Day 2 | Day 3 | |
---|---|---|---|
CTF style | Jeopardy | A/D | Jeopardy |
Weigth percentage | 25% | 50% | 25% |
Weigthed normalized max points | 5000 | 10000 | 5000 |
Calcultation of normalized day score
Calcultation of normalized score per team per day:
where:
is the team number.
is the day number.
is a teams normalized score for day .
is the teams total score of the day .
is the highest total score of the day .
is the weighted normalized max score of the day .
Final ranking score
Final ranking score for a team is:
where:
is the teams' final ranking score.
is the teams' normalized score from day 1.
is the teams' normalized score from day 2.
is the teams' normalized score from day 3.
Tiebreaker
If two teams, and , end up with the same final ranking score, that is , then the results from Attack/Defense on day 2 will be used as a tiebreaker.
The team with the highest score in Attack/Defense will be ranked highest of the teams with equal final ranking score.