示例#1
0
        private static TournamentsManager CreateTournamentsManager(ulong id)
        {
            TournamentsManager manager = new TournamentsManager()
            {
                GuildId = id
            };

            return(manager);
        }
示例#2
0
        protected ITournamentState AddCurrentTournament(
            GlobalTournamentsManager globalManager,
            ulong guildId         = DefaultGuildId,
            string tournamentName = DefaultTournamentName)
        {
            TournamentsManager manager = globalManager.GetOrAdd(guildId, id => new TournamentsManager());
            ITournamentState   state   = new TournamentState(guildId, tournamentName);

            state = manager.AddOrUpdateTournament(tournamentName, state, (name, oldState) => state);
            Assert.IsTrue(
                manager.TrySetCurrentTournament(tournamentName, out string errorMessage),
                "We should be able to set the current tournament.");
            return(state);
        }
示例#3
0
        public void TrySetCurrentTournamentWithUnaddedTournament()
        {
            TournamentsManager manager = new TournamentsManager()
            {
                GuildId = 1
            };

            Assert.IsFalse(
                manager.TrySetCurrentTournament(DefaultTournamentName, out string errorMessage),
                "Shouldn't be able to set current tournament with a non-existent tournament.");
            // TODO: If we go to using resources or string consts, check that value instead of just checking that the
            // message isn't null.
            Assert.IsNotNull(errorMessage, "Error message should not be null.");
        }
示例#4
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.");
        }
示例#5
0
        public static async Task DoReadActionOnCurrentTournamentForMemberAsync(
            this TournamentsManager manager, IUser user, Func <IReadOnlyTournamentState, Task> action)
        {
            Result <Task> result = manager.TryReadActionOnCurrentTournament(action);

            if (result.Success)
            {
                await result.Value;
                return;
            }

            IDMChannel channel = await user.GetOrCreateDMChannelAsync();

            await channel.SendMessageAsync(BotStrings.UnableToPerformCommand(result.ErrorMessage));
        }
示例#6
0
        public void TryGetTournament()
        {
            TournamentsManager manager = new TournamentsManager()
            {
                GuildId = 1
            };

            Assert.IsFalse(
                manager.TryGetTournament(DefaultTournamentName, out ITournamentState state), "No tournament state should exist.");

            TournamentState newState = new TournamentState(1, DefaultTournamentName);

            manager.AddOrUpdateTournament(DefaultTournamentName, newState, (name, oldState) => oldState);
            Assert.IsTrue(manager.TryGetTournament(DefaultTournamentName, out state), "Tournament state should exist.");
            Assert.AreEqual(newState, state, "Wrong tournament retrieved.");
        }
示例#7
0
        public async Task EndTournamentAsync()
        {
            await this.DoReadWriteActionOnCurrentTournamentAsync(
                currentTournament => this.CleanupTournamentArtifactsAsync(currentTournament));

            TournamentsManager manager = this.GlobalManager.GetOrAdd(this.Context.Guild.Id, CreateTournamentsManager);

            if (!manager.TryClearCurrentTournament())
            {
                this.Logger.Debug("Tournament cleanup failed");
                await this.SendChannelMessage(BotStrings.TournamentWasNotRemoved);

                return;
            }

            this.Logger.Debug("Tournament cleanup finished");
        }
示例#8
0
        public async Task RemoveTournamentDirectorAsync(IGuildUser oldDirector, string tournamentName)
        {
            Verify.IsNotNull(oldDirector, nameof(oldDirector));

            if (string.IsNullOrWhiteSpace(tournamentName))
            {
                this.Logger.Debug("Couldn't remove director {id} for tournament with blank name", oldDirector.Id);
                return;
            }

            tournamentName = tournamentName.Trim();
            TournamentsManager manager = this.GlobalManager.GetOrAdd(this.Context.Guild.Id, CreateTournamentsManager);

            // TODO: Harden this. Since it's not guaranteed to be the current tournament, we can't use the helper
            // methods
            if (!manager.TryGetTournament(tournamentName, out ITournamentState state))
            {
                this.Logger.Debug(
                    "Couldn't remove director {id} for nonexistent tournament {tournamentName}",
                    oldDirector.Id,
                    tournamentName);
                await this.Context.Channel.SendMessageAsync(
                    BotStrings.TournamentDoesNotExist(tournamentName, this.Context.Guild.Name),
                    options : RequestOptionsSettings.Default);

                return;
            }

            if (state.TryRemoveDirector(oldDirector.Id))
            {
                this.Logger.Debug(
                    "Removed {id} as a tournament director for {tournamentName}", oldDirector.Id, tournamentName);
                await this.Context.Channel.SendMessageAsync(
                    BotStrings.RemovedTournamentDirector(tournamentName, this.Context.Guild.Name),
                    options : RequestOptionsSettings.Default);

                return;
            }

            this.Logger.Debug(
                "User {id} is not a director for {tournamentName}, so could not be removed",
                oldDirector.Id,
                tournamentName);
            await this.Context.Channel.SendMessageAsync(
                BotStrings.UserNotTournamentDirector(tournamentName, this.Context.Guild.Name), options : RequestOptionsSettings.Default);
        }
