Example #1
0
    def update_player(self, player_name: str, win: bool):

        if not isinstance(win, bool):
            raise OneHeadException("Win parameter must be a valid bool.")

        if not isinstance(player_name, str):
            raise OneHeadException("Player Name not a valid string.")

        if self.player_exists(player_name) is False:
            raise OneHeadException(f"{player_name} cannot be found.")

        if win:
            self.db.update_item(
                Key={"name": player_name},
                UpdateExpression="set win = win + :val, win_streak = win_streak + :val, loss_streak = :zero",
                ExpressionAttributeValues={":val": decimal.Decimal(1)},
                ReturnValues="UPDATED_NEW",
            )
        else:
            self.db.update_item(
                Key={"name": player_name},
                UpdateExpression="set loss = loss + :val, win_streak = :zero, loss_streak = loss_streak + :val",
                ExpressionAttributeValues={":val": decimal.Decimal(1), ":zero": decimal.Decimal(0)},
                ReturnValues="UPDATED_NEW",
            )
Example #2
0
    def _calculate_balance(self, adjusted=False):
        """


        :param adjusted: Specifies whether to use the 'adjusted_mmr' field to balance or just the 'mmr' field.
        :return: Returns a matchup of two, five-man teams that are evenly (or as close to evenly) matched based on
        a rating value associated with each player.
        """

        profiles = self._get_profiles()
        profile_count = len(profiles)
        if profile_count != 10:
            raise OneHeadException(
                f"Error: Only {profile_count} profiles could be found in database."
            )

        if adjusted is False:
            mmr_field_name = "mmr"
        else:
            mmr_field_name = "adjusted_mmr"
            OneHeadStats.calculate_rating(profiles)
            OneHeadStats.calculate_adjusted_mmr(profiles)

        all_5_man_lineups = list(itertools.combinations(
            profiles, 5))  # Calculate all possible 5 man lineups.
        all_5v5_matchups = list(itertools.combinations(
            all_5_man_lineups, 2))  # Calculate all possible 5v5 matchups.

        unique_combinations = self._calculate_unique_team_combinations(
            all_5v5_matchups
        )  # Calculate all valid 5v5 matchups where players are unique to either Team 1 or Team 2.

        if not unique_combinations:
            raise OneHeadException(
                "No valid matchups could be calculated. Possible Duplicate Player Name."
            )

        rating_differences = self._calculate_rating_differences(
            unique_combinations, mmr_field_name
        )  # Calculate the net rating difference between each 5v5 matchup.

        rating_differences_mapping = dict(
            enumerate(rating_differences,
                      start=0))  # Map the net rating differences to a key.
        rating_differences_mapping = {
            k: v
            for k, v in sorted(rating_differences_mapping.items(),
                               key=lambda item: item[1])
        }  # Sort by ascending net rating difference.

        indices = list(
            rating_differences_mapping.keys()
        )[:
          10]  # Obtain the indices for the top 10 closest net rating matchups.
        balanced_teams = unique_combinations[random.choice(
            indices
        )]  # Pick a random matchup from the top 10 closest net rating matchups.

        return balanced_teams
Example #3
0
    def remove_player(self, player_name: str):

        if not isinstance(player_name, str):
            raise OneHeadException("Player name not a valid string.")

        if self.player_exists(player_name):
            try:
                self.db.delete_item(Key={"name": player_name})
            except ClientError as e:
                raise OneHeadException(e)
Example #4
0
    async def move_discord_channels(self, ctx: commands.Context):
        """
        Move players to IHL Team Channels.

        :param ctx: Discord Context
        """

        channel_count = len(self.ihl_discord_channels)
        if channel_count != 2:
            raise OneHeadException(
                f"Expected 2 Discord Channels, Identified {channel_count}.")

        t1_names, t2_names = OneHeadCommon.get_player_names(self.t1, self.t2)
        self._get_discord_members(ctx, t1_names, t2_names)

        await ctx.send("Moving Players to IHL Discord Channels...")

        t1_channel, t2_channel = self.ihl_discord_channels

        for member in self.t1_discord_members:
            try:
                await member.move_to(t1_channel)
            except discord.errors.HTTPException:
                pass

        for member in self.t2_discord_members:
            try:
                await member.move_to(t2_channel)
            except discord.errors.HTTPException:
                pass
Example #5
0
    def __init__(self, bot: commands.Bot, token: str):

        self.game_in_progress = False
        self.t1 = []  # type: list[dict]
        self.t2 = []  # type: list[dict]

        self.bot = bot
        self.token = token

        self.config = OneHeadCommon.load_config()
        self.database = bot.get_cog("OneHeadDB")
        self.scoreboard = bot.get_cog("OneHeadScoreBoard")
        self.pre_game = bot.get_cog("OneHeadPreGame")
        self.team_balance = bot.get_cog("OneHeadBalance")
        self.captains_mode = bot.get_cog("OneHeadCaptainsMode")
        self.channels = bot.get_cog("OneHeadChannels")
        self.registration = bot.get_cog("OneHeadRegistration")

        if None in (
                self.database,
                self.scoreboard,
                self.pre_game,
                self.team_balance,
                self.captains_mode,
                self.channels,
                self.registration,
        ):
            raise OneHeadException("Unable to find cog(s)")
