Esempio n. 1
0
        private async Task <IRole> AssignDirectorRoleAsync(ITournamentState state, IDictionary <ulong, IGuildUser> users)
        {
            this.Logger.Debug("Assigning director roles to {ids}", state.DirectorIds);
            IRole role = await this.Context.Guild.CreateRoleAsync(
                DirectorRoleName, permissions : PrivilegedGuildPermissions, color : Color.Gold);

            List <Task> assignRoleTasks = new List <Task>();

            foreach (ulong directorId in state.DirectorIds)
            {
                if (users.TryGetValue(directorId, out IGuildUser user))
                {
                    assignRoleTasks.Add(user.AddRoleAsync(role, RequestOptionsSettings.Default));
                }
                else
                {
                    this.Logger.Warning("Could not find director {id}", directorId);
                }
            }

            await Task.WhenAll(assignRoleTasks);

            this.Logger.Debug("Finished assigning director roles");
            return(role);
        }
Esempio n. 2
0
        private async Task HandleAddReadersStage(ITournamentState currentTournament, IGuild guild, SocketMessage message)
        {
            IEnumerable <Task <IGuildUser> > getReaderMembers = message.MentionedUsers
                                                                .Select(user => guild.GetUserAsync(user.Id, options: RequestOptionsSettings.Default));

            IGuildUser[] readerMembers = await Task.WhenAll(getReaderMembers);

            IEnumerable <Reader> readers = readerMembers.Select(member => new Reader()
            {
                Id   = member.Id,
                Name = member.Nickname ?? member.Username
            });

            currentTournament.AddReaders(readers);
            if (!currentTournament.Readers.Any())
            {
                this.Logger.Debug("No readers specified, so staying in the AddReaders stage");
                await message.Channel.SendMessageAsync(
                    BotStrings.NoReadersAddedMinimumReaderCount, options : RequestOptionsSettings.Default);

                return;
            }

            await message.Channel.SendMessageAsync(
                BotStrings.ReadersTotalForTournament(currentTournament.Readers.Count()));

            await this.UpdateStage(currentTournament, TournamentStage.SetRoundRobins, message.Channel);
        }
Esempio n. 3
0
        private async Task CreateArtifactsAsync(ITournamentState state)
        {
            // GetAllMembersAsync may contain the same member multiple times, which causes ToDictionary to throw. Add
            // members manually to a dictionary instead.
            IReadOnlyCollection <IGuildUser> allMembers = await this.Context.Guild.GetUsersAsync();

            IDictionary <ulong, IGuildUser> members = new Dictionary <ulong, IGuildUser>();

            foreach (IGuildUser member in allMembers)
            {
                members[member.Id] = member;
            }

            IRole directorRole = await this.AssignDirectorRoleAsync(state, members);

            Dictionary <Reader, IRole> roomReaderRoles = await this.AssignRoomReaderRolesAsync(state, members);

            Dictionary <Team, IRole> teamRoles = await this.AssignPlayerRolesAsync(state, members);

            TournamentRoles roles = new TournamentRoles()
            {
                DirectorRole    = directorRole,
                RoomReaderRoles = roomReaderRoles,
                TeamRoles       = teamRoles
            };

            state.TournamentRoles = roles.ToIds();

            await this.ChannelManager.CreateChannelsForPrelims(this.Context.Client.CurrentUser, state, roles);
        }
