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