Example #1
0
def main():
    arguments = docopt(__doc__, version=__version__)
    settings = PersistentSettings.load()

    if arguments['setup']:
        working_dir = Path(arguments['<working_dir>'])
        working_dir.mkdir(exist_ok=True, parents=True)
        WorkingDir(working_dir)   # Creates relevant directories and files
        settings.working_dir_raw = str(working_dir)
        settings.save()
        print(f'Working directory successfully set to \'{working_dir}\'')

    else:
        # Following commands require a working dir. Make sure it is set.
        if settings.working_dir_raw is None:
            print('No working directory set, use \'autoleagueplay setup <working_dir>\'')
            sys.exit(0)

        working_dir = WorkingDir(Path(settings.working_dir_raw))

        if arguments['odd'] or arguments['even']:

            replay_preference = ReplayPreference(arguments['--replays'])

            if arguments['--results']:
                list_matches(working_dir, arguments['odd'], True)
            elif arguments['--list']:
                list_matches(working_dir, arguments['odd'], False)
            elif arguments['--test']:
                check_bot_folder(working_dir, arguments['odd'])
            else:
                run_league_play(working_dir, arguments['odd'], replay_preference)

        elif arguments['test']:
            check_bot_folder(working_dir)

        elif arguments['fetch']:
            week_num = int(arguments['<week_num>'])
            if week_num < 0:
                print(f'Week number must be a positive integer.')
                sys.exit(1)

            ladder = fetch_ladder_from_sheets(week_num)
            ladder.write(working_dir.ladder)

            print(f'Successfully fetched week {week_num} to \'{working_dir.ladder}\':')
            for bot in ladder.bots:
                print(bot)

        else:
            raise NotImplementedError()
Example #2
0
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}'
            )
Example #3
0
def load_all_bots(working_dir: WorkingDir) -> Mapping[str, BotConfigBundle]:
    bots = dict(working_dir.get_bots())

    psyonix_allstar, psyonix_pro, psyonix_rookie = load_psyonix_bots()
    bots[psyonix_allstar.name] = psyonix_allstar
    bots[psyonix_pro.name] = psyonix_pro
    bots[psyonix_rookie.name] = psyonix_rookie

    return bots
Example #4
0
def load_all_bots(working_dir: WorkingDir) -> Mapping[str, BotConfigBundle]:
    bots = dict(working_dir.get_bots())

    # Psyonix bots
    psyonix_allstar = get_bot_config_bundle(PackageFiles.psyonix_allstar)
    psyonix_pro = get_bot_config_bundle(PackageFiles.psyonix_pro)
    psyonix_rookie = get_bot_config_bundle(PackageFiles.psyonix_rookie)
    bots[psyonix_allstar.name] = psyonix_allstar
    bots[psyonix_pro.name] = psyonix_pro
    bots[psyonix_rookie.name] = psyonix_rookie
    # Skill values for later. This way the user can rename the Psyonix bots by changing the config files, but we still
    # have their correct skill
    psyonix_bots[psyonix_allstar.name] = 1.0
    psyonix_bots[psyonix_pro.name] = 0.5
    psyonix_bots[psyonix_rookie.name] = 0.0

    return bots
Example #5
0
def main():
    arguments = docopt(__doc__, version=__version__)

    if arguments['odd'] or arguments['even'] or arguments['bubble']:

        ladder_path = Path(arguments['<ladder>'])
        if not ladder_path.exists():
            print(f'\'{ladder_path}\' does not exist.')
            sys.exit(1)

        working_dir = WorkingDir(ladder_path)

        replay_preference = ReplayPreference(arguments['--replays'])
        team_size = int(arguments['--teamsize'])

        if arguments['--results']:
            list_matches(working_dir, arguments['odd'], True)
        elif arguments['--list']:
            list_matches(working_dir, arguments['odd'], False)
        elif arguments['bubble']:
            run_bubble_sort(working_dir, team_size, replay_preference)
        else:
            run_league_play(working_dir, arguments['odd'], replay_preference,
                            team_size)

    elif arguments['fetch']:
        week_num = int(arguments['<week_num>'])
        if week_num < 0:
            print(f'Week number must be a positive integer.')
            sys.exit(1)

        league_dir = Path(arguments['<league_dir>'])
        if not league_dir.is_dir():
            league_dir = league_dir.parent
        league_dir.mkdir(exist_ok=True)
        ladder_path = league_dir / 'ladder.txt'

        ladder = fetch_ladder_from_sheets(week_num)
        ladder.write(ladder_path)

        print(f'Successfully fetched week {week_num} to \'{ladder_path}\'')
    else:
        raise NotImplementedError()
def get_stale_match_result(bot1: VersionedBot,
                           bot2: VersionedBot,
                           stale_rematch_threshold: int,
                           working_dir: WorkingDir,
                           print_debug: bool = False):
    if stale_rematch_threshold > 0:
        match_history = MatchHistory(
            working_dir.get_version_specific_match_files(
                bot1.get_key(), bot2.get_key()))
        if not match_history.is_empty():
            streak = match_history.get_current_streak_length()
            if streak >= stale_rematch_threshold:
                if print_debug:
                    print(
                        f'Found stale rematch between {bot1.bot_config.name} and {bot2.bot_config.name}. '
                        f'{match_history.get_latest_result().winner} has won {streak} times in a row.'
                    )
                return match_history.get_latest_result()
    return None