Esempio n. 4
0
        private async Task <Dictionary <Reader, IRole> > AssignRoomReaderRolesAsync(
            ITournamentState state, IDictionary <ulong, IGuildUser> members)
        {
            this.Logger.Debug("Assigning room reader roles to the readers");
            int roomsCount = state.Teams.Count() / 2;

            Task <IRole>[]       roomReaderRoleTasks = new Task <IRole> [roomsCount];
            IEnumerator <Reader> readers             = state.Readers.GetEnumerator();

            for (int i = 0; i < roomsCount && readers.MoveNext(); i++)
            {
                roomReaderRoleTasks[i] = this.AssignRoomReaderRoleAsync(
                    GetRoomReaderRoleName(i), readers.Current.Id, members);
            }

            IRole[] roles = await Task.WhenAll(roomReaderRoleTasks);

            this.Logger.Debug("Finished assigning room reader roles to the readers");

            // TODO: See if there's a better way to create the dictionary in one pass instead of getting all the roles
            // in an array first.
            Dictionary <Reader, IRole> roomReaderRoles = state.Readers
                                                         .Select((reader, index) => new KeyValuePair <Reader, IRole>(reader, roles[index]))
                                                         .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

            return(roomReaderRoles);
        }
Esempio n. 5
0
        public async Task <ITextChannel> CreateChannelsForFinals(
            ISelfUser botUser, ITournamentState state, Game finalsGame, int finalsRoundNumber, int roomIndex)
        {
            Verify.IsNotNull(this.Guild, "guild");
            Verify.IsNotNull(state, nameof(state));
            Verify.IsNotNull(finalsGame, nameof(finalsGame));

            TournamentRoles roles = this.GetTournamentRoles(state);

            ICategoryChannel finalsCategoryChannel = await this.Guild.CreateCategoryAsync("Finals");

            ITextChannel channel = await this.CreateTextChannelAsync(
                botUser,
                finalsCategoryChannel,
                finalsGame,
                roles,
                finalsRoundNumber,
                roomIndex);

            state.ChannelIds = state.ChannelIds
                               .Concat(new ulong[] { channel.Id })
                               .Concat(new ulong[] { finalsCategoryChannel.Id });

            return(channel);
        }
Esempio n. 6
0
        public bool TryClearCurrentTournament()
        {
            if (!this.currentTournamentLock.TryEnterWriteLock(mutexTimeoutMs))
            {
                return(false);
            }

            this.CurrentTournament = null;
            this.currentTournamentLock.ExitWriteLock();

            return(true);
        }
Esempio n. 7
0
        // TODO: Add test for ClearAll that checks that all artifacts are cleared.

        private static void AddTournamentDirectorDirectly(GlobalTournamentsManager globalManager, ulong userId)
        {
            TournamentsManager manager = globalManager.GetOrAdd(
                DefaultGuildId,
                id => new TournamentsManager()
            {
                GuildId = id
            });
            ITournamentState state = manager.AddOrUpdateTournament(
                DefaultTournamentName,
                new TournamentState(DefaultGuildId, DefaultTournamentName),
                (name, oldState) => oldState);

            Assert.IsTrue(state.TryAddDirector(userId), "First TD added should occur.");
        }
Esempio n. 8
0
        public bool TrySetCurrentTournament(string name, out string errorMessage)
        {
            if (!(this.currentTournamentLock.TryEnterUpgradeableReadLock(mutexTimeoutMs) &&
                  this.currentTournamentLock.TryEnterWriteLock(mutexTimeoutMs)))
            {
                errorMessage = TournamentStrings.UnableAccessCurrentTournament;
                return(false);
            }

            try
            {
                if (this.CurrentTournament != null)
                {
                    errorMessage = TournamentStrings.TournamentAlreadyRunning(this.CurrentTournament.Name);
                    return(false);
                }

                if (!this.pendingTournaments.TryGetValue(name, out ITournamentState state))
                {
                    errorMessage = TournamentStrings.TournamentCannotBeFound(name);
                    return(false);
                }

                this.CurrentTournament = state;

                if (!this.pendingTournaments.TryRemove(name, out state))
                {
                    // Couldn't set the current tournament, so roll back the change
                    errorMessage           = TournamentStrings.CannotMoveTournamentFromPending(this.CurrentTournament.Name);
                    this.CurrentTournament = null;
                    return(false);
                }

                errorMessage = null;
                return(true);
            }
            finally
            {
                this.currentTournamentLock.ExitWriteLock();
                this.currentTournamentLock.ExitUpgradeableReadLock();
            }
        }
