Trivia converted, removed currency reward

This commit is contained in:
Kwoth 2016-08-16 15:29:48 +02:00
parent 8a7ae9339e
commit d6465fb6af
10 changed files with 213 additions and 107 deletions

View File

@ -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<TriviaQuestion> oldQuestions { get; } = new HashSet<TriviaQuestion>();
public ConcurrentDictionary<User, int> Users { get; } = new ConcurrentDictionary<User, int>();
public ConcurrentDictionary<IGuildUser, int> Users { get; } = new ConcurrentDictionary<IGuildUser, int>();
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();

View File

@ -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) + "`";
}
}
}

View File

@ -9,7 +9,7 @@ namespace NadekoBot.Modules.Games.Commands.Trivia
public class TriviaQuestionPool
{
public static TriviaQuestionPool Instance { get; } = new TriviaQuestionPool();
//todo DB
public HashSet<TriviaQuestion> pool = new HashSet<TriviaQuestion>();
private Random rng { get; } = new Random();

View File

@ -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<ulong, TriviaGame> RunningTrivias = new ConcurrentDictionary<ulong, TriviaGame>();
// 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<bool, int>(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);
// });
// }
// }
//}

View File

@ -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<ulong, TriviaGame> RunningTrivias = new ConcurrentDictionary<ulong, TriviaGame>();
[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<bool, int>(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);
}
}
}
}

View File

@ -137,5 +137,85 @@ namespace NadekoBot.Extensions
return string.Concat(str.Take(maxLength - 3)) + (hideDots ? "" : "...");
}
/// <summary>
/// Removes trailing S or ES (if specified) on the given string if the num is 1
/// </summary>
/// <param name="str"></param>
/// <param name="num"></param>
/// <param name="es"></param>
/// <returns>String with the correct singular/plural form</returns>
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;
}
}