Acrophobia added, closes #605
This commit is contained in:
parent
b9a87a62b9
commit
c5376f999e
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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to addcustreact acr.
|
/// Looks up a localized string similar to addcustreact acr.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -2790,4 +2790,13 @@
|
|||||||
<data name="overwatch_usage" xml:space="preserve">
|
<data name="overwatch_usage" xml:space="preserve">
|
||||||
<value>`{0}ow us Battletag#1337` or `{0}overwatch eu Battletag#2016`</value>
|
<value>`{0}ow us Battletag#1337` or `{0}overwatch eu Battletag#2016`</value>
|
||||||
</data>
|
</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>
|
</root>
|
Loading…
Reference in New Issue
Block a user