Acrophobia added, closes #605
This commit is contained in:
		
							
								
								
									
										266
									
								
								src/NadekoBot/Modules/Games/Commands/Acropobia.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								src/NadekoBot/Modules/Games/Commands/Acropobia.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,266 @@ | ||||
| using Discord; | ||||
| using Discord.Commands; | ||||
| using NadekoBot.Attributes; | ||||
| using NadekoBot.Extensions; | ||||
| using NadekoBot.Services; | ||||
| using System; | ||||
| using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace NadekoBot.Modules.Games | ||||
| { | ||||
|     public partial class Games | ||||
|     { | ||||
|         [Group] | ||||
|         public class Acropobia | ||||
|         { | ||||
|             //channelId, game | ||||
|             public static ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>(); | ||||
|  | ||||
|             [NadekoCommand, Usage, Description, Aliases] | ||||
|             [RequireContext(ContextType.Guild)] | ||||
|             public async Task Acro(IUserMessage imsg, int time = 45) | ||||
|             { | ||||
|                 var channel = (ITextChannel)imsg.Channel; | ||||
|  | ||||
|                 var game = new AcrophobiaGame(channel, time); | ||||
|                 if (AcrophobiaGames.TryAdd(channel.Id, game)) | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         await game.Run(); | ||||
|                     } | ||||
|                     finally | ||||
|                     { | ||||
|                         game.EnsureStopped(); | ||||
|                         AcrophobiaGames.TryRemove(channel.Id, out game); | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     await channel.SendErrorAsync("Acrophobia game is already running in this channel.").ConfigureAwait(false); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public enum AcroPhase | ||||
|         { | ||||
|             Submitting, | ||||
|             Idle, // used to wait for some other actions while transitioning through phases | ||||
|             Voting | ||||
|         } | ||||
|  | ||||
|         public class AcrophobiaGame | ||||
|         { | ||||
|             private readonly ITextChannel channel; | ||||
|             private readonly int time; | ||||
|             private readonly NadekoRandom rng; | ||||
|             private readonly ImmutableArray<char> startingLetters; | ||||
|             private readonly CancellationTokenSource source; | ||||
|             private AcroPhase phase { get; set; } = AcroPhase.Submitting; | ||||
|  | ||||
|             private readonly ConcurrentDictionary<string, IGuildUser> submissions = new ConcurrentDictionary<string, IGuildUser>(); | ||||
|             public IReadOnlyDictionary<string, IGuildUser> Submissions => submissions; | ||||
|  | ||||
|             private int spamCount = 0; | ||||
|  | ||||
|             //text, votes | ||||
|             private readonly ConcurrentDictionary<string, int> votes = new ConcurrentDictionary<string, int>(); | ||||
|  | ||||
|             public AcrophobiaGame(ITextChannel channel, int time) | ||||
|             { | ||||
|                 this.channel = channel; | ||||
|                 this.time = time; | ||||
|                 this.source = new CancellationTokenSource(); | ||||
|  | ||||
|                 this.rng = new NadekoRandom(); | ||||
|                 var wordCount = rng.Next(3, 6); | ||||
|  | ||||
|                 var lettersArr = new char[wordCount]; | ||||
|  | ||||
|                 for (int i = 0; i < wordCount; i++) | ||||
|                 { | ||||
|                     var randChar = (char)rng.Next(65, 91); | ||||
|                     lettersArr[i] = randChar == 'X' ? (char)rng.Next(65, 88) : randChar; | ||||
|                 } | ||||
|                 startingLetters = lettersArr.ToImmutableArray(); | ||||
|             } | ||||
|  | ||||
|             private EmbedBuilder GetEmbed() | ||||
|             { | ||||
|                 var i = 0; | ||||
|                 return phase == AcroPhase.Submitting | ||||
|  | ||||
|                 ? new EmbedBuilder().WithOkColor() | ||||
|                     .WithTitle("Acrophobia") | ||||
|                     .WithDescription($"Game started. Create a sentence with the following acronym: **{string.Join(".", startingLetters)}.**\n") | ||||
|                     .WithFooter(efb => efb.WithText("You have " + this.time + " seconds to make a submission.")) | ||||
|  | ||||
|                 : new EmbedBuilder() | ||||
|                     .WithOkColor() | ||||
|                     .WithTitle("Acrophobia - Submissions Closed") | ||||
|                     .WithDescription($@"Acronym was **{string.Join(".", startingLetters)}.** | ||||
| -- | ||||
| {this.submissions.Aggregate("", (agg, cur) => agg + $"`{++i}.` **{cur.Key.ToLowerInvariant().ToTitleCase()}**\n")} | ||||
| --") | ||||
|                     .WithFooter(efb => efb.WithText("Vote by retyping one of the submissions")); | ||||
|             } | ||||
|  | ||||
|             public async Task Run() | ||||
|             { | ||||
|                 NadekoBot.Client.MessageReceived += PotentialAcro; | ||||
|                 var embed = GetEmbed(); | ||||
|  | ||||
|                 //SUBMISSIONS PHASE | ||||
|                 await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); | ||||
|                 try | ||||
|                 { | ||||
|                     await Task.Delay(time * 1000, source.Token).ConfigureAwait(false); | ||||
|                     phase = AcroPhase.Idle; | ||||
|                 } | ||||
|                 catch (OperationCanceledException) | ||||
|                 { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 var i = 0; | ||||
|                 if (submissions.Count == 0) | ||||
|                 { | ||||
|                     await channel.SendErrorAsync("Acrophobia", "Game ended with no submissions."); | ||||
|                     return; | ||||
|                 } | ||||
|                 else if (submissions.Count == 1) | ||||
|                 { | ||||
|                     await channel.SendConfirmAsync("Acrophobia", $"{submissions.First().Value.Mention} is the winner for being the only user who made a submission!").ConfigureAwait(false); | ||||
|                     return; | ||||
|                 } | ||||
|                 var submissionClosedEmbed = GetEmbed(); | ||||
|  | ||||
|                 await channel.EmbedAsync(submissionClosedEmbed.Build()).ConfigureAwait(false); | ||||
|  | ||||
|                 //VOTING PHASE | ||||
|                 this.phase = AcroPhase.Voting; | ||||
|                 try | ||||
|                 { | ||||
|                     //30 secondds for voting | ||||
|                     await Task.Delay(30000, source.Token).ConfigureAwait(false); | ||||
|                     this.phase = AcroPhase.Idle; | ||||
|                 } | ||||
|                 catch (OperationCanceledException) | ||||
|                 { | ||||
|                     return; | ||||
|                 } | ||||
|                 await End().ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|             private Task PotentialAcro(IMessage arg) | ||||
|             { | ||||
|                 var t = Task.Run(async () => | ||||
|                 { | ||||
|                     try | ||||
|                     { | ||||
|                         var msg = arg as IUserMessage; | ||||
|                         if (msg == null || msg.Author.IsBot || msg.Channel.Id != channel.Id) | ||||
|                             return; | ||||
|  | ||||
|                         ++spamCount; | ||||
|  | ||||
|                         var guildUser = (IGuildUser)msg.Author; | ||||
|                          | ||||
|                         var input = msg.Content.ToUpperInvariant().Trim(); | ||||
|  | ||||
|                         if (phase == AcroPhase.Submitting) | ||||
|                         { | ||||
|                             if (spamCount > 10) | ||||
|                             { | ||||
|                                 spamCount = 0; | ||||
|                                 try { await channel.EmbedAsync(GetEmbed().Build()).ConfigureAwait(false); } | ||||
|                                 catch { } | ||||
|                             } | ||||
|                             //user didn't input something already | ||||
|                             IGuildUser throwaway; | ||||
|                             if (submissions.TryGetValue(input, out throwaway)) | ||||
|                                 return; | ||||
|                             var inputWords = input.Split(' '); //get all words | ||||
|  | ||||
|                             if (inputWords.Length != startingLetters.Length) // number of words must be the same as the number of the starting letters | ||||
|                                 return; | ||||
|  | ||||
|                             for (int i = 0; i < startingLetters.Length; i++) | ||||
|                             { | ||||
|                                 var letter = startingLetters[i]; | ||||
|  | ||||
|                                 if (!inputWords[i].StartsWith(letter.ToString())) // all first letters must match | ||||
|                                     return; | ||||
|                             } | ||||
|  | ||||
|                             //try adding it to the list of answers | ||||
|                             if (!submissions.TryAdd(input, guildUser)) | ||||
|                                 return; | ||||
|  | ||||
|                             // all good. valid input. answer recorded | ||||
|                             await channel.SendConfirmAsync("Acrophobia", $"{guildUser.Mention} submitted their sentence. ({submissions.Count} total)"); | ||||
|                             try | ||||
|                             { | ||||
|                                 await msg.DeleteAsync(); | ||||
|                             } | ||||
|                             catch | ||||
|                             { | ||||
|                                 await msg.DeleteAsync(); //try twice | ||||
|                             } | ||||
|                         } | ||||
|                         else if (phase == AcroPhase.Voting) | ||||
|                         { | ||||
|                             if (spamCount > 10) | ||||
|                             { | ||||
|                                 spamCount = 0; | ||||
|                                 try { await channel.EmbedAsync(GetEmbed().Build()).ConfigureAwait(false); } | ||||
|                                 catch { } | ||||
|                             } | ||||
|  | ||||
|                             IGuildUser usr; | ||||
|                             if (submissions.TryGetValue(input, out usr) && usr.Id != guildUser.Id) | ||||
|                             { | ||||
|                                 votes.AddOrUpdate(input, 1, (key, old) => ++old); | ||||
|                                 await channel.SendConfirmAsync("Acrophobia", $"{guildUser.Mention} cast their vote!").ConfigureAwait(false); | ||||
|                                 await msg.DeleteAsync().ConfigureAwait(false); | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|                     } | ||||
|                     catch { } | ||||
|                 }); | ||||
|                 return Task.CompletedTask; | ||||
|             } | ||||
|  | ||||
|             public async Task End() | ||||
|             { | ||||
|                 if (!votes.Any()) | ||||
|                 { | ||||
|                     await channel.SendErrorAsync("Acrophobia", "No votes cast. Game ended with no winner.").ConfigureAwait(false); | ||||
|                     return; | ||||
|                 } | ||||
|                 var table = votes.OrderByDescending(v => v.Value); | ||||
|                 var winner = table.First(); | ||||
|                 var embed = new EmbedBuilder().WithOkColor() | ||||
|                     .WithTitle("Acrophobia") | ||||
|                     .WithDescription($"Winner is {submissions[winner.Key].Mention} with {winner.Value} points.\n") | ||||
|                     .WithFooter(efb => efb.WithText(winner.Key.ToLowerInvariant().ToTitleCase())); | ||||
|  | ||||
|                 await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); | ||||
|             } | ||||
|  | ||||
|             public void EnsureStopped() | ||||
|             { | ||||
|                 NadekoBot.Client.MessageReceived -= PotentialAcro;  | ||||
|                 if (!source.IsCancellationRequested) | ||||
|                     source.Cancel(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/NadekoBot/Resources/CommandStrings.Designer.cs
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										27
									
								
								src/NadekoBot/Resources/CommandStrings.Designer.cs
									
									
									
										generated
									
									
									
								
							| @@ -86,6 +86,33 @@ namespace NadekoBot.Resources { | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///    Looks up a localized string similar to acro. | ||||
|         /// </summary> | ||||
|         public static string acro_cmd { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("acro_cmd", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///    Looks up a localized string similar to Starts an Acrophobia game. Second argment is optional round length in seconds. (default is 45). | ||||
|         /// </summary> | ||||
|         public static string acro_desc { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("acro_desc", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///    Looks up a localized string similar to `{0}acro` or `{0}acro 30`. | ||||
|         /// </summary> | ||||
|         public static string acro_usage { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("acro_usage", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///    Looks up a localized string similar to addcustreact acr. | ||||
|         /// </summary> | ||||
|   | ||||
| @@ -2790,4 +2790,13 @@ | ||||
|   <data name="overwatch_usage" xml:space="preserve"> | ||||
|     <value>`{0}ow us Battletag#1337` or  `{0}overwatch eu Battletag#2016`</value> | ||||
|   </data> | ||||
|   <data name="acro_cmd" xml:space="preserve"> | ||||
|     <value>acro</value> | ||||
|   </data> | ||||
|   <data name="acro_desc" xml:space="preserve"> | ||||
|     <value>Starts an Acrophobia game. Second argment is optional round length in seconds. (default is 45)</value> | ||||
|   </data> | ||||
|   <data name="acro_usage" xml:space="preserve"> | ||||
|     <value>`{0}acro` or `{0}acro 30`</value> | ||||
|   </data> | ||||
| </root> | ||||
		Reference in New Issue
	
	Block a user