Source code for sr.comp.winners
"""
Calculation of winners of awards.
The awards calculated are:
* 1st place,
* 2nd place,
* 3rd place,
* Rookie award (rookie team with highest league position).
"""
from __future__ import annotations
from enum import Enum, unique
from pathlib import Path
from typing import List, Mapping
from league_ranker import RankedPosition
from . import yaml_loader
from .match_period import Match, MatchType
from .scores import InvalidTeam, Scores
from .teams import Team
from .types import AwardsData, MatchNumber, TLA
[docs]@unique
class Award(Enum):
"""
Award types.
These correspond with awards as specified in the rulebook.
"""
first = 'first' # First place
second = 'second' # Second place
third = 'third' # Third place
rookie = 'rookie' # Rookie award
committee = 'committee' # Committee award
image = 'image' # Robot and Team Image award
movement = 'movement' # First Movement award
web = 'web' # Online Presence award
Winners = Mapping[Award, List[TLA]]
def _compute_main_awards(scores: Scores, final_match_info: Match) -> Winners:
"""Compute awards resulting from the grand finals."""
last_match_key = (final_match_info.arena, final_match_info.num)
game_positions = scores.knockout.game_positions
if final_match_info.type == MatchType.tiebreaker:
game_positions = scores.tiebreaker.game_positions
try:
positions = game_positions[last_match_key]
except KeyError:
# We haven't scored the last match yet
return {}
awards = {}
for award, key in (
(Award.first, RankedPosition(1)),
(Award.second, RankedPosition(2)),
(Award.third, RankedPosition(3)),
):
candidates = positions.get(key, ())
awards[award] = sorted(candidates)
if not awards[Award.third] and len(final_match_info.teams) == 2:
# Look in the previous match to find the third place
final_key = (final_match_info.arena, MatchNumber(final_match_info.num - 1))
positions = scores.knockout.game_positions[final_key]
candidates = positions.get(RankedPosition(3), ())
awards[Award.third] = sorted(candidates)
return awards
def _compute_rookie_award(scores: Scores, teams: Mapping[TLA, Team]) -> Winners:
"""Compute the winner of the rookie award."""
rookie_positions = {
team: position
for team, position in scores.league.positions.items()
if teams[team].rookie
}
# It's possible there are no rookie teams, in which case nobody gets
# the award.
if not rookie_positions:
return {Award.rookie: []}
# Position go from 1 upwards (1 being first), so the best is the minimum
best_position = min(rookie_positions.values())
return {
Award.rookie: sorted(
team
for team, position in rookie_positions.items()
if position == best_position
),
}
def _compute_explicit_awards(path: Path, teams: Mapping[TLA, Team]) -> Winners:
"""Compute awards explicitly provided in the compstate repo."""
if not path.exists():
return {}
explicit_awards: AwardsData = yaml_loader.load(path)
assert explicit_awards, "Awards file should not be present if empty."
awards = {
Award(key): [value] if isinstance(value, str) else value
for key, value in explicit_awards.items()
}
for award_teams in awards.values():
for tla in award_teams:
if tla not in teams:
raise InvalidTeam(tla, str(path))
return awards
[docs]def compute_awards(
scores: Scores,
final_match: Match,
teams: Mapping[TLA, Team],
path: Path | None = None,
) -> Winners:
"""
Compute the awards handed out from configuration.
:param sr.comp.scores.Scores scores: The scores.
:param Match final_match: The match to use as the final.
:param dict teams: A mapping from TLAs to :class:`sr.comp.teams.Team`
objects.
:return: A dictionary of :class:`Award` types to TLAs is returned. This may
not have a key for any award type that has not yet been
determined.
"""
awards: dict[Award, list[TLA]] = {}
awards.update(_compute_main_awards(scores, final_match))
awards.update(_compute_rookie_award(scores, teams))
if path is not None:
awards.update(_compute_explicit_awards(path, teams))
return awards