Example #7
0
def main():
    arguments = docopt(__doc__, version=__version__)

    ladder_path = Path(arguments['<ladder>'])
    if not ladder_path.exists():
        print(f'\'{ladder_path}\' does not exist.')
        sys.exit(1)

    working_dir = WorkingDir(ladder_path)

    replay_preference = ReplayPreference(arguments['--replays'])

    if arguments['odd'] or arguments['even']:
        if arguments['--results']:
            list_matches(working_dir, arguments['odd'], True)
        elif arguments['--list']:
            list_matches(working_dir, arguments['odd'], False)
        else:
            run_league_play(working_dir, arguments['odd'], replay_preference)
    else:
        raise NotImplementedError()
def parse_results_and_write_files(working_dir: WorkingDir, results_file: Path,
                                  fallback_time: datetime):
    """
    If you have the output of the list_results function as a text document, you can use this function to parse it
    out into MatchResult objects, and write those results with versioned file names. If a bot cannot be found in the
    working_dir, we will use the fallback_time when generating the versioned file name.
    """
    results = parse_results(working_dir._working_dir / results_file)
    bots = load_all_bots_versioned(working_dir)
    for result in results:

        blue_time = fallback_time
        if result.blue in bots:
            blue_time = bots[result.blue].updated_date

        orange_time = fallback_time
        if result.orange in bots:
            orange_time = bots[result.orange].updated_date

        result_path = working_dir.get_version_specific_match_result_from_times(
            result.blue, blue_time, result.orange, orange_time)

        print(f'Writing result to {result_path}')
        result.write(result_path)
Example #9
0
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 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}\'')
Example #12
0
def main():
    arguments = docopt(__doc__, version=__version__)
    settings = PersistentSettings.load()

    if arguments['setup']:
        working_dir = Path(arguments['<working_dir>'])
        working_dir.mkdir(exist_ok=True, parents=True)
        WorkingDir(working_dir)   # Creates relevant directories and files
        settings.working_dir_raw = f'{working_dir}'
        settings.save()
        print(f'Working directory successfully set to \'{working_dir}\'')

    else:
        # Following commands require a working dir. Make sure it is set.
        if settings.working_dir_raw is None:
            print('No working directory set, use \'autoleagueplay setup <working_dir>\'')
            sys.exit(0)

        working_dir = WorkingDir(Path(settings.working_dir_raw))

        stale_rematch_threshold = 0
        if arguments['--stale-rematch-threshold']:
            stale_rematch_threshold = int(arguments['--stale-rematch-threshold'])

        run_strategy = None
        if arguments['odd']:
            run_strategy = RunStrategy.ODD
        elif arguments['even']:
            run_strategy = RunStrategy.EVEN
        elif arguments['rolling']:
            run_strategy = RunStrategy.ROLLING

        half_robin = False
        if arguments['--half-robin']:
            half_robin = True

        if arguments['leaderboard']:
            if run_strategy is not None:
                generate_leaderboard(working_dir, run_strategy, not arguments['--top-only'])
            elif arguments['clip']:
                generate_leaderboard_clip(working_dir)
            elif arguments['symbols']:
                generate_symbols()
            elif arguments['legend']:
                generate_legend(working_dir)
            else:
                raise NotImplementedError()

        elif arguments['run']:

            replay_preference = ReplayPreference(arguments['--replays'])
            team_size = int(arguments['--teamsize'])
            shutdown_time = int(arguments['--autoshutdown'])

            if not arguments['--ignore-missing']:
                all_present = check_bot_folder(working_dir, run_strategy)
                if all_present:
                    run_league_play(working_dir, run_strategy, replay_preference, team_size, shutdown_time,
                                    stale_rematch_threshold, half_robin)
            else:
                run_league_play(working_dir, run_strategy, replay_preference, team_size, shutdown_time,
                                stale_rematch_threshold, half_robin)

        elif arguments['bubble']:

            replay_preference = ReplayPreference(arguments['--replays'])
            team_size = int(arguments['--teamsize'])

            run_bubble_sort(working_dir, team_size, replay_preference)

        elif arguments['list']:
            list_matches(working_dir, run_strategy, stale_rematch_threshold, half_robin)

        elif arguments['results']:
            list_results(working_dir, run_strategy, half_robin)

        elif arguments['check']:
            check_bot_folder(working_dir)

        elif arguments['test']:
            test_all_bots(working_dir)

        elif arguments['fetch']:
            season = int(arguments['<season_num>'])
            week_num = int(arguments['<week_num>'])

            ladder = fetch_ladder_from_sheets(season, week_num)
            ladder.write(working_dir.ladder)

            print(f'Successfully fetched season {season} week {week_num} to \'{working_dir.ladder}\':')
            for bot in ladder.bots:
                print(bot)

        elif arguments['results-to-version-files']:
            results_file = arguments['<results_file>']
            parse_results_and_write_files(working_dir, working_dir._working_dir / results_file, DEFAULT_TIMESTAMP)

        elif arguments['unzip']:
            unzip_all_bots(working_dir)

        else:
            raise NotImplementedError()