Example #1
0
    async def games(self, ctx: commands.Context):
        """
        Creates 100 games in the database with random results
        """
        game_queue.reset_queue()

        for game_count in range(100):

            # We add the context creator as well
            game_queue.add_player(
                ctx.author.id, roles_list[4], ctx.channel.id, ctx.guild.id, name=ctx.author.display_name
            )

            # We put 15 people in the queue, but only the first ones should get picked
            for i in range(0, 15):
                game_queue.add_player(i, roles_list[i % 5], ctx.channel.id, ctx.guild.id, name=str(i))

            game = matchmaking_logic.find_best_game(GameQueue(ctx.channel.id))

            with session_scope() as session:
                session.add(game)
                winner = game.player_ids_list[int(random.random() * 10)]

            game_queue.start_ready_check([i for i in range(0, 9)] + [ctx.author.id], ctx.channel.id, 0)
            game_queue.validate_ready_check(0)

            matchmaking_logic.score_game_from_winning_player(player_id=winner, server_id=ctx.guild.id)

            await ranking_channel_handler.update_ranking_channels(self.bot, ctx.guild.id)

        await ctx.send("100 games have been created in the database")
        await queue_channel_handler.update_queue_channels(bot=self.bot, server_id=ctx.guild.id)
def test_queue_full():
    game_queue.reset_queue()

    for player_id in range(0, 10):
        game_queue.add_player(player_id,
                              roles_list[player_id % 5],
                              0,
                              0,
                              name=str(player_id))

    assert len(GameQueue(0)) == 10

    # We queue our player 0 in channel 1, which he should be allowed to do
    game_queue.add_player(0, roles_list[0], 1, 0, name="0")
    assert len(GameQueue(1)) == 1

    # We queue our player 0 for every other role in channel 0
    for role in roles_list:
        game_queue.add_player(0, role, 0, 0, name="0")

    assert len(GameQueue(
        0)) == 14  # Every role should count as a different QueuePlayer

    # Assuming our matchmaking logic found a good game (id 0)
    game_queue.start_ready_check(list(range(0, 10)), 0, 0)
    assert len(
        GameQueue(0)
    ) == 0  # Player 0 should not be counted in queue for any role anymore

    # Our player 0 in channel 1 should not be counted in queue either
    assert len(GameQueue(1)) == 0

    # We check that our player 0 is not allowed to queue in other channels
    with pytest.raises(game_queue.PlayerInReadyCheck):
        game_queue.add_player(0, roles_list[0], 2, 0, name="0")

    # We cancel the ready check and drop player 0 from all queues on the server
    game_queue.cancel_ready_check(ready_check_id=0,
                                  ids_to_drop=[0],
                                  server_id=0)

    assert len(GameQueue(0)) == 9

    # We check player 0 got dropped from queue 1 too
    assert len(GameQueue(1)) == 0

    # We queue again, with player 10
    game_queue.add_player(10, roles_list[0], 0, 0, name="10")

    # We start and validate the ready check (message id 1)
    game_queue.start_ready_check(list(range(1, 11)), 0, 1)
    game_queue.validate_ready_check(1)

    # We verify that both queues are empty
    assert len(GameQueue(0)) == 0
    assert len(GameQueue(1)) == 0
def test_duo_matchmaking():
    game_queue.reset_queue()

    # Playing 100 games with random outcomes and making sure 0 and 9 are always on the same team
    for game_count in range(100):

        # We queue for everything except the red support
        for player_id in range(0, 9):
            game_queue.add_player(player_id,
                                  roles_list[player_id % 5],
                                  0,
                                  0,
                                  name=str(player_id))

        # We add the last player as duo with player 0
        game_queue.add_duo(
            0,
            "TOP",
            9,
            "SUP",
            0,
            0,
            second_player_name="9",
            first_player_name="0",
        )

        game = find_best_game(GameQueue(0))

        player_0 = next(p for p in game.participants.values()
                        if p.player_id == 0)
        player_9 = next(p for p in game.participants.values()
                        if p.player_id == 9)

        assert player_0.side == player_9.side

        with session_scope() as session:
            session.add(game)
            winner = game.player_ids_list[int(random.random() * 10)]

        game_queue.start_ready_check([i for i in range(0, 10)], 0, 0)
        game_queue.validate_ready_check(0)

        score_game_from_winning_player(player_id=winner, server_id=0)
