def build_5_matches(places): return [ { 'A': Match(0, 'Quarter 1 (#0)', 'A', places[0], datetime(2014, 4, 27, 14, 30), datetime(2014, 4, 27, 14, 35), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(1, 'Quarter 2 (#1)', 'A', places[1], datetime(2014, 4, 27, 14, 35), datetime(2014, 4, 27, 14, 40), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(2, 'Semi 1 (#2)', 'A', places[2], datetime(2014, 4, 27, 14, 45), datetime(2014, 4, 27, 14, 50), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(3, 'Semi 2 (#3)', 'A', places[3], datetime(2014, 4, 27, 14, 50), datetime(2014, 4, 27, 14, 55), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(4, 'Final (#4)', 'A', places[4], datetime(2014, 4, 27, 15, 0), datetime(2014, 4, 27, 15, 5), MatchType.knockout, use_resolved_ranking=False) }, ]
def test_knockout_match_winners_tie(): knockout_positions = { ('A', 2): OrderedDict([ ('JKL', 1), ('GHI', 2), ('DEF', 3), ('ABC', 4), ]) } # Deliberately out of order as some python implementations # use the creation order of the tuples as a fallback sort comparison positions = { 'ABC': 1, 'DEF': 4, 'GHI': 3, 'JKL': 2, } scheduler = get_scheduler(knockout_positions = knockout_positions, \ positions = positions) game = Match(2, 'Match 2', 'A', [], None, None, None, False) winners = scheduler.get_winners(game) assert set(winners) == set(['GHI', 'JKL']), \ "Should used the league positions to resolve the tie"
def _add_match(self, match_info, rounds_remaining, round_num): new_matches = {} arena = match_info['arena'] start_time = match_info['start_time'] end_time = start_time + self.schedule.match_duration num = len(self.schedule.matches) teams = [] for team_ref in match_info['teams']: teams.append(self.get_team(team_ref)) if len(teams) < 4: "Fill empty zones with None" teams += [None] * (4 - len(teams)) display_name = self.get_match_display_name(rounds_remaining, round_num, 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(new_matches) self.period.matches.append(new_matches)
def make_schedule(): settings = {'match_periods': {'league': [], 'knockout': []}, 'match_slot_lengths': {'pre': 90, 'match': 180, 'post': 30, 'total': 300}, 'staging': {'opens': 300, 'closes': 120, 'duration': 180, 'signal_shepherds': {'Blue': 241, 'Green': 181}, 'signal_teams': 240}, 'league': { 'extra_spacing': [], }, 'delays': []} teams = defaultdict(lambda: Team(None, None, False, None)) schedule = MatchSchedule(settings, {}, teams, 4) finals = Match(num=0, display_name='Match 0', arena='A', teams=['AAA', 'BBB', 'CCC', 'DDD'], start_time=datetime.datetime(2014, 4, 25, 12, 0), end_time=datetime.datetime(2014, 4, 25, 12, 5), type=MatchType.knockout, use_resolved_ranking=False) schedule.knockout_rounds = [[finals]] schedule.matches.append({'A':finals}) return schedule
def test_first_round_before_league_end(): positions = OrderedDict() positions['ABC'] = 1 positions['CDE'] = 2 positions['EFG'] = 3 positions['GHI'] = 4 # Fake a couple of league matches that won't have been scored matches = [ { 'A': Match(0, 'Match 0', 'A', [], None, None, MatchType.league, False) }, { 'A': Match(1, 'Match 1', 'A', [], None, None, MatchType.league, False) }, ] scheduler = get_scheduler(matches, positions=positions) def seeder(*args): assert args[0] == 4, "Wrong number of teams" return [[0, 1, 2, 3]] # Mock the random (even thought it's not really random) scheduler.R = mock.Mock() # Mock the seeder to make it less interesting with mock.patch( 'sr.comp.knockout.first_round_seeding') as first_round_seeding: first_round_seeding.side_effect = seeder scheduler.add_knockouts() knockout_rounds = scheduler.knockout_rounds assert len(knockout_rounds) == 1, "Should be finals only" finals = knockout_rounds[0] assert len(finals) == 1, "Should be one final" final = finals[0] final_teams = final.teams # No scores yet -- should just list as ??? expected_teams = [UNKNOWABLE_TEAM] * 4 assert expected_teams == final_teams, "Should not show teams until league complete"
def _build_matchlist(self, yamldata): """Build the match list.""" self.matches = [] if yamldata is None: self.n_planned_league_matches = 0 return match_numbers = sorted(yamldata.keys()) self.n_planned_league_matches = len(match_numbers) if tuple(match_numbers) != tuple(range(len(match_numbers))): raise Exception("Matches are not a complete 0-N range") # Effectively just the .values(), except that it's ordered by number raw_matches = [yamldata[m] for m in match_numbers] match_n = 0 for period in self.match_periods: # Fill this period with matches clock = MatchPeriodClock(period, self.delays) # No extra spacing for matches at the start of a period # Fill this match period with matches for start in clock.iterslots(self.match_duration): try: arenas = raw_matches.pop(0) except IndexError: # no more matches left break m = {} end_time = start + self.match_duration for arena_name, teams in arenas.items(): teams = self.remove_drop_outs(teams, match_n) display_name = 'Match {n}'.format(n=match_n) match = Match(match_n, display_name, arena_name, teams, start, end_time, MatchType.league, use_resolved_ranking=False) m[arena_name] = match period.matches.append(m) self.matches.append(m) match_n += 1 extra_spacing = self._spacing.get(match_n) if extra_spacing: clock.advance_time(extra_spacing)
def build_match(num, arena, teams=None, start_time=None, end_time=None, type_=None, use_resolved_ranking=False): return Match(num, 'Match {n}'.format(n=num), arena, teams, start_time, end_time, type_, use_resolved_ranking)
def _add_round_of_matches(self, matches, arenas, rounds_remaining): """ Add a whole round of matches. :param list matches: A list of lists of teams for each match. """ self.knockout_rounds += [[]] round_num = 0 while len(matches): # Deliberately not using iterslots since we need to ensure # that the time advances even after we've run out of matches start_time = self.clock.current_time end_time = start_time + self.schedule.match_duration new_matches = {} for arena in arenas: teams = matches.pop(0) if len(teams) < 4: "Fill empty zones with None" teams += [None] * (4 - len(teams)) # Randomise the zones self.R.shuffle(teams) num = len(self.schedule.matches) display_name = self.get_match_display_name( rounds_remaining, round_num, num) match = Match( num, display_name, arena, teams, start_time, end_time, MatchType.knockout, # Just the finals don't use the resolved ranking use_resolved_ranking=rounds_remaining != 0) self.knockout_rounds[-1].append(match) new_matches[arena] = match if len(matches) == 0: break self.clock.advance_time(self.schedule.match_duration) self.schedule.matches.append(new_matches) self.period.matches.append(new_matches) round_num += 1
def test_knockout_match_winners_irrelevant_tie_1(): knockout_positions = { ('A', 2): OrderedDict([ ('JKL', 1), ('GHI', 2), ('ABC', 3), ('DEF', 3), ]) } scheduler = get_scheduler(knockout_positions=knockout_positions) game = Match(2, 'Match 2', 'A', [], None, None, None, False) winners = scheduler.get_winners(game) assert set(winners) == set(['GHI', 'JKL'])
def add_tiebreaker(self, scores, time): """ Add a tie breaker to the league if required. Also set a ``tiebreaker`` attribute if necessary. :param scores: The scores. :param time: The time. """ finals_info = self.knockout_rounds[-1][0] finals_key = (finals_info.arena, finals_info.num) try: finals_positions = scores.knockout.game_positions[finals_key] except KeyError: return winners = finals_positions.get(1) if not winners: raise AssertionError('The only winning move is not to play.') if len(winners) > 1: # Act surprised! # Start with the winning teams in the same order as in the finals tiebreaker_teams = [ team if team in winners else None for team in finals_info.teams ] # Use a static permutation permutation = [3, 2, 0, 1] tiebreaker_teams = [ tiebreaker_teams[permutation[n]] for n in permutation ] # Inject new match end_time = time + self.match_duration num = self.n_matches() arena = finals_info.arena match = Match(num=num, display_name="Tiebreaker (#{0})".format(num), arena=arena, teams=tiebreaker_teams, type=MatchType.tiebreaker, start_time=time, end_time=end_time, use_resolved_ranking=False) slot = {arena: match} self.matches.append(slot) match_period = MatchPeriod(time, end_time, end_time, 'Tiebreaker', [slot], MatchType.tiebreaker) self.match_periods.append(match_period) self.tiebreaker = match
def build_match( num: int = 0, arena: str = 'main', teams: Sequence[Optional[TLA]] = (), start_time: datetime.datetime = _DEFAULT_START_TIME, end_time: datetime.datetime = _DEFAULT_END_TIME, type_: MatchType = MatchType.league, use_resolved_ranking: bool = False, ) -> Match: return Match( MatchNumber(num), "Match {n}".format(n=num), ArenaName(arena), list(teams), start_time, end_time, type_, use_resolved_ranking, )
def test_get_staging_times(): start = datetime(2014, 3, 26, 13, 0, 0) match = Match(0, None, 'A', [], start, None, None, None) matches = load_basic_data() staging_times = matches.get_staging_times(match) expected = { 'opens': datetime(2014, 3, 26, 12, 56, 30), 'closes': datetime(2014, 3, 26, 12, 59, 30), 'duration': timedelta(seconds=180), 'signal_shepherds': { 'Blue': datetime(2014, 3, 26, 12, 57, 29), 'Green': datetime(2014, 3, 26, 12, 58, 29), }, 'signal_teams': datetime(2014, 3, 26, 12, 57, 30), } assert expected == staging_times, "Wrong staging times for given match"
def test_tiebreaker(): schedule = make_schedule() scores = make_finals_score({'AAA': 1, 'BBB': 1, 'CCC': 1, 'DDD': 0}) schedule.add_tiebreaker(scores, datetime.datetime(2014, 4, 25, 13, 0)) assert schedule.tiebreaker start_time = datetime.datetime(2014, 4, 25, 13, 0) end_time = datetime.datetime(2014, 4, 25, 13, 5) tiebreaker_match = { 'A': Match(num=1, display_name='Tiebreaker (#1)', arena='A', teams=['BBB', 'AAA', None, 'CCC'], start_time=start_time, end_time=end_time, type=MatchType.tiebreaker, use_resolved_ranking=False) } eq_(schedule.matches[-1], tiebreaker_match) last_period = schedule.match_periods[-1] last_period_matches = last_period.matches assert last_period_matches == [tiebreaker_match ], "Wrong matches in last period" last_period_matches.pop() # simplify the next comparison expected_period = MatchPeriod(start_time, end_time, end_time, 'Tiebreaker', [], MatchType.tiebreaker) assert last_period == expected_period, "Wrong last period"
def test_two_teams_before(): league_matches = [{ 'A': Match(0, 'Match 0', 'A', [], datetime(2014, 4, 27, 12, 30), datetime(2014, 4, 27, 12, 35), MatchType.league, use_resolved_ranking=False) }] expected = [ { 'A': Match(1, 'Quarter 1 (#1)', 'A', [UNKNOWABLE_TEAM] * 2, datetime(2014, 4, 27, 14, 30), datetime(2014, 4, 27, 14, 35), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(2, 'Quarter 2 (#2)', 'A', [UNKNOWABLE_TEAM] * 2, datetime(2014, 4, 27, 14, 35), datetime(2014, 4, 27, 14, 40), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(3, 'Semi 1 (#3)', 'A', [UNKNOWABLE_TEAM] * 2, datetime(2014, 4, 27, 14, 45), datetime(2014, 4, 27, 14, 50), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(4, 'Semi 2 (#4)', 'A', [UNKNOWABLE_TEAM] * 2, datetime(2014, 4, 27, 14, 50), datetime(2014, 4, 27, 14, 55), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(5, 'Final (#5)', 'A', [UNKNOWABLE_TEAM] * 2, datetime(2014, 4, 27, 15, 0), datetime(2014, 4, 27, 15, 5), MatchType.knockout, use_resolved_ranking=False) }, ] assertMatches( expected, matches_config=get_two_team_config(), matches=league_matches, )
def test_before(): league_matches = [{ 'A': Match(0, 'Match 0', 'A', [], datetime(2014, 4, 27, 12, 30), datetime(2014, 4, 27, 12, 35), MatchType.league, use_resolved_ranking=False) }] scheduler = get_scheduler(matches=league_matches) scheduler.add_knockouts() period = scheduler.period expected = [ { 'A': Match(1, 'Quarter 1 (#1)', 'A', [UNKNOWABLE_TEAM] * 4, datetime(2014, 4, 27, 14, 30), datetime(2014, 4, 27, 14, 35), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(2, 'Quarter 2 (#2)', 'A', [UNKNOWABLE_TEAM] * 4, datetime(2014, 4, 27, 14, 35), datetime(2014, 4, 27, 14, 40), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(3, 'Semi 1 (#3)', 'A', [UNKNOWABLE_TEAM] * 4, datetime(2014, 4, 27, 14, 45), datetime(2014, 4, 27, 14, 50), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(4, 'Semi 2 (#4)', 'A', [UNKNOWABLE_TEAM] * 4, datetime(2014, 4, 27, 14, 50), datetime(2014, 4, 27, 14, 55), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(5, 'Final (#5)', 'A', [UNKNOWABLE_TEAM] * 4, datetime(2014, 4, 27, 15, 0), datetime(2014, 4, 27, 15, 5), MatchType.knockout, use_resolved_ranking=False) }, ] for i in range(len(expected)): e = expected[i] a = period.matches[i] assert e == a, "Match {0} in the knockouts".format(i)
def test_knockout_match_winners_empty(): scheduler = get_scheduler() game = Match(2, 'Match 2', 'A', [], None, None, None, False) winners = scheduler.get_winners(game) assert winners == [UNKNOWABLE_TEAM] * 2
def helper(places, knockout_positions=None): scheduler = get_scheduler(knockout_positions=knockout_positions) scheduler.add_knockouts() period = scheduler.period expected = [ { 'A': Match(0, 'Quarter 1 (#0)', 'A', places[0], datetime(2014, 4, 27, 14, 30), datetime(2014, 4, 27, 14, 35), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(1, 'Quarter 2 (#1)', 'A', places[1], datetime(2014, 4, 27, 14, 35), datetime(2014, 4, 27, 14, 40), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(2, 'Semi 1 (#2)', 'A', places[2], datetime(2014, 4, 27, 14, 45), datetime(2014, 4, 27, 14, 50), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(3, 'Semi 2 (#3)', 'A', places[3], datetime(2014, 4, 27, 14, 50), datetime(2014, 4, 27, 14, 55), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(4, 'Final (#4)', 'A', places[4], datetime(2014, 4, 27, 15, 0), datetime(2014, 4, 27, 15, 5), MatchType.knockout, use_resolved_ranking=False) }, ] for i in range(len(expected)): e = expected[i] a = period.matches[i] assert e == a, "Match {0} in the knockouts".format(i)
def test_four_teams_before(): # Add an unscored league match so that we don't appear to have played them all league_matches = [{ 'A': Match(0, 'Match 0', 'A', [], datetime(2014, 4, 27, 12, 30), datetime(2014, 4, 27, 12, 35), MatchType.league, use_resolved_ranking=False) }] expected = [ { 'A': Match(1, 'Quarter 1 (#1)', 'A', [UNKNOWABLE_TEAM] * 4, datetime(2014, 4, 27, 14, 30), datetime(2014, 4, 27, 14, 35), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(2, 'Quarter 2 (#2)', 'A', [UNKNOWABLE_TEAM] * 4, datetime(2014, 4, 27, 14, 35), datetime(2014, 4, 27, 14, 40), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(3, 'Semi 1 (#3)', 'A', [UNKNOWABLE_TEAM] * 4, datetime(2014, 4, 27, 14, 45), datetime(2014, 4, 27, 14, 50), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(4, 'Semi 2 (#4)', 'A', [UNKNOWABLE_TEAM] * 4, datetime(2014, 4, 27, 14, 50), datetime(2014, 4, 27, 14, 55), MatchType.knockout, use_resolved_ranking=True) }, { 'A': Match(5, 'Final (#5)', 'A', [UNKNOWABLE_TEAM] * 4, datetime(2014, 4, 27, 15, 0), datetime(2014, 4, 27, 15, 5), MatchType.knockout, use_resolved_ranking=False) }, ] assertMatches( expected, matches_config=get_four_team_config(), matches=league_matches, )
from dateutil.tz import tzutc from collections import OrderedDict from sr.comp.winners import Award, compute_awards from sr.comp.match_period import Match, MatchType from sr.comp.teams import Team from sr.comp.scores import TeamScore from sr.comp.ranker import calc_positions, calc_ranked_points from nose.tools import eq_ import mock FINAL_INFO = Match(num=1, display_name='Match 1', arena='A', teams=['AAA', 'BBB', 'CCC', 'DDD'], start_time=datetime(2014, 4, 26, 16, 30, tzinfo=tzutc()), end_time=datetime(2014, 4, 26, 16, 35, tzinfo=tzutc()), type=MatchType.knockout, use_resolved_ranking=False) TIEBREAKER_INFO = Match(num=2, display_name='Tiebreaker (#2)', arena='A', teams=['AAA', 'BBB'], start_time=datetime(2014, 4, 26, 16, 30, tzinfo=tzutc()), end_time=datetime(2014, 4, 26, 16, 35, tzinfo=tzutc()),