Source code for sr.comp.knockout_scheduler.static_scheduler
"""
A static knockout schedule.
"""
from __future__ import annotations
import datetime
from typing import Any, NewType
from typing_extensions import TypedDict
from ..match_period import Match, MatchSlot, MatchType
from ..types import ArenaName, MatchNumber, TLA
from .base_scheduler import BaseKnockoutScheduler, UNKNOWABLE_TEAM
StaticMatchTeamReference = NewType('StaticMatchTeamReference', str)
class StaticMatchInfo(TypedDict):
arena: ArenaName
start_time: datetime.datetime
teams: list[StaticMatchTeamReference]
display_name: str | None
[docs]class StaticScheduler(BaseKnockoutScheduler):
"""
A knockout scheduler which loads almost fixed data from the config. Assumes
only a single arena.
Due to the nature of its interaction with the seedings, this scheduler has a
very limited handling of dropped-out teams: it only adjusts its scheduling
for dropouts before the knockouts.
The practical results of this dropout behaviour are:
* the schedule is stable when teams drop out, as this either affects the
entire knockout or none of it
* dropping out a team such that there are no longer enough seeds requires
manual changes to the schedule to remove the seeds which cannot be filled
"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
# Collect a list of the teams eligible for the knockouts, in seeded order.
last_league_match_num = self.schedule.n_matches()
self._knockout_seeds = self._get_non_dropped_out_teams(
MatchNumber(last_league_match_num),
)
[docs] def get_team(self, team_ref: StaticMatchTeamReference | None) -> TLA | None:
if not self._played_all_league_matches():
return UNKNOWABLE_TEAM
if team_ref is None:
return None
if team_ref.startswith('S'):
# get a seeded position
pos = int(team_ref[1:])
# seed numbers are 1 based
if pos < 1:
raise ValueError(f"Invalid seed {team_ref!r} (seed numbers start at 1)")
pos -= 1
try:
return self._knockout_seeds[pos]
except IndexError:
raise ValueError(
"Cannot reference seed {}, there are only {} eligible teams!".format(
team_ref,
len(self._knockout_seeds),
),
) from None
# get a position from a match
assert len(team_ref) == 3
round_num, match_num, pos = (int(x) for x in team_ref)
try:
match = self.knockout_rounds[round_num][match_num]
except IndexError:
raise ValueError(
f"Reference '{team_ref}' to unscheduled match!",
) from None
try:
ranking = self.get_ranking(match)
return ranking[pos]
except IndexError:
raise ValueError(
f"Reference '{team_ref}' to invalid ranking!",
) from None
def _add_match(
self,
match_info: StaticMatchInfo,
rounds_remaining: int,
round_num: int,
) -> None:
new_matches = {}
arena = match_info['arena']
start_time = match_info['start_time']
end_time = start_time + self.schedule.match_duration
num = MatchNumber(len(self.schedule.matches))
teams = [
self.get_team(team_ref)
for team_ref in match_info['teams']
]
if len(teams) != self.num_teams_per_arena:
raise ValueError(
f"Unexpected number of teams in match {num} (round {round_num}); "
f"got {len(teams)}, expecting {self.num_teams_per_arena}." + (
" Fill any expected empty places with `null`."
if len(teams) < self.num_teams_per_arena
else ""
),
)
display_name = self.get_match_display_name(
rounds_remaining,
round_num,
num,
)
# allow overriding the name
override_name = match_info.get('display_name')
if override_name is not None:
display_name = f"{override_name} (#{num})"
is_final = rounds_remaining == 0
match = Match(
num,
display_name,
arena,
teams,
start_time,
end_time,
MatchType.knockout,
use_resolved_ranking=not is_final,
)
self.knockout_rounds[-1].append(match)
new_matches[match_info['arena']] = match
self.schedule.matches.append(MatchSlot(new_matches))
self.period.matches.append(MatchSlot(new_matches))
[docs] def add_knockouts(self) -> None:
knockout_conf = self.config['static_knockout']['matches']
for round_num, round_info in sorted(knockout_conf.items()):
self.knockout_rounds += [[]]
rounds_remaining = len(knockout_conf) - round_num - 1
for match_num, match_info in sorted(round_info.items()):
self._add_match(match_info, rounds_remaining, match_num)