Example #4
0
    async def game(self, ctx: commands.Context):
        """
        Creating a fake game in the database with players 0 to 8 and the ctx author
        """
        # We reset the queue
        # We put 9 people in the queue
        for i in range(0, 9):
            game_queue.add_player(i, roles_list[i % 5], ctx.channel.id, ctx.guild.id, name=str(i))

        game_queue.add_player(
            ctx.author.id, roles_list[4], ctx.channel.id, ctx.guild.id, name=ctx.author.display_name
        )

        game = matchmaking_logic.find_best_game(GameQueue(ctx.channel.id))

        with session_scope() as session:
            session.add(game)

        msg = await ctx.send("The queue has been reset, filled again, and a game created (with no winner)")

        game_queue.start_ready_check([i for i in range(0, 9)] + [ctx.author.id], ctx.channel.id, msg.id)
        game_queue.validate_ready_check(msg.id)
        await queue_channel_handler.update_queue_channels(bot=self.bot, server_id=ctx.guild.id)
Example #5
0
    async def run_matchmaking_logic(
        self,
        ctx: commands.Context,
    ):
        """
        Runs the matchmaking logic in the channel defined by the context

        Should only be called inside guilds
        """
        queue = game_queue.GameQueue(ctx.channel.id)

        game = matchmaking_logic.find_best_game(queue)

        if not game:
            return

        elif game and game.matchmaking_score < 0.2:
            embed = game.get_embed(embed_type="GAME_FOUND",
                                   validated_players=[],
                                   bot=self.bot)

            # We notify the players and send the message
            ready_check_message = await ctx.send(content=game.players_ping,
                                                 embed=embed,
                                                 delete_after=60 * 15)

            # We mark the ready check as ongoing (which will be used to the queue)
            game_queue.start_ready_check(
                player_ids=game.player_ids_list,
                channel_id=ctx.channel.id,
                ready_check_message_id=ready_check_message.id,
            )

            # We update the queue in all channels
            await queue_channel_handler.update_queue_channels(
                bot=self.bot, server_id=ctx.guild.id)

            # And then we wait for the validation
            try:
                ready, players_to_drop = await checkmark_validation(
                    bot=self.bot,
                    message=ready_check_message,
                    validating_players_ids=game.player_ids_list,
                    validation_threshold=10,
                    game=game,
                )

            # We catch every error here to make sure it does not become blocking
            except Exception as e:
                self.bot.logger.error(e)
                game_queue.cancel_ready_check(
                    ready_check_id=ready_check_message.id,
                    ids_to_drop=game.player_ids_list,
                    server_id=ctx.guild.id,
                )
                await ctx.send(
                    "There was a bug with the ready-check message, all players have been dropped from queue\n"
                    "Please queue again to restart the process")

                await queue_channel_handler.update_queue_channels(
                    bot=self.bot, server_id=ctx.guild.id)

                return

            if ready is True:
                # We drop all 10 players from the queue
                game_queue.validate_ready_check(ready_check_message.id)

                # We commit the game to the database (without a winner)
                with session_scope() as session:
                    session.expire_on_commit = False
                    game = session.merge(game)  # This gets us the game ID

                queue_channel_handler.mark_queue_related_message(
                    await ctx.send(embed=game.get_embed("GAME_ACCEPTED"), ))

            elif ready is False:
                # We remove the player who cancelled
                game_queue.cancel_ready_check(
                    ready_check_id=ready_check_message.id,
                    ids_to_drop=players_to_drop,
                    channel_id=ctx.channel.id,
                )

                await ctx.send(
                    f"A player cancelled the game and was removed from the queue\n"
                    f"All other players have been put back in the queue", )

                # We restart the matchmaking logic
                await self.run_matchmaking_logic(ctx)

            elif ready is None:
                # We remove the timed out players from *all* channels (hence giving server id)
                game_queue.cancel_ready_check(
                    ready_check_id=ready_check_message.id,
                    ids_to_drop=players_to_drop,
                    server_id=ctx.guild.id,
                )

                await ctx.send(
                    "The check timed out and players who did not answer have been dropped from all queues",
                )

                # We restart the matchmaking logic
                await self.run_matchmaking_logic(ctx)

        elif game and game.matchmaking_score >= 0.2:
            # One side has over 70% predicted winrate, we do not start anything
            await ctx.send(
                f"The best match found had a side with a {(.5 + game.matchmaking_score)*100:.1f}%"
                f" predicted winrate and was not started")