示例#9
0
        public Task AddTournamentDirectorAsync(IGuildUser newDirector, string tournamentName)
        {
            Verify.IsNotNull(newDirector, nameof(newDirector));

            if (string.IsNullOrWhiteSpace(tournamentName))
            {
                this.Logger.Debug("Did not add {id} to tournament with blank name", newDirector.Id);
                return(Task.CompletedTask);
            }

            TournamentsManager manager = this.GlobalManager.GetOrAdd(this.Context.Guild.Id, CreateTournamentsManager);

            ITournamentState state            = new TournamentState(this.Context.Guild.Id, tournamentName.Trim());
            bool             updateSuccessful = state.TryAddDirector(newDirector.Id);

            manager.AddOrUpdateTournament(
                tournamentName,
                state,
                (name, tournamentState) =>
            {
                updateSuccessful = tournamentState.TryAddDirector(newDirector.Id);
                return(tournamentState);
            });

            // TODO: Need to handle this differently depending on the stage. Completed shouldn't do anything, and
            // after RoleSetup we should give them the TD role.
            if (updateSuccessful)
            {
                this.Logger.Debug(
                    "Added {id} as a tournament director for {tournamentName}", newDirector.Id, tournamentName);
                return(this.SendChannelMessage(
                           BotStrings.AddTournamentDirectorSuccessful(tournamentName, this.Context.Guild.Name)));
            }

            this.Logger.Debug(
                "{id} already a tournament director for {tournamentName}", newDirector.Id, tournamentName);
            return(this.SendChannelMessage(
                       BotStrings.UserAlreadyTournamentDirector(tournamentName, this.Context.Guild.Name)));
        }
示例#10
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).");
            }
        }
示例#11
0
        public async Task AddTournamentDirector()
        {
            MessageStore             messageStore  = new MessageStore();
            ICommandContext          context       = this.CreateCommandContext(messageStore);
            GlobalTournamentsManager globalManager = new GlobalTournamentsManager();

            BotCommandHandler commandHandler = new BotCommandHandler(context, globalManager);

            IGuildUser guildUser = this.CreateGuildUser(DefaultUserId);
            await commandHandler.AddTournamentDirectorAsync(guildUser, DefaultTournamentName);

            string expectedMessage = BotStrings.AddTournamentDirectorSuccessful(
                DefaultTournamentName, DefaultGuildName);

            messageStore.VerifyChannelMessages(expectedMessage);

            TournamentsManager manager = globalManager.GetOrAdd(DefaultGuildId, id => new TournamentsManager());

            Assert.IsTrue(
                manager.TryGetTournament(DefaultTournamentName, out ITournamentState state),
                "Could not find tournament.");
            Assert.IsTrue(state.IsDirector(DefaultUserId), "Director was not added.");
        }
示例#12
0
        public async Task RemoveNonexistentTournamentDirector()
        {
            const ulong              otherId        = DefaultUserId + 1;
            MessageStore             messageStore   = new MessageStore();
            ICommandContext          context        = this.CreateCommandContext(messageStore);
            GlobalTournamentsManager globalManager  = new GlobalTournamentsManager();
            BotCommandHandler        commandHandler = new BotCommandHandler(context, globalManager);

            AddTournamentDirectorDirectly(globalManager, DefaultUserId);

            IGuildUser guildUser = this.CreateGuildUser(otherId);
            await commandHandler.RemoveTournamentDirectorAsync(guildUser, DefaultTournamentName);

            string expectedMessage = BotStrings.UserNotTournamentDirector(DefaultTournamentName, DefaultGuildName);

            messageStore.VerifyChannelMessages(expectedMessage);

            TournamentsManager manager = globalManager.GetOrAdd(DefaultGuildId, id => new TournamentsManager());

            Assert.IsTrue(
                manager.TryGetTournament(DefaultTournamentName, out ITournamentState state), "Could not find tournament.");
            Assert.IsFalse(state.IsDirector(otherId), "Director should not have been added.");
        }
