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)
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)
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")
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")