Merge pull request #5 from micmorris/update-trivia-correctness

Trivia improved
This commit is contained in:
Master Kwoth 2016-01-23 08:32:32 +01:00
commit 80077445f8

View File

@ -1,326 +1,436 @@
using Discord; using Discord;
using Discord.Commands; using Discord.Commands;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Text.RegularExpressions;
using System.Timers; using System.Threading.Tasks;
using NadekoBot.Extensions; using System.Timers;
using NadekoBot.Extensions;
namespace NadekoBot using System.Collections;
{
public class Trivia : DiscordCommand namespace NadekoBot
{ {
public static Dictionary<ulong, TriviaGame> runningTrivias; public class Trivia : DiscordCommand
{
public Trivia() : base() { public static Dictionary<ulong, TriviaGame> runningTrivias;
runningTrivias = new Dictionary<ulong, TriviaGame>();
} public Trivia() : base() {
runningTrivias = new Dictionary<ulong, TriviaGame>();
public static TriviaGame StartNewGame(CommandEventArgs e) { }
if (runningTrivias.ContainsKey(e.User.Server.Id))
return null; public static TriviaGame StartNewGame(CommandEventArgs e) {
if (runningTrivias.ContainsKey(e.User.Server.Id))
var tg = new TriviaGame(e, NadekoBot.client); return null;
runningTrivias.Add(e.Server.Id, tg);
var tg = new TriviaGame(e, NadekoBot.client);
return tg; runningTrivias.Add(e.Server.Id, tg);
}
return tg;
public TriviaQuestion GetCurrentQuestion(ulong serverId) => runningTrivias[serverId].currentQuestion; }
public override Func<CommandEventArgs, Task> DoFunc() => async e => public TriviaQuestion GetCurrentQuestion(ulong serverId) => runningTrivias[serverId].currentQuestion;
{
TriviaGame tg; public override Func<CommandEventArgs, Task> DoFunc() => async e =>
if ((tg = StartNewGame(e)) != null) {
{ TriviaGame tg;
await e.Send("**Trivia game started!**\nFirst player to get to 10 points wins! You have 30 seconds per question.\nUse command [tq] if game was started by accident.\nTyping [idfk] 15 seconds after the question has started will give you a hint."); if ((tg = StartNewGame(e)) != null)
} {
else await e.Send("**Trivia game started!**\nFirst player to get to 10 points wins! You have 30 seconds per question.\nUse command [tq] if game was started by accident.\nTyping [idfk] 15 seconds after the question has started will give you a hint.");
await e.Send("Trivia game is already running on this server. The question is:\n**" + GetCurrentQuestion(e.Server.Id).Question + "**"); }
}; else
await e.Send("Trivia game is already running on this server. The question is:\n**" + GetCurrentQuestion(e.Server.Id).Question + "**");
private Func<CommandEventArgs, Task> LbFunc() => async e => };
{
if (runningTrivias.ContainsKey(e.Server.Id)) private Func<CommandEventArgs, Task> LbFunc() => async e =>
{ {
var lb = runningTrivias[e.User.Server.Id].GetLeaderboard(); if (runningTrivias.ContainsKey(e.Server.Id))
await e.Send(lb); {
} var lb = runningTrivias[e.User.Server.Id].GetLeaderboard();
else await e.Send(lb);
await e.Send("Trivia game is not running on this server."); }
}; else
await e.Send("Trivia game is not running on this server.");
private Func<CommandEventArgs, Task> RepeatFunc() => async e => };
{
if (runningTrivias.ContainsKey(e.Server.Id)) private Func<CommandEventArgs, Task> RepeatFunc() => async e =>
{ {
var lb = runningTrivias[e.User.Server.Id].GetLeaderboard(); if (runningTrivias.ContainsKey(e.Server.Id))
await e.Send(lb); {
} var lb = runningTrivias[e.User.Server.Id].GetLeaderboard();
else await e.Send(lb);
await e.Send("Trivia game is not running on this server."); }
}; else
await e.Send("Trivia game is not running on this server.");
public override void Init(CommandGroupBuilder cgb) };
{
cgb.CreateCommand("t") public override void Init(CommandGroupBuilder cgb)
.Description("Starts a game of trivia.") {
.Alias("-t") cgb.CreateCommand("t")
.Do(DoFunc()); .Description("Starts a game of trivia.")
.Alias("-t")
cgb.CreateCommand("tl") .Do(DoFunc());
.Description("Shows a current trivia leaderboard.")
.Alias("-tl") cgb.CreateCommand("tl")
.Alias("tlb") .Description("Shows a current trivia leaderboard.")
.Alias("-tlb") .Alias("-tl")
.Do(LbFunc()); .Alias("tlb")
.Alias("-tlb")
cgb.CreateCommand("tq") .Do(LbFunc());
.Description("Quits current trivia after current question.")
.Alias("-tq") cgb.CreateCommand("tq")
.Do(QuitFunc()); .Description("Quits current trivia after current question.")
} .Alias("-tq")
.Do(QuitFunc());
private Func<CommandEventArgs, Task> QuitFunc() => async e => }
{
if (runningTrivias.ContainsKey(e.Server.Id) && runningTrivias[e.Server.Id].ChannelId == e.Channel.Id) private Func<CommandEventArgs, Task> QuitFunc() => async e =>
{ {
await e.Send("Trivia will stop after this question. Run [**@NadekoBot clr**] to remove this bot's messages from the channel."); if (runningTrivias.ContainsKey(e.Server.Id) && runningTrivias[e.Server.Id].ChannelId == e.Channel.Id)
runningTrivias[e.Server.Id].StopGame(); {
} await e.Send("Trivia will stop after this question. Run [**@NadekoBot clr**] to remove this bot's messages from the channel.");
else await e.Send("No trivias are running on this channel."); runningTrivias[e.Server.Id].StopGame();
}; }
else await e.Send("No trivias are running on this channel.");
internal static void FinishGame(TriviaGame triviaGame) };
{
runningTrivias.Remove(runningTrivias.Where(kvp => kvp.Value == triviaGame).First().Key); internal static void FinishGame(TriviaGame triviaGame)
} {
} runningTrivias.Remove(runningTrivias.Where(kvp => kvp.Value == triviaGame).First().Key);
}
public class TriviaGame { }
private DiscordClient client; public class TriviaGame {
private ulong _serverId;
private ulong _channellId; private DiscordClient client;
private ulong _serverId;
public ulong ChannelId => _channellId; private ulong _channellId;
private Dictionary<ulong, int> users; public ulong ChannelId => _channellId;
public List<string> oldQuestions; private Dictionary<ulong, int> users;
public TriviaQuestion currentQuestion = null; public List<string> oldQuestions;
private bool active = false; public TriviaQuestion currentQuestion = null;
private Timer timeout; private bool active = false;
private Stopwatch stopwatch;
private bool isQuit = false; //represents the min size to judge levDistance with
private List<Tuple<int, int>> strictness;
public TriviaGame(CommandEventArgs starter, DiscordClient client) { private int maxStringLength;
this.users = new Dictionary<ulong, int>();
this.client = client; private Timer timeout;
this._serverId = starter.Server.Id; private Stopwatch stopwatch;
this._channellId= starter.Channel.Id; private bool isQuit = false;
oldQuestions = new List<string>(); public TriviaGame(CommandEventArgs starter, DiscordClient client) {
client.MessageReceived += PotentialGuess; this.users = new Dictionary<ulong, int>();
this.client = client;
timeout = new Timer(); this._serverId = starter.Server.Id;
timeout.Interval = 30000; this._channellId= starter.Channel.Id;
stopwatch = new Stopwatch();
timeout.Elapsed += (s, e) => { TimeUp(); }; oldQuestions = new List<string>();
client.MessageReceived += PotentialGuess;
LoadNextRound();
} strictness = new List<Tuple<int, int>>();
strictness.Add(new Tuple<int, int>(6, 0));
private async void PotentialGuess(object sender, MessageEventArgs e) strictness.Add(new Tuple<int, int>(9, 1));
{ strictness.Add(new Tuple<int, int>(13, 2));
if (e.Server == null || e.Channel == null) return; strictness.Add(new Tuple<int, int>(16, 3));
if (e.Server.Id != _serverId || !active) strictness.Add(new Tuple<int, int>(21, 4));
return; maxStringLength = 22;
if (e.Message.Text.ToLower().Equals("idfk")) { timeout = new Timer();
GetHint(e); timeout.Interval = 30000;
return; stopwatch = new Stopwatch();
} timeout.Elapsed += (s, e) => { TimeUp(); };
if (e.Message.Text.ToLower() == currentQuestion.Answer.ToLower()) LoadNextRound();
{ }
active = false; //start pause between rounds
timeout.Enabled = false; private async void PotentialGuess(object sender, MessageEventArgs e)
stopwatch.Stop(); {
if (e.Server == null || e.Channel == null) return;
if (!users.ContainsKey(e.User.Id)) if (e.Server.Id != _serverId || !active)
users.Add(e.User.Id, 1); return;
else
{ if (e.User.Id == client.CurrentUser.Id)
users[e.User.Id]++; return;
}
await e.Send( e.User.Mention + " Guessed it!\n The answer was: **" + currentQuestion.Answer + "**"); if (e.Message.Text.ToLower().Equals("idfk")) {
GetHint(e);
if (users[e.User.Id] >= 10) { return;
await e.Send( " We have a winner! It's " + e.User.Mention+"\n"+GetLeaderboard()+"\n To start a new game type '@NadekoBot t'"); }
FinishGame();
return; if (IsAnswerCorrect(e.Message.Text.ToLower(), currentQuestion.Answer.ToLower()))
} {
active = false; //start pause between rounds
//if it still didnt return, we can safely start another round :D timeout.Enabled = false;
LoadNextRound(); stopwatch.Stop();
}
} if (!users.ContainsKey(e.User.Id))
users.Add(e.User.Id, 1);
public async void GetHint(MessageEventArgs e) { else
if (timeout != null && !isQuit && stopwatch.ElapsedMilliseconds > 10000) {
await e.Send( currentQuestion.Answer.Scramble()); users[e.User.Id]++;
else { }
await e.Send( $"You have to wait {10-stopwatch.ElapsedMilliseconds/1000} more seconds in order to get a hint."); await e.Send( e.User.Mention + " Guessed it!\n The answer was: **" + currentQuestion.Answer + "**");
}
} if (users[e.User.Id] >= 10) {
await e.Send( " We have a winner! It's " + e.User.Mention+"\n"+GetLeaderboard()+"\n To start a new game type '@NadekoBot t'");
public void StopGame() { FinishGame();
isQuit = true; return;
} }
private void LoadNextRound() //if it still didnt return, we can safely start another round :D
{ LoadNextRound();
Channel ch = client.GetChannel(_channellId); }
}
if(currentQuestion!=null) private bool IsAnswerCorrect(string guess, string answer)
oldQuestions.Add(currentQuestion.Question); {
if(guess.Equals(answer))
currentQuestion = TriviaQuestionsPool.Instance.GetRandomQuestion(oldQuestions); {
return true;
if (currentQuestion == null || isQuit) }
{ guess = CleanString(guess);
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed answer = CleanString(answer);
ch.Send("Trivia bot stopping. :\\\n" + GetLeaderboard()); if (guess.Equals(answer))
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed {
FinishGame(); return true;
return; }
}
Timer t = new Timer(); int levDistance = ComputeLevenshteinDistance(guess, answer);
t.Interval = 2500; return Judge(guess.Length, answer.Length, levDistance);
t.Enabled = true; }
t.Elapsed += async (s, ev) => {
active = true; private bool Judge(int guessLength, int answerLength, int levDistance)
await ch.Send(currentQuestion.ToString()); {
t.Enabled = false; foreach(Tuple<int, int> level in strictness)
timeout.Enabled = true;//starting countdown of the next question {
stopwatch.Reset(); if(guessLength <= level.Item1 || answerLength <= level.Item1)
stopwatch.Start(); {
}; if (levDistance <= level.Item2)
return; return true;
} else
return false;
private async void TimeUp() { }
await client.GetChannel(_channellId)?.Send("**Time's up.**\nCorrect answer was: **" + currentQuestion.Answer+"**\n\n*[tq quits trivia][tl shows leaderboard]["+NadekoBot.botMention+" clr clears my messages]*"); }
LoadNextRound(); return false;
} }
public void FinishGame() { private string CleanString(string str)
isQuit = true; {
active = false; str = " " + str + " ";
client.MessageReceived -= PotentialGuess; str = Regex.Replace(str, "\\s+", " ");
timeout.Enabled = false; str = Regex.Replace(str, "[^\\w\\d\\s]", "");
timeout.Dispose(); //Here's where custom modification can be done
stopwatch.Stop(); str = Regex.Replace(str, "\\s(a|an|the|of|in|for|to|as|at|be)\\s", " ");
stopwatch.Reset(); //End custom mod and cleanup whitespace
Trivia.FinishGame(this); str = Regex.Replace(str, "^\\s+", "");
} str = Regex.Replace(str, "\\s+$", "");
//Trim the really long answers
public string GetLeaderboard() { str = str.Length <= maxStringLength ? str : str.Substring(0, maxStringLength);
if (users.Count == 0) return str;
return ""; }
//http://www.dotnetperls.com/levenshtein
string str = "**Leaderboard:**\n-----------\n"; private int ComputeLevenshteinDistance(string s, string t)
{
if(users.Count>1) int n = s.Length;
users.OrderBy(kvp => kvp.Value); int m = t.Length;
int[,] d = new int[n + 1, m + 1];
foreach (var KeyValuePair in users)
{ // Step 1
str += "**" + client.GetServer(_serverId).GetUser(KeyValuePair.Key).Name + "** has " +KeyValuePair.Value + (KeyValuePair.Value == 1 ? "point." : "points.") + Environment.NewLine; if (n == 0)
} {
return m;
return str; }
}
} if (m == 0)
{
public class TriviaQuestion return n;
{ }
public string Category;
public string Question; // Step 2
public string Answer; for (int i = 0; i <= n; d[i, 0] = i++)
public TriviaQuestion(string q, string a) {
{ }
this.Question = q;
this.Answer = a; for (int j = 0; j <= m; d[0, j] = j++)
} {
}
public override string ToString() =>
this.Category == null ? // Step 3
"--------**Q**--------\nQuestion: **" + this.Question + "?**" : for (int i = 1; i <= n; i++)
"--------Q--------\nCategory: " + this.Category + "\nQuestion: **" + this.Question + "?**"; {
} //Step 4
for (int j = 1; j <= m; j++)
public class TriviaQuestionsPool { {
private static TriviaQuestionsPool instance = null; // Step 5
int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;
public static TriviaQuestionsPool Instance
{ // Step 6
get { d[i, j] = Math.Min(
if (instance == null) Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
instance = new TriviaQuestionsPool(); d[i - 1, j - 1] + cost);
return instance; }
} }
private set { instance = value; } // Step 7
} return d[n, m];
}
public List<TriviaQuestion> pool;
public async void GetHint(MessageEventArgs e) {
private Random _r; if (timeout != null && !isQuit && stopwatch.ElapsedMilliseconds > 10000)
await e.Send( currentQuestion.Answer.Scramble());
public TriviaQuestionsPool() else {
{ await e.Send( $"You have to wait {10-stopwatch.ElapsedMilliseconds/1000} more seconds in order to get a hint.");
_r = new Random(); }
pool = new List<TriviaQuestion>(); }
JArray arr = JArray.Parse(File.ReadAllText("questions.txt"));
public void StopGame() {
foreach (var item in arr) isQuit = true;
{ }
TriviaQuestion tq;
tq = new TriviaQuestion((string)item["Question"], (string)item["Answer"]); private void LoadNextRound()
{
if (item?["Category"] != null) Channel ch = client.GetChannel(_channellId);
{
tq.Category = item["Category"].ToString();
} if(currentQuestion!=null)
oldQuestions.Add(currentQuestion.Question);
pool.Add(tq);
} currentQuestion = TriviaQuestionsPool.Instance.GetRandomQuestion(oldQuestions);
}
if (currentQuestion == null || isQuit)
{
public TriviaQuestion GetRandomQuestion(List<string> exclude) { #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
if (pool.Count == 0) ch.Send("Trivia bot stopping. :\\\n" + GetLeaderboard());
return null; #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
FinishGame();
TriviaQuestion tq = pool[_r.Next(0, pool.Count)]; return;
if (exclude.Count > 0 && exclude.Count < pool.Count) }
{ Timer t = new Timer();
while (exclude.Contains(tq.Question)) t.Interval = 2500;
{ t.Enabled = true;
tq = pool[_r.Next(0, pool.Count)]; t.Elapsed += async (s, ev) => {
} active = true;
} await ch.Send(currentQuestion.ToString());
return tq; t.Enabled = false;
} timeout.Enabled = true;//starting countdown of the next question
} stopwatch.Reset();
} stopwatch.Start();
};
return;
}
private async void TimeUp() {
await client.GetChannel(_channellId)?.Send("**Time's up.**\nCorrect answer was: **" + currentQuestion.Answer+"**\n\n*[tq quits trivia][tl shows leaderboard]["+NadekoBot.botMention+" clr clears my messages]*");
LoadNextRound();
}
public void FinishGame() {
isQuit = true;
active = false;
client.MessageReceived -= PotentialGuess;
timeout.Enabled = false;
timeout.Dispose();
stopwatch.Stop();
stopwatch.Reset();
Trivia.FinishGame(this);
}
public string GetLeaderboard() {
if (users.Count == 0)
return "";
string str = "**Leaderboard:**\n-----------\n";
if(users.Count>1)
users.OrderBy(kvp => kvp.Value);
foreach (var KeyValuePair in users)
{
str += "**" + client.GetServer(_serverId).GetUser(KeyValuePair.Key).Name + "** has " +KeyValuePair.Value + (KeyValuePair.Value == 1 ? "point." : "points.") + Environment.NewLine;
}
return str;
}
}
public class TriviaQuestion
{
public string Category;
public string Question;
public string Answer;
public TriviaQuestion(string q, string a)
{
this.Question = q;
this.Answer = a;
}
public override string ToString() =>
this.Category == null ?
"--------**Q**--------\nQuestion: **" + this.Question + "?**" :
"--------Q--------\nCategory: " + this.Category + "\nQuestion: **" + this.Question + "?**";
}
public class TriviaQuestionsPool {
private static TriviaQuestionsPool instance = null;
public static TriviaQuestionsPool Instance
{
get {
if (instance == null)
instance = new TriviaQuestionsPool();
return instance;
}
private set { instance = value; }
}
public List<TriviaQuestion> pool;
private Random _r;
public TriviaQuestionsPool()
{
_r = new Random();
pool = new List<TriviaQuestion>();
JArray arr = JArray.Parse(File.ReadAllText("questions.txt"));
foreach (var item in arr)
{
TriviaQuestion tq;
tq = new TriviaQuestion((string)item["Question"], (string)item["Answer"]);
if (item?["Category"] != null)
{
tq.Category = item["Category"].ToString();
}
pool.Add(tq);
}
}
public TriviaQuestion GetRandomQuestion(List<string> exclude) {
if (pool.Count == 0)
return null;
TriviaQuestion tq = pool[_r.Next(0, pool.Count)];
if (exclude.Count > 0 && exclude.Count < pool.Count)
{
while (exclude.Contains(tq.Question))
{
tq = pool[_r.Next(0, pool.Count)];
}
}
return tq;
}
}
}