# 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

$T_{s} = \left( D_{s} + S_{s} \right) + \sum_{a=1}^{t} A_{s_{a}}$where:

$s$ is the service id.

$T_{s}$ is the teams' total score in a tick for a service.

$D_{s}$ is the defense score in a tick for a service.

$S_{s}$ is the SLA score in a tick for a service.

$a$ is the attack teams' id.

$t$ is the total number of teams.

$A_{s_{a}}$ is the score for a successful attack on team $a$'s service in a tick.

### Attack

$A = 1 + \frac{1}{f} + 1 \cdot \left\{ \begin{array}{cl} 1 & if\ r_{a} \leq r_{v} \\ 1 - R & if\ r_{a} > r_{v} \\ \end{array} \right. and\ f > 0$where:

$R$ is the score reduction for attacking down, and $R$ is calculated with:

$R = \frac{4}{5} \cdot \left ( \frac{r_{v}-r_{a}}{t} \right )^{2}$and:

$A$ is a the attack score for a successful attack on a vitcim in a tick.

$f$ is the number of teams that have captured a flag from the victimin a tick.

$r_{a}$ is the rank of attacker in tick flag was lost captured from victim.

$r_{v}$ is the rank of the victim in tick flag was lost to attacker.

$t$ is the total number of teams.

Attack score is only calculated if a flag was captured.

### SLA

$S = \left\{ \begin{array}{cl} 3 & if\ service\ is\ up \\ \frac{3}{2} & if\ service\ is\ recovering\\ 0 & if\ service\ is\ down \\ \end{array} \right.$where:

$S$ 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

$D = \left\{ \begin{array}{cl} 2 + \frac{1}{d} & if\ S > 0\ and\ d > 0 \\ 0 & if\ S = 0 \\ \end{array} \right.$where:

$D$ is a the defense score for successfully defending a service in a tick from all attackers.

$d$ 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:

$D_{t_{d}} = \frac{S_{t_{d}}}{H_{d}} \cdot N_{d}$

where:

$t$ is the team number.

$d$ is the day number.

$D_{t_{d}}$ is a teams normalized score for day $d$.

$S_{t_{d}}$ is the teams total score of the day $d$.

$H_{d}$ is the highest total score of the day $d$.

$N_{d}$ is the weighted normalized max score of the day $d$.

### Final ranking score

Final ranking score for a team is:

$F_{t} = D_{t_{1}} + D_{t_{2}} + D_{t_{3}}$

where:

$F_{t}$ is the teams' final ranking score.

$D_{t_{1}}$ is the teams' normalized score from day 1.

$D_{t_{2}}$ is the teams' normalized score from day 2.

$D_{t_{3}}$ is the teams' normalized score from day 3.

### Tiebreaker

If two teams, $x$ and $y$, end up with the same final ranking score, that is $F_{x} = F_{y}$, 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.