Esempio n. 9
0
        private async Task <Dictionary <Team, IRole> > AssignPlayerRolesAsync(
            ITournamentState state, IDictionary <ulong, IGuildUser> users)
        {
            this.Logger.Debug("Creating team roles");
            List <Task <KeyValuePair <Team, IRole> > > addTeamRoleTasks = new List <Task <KeyValuePair <Team, IRole> > >();

            foreach (Team team in state.Teams)
            {
                addTeamRoleTasks.Add(this.CreateTeamRoleAsync(team));
            }

            IEnumerable <KeyValuePair <Team, IRole> > teamRolePairs = await Task.WhenAll(addTeamRoleTasks);

            Dictionary <Team, IRole> teamRoles = new Dictionary <Team, IRole>(teamRolePairs);

            this.Logger.Debug("Team roles created");

            this.Logger.Debug("Assigning player roles");
            List <Task> assignPlayerRoleTasks = new List <Task>();

            foreach (Player player in state.Players)
            {
                if (!teamRoles.TryGetValue(player.Team, out IRole role))
                {
                    this.Logger.Warning("Player {0} does not have a team role for team {1}", player.Id, player.Team);
                    continue;
                }

                if (!users.TryGetValue(player.Id, out IGuildUser user))
                {
                    this.Logger.Warning("Player {id} does not have a IGuildUser", player.Id);
                    continue;
                }

                assignPlayerRoleTasks.Add(user.AddRoleAsync(role, RequestOptionsSettings.Default));
            }

            await Task.WhenAll(assignPlayerRoleTasks);

            this.Logger.Debug("Finished assigning player roles");
            return(teamRoles);
        }
Esempio n. 10
0
        private async Task HandleSetRoundRobinsStage(ITournamentState currentTournament, SocketMessage message)
        {
            if (!int.TryParse(message.Content, out int rounds))
            {
                this.Logger.Debug("Round robin count specified couldn't be parsed as an int");
                return;
            }
            else if (rounds <= 0 || rounds > TournamentState.MaxRoundRobins)
            {
                this.Logger.Debug("Round robin count ({0}) specified is invalid", rounds);
                await message.Channel.SendMessageAsync(
                    BotStrings.InvalidNumberOfRoundRobins(TournamentState.MaxRoundRobins),
                    options : RequestOptionsSettings.Default);

                return;
            }

            currentTournament.RoundRobinsCount = rounds;
            await this.UpdateStage(currentTournament, TournamentStage.AddTeams, message.Channel);
        }
Esempio n. 11
0
        public async Task CreateChannelsForRebracket(
            ISelfUser botUser,
            ITournamentState state,
            IEnumerable <Round> rounds,
            int startingRoundNumber)
        {
            Verify.IsNotNull(this.Guild, "guild");
            Verify.IsNotNull(state, nameof(state));
            Verify.IsNotNull(rounds, nameof(rounds));

            TournamentRoles roles = this.GetTournamentRoles(state);

            ITextChannel[] textChannels = await this.CreateTextChannelsForRounds(
                botUser, rounds, roles, startingRoundNumber);

            IEnumerable <ulong> textCategoryChannelIds = GetCategoryChannelIds(textChannels);

            state.ChannelIds = state.ChannelIds
                               .Concat(textChannels.Select(channel => channel.Id))
                               .Concat(textCategoryChannelIds);
        }
