diff --git a/src/NadekoBot/Modules/Games/Commands/Leet.cs b/src/NadekoBot/Modules/Games/Commands/LeetCommands.cs similarity index 100% rename from src/NadekoBot/Modules/Games/Commands/Leet.cs rename to src/NadekoBot/Modules/Games/Commands/LeetCommands.cs diff --git a/src/NadekoBot/Modules/Games/Commands/PlantPick.cs b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs similarity index 100% rename from src/NadekoBot/Modules/Games/Commands/PlantPick.cs rename to src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs diff --git a/src/NadekoBot/Modules/Games/Commands/PollCommand.cs b/src/NadekoBot/Modules/Games/Commands/PollCommands.cs similarity index 100% rename from src/NadekoBot/Modules/Games/Commands/PollCommand.cs rename to src/NadekoBot/Modules/Games/Commands/PollCommands.cs diff --git a/src/NadekoBot/Modules/Games/Commands/SpeedTyping.cs b/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs similarity index 100% rename from src/NadekoBot/Modules/Games/Commands/SpeedTyping.cs rename to src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs diff --git a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs index 3f38529f..f5c41df6 100644 --- a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs +++ b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs @@ -9,14 +9,16 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +// todo rewrite? +// todo DB namespace NadekoBot.Modules.Games.Commands.Trivia { - internal class TriviaGame + public class TriviaGame { - private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1,1); + private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1, 1); - private Server server { get; } - private Channel channel { get; } + private IGuild guild { get; } + private ITextChannel channel { get; } private int QuestionDurationMiliseconds { get; } = 30000; private int HintTimeoutMiliseconds { get; } = 6000; @@ -26,18 +28,18 @@ namespace NadekoBot.Modules.Games.Commands.Trivia public TriviaQuestion CurrentQuestion { get; private set; } public HashSet oldQuestions { get; } = new HashSet(); - public ConcurrentDictionary Users { get; } = new ConcurrentDictionary(); + public ConcurrentDictionary Users { get; } = new ConcurrentDictionary(); public bool GameActive { get; private set; } = false; public bool ShouldStopGame { get; private set; } public int WinRequirement { get; } = 10; - public TriviaGame(CommandEventArgs e, bool showHints, int winReq = 10) + public TriviaGame(IGuild guild, ITextChannel channel, bool showHints, int winReq = 10) { ShowHints = showHints; - server = e.Server; - channel = e.Channel; + this.guild = guild; + this.channel = channel; WinRequirement = winReq; Task.Run(StartGame); } @@ -53,13 +55,13 @@ namespace NadekoBot.Modules.Games.Commands.Trivia CurrentQuestion = TriviaQuestionPool.Instance.GetRandomQuestion(oldQuestions); if (CurrentQuestion == null) { - await channel.SendMessage($":exclamation: Failed loading a trivia question").ConfigureAwait(false); + await channel.SendMessageAsync($":exclamation: Failed loading a trivia question").ConfigureAwait(false); await End().ConfigureAwait(false); return; } oldQuestions.Add(CurrentQuestion); //add it to exclusion list so it doesn't show up again //sendquestion - await channel.SendMessage($":question: **{CurrentQuestion.Question}**").ConfigureAwait(false); + await channel.SendMessageAsync($":question: **{CurrentQuestion.Question}**").ConfigureAwait(false); //receive messages NadekoBot.Client.MessageReceived += PotentialGuess; @@ -72,7 +74,7 @@ namespace NadekoBot.Modules.Games.Commands.Trivia //hint await Task.Delay(HintTimeoutMiliseconds, token).ConfigureAwait(false); if (ShowHints) - await channel.SendMessage($":exclamation:**Hint:** {CurrentQuestion.GetHint()}").ConfigureAwait(false); + await channel.SendMessageAsync($":exclamation:**Hint:** {CurrentQuestion.GetHint()}").ConfigureAwait(false); //timeout await Task.Delay(QuestionDurationMiliseconds - HintTimeoutMiliseconds, token).ConfigureAwait(false); @@ -81,7 +83,7 @@ namespace NadekoBot.Modules.Games.Commands.Trivia catch (TaskCanceledException) { } //means someone guessed the answer GameActive = false; if (!triviaCancelSource.IsCancellationRequested) - await channel.Send($":clock2: :question: **Time's up!** The correct answer was **{CurrentQuestion.Answer}**").ConfigureAwait(false); + await channel.SendMessageAsync($":clock2: :question: **Time's up!** The correct answer was **{CurrentQuestion.Answer}**").ConfigureAwait(false); NadekoBot.Client.MessageReceived -= PotentialGuess; // load next question if game is still running await Task.Delay(2000).ConfigureAwait(false); @@ -92,46 +94,43 @@ namespace NadekoBot.Modules.Games.Commands.Trivia private async Task End() { ShouldStopGame = true; - await channel.SendMessage("**Trivia game ended**\n" + GetLeaderboard()).ConfigureAwait(false); - TriviaGame throwAwayValue; - TriviaCommands.RunningTrivias.TryRemove(server.Id, out throwAwayValue); + await channel.SendMessageAsync("**Trivia game ended**\n" + GetLeaderboard()).ConfigureAwait(false); } public async Task StopGame() { if (!ShouldStopGame) - await channel.SendMessage(":exclamation: Trivia will stop after this question.").ConfigureAwait(false); + await channel.SendMessageAsync(":exclamation: Trivia will stop after this question.").ConfigureAwait(false); ShouldStopGame = true; } - private async void PotentialGuess(object sender, MessageEventArgs e) + private async Task PotentialGuess(IMessage imsg) { try { - if (e.Channel.IsPrivate) return; - if (e.Server != server) return; - if (e.User.Id == NadekoBot.Client.CurrentUser.Id) return; + if (!(imsg.Channel is IGuildChannel && imsg.Channel is ITextChannel)) return; + if ((imsg.Channel as IGuildChannel).Guild != guild) return; + if (imsg.Author.Id == (await NadekoBot.Client.GetCurrentUserAsync()).Id) return; + + var guildUser = imsg.Author as IGuildUser; var guess = false; await _guessLock.WaitAsync().ConfigureAwait(false); try { - if (GameActive && CurrentQuestion.IsAnswerCorrect(e.Message.Text) && !triviaCancelSource.IsCancellationRequested) + if (GameActive && CurrentQuestion.IsAnswerCorrect(imsg.Content) && !triviaCancelSource.IsCancellationRequested) { - Users.TryAdd(e.User, 0); //add if not exists - Users[e.User]++; //add 1 point to the winner + Users.AddOrUpdate(guildUser, 0, (gu, old) => old++); guess = true; } } finally { _guessLock.Release(); } if (!guess) return; triviaCancelSource.Cancel(); - await channel.SendMessage($"☑️ {e.User.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false); - if (Users[e.User] != WinRequirement) return; + await channel.SendMessageAsync($"☑️ {guildUser.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false); + if (Users[guildUser] != WinRequirement) return; ShouldStopGame = true; - await channel.Send($":exclamation: We have a winner! It's {e.User.Mention}.").ConfigureAwait(false); - // add points to the winner - await FlowersHandler.AddFlowersAsync(e.User, "Won Trivia", 2).ConfigureAwait(false); + await channel.SendMessageAsync($":exclamation: We have a winner! It's {guildUser.Mention}.").ConfigureAwait(false); } catch { } } @@ -146,7 +145,7 @@ namespace NadekoBot.Modules.Games.Commands.Trivia foreach (var kvp in Users.OrderBy(kvp => kvp.Value)) { - sb.AppendLine($"**{kvp.Key.Name}** has {kvp.Value} points".ToString().SnPl(kvp.Value)); + sb.AppendLine($"**{kvp.Key.Username}** has {kvp.Value} points".ToString().SnPl(kvp.Value)); } return sb.ToString(); diff --git a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs index 9bfa2da4..f6ec9cd3 100644 --- a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs +++ b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs @@ -1,6 +1,7 @@ using NadekoBot.Extensions; using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; // THANKS @ShoMinamimoto for suggestions and coding help @@ -28,7 +29,7 @@ namespace NadekoBot.Modules.Games.Commands.Trivia this.Category = c; } - public string GetHint() => Answer.Scramble(); + public string GetHint() => Scramble(Answer); public bool IsAnswerCorrect(string guess) { @@ -80,5 +81,27 @@ namespace NadekoBot.Modules.Games.Commands.Trivia public override string ToString() => "Question: **" + this.Question + "?**"; + + private static string Scramble(string word) + { + var letters = word.ToArray(); + var count = 0; + for (var i = 0; i < letters.Length; i++) + { + if (letters[i] == ' ') + continue; + + count++; + if (count <= letters.Length / 5) + continue; + + if (count % 3 == 0) + continue; + + if (letters[i] != ' ') + letters[i] = '_'; + } + return "`" + string.Join(" ", letters) + "`"; + } } } diff --git a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs index 3aa09a56..7ffe615f 100644 --- a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs +++ b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs @@ -9,7 +9,7 @@ namespace NadekoBot.Modules.Games.Commands.Trivia public class TriviaQuestionPool { public static TriviaQuestionPool Instance { get; } = new TriviaQuestionPool(); - + //todo DB public HashSet pool = new HashSet(); private Random rng { get; } = new Random(); diff --git a/src/NadekoBot/Modules/Games/Commands/TriviaCommand.cs b/src/NadekoBot/Modules/Games/Commands/TriviaCommand.cs deleted file mode 100644 index ecbc0c4a..00000000 --- a/src/NadekoBot/Modules/Games/Commands/TriviaCommand.cs +++ /dev/null @@ -1,76 +0,0 @@ -//using Discord.Commands; -//using NadekoBot.Classes; -//using NadekoBot.Modules.Games.Commands.Trivia; -//using System; -//using System.Collections.Concurrent; -//using System.Linq; - -////todo DB -////todo Rewrite? - -//namespace NadekoBot.Modules.Games.Commands -//{ -// internal class TriviaCommands : DiscordCommand -// { -// public static ConcurrentDictionary RunningTrivias = new ConcurrentDictionary(); - -// public TriviaCommands(DiscordModule module) : base(module) -// { -// } - -// internal override void Init(CommandGroupBuilder cgb) -// { -// cgb.CreateCommand(Module.Prefix + "t") -// .Description($"Starts a game of trivia. You can add nohint to prevent hints." + -// "First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question." + -// $" |`{Module.Prefix}t nohint` or `{Module.Prefix}t 5 nohint`") -// .Parameter("args", ParameterType.Multiple) -// .Do(async e => -// { -// TriviaGame trivia; -// if (!RunningTrivias.TryGetValue(e.Server.Id, out trivia)) -// { -// var showHints = !e.Args.Contains("nohint"); -// var number = e.Args.Select(s => -// { -// int num; -// return new Tuple(int.TryParse(s, out num), num); -// }).Where(t => t.Item1).Select(t => t.Item2).FirstOrDefault(); -// if (number < 0) -// return; -// var triviaGame = new TriviaGame(e, showHints, number == 0 ? 10 : number); -// if (RunningTrivias.TryAdd(e.Server.Id, triviaGame)) -// await imsg.Channel.SendMessageAsync($"**Trivia game started! {triviaGame.WinRequirement} points needed to win.**").ConfigureAwait(false); -// else -// await triviaGame.StopGame().ConfigureAwait(false); -// } -// else -// await imsg.Channel.SendMessageAsync("Trivia game is already running on this server.\n" + trivia.CurrentQuestion).ConfigureAwait(false); -// }); - -// cgb.CreateCommand(Module.Prefix + "tl") -// .Description($"Shows a current trivia leaderboard. | `{Prefix}tl`") -// .Do(async e => -// { -// TriviaGame trivia; -// if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) -// await imsg.Channel.SendMessageAsync(trivia.GetLeaderboard()).ConfigureAwait(false); -// else -// await imsg.Channel.SendMessageAsync("No trivia is running on this server.").ConfigureAwait(false); -// }); - -// cgb.CreateCommand(Module.Prefix + "tq") -// .Description($"Quits current trivia after current question. | `{Prefix}tq`") -// .Do(async e => -// { -// TriviaGame trivia; -// if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) -// { -// await trivia.StopGame().ConfigureAwait(false); -// } -// else -// await imsg.Channel.SendMessageAsync("No trivia is running on this server.").ConfigureAwait(false); -// }); -// } -// } -//} diff --git a/src/NadekoBot/Modules/Games/Commands/TriviaCommands.cs b/src/NadekoBot/Modules/Games/Commands/TriviaCommands.cs new file mode 100644 index 00000000..e91133a6 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Commands/TriviaCommands.cs @@ -0,0 +1,80 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Classes; +using NadekoBot.Modules.Games.Commands.Trivia; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +//todo DB +//todo Rewrite? + +namespace NadekoBot.Modules.Games.Commands +{ + public partial class GamesModule + { + + [Group] + public class TriviaCommands + { + public static ConcurrentDictionary RunningTrivias = new ConcurrentDictionary(); + + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + public async Task Trivia(IMessage imsg, string[] args) + { + var channel = imsg.Channel as IGuildChannel; + + TriviaGame trivia; + if (!RunningTrivias.TryGetValue(channel.Guild.Id, out trivia)) + { + var showHints = !args.Contains("nohint"); + var number = args.Select(s => + { + int num; + return new Tuple(int.TryParse(s, out num), num); + }).Where(t => t.Item1).Select(t => t.Item2).FirstOrDefault(); + if (number < 0) + return; + var triviaGame = new TriviaGame(channel.Guild, imsg.Channel as ITextChannel, showHints, number == 0 ? 10 : number); + if (RunningTrivias.TryAdd(channel.Guild.Id, triviaGame)) + await imsg.Channel.SendMessageAsync($"**Trivia game started! {triviaGame.WinRequirement} points needed to win.**").ConfigureAwait(false); + else + await triviaGame.StopGame().ConfigureAwait(false); + } + else + await imsg.Channel.SendMessageAsync("Trivia game is already running on this server.\n" + trivia.CurrentQuestion).ConfigureAwait(false); + } + + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + public async Task Tl(IMessage imsg) + { + var channel = imsg.Channel as IGuildChannel; + + TriviaGame trivia; + if (RunningTrivias.TryGetValue(channel.Guild.Id, out trivia)) + await imsg.Channel.SendMessageAsync(trivia.GetLeaderboard()).ConfigureAwait(false); + else + await imsg.Channel.SendMessageAsync("No trivia is running on this server.").ConfigureAwait(false); + } + + [LocalizedCommand, LocalizedDescription, LocalizedSummary] + [RequireContext(ContextType.Guild)] + public async Task Tq(IMessage imsg) + { + var channel = imsg.Channel as IGuildChannel; + + TriviaGame trivia; + if (RunningTrivias.TryRemove(channel.Guild.Id, out trivia)) + { + await trivia.StopGame().ConfigureAwait(false); + } + else + await imsg.Channel.SendMessageAsync("No trivia is running on this server.").ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs index 1f9f3989..982f8b14 100644 --- a/src/NadekoBot/_Extensions/Extensions.cs +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -137,5 +137,85 @@ namespace NadekoBot.Extensions return string.Concat(str.Take(maxLength - 3)) + (hideDots ? "" : "..."); } + /// + /// Removes trailing S or ES (if specified) on the given string if the num is 1 + /// + /// + /// + /// + /// String with the correct singular/plural form + public static string SnPl(this string str, int? num, bool es = false) + { + if (str == null) + throw new ArgumentNullException(nameof(str)); + if (num == null) + throw new ArgumentNullException(nameof(num)); + return num == 1 ? str.Remove(str.Length - 1, es ? 2 : 1) : str; + } + + //http://www.dotnetperls.com/levenshtein + public static int LevenshteinDistance(this string s, string t) + { + var n = s.Length; + var m = t.Length; + var d = new int[n + 1, m + 1]; + + // Step 1 + if (n == 0) + { + return m; + } + + if (m == 0) + { + return n; + } + + // Step 2 + for (var i = 0; i <= n; d[i, 0] = i++) + { + } + + for (var j = 0; j <= m; d[0, j] = j++) + { + } + + // Step 3 + for (var i = 1; i <= n; i++) + { + //Step 4 + for (var j = 1; j <= m; j++) + { + // Step 5 + var cost = (t[j - 1] == s[i - 1]) ? 0 : 1; + + // Step 6 + d[i, j] = Math.Min( + Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), + d[i - 1, j - 1] + cost); + } + } + // Step 7 + return d[n, m]; + } + + public static int KiB(this int value) => value * 1024; + public static int KB(this int value) => value * 1000; + + public static int MiB(this int value) => value.KiB() * 1024; + public static int MB(this int value) => value.KB() * 1000; + + public static int GiB(this int value) => value.MiB() * 1024; + public static int GB(this int value) => value.MB() * 1000; + + public static ulong KiB(this ulong value) => value * 1024; + public static ulong KB(this ulong value) => value * 1000; + + public static ulong MiB(this ulong value) => value.KiB() * 1024; + public static ulong MB(this ulong value) => value.KB() * 1000; + + public static ulong GiB(this ulong value) => value.MiB() * 1024; + public static ulong GB(this ulong value) => value.MB() * 1000; + } } \ No newline at end of file