def __init__(self, division: int, blue_bot: VersionedBot, orange_bot: VersionedBot, ladder: Ladder, versioned_map, old_match_result: MatchResult, rr_bots: List[str], rr_results: List[MatchResult], message: str=''): self.division = division self.blue_config_path = blue_bot.bot_config.config_path if blue_bot is not None else None self.orange_config_path = orange_bot.bot_config.config_path if orange_bot is not None else None self.ladder = ladder.bots self.division_names = Ladder.DIVISION_NAMES[:ladder.division_count()] self.old_match_result = { 'winner': old_match_result.winner, 'loser': old_match_result.loser, 'blue_goals': old_match_result.blue_goals, 'orange_goals': old_match_result.orange_goals} if old_match_result is not None else None self.division_bots = ladder.round_robin_participants(division) self.rr_bots = rr_bots self.rr_results = [convert_match_result(mr) for mr in rr_results] self.blue_name = blue_bot.bot_config.name if blue_bot is not None else '' self.orange_name = orange_bot.bot_config.name if orange_bot is not None else '' self.message = message self.bot_map = {} for bot in ladder.bots: self.bot_map[bot] = { 'name': versioned_map[bot].bot_config.name, 'updated_date': (versioned_map[bot].updated_date + timedelta(seconds=0)).timestamp(), }
def list_matches(working_dir: WorkingDir, odd_week: bool, show_results: bool): """ Prints all the matches that will be run this week. """ ladder = Ladder.read(working_dir.ladder) playing_division_indices = get_playing_division_indices(ladder, odd_week) print(f'Matches to play:') # The divisions play in reverse order, but we don't print them that way. for div_index in playing_division_indices: print(f'--- {Ladder.DIVISION_NAMES[div_index]} division ---') rr_bots = ladder.round_robin_participants(div_index) rr_matches = generate_round_robin_matches(rr_bots) for match_participants in rr_matches: # Find result if show_results==True result_str = '' if show_results: result_path = working_dir.get_match_result( div_index, match_participants[0], match_participants[1]) if result_path.exists(): result = MatchResult.read(result_path) result_str = f' (result: {result.blue_goals}-{result.orange_goals})' print( f'{match_participants[0]} vs {match_participants[1]}{result_str}' )
def run_bubble_sort(working_dir: WorkingDir, team_size: int, replay_preference: ReplayPreference): # Ladder is a list of name.lower() ladder = Ladder.read(working_dir.ladder) sorter = BubbleSorter(ladder, working_dir, team_size, replay_preference) sorter.begin() print('Bubble sort is complete!') time.sleep(10) # Leave some time to display the overlay.
def __init__(self, working_dir: WorkingDir, team_size: int, replay_preference: ReplayPreference): self.ladder = Ladder.read(working_dir.ladder) self.working_dir = working_dir self.team_size = team_size self.replay_preference = replay_preference self.bundle_map = {} self.versioned_bots_by_name = {} self.num_already_played_during_iteration = 0
def list_matches(working_dir: WorkingDir, run_strategy: RunStrategy, stale_rematch_threshold: int = 0, half_robin: bool = False): """ Prints all the matches that will be run this week. :param stale_rematch_threshold: If a bot has won this number of matches in a row against a particular opponent and neither have had their code updated, we will consider it to be a stale rematch and skip future matches. If 0 is passed, we will not skip anything. :param half_robin: If true, we will split the division into an upper and lower round-robin, which reduces the number of matches required. """ ladder = Ladder.read(working_dir.ladder) playing_division_indices = ladder.playing_division_indices(run_strategy) bots = load_all_bots_versioned(working_dir) if len(ladder.bots) < 2: print(f'Not enough bots on the ladder to play any matches') return print(f'Matches to play:') num_matches = 0 num_skipped = 0 # The divisions play in reverse order. for div_index in playing_division_indices[::-1]: division_name = Ladder.DIVISION_NAMES[div_index] if div_index < len( Ladder.DIVISION_NAMES) else div_index print(f'--- {division_name} division ---') round_robin_ranges = get_round_robin_ranges(ladder, div_index, half_robin) for start_index, end_index in round_robin_ranges: rr_bots = ladder.bots[start_index:end_index + 1] rr_matches = generate_round_robin_matches(rr_bots) for match_participants in rr_matches: bot1 = bots[match_participants[0]] bot2 = bots[match_participants[1]] stale_match_result = get_stale_match_result( bot1, bot2, stale_rematch_threshold, working_dir) if stale_match_result is not None: num_skipped += 1 continue num_matches += 1 print(f'{match_participants[0]} vs {match_participants[1]}') print(f'Matches to run: {num_matches} Matches skipped: {num_skipped}')
def check_bot_folder(working_dir: WorkingDir, odd_week: Optional[bool] = None): """ Prints all bots missing from the bot folder. If odd_week is not None, it will filter for bots needed for the given type of week. """ bots = load_all_bots(working_dir) ladder = Ladder.read(working_dir.ladder) needed_bots = ladder.all_playing_bots( odd_week) if odd_week is not None else ladder.bots none_missing = True for bot in needed_bots: if bot not in bots.keys(): print(f'{bot} is missing from the bot folder.') none_missing = False if none_missing: print('No needed bots are missing from the bot folder.')
def check_bot_folder(working_dir: WorkingDir, run_strategy: Optional[RunStrategy] = None) -> bool: """ Prints all bots missing from the bot folder. If odd_week is not None, it will filter for bots needed for the given type of week. Returns True if everything is okay and no bots are missing. """ bots = load_all_bots(working_dir) ladder = Ladder.read(working_dir.ladder) needed_bots = ladder.all_playing_bots( run_strategy) if run_strategy is not None else ladder.bots none_missing = True for bot in needed_bots: if bot not in bots.keys(): print(f'{bot} is missing from the bot folder.') none_missing = False if none_missing: print('No needed bots are missing from the bot folder.') return True return False
def test_all_bots(working_dir: WorkingDir): """ Tests if all bots work by starting a series of matches and check if the bots move """ ladder = Ladder.read(working_dir.ladder) bot_count = len(ladder.bots) bots = load_all_bots(working_dir) # Pair each bot for a match. If there's an odd amount of bots, the last bot plays against the first bot pairs = [(ladder.bots[2 * i], ladder.bots[2 * i + 1]) for i in range(bot_count // 2)] if bot_count % 2 == 1: pairs.append((ladder.bots[0], ladder.bots[-1])) # Run matches fails = [] for match_participant_pair in pairs: participant_1 = bots[match_participant_pair[0]] participant_2 = bots[match_participant_pair[1]] match_config = make_match_config(participant_1, participant_2) grade = run_test_match(participant_1.name, participant_2.name, match_config) if isinstance(grade, Fail): fails.append(grade) time.sleep(1) time.sleep(2) # Print summary print( f'All test matches have been played ({len(pairs)} in total). Summary:') if len(fails) == 0: print(f'All bots seem to work!') else: for fail in fails: print(fail)
def get_playing_division_indices(ladder: Ladder, odd_week: bool) -> List[int]: # Result is a list containing either even or odd indices. # If there is only one division always play that division (division 0, quantum). return range( ladder.division_count())[int(odd_week) % 2::2] if ladder.division_count() > 1 else [0]
def fetch_ladder_from_sheets(week_num: int) -> Ladder: values = get_values_from_sheet(get_credentials(), SHEET_ID, get_ladder_range(week_num), SHEET_NAME) bots = [row[0] for row in values] return Ladder(bots)
def generate_leaderboard(working_dir: WorkingDir, odd_week: bool, extra: bool = False, background: bool = True): """ Created a leaderboard that shows differences between the old ladder and the new ladder. :param working_dir: The working directory :param odd_week: Whether odd or even divisions played this week :param extra: Whether to include the next 5 divisions. :param background: Whether to use a background for the leaderboard. :param make_clip: Whether to also make an mp4 clip. :param duration: Duration of the clip in seconds. :param frames_per_second: frames per second of the clip. """ assert working_dir.ladder.exists( ), f'\'{working_dir.ladder}\' does not exist.' assert working_dir.new_ladder.exists( ), f'\'{working_dir.new_ladder}\' does not exist yet.' old_ladder = Ladder.read(working_dir.ladder) new_ladder = Ladder.read(working_dir.new_ladder) new_bots, moved_up, moved_down = ladder_differences(old_ladder, new_ladder) played = old_ladder.all_playing_bots(odd_week) # --------------------------------------------------------------- # PARAMETERS FOR DRAWING: # Divisions divisions = ('Quantum', 'Overclocked', 'Processor', 'Circuit', 'Transistor', 'Abacus', 'Babbage', 'Colossus', 'Dragon', 'ENIAC', 'Ferranti') ''' Each division has the origin at the top left corner of their emblem. Offsets: title offsets determine where the division title is drawn relative to the emblem. bot offsets determine where bot names are drawn relative to the emblem. sym offsets determine where the symbol is placed relative to the bot name. Increments: div increments are how much to move the origin between each division. bot increment is how much to move down for each bot name. ''' # Start positions for drawing. start_x = 0 start_y = 0 # Division emblem offsets from the division name position. title_x_offset = 350 title_y_offset = 85 # Bot name offsets from the division name position. bot_x_offset = 200 bot_y_offset = 300 # Offsets for the symbols from the bot name position. sym_x_offset = 1295 sym_y_offset = 5 # Incremenets for x and y. div_x_incr = 1790 div_y_incr = 790 bot_y_incr = 140 # --------------------------------------------------------------- # DRAWING: # Opening image for drawing. if background: if extra: leaderboard = Image.open(LeaderboardPaths.leaderboard_extra_empty) else: leaderboard = Image.open(LeaderboardPaths.leaderboard_empty) else: if extra: leaderboard = Image.open( LeaderboardPaths.leaderboard_extra_no_background) else: leaderboard = Image.open( LeaderboardPaths.leaderboard_no_background) draw = ImageDraw.Draw(leaderboard) # Fonts for division titles and bot names. div_font = ImageFont.truetype(str(LeaderboardPaths.font_medium), 120) bot_font = ImageFont.truetype(str(LeaderboardPaths.font_medium), 80) # Bot name colour. bot_colour = (0, 0, 0) # For each divion, draw the division name, and each bot in the division. for i, div in enumerate(divisions): # Calculates position for the division. div_pos = (start_x + div_x_incr * (i // 5), start_y + div_y_incr * (i % 5)) # Draws the division emblem. try: # Opens the division emblem image. emblem = Image.open(LeaderboardPaths.emblems / f'{div}.png') # Pastes emblem onto image. leaderboard.paste(emblem, div_pos, emblem) except: # Sends warning message if it can't find the emblem. print(f'WARNING: Missing emblem for {div}.') # Draws the division title at an offset. title_pos = (div_pos[0] + title_x_offset, div_pos[1] + title_y_offset) draw.text(xy=title_pos, text=div, fill=Symbols.palette[div][0], font=div_font) # Loops through the four bots in the division and draws each. for ii, bot in enumerate(new_ladder.division(i)): # Calculates position for the bot name and draws it. bot_pos = (div_pos[0] + bot_x_offset, div_pos[1] + bot_y_offset + ii * bot_y_incr) draw.text(xy=bot_pos, text=bot, fill=bot_colour, font=bot_font) # Calculates symbol position. sym_pos = (bot_pos[0] + sym_x_offset, bot_pos[1] + sym_y_offset) # Pastes appropriate symbol if bot in new_bots: symbol = Image.open(LeaderboardPaths.symbols / f'{div}_new.png') leaderboard.paste(symbol, sym_pos, symbol) elif bot in moved_up: symbol = Image.open(LeaderboardPaths.symbols / f'{div}_up.png') leaderboard.paste(symbol, sym_pos, symbol) elif bot in moved_down: symbol = Image.open(LeaderboardPaths.symbols / f'{div}_down.png') leaderboard.paste(symbol, sym_pos, symbol) elif bot in played: symbol = Image.open(LeaderboardPaths.symbols / f'{div}_played.png') leaderboard.paste(symbol, sym_pos, symbol) # Saves the image. leaderboard.save(working_dir.leaderboard, 'PNG') print('Successfully generated leaderboard.')
def fetch_ladder_from_sheets(season: int, week_num: int) -> Ladder: bots = fetch_bots_from_sheets(season, week_num) return Ladder(bots)
def run_league_play(working_dir: WorkingDir, odd_week: bool, replay_preference: ReplayPreference): """ Run a league play event by running round robins for half the divisions. When done, a new ladder file is created. """ bots = load_all_bots(working_dir) ladder = Ladder.read(working_dir.ladder) # We need the result of every match to create the next ladder. For each match in each round robin, if a result # exist already, it will be parsed, if it doesn't exist, it will be played. # When all results have been found, the new ladder can be completed and saved. new_ladder = Ladder(ladder.bots) event_results = [] # playing_division_indices contains either even or odd indices. # If there is only one division always play that division (division 0, quantum). playing_division_indices = range( ladder.division_count())[int(odd_week) % 2::2] if ladder.division_count() > 1 else [0] # The divisions play in reverse order, so quantum/overclocked division plays last for div_index in playing_division_indices[::-1]: print( f'Starting round robin for the {Ladder.DIVISION_NAMES[div_index]} division' ) rr_bots = ladder.round_robin_participants(div_index) rr_matches = generate_round_robin_matches(rr_bots) rr_results = [] for match_participants in rr_matches: # Check if match has already been play, i.e. the result file already exist result_path = working_dir.get_match_result(div_index, match_participants[0], match_participants[1]) if result_path.exists(): # Found existing result try: print(f'Found existing result {result_path.name}') result = MatchResult.read(result_path) rr_results.append(result) except Exception as e: print( f'Error loading result {result_path.name}. Fix/delete the result and run script again.' ) raise e else: assert match_participants[ 0] in bots, f'{match_participants[0]} was not found in \'{working_dir.bots}\'' assert match_participants[ 1] in bots, f'{match_participants[1]} was not found in \'{working_dir.bots}\'' # Play the match print( f'Starting match: {match_participants[0]} vs {match_participants[1]}. Waiting for match to finish...' ) match_config = make_match_config(working_dir, bots[match_participants[0]], bots[match_participants[1]]) match = MatchExercise( name=f'{match_participants[0]} vs {match_participants[1]}', match_config=match_config, grader=MatchGrader(replay_monitor=ReplayMonitor( replay_preference=replay_preference), )) # Let overlay know which match we are about to start overlay_data = OverlayData( div_index, bots[match_participants[0]].config_path, bots[match_participants[1]].config_path) overlay_data.write(working_dir.overlay_interface) with setup_manager_context() as setup_manager: # Disable rendering by replacing renderer with a renderer that does nothing setup_manager.game_interface.renderer = FakeRenderer() # For loop, but should only run exactly once for exercise_result in run_playlist( [match], setup_manager=setup_manager): # Warn users if no replay was found if isinstance( exercise_result.grade, Fail ) and exercise_result.exercise.grader.replay_monitor.replay_id == None: print( f'WARNING: No replay was found for the match \'{match_participants[0]} vs {match_participants[1]}\'. Is Bakkesmod injected and \'Automatically save all replays\' enabled?' ) # Save result in file result = exercise_result.exercise.grader.match_result result.write(result_path) print( f'Match finished {result.blue_goals}-{result.orange_goals}. Saved result as {result_path}' ) rr_results.append(result) # Let the winner celebrate and the scoreboard show for a few seconds. # This sleep not required. time.sleep(8) print(f'{Ladder.DIVISION_NAMES[div_index]} division done') event_results.append(rr_results) # Find bots' overall score for the round robin overall_scores = [ CombinedScore.calc_score(bot, rr_results) for bot in rr_bots ] sorted_overall_scores = sorted(overall_scores)[::-1] print( f'Bots\' overall performance in {Ladder.DIVISION_NAMES[div_index]} division:' ) for score in sorted_overall_scores: print( f'> {score.bot}: goal_diff={score.goal_diff}, goals={score.goals}, shots={score.shots}, saves={score.saves}, points={score.points}' ) # Rearrange bots in division on the new ladder first_bot_index = new_ladder.division_size * div_index bots_to_rearrange = len(rr_bots) for i in range(bots_to_rearrange): new_ladder.bots[first_bot_index + i] = sorted_overall_scores[i].bot # Save new ladder Ladder.write(new_ladder, working_dir.new_ladder) print(f'Done. Saved new ladder as {working_dir.new_ladder.name}') # Remove overlay interface file now that we are done if working_dir.overlay_interface.exists(): working_dir.overlay_interface.unlink() return new_ladder
def run_league_play(working_dir: WorkingDir, run_strategy: RunStrategy, replay_preference: ReplayPreference, team_size: int, shutdowntime: int, stale_rematch_threshold: int = 0, half_robin: bool = False): """ Run a league play event by running round robins for half the divisions. When done, a new ladder file is created. :param stale_rematch_threshold: If a bot has won this number of matches in a row against a particular opponent and neither have had their code updated, we will consider it to be a stale rematch and skip future matches. If 0 is passed, we will not skip anything. :param half_robin: If true, we will split the division into an upper and lower round-robin, which reduces the number of matches required. """ bots = load_all_bots_versioned(working_dir) ladder = Ladder.read(working_dir.ladder) latest_bots = [ bot for bot in bots.values() if bot.bot_config.name in ladder.bots ] latest_bots.sort(key=lambda b: b.updated_date, reverse=True) print('Most recently updated bots:') for bot in latest_bots: print(f'{bot.updated_date.isoformat()} {bot.bot_config.name}') # We need the result of every match to create the next ladder. For each match in each round robin, if a result # exist already, it will be parsed, if it doesn't exist, it will be played. # When all results have been found, the new ladder can be completed and saved. new_ladder = Ladder(ladder.bots) event_results = [] # playing_division_indices contains either even or odd indices. # If there is only one division always play that division (division 0, quantum). playing_division_indices = ladder.playing_division_indices(run_strategy) # The divisions play in reverse order, so quantum/overclocked division plays last for div_index in playing_division_indices[::-1]: print( f'Starting round robin for the {Ladder.DIVISION_NAMES[div_index]} division' ) round_robin_ranges = get_round_robin_ranges(ladder, div_index, half_robin) for start_index, end_index in round_robin_ranges: rr_bots = ladder.bots[start_index:end_index + 1] rr_matches = generate_round_robin_matches(rr_bots) rr_results = [] for match_participants in rr_matches: # Check if match has already been played during THIS session. Maybe something crashed and we had to # restart autoleague, but we want to pick up where we left off. session_result_path = working_dir.get_match_result( div_index, match_participants[0], match_participants[1]) participant_1 = bots[match_participants[0]] participant_2 = bots[match_participants[1]] if session_result_path.exists(): print(f'Found existing result {session_result_path.name}') rr_results.append(MatchResult.read(session_result_path)) else: historical_result = get_stale_match_result( participant_1, participant_2, stale_rematch_threshold, working_dir, True) if historical_result is not None: rr_results.append(historical_result) # Don't write to result files at all, since this match didn't actually occur. overlay_data = OverlayData(div_index, participant_1, participant_2, new_ladder, bots, historical_result, rr_bots, rr_results) overlay_data.write(working_dir.overlay_interface) time.sleep( 8 ) # Show the overlay for a while. Not needed for any other reason. else: # Let overlay know which match we are about to start overlay_data = OverlayData(div_index, participant_1, participant_2, new_ladder, bots, None, rr_bots, rr_results) overlay_data.write(working_dir.overlay_interface) match_config = make_match_config( participant_1.bot_config, participant_2.bot_config, team_size) result = run_match(participant_1.bot_config.name, participant_2.bot_config.name, match_config, replay_preference) result.write(session_result_path) versioned_result_path = working_dir.get_version_specific_match_result( participant_1, participant_2) result.write(versioned_result_path) print( f'Match finished {result.blue_goals}-{result.orange_goals}. Saved result as ' f'{session_result_path} and also {versioned_result_path}' ) rr_results.append(result) # Let the winner celebrate and the scoreboard show for a few seconds. # This sleep not required. time.sleep(8) # Find bots' overall score for the round robin overall_scores = [ CombinedScore.calc_score(bot, rr_results) for bot in rr_bots ] sorted_overall_scores = sorted(overall_scores)[::-1] division_result_message = f'Bots\' overall round-robin performance ({Ladder.DIVISION_NAMES[div_index]} division):\n' for score in sorted_overall_scores: division_result_message += f'> {score.bot:<32}: wins={score.wins:>2}, goal_diff={score.goal_diff:>3}\n' print(division_result_message) overlay_data = OverlayData(div_index, None, None, new_ladder, bots, None, rr_bots, rr_results, division_result_message) overlay_data.write(working_dir.overlay_interface) # Rearrange bots in division on the new ladder first_bot_index = start_index bots_to_rearrange = len(rr_bots) for i in range(bots_to_rearrange): new_ladder.bots[first_bot_index + i] = sorted_overall_scores[i].bot event_results.append(rr_results) time.sleep(8) # Show the division overlay for a while. print(f'{Ladder.DIVISION_NAMES[div_index]} division done') # Save new ladder Ladder.write(new_ladder, working_dir.new_ladder) print(f'Done. Saved new ladder as {working_dir.new_ladder.name}') if shutdowntime != 0: import subprocess subprocess.call("shutdown.exe -s -t " + str(shutdowntime)) # Remove overlay interface file now that we are done if working_dir.overlay_interface.exists(): working_dir.overlay_interface.unlink() return new_ladder
def generate_leaderboard(working_dir: WorkingDir, run_strategy: RunStrategy, allow_extra: bool = True, background: bool = True): """ Created a leaderboard that shows differences between the old ladder and the new ladder. :param working_dir: The working directory :param run_strategy: The strategy for running the ladder that was used this week. :param extra: Whether to include the next 5 divisions. :param background: Whether to use a background for the leaderboard. :param make_clip: Whether to also make an mp4 clip. :param duration: Duration of the clip in seconds. :param frames_per_second: frames per second of the clip. """ assert working_dir.ladder.exists( ), f'\'{working_dir.ladder}\' does not exist.' if not working_dir.new_ladder.exists(): print(f'The new ladder has not been determined yet.') return old_ladder = Ladder.read(working_dir.ladder) new_ladder = Ladder.read(working_dir.new_ladder) new_bots, ranks_moved = ladder_differences(old_ladder, new_ladder) played = old_ladder.all_playing_bots(run_strategy) # --------------------------------------------------------------- # PARAMETERS FOR DRAWING: # Divisions. We only have color palettes configured for a certain number of them, so enforce a limit. divisions = Ladder.DIVISION_NAMES[:len(Symbols.palette)] extra = (allow_extra and len(new_ladder.bots) > 40) ''' Each division has the origin at the top left corner of their emblem. Offsets: title offsets determine where the division title is drawn relative to the emblem. bot offsets determine where bot names are drawn relative to the emblem. sym offsets determine where the symbol is placed relative to the bot name. Increments: div increments are how much to move the origin between each division. bot increment is how much to move down for each bot name. ''' # Start positions for drawing. start_x = 0 start_y = 0 # Division emblem offsets from the division name position. title_x_offset = 350 title_y_offset = 85 # Bot name offsets from the division name position. bot_x_offset = 200 bot_y_offset = 292 # Offsets for the symbols from the bot name position. sym_x_offset = 1300 sym_y_offset = 5 # Offsets for the symbols' description from the bot name position. sym_desc_x_offset = 1220 sym_desc_y_offset = 0 # Incremenets for x and y. div_x_incr = 1790 div_y_incr = 790 bot_y_incr = 140 # --------------------------------------------------------------- # DRAWING: # Opening image for drawing. if background: if extra: leaderboard = Image.open(LeaderboardPaths.leaderboard_extra_empty) else: leaderboard = Image.open(LeaderboardPaths.leaderboard_empty) else: if extra: leaderboard = Image.open( LeaderboardPaths.leaderboard_extra_no_background) else: leaderboard = Image.open( LeaderboardPaths.leaderboard_no_background) draw = ImageDraw.Draw(leaderboard) # Fonts for division titles and bot names. div_font = ImageFont.truetype(str(LeaderboardPaths.font_medium), 120) bot_font = ImageFont.truetype(str(LeaderboardPaths.font_medium), 80) # Bot name colour. bot_colour = (0, 0, 0) # For each divion, draw the division name, and each bot in the division. for i, div in enumerate(divisions): # Calculates position for the division. div_pos = (start_x + div_x_incr * (i // 5), start_y + div_y_incr * (i % 5)) # Draws the division emblem. try: # Opens the division emblem image. emblem = Image.open(LeaderboardPaths.emblems / f'{div}.png') # Pastes emblem onto image. leaderboard.paste(emblem, div_pos, emblem) except: # Sends warning message if it can't find the emblem. print(f'WARNING: Missing emblem for {div}.') # Draws the division title at an offset. title_pos = (div_pos[0] + title_x_offset, div_pos[1] + title_y_offset) draw.text(xy=title_pos, text=div, fill=Symbols.palette[div][0], font=div_font) # Loops through the four bots in the division and draws each. for ii, bot in enumerate(new_ladder.division(i)): # Calculates position for the bot name and draws it. bot_pos = (div_pos[0] + bot_x_offset, div_pos[1] + bot_y_offset + ii * bot_y_incr) draw.text(xy=bot_pos, text=bot, fill=bot_colour, font=bot_font) # Calculates symbol position. sym_pos = (bot_pos[0] + sym_x_offset, bot_pos[1] + sym_y_offset) sym_desc_pos = (bot_pos[0] + sym_desc_x_offset, bot_pos[1] + sym_desc_y_offset) sym_div_colors = Symbols.palette[div] # Pastes appropriate symbol if bot in new_bots: symbol = Image.open(LeaderboardPaths.symbols / f'{div}_new.png') leaderboard.paste(symbol, sym_pos, symbol) elif bot in played: # Insert symbol to show rank movement if ranks_moved[bot] > 0: symbol = Image.open(LeaderboardPaths.symbols / f'{div}_up.png') elif ranks_moved[bot] < 0: symbol = Image.open(LeaderboardPaths.symbols / f'{div}_down.png') else: symbol = Image.open(LeaderboardPaths.symbols / f'{div}_played.png') leaderboard.paste(symbol, sym_pos, symbol) if ranks_moved[bot] != 0: move_txt = f'{abs(ranks_moved[bot])}' w, h = draw.textsize(move_txt, font=bot_font) color = sym_div_colors[Symbols.DARK if ranks_moved[bot] < 0 else Symbols.LIGHT] draw.text(xy=(sym_desc_pos[0] - w / 2, sym_desc_pos[1]), text=move_txt, fill=color, font=bot_font) # Saves the image. leaderboard.save(working_dir.leaderboard, 'PNG') print('Successfully generated leaderboard.')
def list_results(working_dir: WorkingDir, run_strategy: RunStrategy, half_robin: bool): ladder = Ladder.read(working_dir.ladder) playing_division_indices = ladder.playing_division_indices(run_strategy) if len(ladder.bots) < 2: print(f'Not enough bots on the ladder to play any matches') return # Write overview to file first, then print content of the file with open(working_dir.results_overview, 'w') as f: f.write(f'Matches:\n') if run_strategy == RunStrategy.ROLLING or half_robin: # The ladder was dynamic, so we can't print divisions neatly. # Just print everything in one blob. match_results = [ MatchResult.read(path) for path in working_dir.match_results.glob('*') ] for result in match_results: result_str = f' (result: {result.blue_goals}-{result.orange_goals})' f.write(f'{result.blue} vs {result.orange}{result_str}\n') write_overall_scores(f, ladder.bots, match_results) else: # The divisions play in reverse order, but we don't print them that way. for div_index in playing_division_indices: f.write( f'--- {Ladder.DIVISION_NAMES[div_index]} division ---\n') rr_bots = ladder.round_robin_participants(div_index) rr_matches = generate_round_robin_matches(rr_bots) for match_participants in rr_matches: result_str = '' result_path = working_dir.get_match_result( div_index, match_participants[0], match_participants[1]) if result_path.exists(): result = MatchResult.read(result_path) result_str = f' (result: {result.blue_goals}-{result.orange_goals})' f.write( f'{match_participants[0]} vs {match_participants[1]}{result_str}\n' ) f.write('\n') # Print a table with all the combined scores for div_index in playing_division_indices: rr_bots = ladder.round_robin_participants(div_index) rr_matches = generate_round_robin_matches(rr_bots) rr_results = [] for match_participants in rr_matches: result_path = working_dir.get_match_result( div_index, match_participants[0], match_participants[1]) if result_path.exists(): rr_results.append(MatchResult.read(result_path)) write_overall_scores(f, rr_bots, rr_results, div_index) f.write( f'--------------------------------+------+----------+-------+-------+-------+-------+\n' ) # Results have been writen to the file, now display the content with open(working_dir.results_overview, 'r') as f: print(f.read()) print(f'Result overview was written to \'{working_dir.results_overview}\'')