Example #6
0
    def add_player(self, player_name: str, mmr: int):

        if not isinstance(player_name, str):
            raise OneHeadException("Player Name not a valid string.")

        if self.player_exists(player_name) is False:
            self.db.put_item(
                Item={"name": player_name, "win": 0, "loss": 0, "mmr": mmr, "win_streak": 0, "loss_streak": 0}
            )
Example #7
0
    def lookup_player(self, player_name: str) -> dict:

        try:
            response = self.db.get_item(Key={"name": player_name})
        except ClientError as e:
            raise OneHeadException(e)
        else:
            item = response.get("Item")
            player = json.dumps(item, indent=4, cls=DecimalEncoder)
            player = json.loads(player)
            return player
Example #8
0
    def player_exists(self, player_name: str) -> bool:

        try:
            response = self.db.get_item(Key={"name": player_name})
        except ClientError as e:
            raise OneHeadException(e)
        else:
            if response.get("Item"):
                return True
            else:
                return False
Example #9
0
    def retrieve_table(self) -> list[dict]:

        try:
            response = self.db.scan()
        except ClientError as e:
            raise OneHeadException(e)
        else:
            table = response.get("Items")
            table = json.dumps(table, indent=4, cls=DecimalEncoder)
            table = json.loads(table)
            return table
Example #10
0
    async def register(self, ctx: commands.Context, mmr: str):
        """
        Register yourself to the IHL by typing !register <your mmr>.
        """

        name = ctx.author.display_name

        try:
            mmr = int(mmr)
        except ValueError:
            raise OneHeadException(f"{mmr} is not a valid integer.")

        if mmr < 1000:
            await ctx.send(f"{mmr} MMR is too low, must be greater or equal to 1000.")
            return

        if self.database.player_exists(name) is False:
            self.database.add_player(ctx.author.display_name, mmr)
            await ctx.send(f"{name} successfully registered.")
        else:
            await ctx.send(f"{name} is already registered.")
Example #11
0
    def _get_scoreboard(self) -> str:
        """
        Returns current scoreboard for the IHL.

        :return: Scoreboard string to be displayed in Discord chat.
        """

        scoreboard = self.db.retrieve_table()

        if not scoreboard:
            raise OneHeadException("No users found in database.")

        OneHeadStats.calculate_win_percentage(scoreboard)
        OneHeadStats.calculate_rating(scoreboard)

        scoreboard_sorted_rows = self._calculate_positions(
            scoreboard, "rating")
        scoreboard_sorted_rows_and_columns = self._sort_scoreboard_key_order(
            scoreboard_sorted_rows)
        sorted_scoreboard = tabulate(scoreboard_sorted_rows_and_columns,
                                     headers="keys",
                                     tablefmt="simple")

        return sorted_scoreboard
Example #12
0
    async def picking_phase(self, ctx: commands.Context):
        """
        Initiates the picking phase where Captains can select which players they want to join their team
        using the '!pick' command. Each Captain has 30 seconds to select a player, if they fail to do this,
        a player will be randomly chosen for them.

        :param ctx: Discord context.
        :return: The teams selected by each Captain.
        :type: tuple of lists, each list contains 5 dicts corresponding to each player profile.
        """

        self.remaining_players = self.pre_game.signups.copy()

        if not self.captain_1 or not self.captain_2:
            raise OneHeadException("Captains have not been selected.")

        for captain in (self.captain_1, self.captain_2):
            idx = self.remaining_players.index(captain)
            player = self.remaining_players.pop(idx)
            if captain == self.captain_1:
                self.team_1.append(player)
            else:
                self.team_2.append(player)

        self.pick_phase_in_progress = True
        await ctx.send("You have 30 seconds to pick a player each turn.")

        captain_round_order = (
            self.captain_1,
            self.captain_2,
            self.captain_2,
            self.captain_1,
            self.captain_1,
            self.captain_2,
            self.captain_2,
            self.captain_1,
        )

        while self.remaining_players:
            for round_number, captain in enumerate(captain_round_order):
                if captain == self.captain_1:
                    self.captain_1_turn = True
                    self.captain_2_turn = False
                else:
                    self.captain_2_turn = True
                    self.captain_1_turn = False
                await ctx.send(f"{captain}'s turn to pick.")
                await ctx.send(
                    f"**Remaining Player Pool** : ```{self.remaining_players}```"
                )
                self.future = self.event_loop.create_future()

                if round_number == 7:
                    last_player = self.remaining_players.pop(0)
                    self.team_1.append(last_player)
                    await ctx.send(
                        f"{last_player} has been automatically added to Team 2 as {last_player} was the last remaining player."
                    )
                else:
                    try:
                        await asyncio.wait_for(self.future, timeout=30)
                    except TimeoutError:
                        idx = self.remaining_players.index(
                            random.choice(self.remaining_players))
                        pick = self.remaining_players.pop(idx)
                        if captain == self.captain_1:
                            self.team_1.append(pick)
                        else:
                            self.team_2.append(pick)
                        await ctx.send(
                            f"{captain} has randomed {pick} to join their team."
                        )

        self.pick_phase_in_progress = False
        self.captain_1_turn = False
        self.captain_2_turn = False

        t1 = []
        t2 = []

        for team in (self.team_1, self.team_2):
            for player in team:
                profile = self.database.lookup_player(player)
                if team == self.team_1:
                    t1.append(profile)
                else:
                    t2.append(profile)

        return t1, t2