Esempio n. 12
0
        public async Task CreateChannelsForPrelims(ISelfUser botUser, ITournamentState state, TournamentRoles roles)
        {
            Verify.IsNotNull(this.Guild, "guild");
            Verify.IsNotNull(state, nameof(state));
            Verify.IsNotNull(roles, nameof(roles));

            List <Task <IVoiceChannel> > createVoiceChannelsTasks = new List <Task <IVoiceChannel> >();
            // We only need to go through the games for the first round to get all of the readers.
            Round firstRound = state.Schedule.Rounds.First();

            Debug.Assert(firstRound.Games.Select(game => game.Reader.Name).Count() ==
                         firstRound.Games.Select(game => game.Reader.Name).Distinct().Count(),
                         "All reader names should be unique.");
            ICategoryChannel voiceCategoryChannel = await this.Guild.CreateCategoryAsync(
                "Readers", options : RequestOptionsSettings.Default);

            foreach (Game game in firstRound.Games)
            {
                createVoiceChannelsTasks.Add(
                    this.CreateVoiceChannelAsync(voiceCategoryChannel, game.Reader));
            }

            IVoiceChannel[] voiceChannels = await Task.WhenAll(createVoiceChannelsTasks);

            // Create the text channels
            const int startingRoundNumber = 1;

            ITextChannel[] textChannels = await this.CreateTextChannelsForRounds(
                botUser, state.Schedule.Rounds, roles, startingRoundNumber);

            IEnumerable <ulong> textCategoryChannelIds = GetCategoryChannelIds(textChannels);

            state.ChannelIds = voiceChannels.Select(channel => channel.Id)
                               .Concat(textChannels.Select(channel => channel.Id))
                               .Concat(new ulong[] { voiceCategoryChannel.Id })
                               .Concat(textCategoryChannelIds)
                               .ToArray();
        }
Esempio n. 13
0
        public void AddOrUpdateTournamentAddsThenUpdates()
        {
            const string       originalTournamentName = "Tournament1";
            const string       updatedTournamentName  = "Tournament2";
            TournamentsManager manager = new TournamentsManager()
            {
                GuildId = 1
            };

            TournamentState originalState = new TournamentState(1, originalTournamentName);

            string[] expectedNames = new string[] { originalTournamentName, updatedTournamentName };

            for (int i = 0; i < expectedNames.Length; i++)
            {
                ITournamentState state = manager.AddOrUpdateTournament(
                    originalTournamentName,
                    originalState,
                    (name, oldState) => new TournamentState(1, updatedTournamentName));
                Assert.AreEqual(
                    expectedNames[i], state.Name, $"Unexpected tournament returned after {i + 1} call(s).");
            }
        }
