196 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			196 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using Discord;
 | |
| using Discord.Commands;
 | |
| using NadekoBot.Classes;
 | |
| using NadekoBot.DataModels;
 | |
| using NadekoBot.Extensions;
 | |
| using System;
 | |
| using System.Collections.Concurrent;
 | |
| using System.Collections.Generic;
 | |
| using System.Diagnostics;
 | |
| using System.Linq;
 | |
| using System.Threading.Tasks;
 | |
| 
 | |
| namespace NadekoBot.Modules.Games.Commands
 | |
| {
 | |
| 
 | |
|     public static class SentencesProvider
 | |
|     {
 | |
|         internal static string GetRandomSentence()
 | |
|         {
 | |
|             var data = DbHandler.Instance.GetAllRows<TypingArticle>();
 | |
|             try
 | |
|             {
 | |
|                 return data.ToList()[new Random().Next(0, data.Count())].Text;
 | |
|             }
 | |
|             catch
 | |
|             {
 | |
|                 return "Failed retrieving data from parse. Owner didn't add any articles to type using `typeadd`.";
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public class TypingGame
 | |
|     {
 | |
|         public const float WORD_VALUE = 4.5f;
 | |
|         private readonly Channel channel;
 | |
|         public string CurrentSentence;
 | |
|         public bool IsActive;
 | |
|         private readonly Stopwatch sw;
 | |
|         private readonly List<ulong> finishedUserIds;
 | |
| 
 | |
|         public TypingGame(Channel channel)
 | |
|         {
 | |
|             this.channel = channel;
 | |
|             IsActive = false;
 | |
|             sw = new Stopwatch();
 | |
|             finishedUserIds = new List<ulong>();
 | |
|         }
 | |
| 
 | |
|         public Channel Channell { get; internal set; }
 | |
| 
 | |
|         internal async Task<bool> Stop()
 | |
|         {
 | |
|             if (!IsActive) return false;
 | |
|             NadekoBot.Client.MessageReceived -= AnswerReceived;
 | |
|             finishedUserIds.Clear();
 | |
|             IsActive = false;
 | |
|             sw.Stop();
 | |
|             sw.Reset();
 | |
|             await channel.Send("Typing contest stopped").ConfigureAwait(false);
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         internal async Task Start()
 | |
|         {
 | |
|             while (true)
 | |
|             {
 | |
|                 if (IsActive) return; // can't start running game
 | |
|                 IsActive = true;
 | |
|                 CurrentSentence = SentencesProvider.GetRandomSentence();
 | |
|                 var i = (int)(CurrentSentence.Length / WORD_VALUE * 1.7f);
 | |
|                 await channel.SendMessage($":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.").ConfigureAwait(false);
 | |
| 
 | |
| 
 | |
|                 var msg = await channel.SendMessage("Starting new typing contest in **3**...").ConfigureAwait(false);
 | |
|                 await Task.Delay(1000).ConfigureAwait(false);
 | |
|                 await msg.Edit("Starting new typing contest in **2**...").ConfigureAwait(false);
 | |
|                 await Task.Delay(1000).ConfigureAwait(false);
 | |
|                 await msg.Edit("Starting new typing contest in **1**...").ConfigureAwait(false);
 | |
|                 await Task.Delay(1000).ConfigureAwait(false);
 | |
|                 await msg.Edit($":book:**{CurrentSentence.Replace(" ", " \x200B")}**:book:").ConfigureAwait(false);
 | |
|                 sw.Start();
 | |
|                 HandleAnswers();
 | |
| 
 | |
|                 while (i > 0)
 | |
|                 {
 | |
|                     await Task.Delay(1000).ConfigureAwait(false);
 | |
|                     i--;
 | |
|                     if (!IsActive)
 | |
|                         return;
 | |
|                 }
 | |
| 
 | |
|                 await Stop().ConfigureAwait(false);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private void HandleAnswers()
 | |
|         {
 | |
|             NadekoBot.Client.MessageReceived += AnswerReceived;
 | |
|         }
 | |
| 
 | |
|         private async void AnswerReceived(object sender, MessageEventArgs e)
 | |
|         {
 | |
|             try
 | |
|             {
 | |
|                 if (e.Channel == null || e.Channel.Id != channel.Id || e.User.Id == NadekoBot.Client.CurrentUser.Id) return;
 | |
| 
 | |
|                 var guess = e.Message.RawText;
 | |
| 
 | |
|                 var distance = CurrentSentence.LevenshteinDistance(guess);
 | |
|                 var decision = Judge(distance, guess.Length);
 | |
|                 if (decision && !finishedUserIds.Contains(e.User.Id))
 | |
|                 {
 | |
|                     finishedUserIds.Add(e.User.Id);
 | |
|                     await channel.Send($"{e.User.Mention} finished in **{sw.Elapsed.Seconds}** seconds with { distance } errors, **{ CurrentSentence.Length / WORD_VALUE / sw.Elapsed.Seconds * 60 }** WPM!").ConfigureAwait(false);
 | |
|                     if (finishedUserIds.Count % 2 == 0)
 | |
|                     {
 | |
|                         await e.Channel.SendMessage($":exclamation: `A lot of people finished, here is the text for those still typing:`\n\n:book:**{CurrentSentence}**:book:").ConfigureAwait(false);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             catch { }
 | |
|         }
 | |
| 
 | |
|         private bool Judge(int errors, int textLength) => errors <= textLength / 25;
 | |
| 
 | |
|     }
 | |
| 
 | |
|     internal class SpeedTyping : DiscordCommand
 | |
|     {
 | |
| 
 | |
|         public static ConcurrentDictionary<ulong, TypingGame> RunningContests;
 | |
| 
 | |
|         public SpeedTyping(DiscordModule module) : base(module)
 | |
|         {
 | |
|             RunningContests = new ConcurrentDictionary<ulong, TypingGame>();
 | |
|         }
 | |
| 
 | |
|         public Func<CommandEventArgs, Task> DoFunc() =>
 | |
|             async e =>
 | |
|             {
 | |
|                 var game = RunningContests.GetOrAdd(e.User.Server.Id, id => new TypingGame(e.Channel));
 | |
| 
 | |
|                 if (game.IsActive)
 | |
|                 {
 | |
|                     await e.Channel.SendMessage(
 | |
|                             $"Contest already running in " +
 | |
|                             $"{game.Channell.Mention} channel.")
 | |
|                                 .ConfigureAwait(false);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     await game.Start().ConfigureAwait(false);
 | |
|                 }
 | |
|             };
 | |
| 
 | |
|         private Func<CommandEventArgs, Task> QuitFunc() =>
 | |
|             async e =>
 | |
|             {
 | |
|                 TypingGame game;
 | |
|                 if (RunningContests.TryRemove(e.User.Server.Id, out game))
 | |
|                 {
 | |
|                     await game.Stop().ConfigureAwait(false);
 | |
|                     return;
 | |
|                 }
 | |
|                 await e.Channel.SendMessage("No contest to stop on this channel.").ConfigureAwait(false);
 | |
|             };
 | |
| 
 | |
|         internal override void Init(CommandGroupBuilder cgb)
 | |
|         {
 | |
|             cgb.CreateCommand(Module.Prefix + "typestart")
 | |
|                 .Description($"Starts a typing contest. | `{Prefix}typestart`")
 | |
|                 .Do(DoFunc());
 | |
| 
 | |
|             cgb.CreateCommand(Module.Prefix + "typestop")
 | |
|                 .Description($"Stops a typing contest on the current channel. | `{Prefix}typestop`")
 | |
|                 .Do(QuitFunc());
 | |
| 
 | |
|             cgb.CreateCommand(Module.Prefix + "typeadd")
 | |
|                 .Description($"Adds a new article to the typing contest. Owner only. | `{Prefix}typeadd wordswords`")
 | |
|                 .Parameter("text", ParameterType.Unparsed)
 | |
|                 .Do(async e =>
 | |
|                 {
 | |
|                     if (!NadekoBot.IsOwner(e.User.Id) || string.IsNullOrWhiteSpace(e.GetArg("text"))) return;
 | |
| 
 | |
|                     DbHandler.Instance.Connection.Insert(new TypingArticle
 | |
|                     {
 | |
|                         Text = e.GetArg("text"),
 | |
|                         DateAdded = DateTime.Now
 | |
|                     });
 | |
| 
 | |
|                     await e.Channel.SendMessage("Added new article for typing game.").ConfigureAwait(false);
 | |
|                 });
 | |
|         }
 | |
|     }
 | |
| }
 |