示例#13
0
        public void TryClearCurrentTournament()
        {
            const string       otherTournamentName = DefaultTournamentName + "2";
            TournamentsManager manager             = new TournamentsManager()
            {
                GuildId = 1
            };

            TournamentState state      = new TournamentState(1, DefaultTournamentName);
            TournamentState otherState = new TournamentState(1, otherTournamentName);

            manager.AddOrUpdateTournament(DefaultTournamentName, state, (name, oldState) => oldState);
            manager.AddOrUpdateTournament(otherTournamentName, otherState, (name, oldState) => oldState);

            Assert.IsTrue(
                manager.TrySetCurrentTournament(DefaultTournamentName, out string errorMessage),
                "Couldn't set current tournament initially.");
            Assert.IsTrue(manager.TryClearCurrentTournament(), "Couldn't clear current tournament.");

            // TrySetCurrentTournament should work again if we've just cleared it.
            Assert.IsTrue(
                manager.TrySetCurrentTournament(otherTournamentName, out errorMessage),
                "Couldn't set current tournament after clearing it.");
        }
示例#14
0
        public void TrySetCurrentTournamentWhenCurrentTournamentAlreadyExists()
        {
            const string       otherTournamentName = DefaultTournamentName + "2";
            TournamentsManager manager             = new TournamentsManager()
            {
                GuildId = 1
            };

            TournamentState state      = new TournamentState(1, DefaultTournamentName);
            TournamentState otherState = new TournamentState(1, otherTournamentName);

            manager.AddOrUpdateTournament(DefaultTournamentName, state, (name, oldState) => oldState);
            manager.AddOrUpdateTournament(otherTournamentName, otherState, (name, oldState) => oldState);

            Assert.IsTrue(
                manager.TrySetCurrentTournament(DefaultTournamentName, out string errorMessage),
                "First TrySet should succeed.");
            Assert.IsFalse(
                manager.TrySetCurrentTournament(otherTournamentName, out errorMessage),
                "Shouldn't be able to set the current tournament when one is already set.");
            // TODO: If we go to using resources or string consts, check that value instead of just checking that the
            // message isn't null.
            Assert.IsNotNull(errorMessage, "Error message should not be null.");
        }
示例#15
0
        public async Task SimplestSchedule()
        {
            const string readerName     = "#Reader";
            const string firstTeamName  = "#TeamA";
            const string secondTeamName = "#TeamB";

            MessageStore             messageStore  = new MessageStore();
            ICommandContext          context       = this.CreateCommandContext(messageStore, guildId: DefaultGuildId);
            GlobalTournamentsManager globalManager = new GlobalTournamentsManager();
            TournamentsManager       manager       = globalManager.GetOrAdd(DefaultGuildId, id => new TournamentsManager());

            HashSet <Team> teams = new HashSet <Team>()
            {
                new Team()
                {
                    Name = firstTeamName
                },
                new Team()
                {
                    Name = secondTeamName
                }
            };
            HashSet <Reader> readers = new HashSet <Reader>()
            {
                new Reader()
                {
                    Id   = 0,
                    Name = readerName
                }
            };
            RoundRobinScheduleFactory factory = new RoundRobinScheduleFactory(2, 0);
            Schedule schedule = factory.Generate(teams, readers);

            ITournamentState state = new TournamentState(DefaultGuildId, "T");

            state.Schedule = schedule;
            manager.AddOrUpdateTournament(state.Name, state, (name, oldState) => oldState);
            Assert.IsTrue(
                manager.TrySetCurrentTournament(state.Name, out string errorMessage),
                $"Failed to set the tournament: '{errorMessage}'");
            globalManager.GetOrAdd(DefaultGuildId, id => manager);

            BotCommandHandler commandHandler = new BotCommandHandler(context, globalManager);

            await commandHandler.GetScheduleAsync();

            Assert.AreEqual(1, messageStore.ChannelEmbeds.Count, "Unexpected number of embeds");

            string embed = messageStore.ChannelEmbeds[0];

            for (int round = 0; round < schedule.Rounds.Count; round++)
            {
                Assert.IsTrue(
                    embed.Contains(BotStrings.RoundNumber(round + 1)),
                    $"Round {round + 1} not found in embed. Embed: '{embed}'");
                string expectedGame = BotStrings.ScheduleLine(
                    readerName, schedule.Rounds[round].Games[0].Teams.Select(team => team.Name).ToArray());
                Assert.IsTrue(
                    embed.Contains(expectedGame),
                    $"Game '{expectedGame}' not foudn in embed. Embed: '{embed}'");
            }
        }