Esempio n. 14
0
        // TODO: Make string message and IGuildChannel?
        private async Task HandleAddTeamsStage(ITournamentState currentTournament, SocketMessage message)
        {
            if (!TeamsParser.TryParseTeams(message.Content, out IEnumerable <Team> teams, out string errorMessage))
            {
                await message.Channel.SendMessageAsync(errorMessage, options : RequestOptionsSettings.Default);

                this.Logger.Debug("Team names could not be parsed. Error message: {errorMessage}", errorMessage);
                return;
            }

            currentTournament.AddTeams(teams);

            int teamsCount = currentTournament.Teams.Count();

            if (teamsCount < 2)
            {
                await message.Channel.SendMessageAsync(
                    BotStrings.MustBeTwoTeamsPerTournament, options : RequestOptionsSettings.Default);

                currentTournament.RemoveTeams(teams);
                this.Logger.Debug("Too few teams specified in AddTeams stage ({0})", teamsCount);
                return;
            }

            int maxTeamsCount = GetMaximumTeamCount(currentTournament);

            if (teamsCount > maxTeamsCount)
            {
                currentTournament.TryClearTeams();
                await message.Channel.SendMessageAsync(
                    BotStrings.TooManyTeams(maxTeamsCount), options : RequestOptionsSettings.Default);

                this.Logger.Debug("Too many teams specified in AddTeams stage ({0})", teamsCount);
                return;
            }

            if (!TryGetEmojis(teamsCount, out IEmote[] emotes, out errorMessage))
Esempio n. 15
0
        private async Task <IUserMessage> UpdateStageAsync(ITournamentState state, TournamentStage stage, bool sendMessage = true)
        {
            state.UpdateStage(stage, out string title, out string instructions);
            if (title == null && instructions == null)
            {
                return(null);
            }

            IUserMessage updateMessage = null;

            if (sendMessage)
            {
                EmbedBuilder embedBuilder = new EmbedBuilder
                {
                    Title       = title,
                    Description = instructions
                };
                updateMessage = await this.Context.Channel.SendMessageAsync(
                    embed : embedBuilder.Build(), options : RequestOptionsSettings.Default);
            }

            this.Logger.Debug("Moved to stage {stage}", stage);
            return(updateMessage);
        }
Esempio n. 16
0
 public bool TryGetTournament(string name, out ITournamentState state)
 {
     return(this.pendingTournaments.TryGetValue(name, out state));
 }
Esempio n. 17
0
        // Removes channels and roles.
        private async Task CleanupTournamentArtifactsAsync(ITournamentState state)
        {
            IEnumerable <Task> deleteChannelTasks;

            if (state.ChannelIds != null)
            {
                IGuildChannel[] channels = await Task.WhenAll(state.ChannelIds
                                                              .Select(id => this.Context.Guild.GetChannelAsync(id, options: RequestOptionsSettings.Default)));

                deleteChannelTasks = channels
                                     .Where(channel => channel != null)
                                     .Select(channel => channel.DeleteAsync(options: RequestOptionsSettings.Default));
            }
            else
            {
                deleteChannelTasks = Array.Empty <Task>();
            }

            // To prevent spamming Discord too much, delete all the channels, then delete the roles
            this.Logger.Debug("Deleting all channels created by the tournament {name}", state.Name);
            await Task.WhenAll(deleteChannelTasks);

            this.Logger.Debug("All channels created by the tournament {name} are deleted", state.Name);

            IEnumerable <ulong> roleIds = state.TournamentRoles?.ReaderRoomRoleIds.Select(kvp => kvp.Value)
                                          .Concat(state.TournamentRoles.TeamRoleIds.Select(kvp => kvp.Value))
                                          .Concat(new ulong[] { state.TournamentRoles.DirectorRoleId });
            IEnumerable <Task> deleteRoleTasks = roleIds?
                                                 .Select(id => this.Context.Guild.GetRole(id))
                                                 .Where(role => role != null)
                                                 .Select(role => role.DeleteAsync(RequestOptionsSettings.Default));

            if (deleteRoleTasks == null)
            {
                deleteRoleTasks = Array.Empty <Task>();
            }

            if (state.PinnedStartMessageId != null)
            {
                // TODO: This will fail if !end is called in the non-general channel
                this.Logger.Debug("Deleting the pinned start message created by tournament {name}", state.Name);
                IMessage message = await this.Context.Channel.GetMessageAsync(
                    state.PinnedStartMessageId.Value, options : RequestOptionsSettings.Default);

                if (message?.IsPinned == true && message is IUserMessage pinnedMessage)
                {
                    await pinnedMessage.UnpinAsync(RequestOptionsSettings.Default);

                    this.Logger.Debug("Unpinned start message created by the tournament {name}", state.Name);
                }
                else
                {
                    this.Logger.Debug(
                        "Couldn't find pinned start message created by the tournament {name}", state.Name);
                }
            }

            this.Logger.Debug("Deleting all roles created by the tournament {name}", state.Name);
            await Task.WhenAll(deleteRoleTasks);

            this.Logger.Debug("All roles created by the tournament {name} are deleted", state.Name);
            await this.UpdateStageAsync(state, TournamentStage.Complete);
        }
Esempio n. 18
0
 private static async Task AddReactionsToMessage(
     IUserMessage message, IEnumerable <IEmote> emotesForMessage, ITournamentState currentTournament)
 {
     currentTournament.AddJoinTeamMessageId(message.Id);
     await message.AddReactionsAsync(emotesForMessage.ToArray(), RequestOptionsSettings.Default);
 }
Esempio n. 19
0
 public ITournamentState AddOrUpdateTournament(
     string name, ITournamentState addState, Func <string, ITournamentState, ITournamentState> updateStateFunc)
 {
     return(this.pendingTournaments.AddOrUpdate(name, addState, updateStateFunc));
 }