Trivia rewritten, small cleanup.
This commit is contained in:
135
NadekoBot/Classes/Trivia/TriviaGame.cs
Normal file
135
NadekoBot/Classes/Trivia/TriviaGame.cs
Normal file
@ -0,0 +1,135 @@
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Classes.Trivia {
|
||||
class TriviaGame {
|
||||
private readonly object _guessLock = new object();
|
||||
|
||||
private Server _server { get; }
|
||||
private Channel _channel { get; }
|
||||
|
||||
private int QuestionDurationMiliseconds { get; } = 30000;
|
||||
private int HintTimeoutMiliseconds { get; } = 6000;
|
||||
private CancellationTokenSource triviaCancelSource { get; set; }
|
||||
|
||||
public TriviaQuestion CurrentQuestion { get; private set; }
|
||||
public List<TriviaQuestion> oldQuestions { get; } = new List<TriviaQuestion>();
|
||||
|
||||
public ConcurrentDictionary<User, int> users { get; } = new ConcurrentDictionary<User, int>();
|
||||
|
||||
public bool GameActive { get; private set; } = false;
|
||||
public bool ShouldStopGame { get; private set; }
|
||||
|
||||
public int WinRequirement { get; } = 3;
|
||||
|
||||
public TriviaGame(CommandEventArgs e) {
|
||||
_server = e.Server;
|
||||
_channel = e.Channel;
|
||||
Task.Run(() => StartGame());
|
||||
}
|
||||
|
||||
private async Task StartGame() {
|
||||
while (!ShouldStopGame) {
|
||||
// reset the cancellation source
|
||||
triviaCancelSource = new CancellationTokenSource();
|
||||
// load question
|
||||
CurrentQuestion = TriviaQuestionPool.Instance.GetRandomQuestion(oldQuestions);
|
||||
if (CurrentQuestion == null) {
|
||||
await _channel.SendMessage($":exclamation: Failed loading a trivia question");
|
||||
End();
|
||||
return;
|
||||
}
|
||||
oldQuestions.Add(CurrentQuestion); //add it to exclusion list so it doesn't show up again
|
||||
//sendquestion
|
||||
await _channel.SendMessage($":question: **{CurrentQuestion.Question}**");
|
||||
|
||||
//receive messages
|
||||
NadekoBot.client.MessageReceived += PotentialGuess;
|
||||
|
||||
//allow people to guess
|
||||
GameActive = true;
|
||||
|
||||
try {
|
||||
//hint
|
||||
await Task.Delay(HintTimeoutMiliseconds, triviaCancelSource.Token);
|
||||
await _channel.SendMessage($":exclamation:**Hint:** {CurrentQuestion.GetHint()}");
|
||||
|
||||
//timeout
|
||||
await Task.Delay(QuestionDurationMiliseconds - HintTimeoutMiliseconds, triviaCancelSource.Token);
|
||||
|
||||
|
||||
} catch (TaskCanceledException) {
|
||||
Console.WriteLine("Trivia cancelled");
|
||||
|
||||
} finally {
|
||||
GameActive = false;
|
||||
if (!triviaCancelSource.IsCancellationRequested)
|
||||
await _channel.Send($":clock2: :question: **Time's up!** The correct answer was **{CurrentQuestion.Answer}**");
|
||||
NadekoBot.client.MessageReceived -= PotentialGuess;
|
||||
}
|
||||
// load next question if game is still running
|
||||
await Task.Delay(2000);
|
||||
}
|
||||
End();
|
||||
}
|
||||
|
||||
private void End() {
|
||||
_channel.SendMessage("**Trivia game ended**");
|
||||
_channel.SendMessage(GetLeaderboard());
|
||||
TriviaGame throwAwayValue;
|
||||
Commands.Trivia.runningTrivias.TryRemove(_server, out throwAwayValue);
|
||||
}
|
||||
|
||||
public void StopGame() {
|
||||
if (!ShouldStopGame)
|
||||
_channel.SendMessage(":exclamation: Trivia will stop after this question.");
|
||||
ShouldStopGame = true;
|
||||
}
|
||||
|
||||
private async void PotentialGuess(object sender, MessageEventArgs e) {
|
||||
if (e.Channel.IsPrivate) return;
|
||||
if (e.Server != _server) return;
|
||||
|
||||
bool guess = false;
|
||||
lock (_guessLock) {
|
||||
if (GameActive && CurrentQuestion.IsAnswerCorrect(e.Message.Text) && !triviaCancelSource.IsCancellationRequested) {
|
||||
users.TryAdd(e.User, 0); //add if not exists
|
||||
users[e.User]++; //add 1 point to the winner
|
||||
guess = true;
|
||||
triviaCancelSource.Cancel();
|
||||
}
|
||||
}
|
||||
if (guess) {
|
||||
await _channel.SendMessage($"☑️ {e.User.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**");
|
||||
if (users[e.User] == WinRequirement) {
|
||||
ShouldStopGame = true;
|
||||
await _channel.Send($":exclamation: We have a winner! Its {e.User.Mention}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string GetLeaderboard() {
|
||||
if (users.Count == 0)
|
||||
return "";
|
||||
|
||||
string str = "**Leaderboard:**\n-----------\n";
|
||||
|
||||
if (users.Count > 1)
|
||||
users.OrderBy(kvp => kvp.Value);
|
||||
|
||||
foreach (var kvp in users) {
|
||||
str += $"**{kvp.Key.Name}** has {kvp.Value} points".ToString().SnPl(kvp.Value) + Environment.NewLine;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
78
NadekoBot/Classes/Trivia/TriviaQuestion.cs
Normal file
78
NadekoBot/Classes/Trivia/TriviaQuestion.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// THANKS @ShoMinamimoto for suggestions and coding help
|
||||
namespace NadekoBot.Classes.Trivia {
|
||||
public class TriviaQuestion {
|
||||
//represents the min size to judge levDistance with
|
||||
public static List<Tuple<int, int>> strictness = new List<Tuple<int, int>>() {
|
||||
new Tuple<int, int>(6, 0),
|
||||
new Tuple<int, int>(7, 1),
|
||||
new Tuple<int, int>(12, 2),
|
||||
new Tuple<int, int>(17, 3),
|
||||
new Tuple<int, int>(22, 4),
|
||||
};
|
||||
public static int maxStringLength = 22;
|
||||
|
||||
public string Category;
|
||||
public string Question;
|
||||
public string Answer;
|
||||
|
||||
public TriviaQuestion(string q, string a, string c) {
|
||||
this.Question = q;
|
||||
this.Answer = a;
|
||||
this.Category = c;
|
||||
}
|
||||
|
||||
public string GetHint() => Answer.Scramble();
|
||||
|
||||
public bool IsAnswerCorrect(string guess) {
|
||||
guess = CleanGuess(guess);
|
||||
if (Answer.Equals(guess)) {
|
||||
return true;
|
||||
}
|
||||
Answer = CleanGuess(Answer);
|
||||
guess = CleanGuess(guess);
|
||||
if (Answer.Equals(guess)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int levDistance = Answer.LevenshteinDistance(guess);
|
||||
return JudgeGuess(Answer.Length, guess.Length, levDistance);
|
||||
}
|
||||
|
||||
private bool JudgeGuess(int guessLength, int answerLength, int levDistance) {
|
||||
foreach (Tuple<int, int> level in strictness) {
|
||||
if (guessLength <= level.Item1 || answerLength <= level.Item1) {
|
||||
if (levDistance <= level.Item2)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private string CleanGuess(string str) {
|
||||
str = " " + str.ToLower() + " ";
|
||||
str = Regex.Replace(str, "\\s+", " ");
|
||||
str = Regex.Replace(str, "[^\\w\\d\\s]", "");
|
||||
//Here's where custom modification can be done
|
||||
str = Regex.Replace(str, "\\s(a|an|the|of|in|for|to|as|at|be)\\s", " ");
|
||||
//End custom mod and cleanup whitespace
|
||||
str = Regex.Replace(str, "^\\s+", "");
|
||||
str = Regex.Replace(str, "\\s+$", "");
|
||||
//Trim the really long answers
|
||||
str = str.Length <= maxStringLength ? str : str.Substring(0, maxStringLength);
|
||||
return str;
|
||||
}
|
||||
|
||||
public override string ToString() =>
|
||||
"Question: **" + this.Question + "?**";
|
||||
}
|
||||
}
|
38
NadekoBot/Classes/Trivia/TriviaQuestionPool.cs
Normal file
38
NadekoBot/Classes/Trivia/TriviaQuestionPool.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NadekoBot.Classes.Trivia {
|
||||
public class TriviaQuestionPool {
|
||||
private static readonly TriviaQuestionPool _instance = new TriviaQuestionPool();
|
||||
public static TriviaQuestionPool Instance => _instance;
|
||||
|
||||
public List<TriviaQuestion> pool = new List<TriviaQuestion>();
|
||||
|
||||
private Random _r { get; } = new Random();
|
||||
|
||||
static TriviaQuestionPool() { }
|
||||
|
||||
private TriviaQuestionPool() {
|
||||
Reload();
|
||||
}
|
||||
|
||||
public TriviaQuestion GetRandomQuestion(List<TriviaQuestion> exclude) =>
|
||||
pool.Except(exclude).OrderBy(x => _r.Next()).FirstOrDefault();
|
||||
|
||||
internal void Reload() {
|
||||
JArray arr = JArray.Parse(File.ReadAllText("data/questions.txt"));
|
||||
|
||||
foreach (var item in arr) {
|
||||
TriviaQuestion tq;
|
||||
tq = new TriviaQuestion(item["Question"].ToString(), item["Answer"].ToString(),item["Category"]?.ToString());
|
||||
|
||||
pool.Add(tq);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user