示例#16
0
        // TODO: Test these methods
        internal async Task HandleOnMessageReceived(SocketMessage message)
        {
            if (message.Content.TrimStart().StartsWith("!", StringComparison.InvariantCulture))
            {
                // Ignore commands
                this.Logger.Verbose("Message ignored because it's a command");
                return;
            }

            if (message.Author.Id == this.Client.CurrentUser.Id)
            {
                this.Logger.Verbose("Message ignored because it's from the bot");
                return;
            }

            // Only pay attention to messages in a guild channel
            if (!(message is IUserMessage userMessage &&
                  userMessage.Channel is IGuildChannel guildChannel &&
                  userMessage.Author is IGuildUser guildUser))
            {
                this.Logger.Verbose("Message ignored because it's not in a guild");
                return;
            }

            // TODO: See if there's a cheaper way to check if we have a current tournament without making it public.
            // This is a read-only lock, so it shouldn't be too bad, but it'll be blocked during write operations.
            // Don't use the helper method because we don't want to message the user each time if there's no tournament.
            // We also want to split this check and the TD check because we don't need to get the Discord member for
            // this check.
            TournamentsManager manager = this.GetTournamentsManager(guildChannel.Guild);
            Result <bool>      currentTournamentExists = manager.TryReadActionOnCurrentTournament(currentTournament => true);

            if (!currentTournamentExists.Success)
            {
                this.Logger.Verbose("Message ignored because no current tournament is running");
                return;
            }

            // We want to do the basic access checks before using the more expensive write lock.

            Result <bool> hasDirectorPrivileges = manager.TryReadActionOnCurrentTournament(
                currentTournament => HasTournamentDirectorPrivileges(currentTournament, guildChannel, guildUser));

            if (!(hasDirectorPrivileges.Success && hasDirectorPrivileges.Value))
            {
                this.Logger.Verbose(
                    "Message ignored because user {id} does not have tournament director privileges", guildUser.Id);
                return;
            }

            await manager.DoReadWriteActionOnCurrentTournamentForMemberAsync(
                guildUser,
                async currentTournament =>
            {
                // TODO: We need to have locks on these. Need to check the stages between locks, and ignore the message if
                // it changes.
                // Issue is that we should really rely on a command for this case. Wouldn't quite work with locks.
                // But that would be something like !start, which would stop the rest of the interaction.
                switch (currentTournament.Stage)
                {
                case TournamentStage.AddReaders:
                    await this.HandleAddReadersStage(currentTournament, guildChannel.Guild, message);
                    break;

                case TournamentStage.SetRoundRobins:
                    await this.HandleSetRoundRobinsStage(currentTournament, message);
                    break;

                case TournamentStage.AddTeams:
                    await this.HandleAddTeamsStage(currentTournament, message);
                    break;

                case TournamentStage.Rebracketing:
                    await this.HandleRebracketingStage(currentTournament, guildChannel.Guild, message);
                    break;

                default:
                    this.Logger.Verbose(
                        "Message ignored because it is during the stage {stage}", currentTournament.Stage);
                    break;
                }
            });
        }
示例#17
0
        private Task DoReadActionOnCurrentTournamentAsync(Func <IReadOnlyTournamentState, Task> action)
        {
            TournamentsManager manager = this.GlobalManager.GetOrAdd(this.Context.Guild.Id, CreateTournamentsManager);

            return(manager.DoReadActionOnCurrentTournamentForMemberAsync(this.Context.User, action));
        }
示例#18
0
 static async Task Main(string[] args)
 {
     var tournamentManager = new TournamentsManager();
     await tournamentManager.StartTournamentManager();
 }