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); }
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); }
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); }
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); }
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); }
public bool TryClearCurrentTournament() { if (!this.currentTournamentLock.TryEnterWriteLock(mutexTimeoutMs)) { return(false); } this.CurrentTournament = null; this.currentTournamentLock.ExitWriteLock(); return(true); }
// 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."); }
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(); } }
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); }
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); }
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); }
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(); }
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)."); } }
// 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))
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); }
public bool TryGetTournament(string name, out ITournamentState state) { return(this.pendingTournaments.TryGetValue(name, out state)); }
// 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); }
private static async Task AddReactionsToMessage( IUserMessage message, IEnumerable <IEmote> emotesForMessage, ITournamentState currentTournament) { currentTournament.AddJoinTeamMessageId(message.Id); await message.AddReactionsAsync(emotesForMessage.ToArray(), RequestOptionsSettings.Default); }
public ITournamentState AddOrUpdateTournament( string name, ITournamentState addState, Func <string, ITournamentState, ITournamentState> updateStateFunc) { return(this.pendingTournaments.AddOrUpdate(name, addState, updateStateFunc)); }