Example #6
0
    async def run_matchmaking_logic(
        self,
        ctx: commands.Context,
    ):
        """
        Runs the matchmaking logic in the channel defined by the context

        Should only be called inside guilds
        """
        queue = game_queue.GameQueue(ctx.channel.id)

        game = matchmaking_logic.find_best_game(queue)

        if not game:
            return

        elif game and game.matchmaking_score < 0.2:
            embed = Embed(
                title="📢 Game found 📢",
                description=
                f"Blue side expected winrate is {game.blue_expected_winrate * 100:.1f}%\n"
                "If you are ready to play, press ✅\n"
                "If you cannot play, press �",
            )

            embed = game.add_game_field(embed, [])

            # We notify the players and send the message
            ready_check_message = await ctx.send(
                content=
                f"||{' '.join([f'<@{discord_id}>' for discord_id in game.player_ids_list])}||",
                embed=embed,
            )

            # Because it still takes some time, we *directly* add it to the *no delete* list
            # That’s dirty and should likely be handled in a better way (maybe by *not* using purge
            # and choosing what to delete instead, but it also has its issues)
            # TODO HIGH PRIO Think about saving a "messages to not delete list" in the queue handler memory and
            #  use it in the cog listener, and automatically delete any other one after 5s? should work better (less bugs)

            queue_channel_handler.mark_queue_related_message(
                ready_check_message)

            # We mark the ready check as ongoing (which will be used to the queue)
            game_queue.start_ready_check(
                player_ids=game.player_ids_list,
                channel_id=ctx.channel.id,
                ready_check_message_id=ready_check_message.id,
            )

            # We update the queue in all channels
            await queue_channel_handler.update_server_queues(
                bot=self.bot, server_id=ctx.guild.id)

            # And then we wait for the validation
            ready, players_to_drop = await checkmark_validation(
                bot=self.bot,
                message=ready_check_message,
                validating_players_ids=game.player_ids_list,
                validation_threshold=10,
                timeout=3 * 60,
                game=game,
            )

            if ready is True:
                # We drop all 10 players from the queue
                game_queue.validate_ready_check(ready_check_message.id)

                # We commit the game to the database (without a winner)
                with session_scope() as session:
                    session.add(game)

                    embed = Embed(
                        title="📢 Game accepted 📢",
                        description=
                        f"Game {game.id} has been validated and added to the database\n"
                        f"Once the game has been played, one of the winners can score it with `!won`\n"
                        f"If you wish to cancel the game, use `!cancel`",
                    )

                    embed = game.add_game_field(embed)

                    queue_channel_handler.mark_queue_related_message(
                        await ctx.send(embed=embed, ))

            elif ready is False:
                # We remove the player who cancelled
                game_queue.cancel_ready_check(
                    ready_check_id=ready_check_message.id,
                    ids_to_drop=players_to_drop,
                    channel_id=ctx.channel.id,
                )

                queue_channel_handler.mark_queue_related_message(await ctx.send(
                    f"A player cancelled the game and was removed from the queue\n"
                    f"All other players have been put back in the queue"))

                # We restart the matchmaking logic
                await self.run_matchmaking_logic(ctx)

            elif ready is None:
                # We remove the timed out players from *all* channels (hence giving server id)
                game_queue.cancel_ready_check(
                    ready_check_id=ready_check_message.id,
                    ids_to_drop=players_to_drop,
                    server_id=ctx.guild.id,
                )

                queue_channel_handler.mark_queue_related_message(await ctx.send(
                    "The check timed out and players who did not answer have been dropped from all queues"
                ))

                # We restart the matchmaking logic
                await self.run_matchmaking_logic(ctx)

        elif game and game.matchmaking_score >= 0.2:
            # One side has over 70% predicted winrate, we do not start anything
            await ctx.send(
                f"The best match found had a side with a {(.5 + game.matchmaking_score)*100:.1f}%"
                f" predicted winrate and was not started")