Hangman Rewrite

This commit is contained in:
Kwoth
2021-09-27 02:00:19 +00:00
parent 68e96cd1bb
commit 486916944b
21 changed files with 2640 additions and 4835 deletions

View File

@@ -0,0 +1,72 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using NadekoBot.Common;
using NadekoBot.Common.Yml;
using Serilog;
namespace NadekoBot.Modules.Games.Hangman
{
public sealed class DefaultHangmanSource : IHangmanSource
{
private IReadOnlyDictionary<string, HangmanTerm[]> _terms = new Dictionary<string, HangmanTerm[]>();
private readonly Random _rng;
public DefaultHangmanSource()
{
_rng = new NadekoRandom();
Reload();
}
public void Reload()
{
if (!Directory.Exists("data/hangman"))
{
Log.Error("Hangman game won't work. Folder 'data/hangman' is missing.");
return;
}
var qs = new Dictionary<string, HangmanTerm[]>();
foreach (var file in Directory.EnumerateFiles("data/hangman/", "*.yml"))
{
try
{
var data = Yaml.Deserializer.Deserialize<HangmanTerm[]>(File.ReadAllText(file));
qs[Path.GetFileNameWithoutExtension(file).ToLowerInvariant()] = data;
}
catch (Exception ex)
{
Log.Error(ex, "Loading {HangmanFile} failed.", file);
}
}
_terms = qs;
Log.Information("Loaded {HangmanCategoryCount} hangman categories.", qs.Count);
}
public IReadOnlyCollection<string> GetCategories()
=> _terms.Keys.ToList();
public bool GetTerm(string? category, [NotNullWhen(true)] out HangmanTerm? term)
{
if (category is null)
{
var cats = GetCategories();
category = cats.ElementAt(_rng.Next(0, cats.Count));
}
if (_terms.TryGetValue(category, out var terms))
{
term = terms[_rng.Next(0, terms.Length)];
return true;
}
term = null;
return false;
}
}
}

View File

@@ -1,19 +0,0 @@
using System;
namespace NadekoBot.Modules.Games.Common.Hangman.Exceptions
{
public class TermNotFoundException : Exception
{
public TermNotFoundException() : base("Term of that type couldn't be found")
{
}
public TermNotFoundException(string message) : base(message)
{
}
public TermNotFoundException(string message, Exception innerException) : base(message, innerException)
{
}
}
}

View File

@@ -1,170 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NadekoBot.Extensions;
namespace NadekoBot.Modules.Games.Common.Hangman
{
public sealed class Hangman : IDisposable
{
public string TermType { get; }
public TermPool TermPool { get; }
public HangmanObject Term { get; }
public string ScrambledWord => "`" + String.Concat(Term.Word.Select(c =>
{
if (c == ' ')
return " \u2000";
if (!(char.IsLetter(c) || char.IsDigit(c)))
return $" {c}";
c = char.ToLowerInvariant(c);
return _previousGuesses.Contains(c) ? $" {c}" : " ◯";
})) + "`";
private Phase _currentPhase = Phase.Active;
public Phase CurrentPhase
{
get => _currentPhase;
set
{
if (value == Phase.Ended)
_endingCompletionSource.TrySetResult(true);
_currentPhase = value;
}
}
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
private readonly HashSet<ulong> _recentUsers = new HashSet<ulong>();
public uint Errors { get; private set; } = 0;
public uint MaxErrors { get; } = 6;
public event Func<Hangman, string, ulong, Task> OnGameEnded = delegate { return Task.CompletedTask; };
public event Func<Hangman, string, char, Task> OnLetterAlreadyUsed = delegate { return Task.CompletedTask; };
public event Func<Hangman, string, char, Task> OnGuessFailed = delegate { return Task.CompletedTask; };
public event Func<Hangman, string, char, Task> OnGuessSucceeded = delegate { return Task.CompletedTask; };
private readonly HashSet<char> _previousGuesses = new HashSet<char>();
public ImmutableArray<char> PreviousGuesses => _previousGuesses.ToImmutableArray();
private readonly TaskCompletionSource<bool> _endingCompletionSource = new TaskCompletionSource<bool>();
public Task EndedTask => _endingCompletionSource.Task;
public Hangman(string type, TermPool tp = null)
{
this.TermType = type.Trim().ToLowerInvariant().ToTitleCase();
this.TermPool = tp ?? new TermPool();
this.Term = this.TermPool.GetTerm(type);
}
private void AddError()
{
if (++Errors > MaxErrors)
{
var _ = OnGameEnded(this, null, 0);
CurrentPhase = Phase.Ended;
}
}
public string GetHangman() => $@". ┌─────┐
.┃...............┋
.┃...............┋
.┃{(Errors > 0 ? ".............😲" : "")}
.{(Errors > 1 ? "............./" : "")} {(Errors > 2 ? "|" : "")} {(Errors > 3 ? "\\" : "")}
.{(Errors > 4 ? "............../" : "")} {(Errors > 5 ? "\\" : "")}
/-\";
public async Task Input(ulong userId, string userName, string input)
{
if (CurrentPhase == Phase.Ended)
return;
if (string.IsNullOrWhiteSpace(input))
return;
input = input.Trim().ToLowerInvariant();
await _locker.WaitAsync().ConfigureAwait(false);
try
{
if (CurrentPhase == Phase.Ended)
return;
if (input.Length > 1) // tried to guess the whole word
{
if (input != Term.Word) // failed
return;
var _ = OnGameEnded?.Invoke(this, userName, userId);
CurrentPhase = Phase.Ended;
return;
}
var ch = input[0];
if (!char.IsLetterOrDigit(ch))
return;
if (!_recentUsers.Add(userId)) // don't let a single user spam guesses
return;
if (!_previousGuesses.Add(ch)) // that letter was already guessed
{
var _ = OnLetterAlreadyUsed?.Invoke(this, userName, ch);
}
else if (!Term.Word.Contains(ch)) // guessed letter doesn't exist
{
var _ = OnGuessFailed?.Invoke(this, userName, ch);
AddError();
}
else if (Term.Word.All(x => _previousGuesses.IsSupersetOf(Term.Word.ToLowerInvariant()
.Where(char.IsLetterOrDigit))))
{
var _ = OnGameEnded.Invoke(this, userName, userId); // if all letters are guessed
CurrentPhase = Phase.Ended;
}
else // guessed but not last letter
{
var _ = OnGuessSucceeded?.Invoke(this, userName, ch);
_recentUsers.Remove(userId); // he can guess again right away
return;
}
var clearSpam = Task.Run(async () =>
{
await Task.Delay(3000).ConfigureAwait(false); // remove the user from the spamlist after 5 seconds
_recentUsers.Remove(userId);
});
}
finally { _locker.Release(); }
}
public async Task Stop()
{
await _locker.WaitAsync().ConfigureAwait(false);
try
{
CurrentPhase = Phase.Ended;
}
finally { _locker.Release(); }
}
public void Dispose()
{
OnGameEnded = null;
OnGuessFailed = null;
OnGuessSucceeded = null;
OnLetterAlreadyUsed = null;
_previousGuesses.Clear();
_recentUsers.Clear();
// _locker.Dispose();
}
}
}

View File

@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AngleSharp.Text;
namespace NadekoBot.Modules.Games.Hangman
{
public sealed class HangmanGame
{
public enum Phase { Running, Ended }
public enum GuessResult { NoAction, AlreadyTried, Incorrect, Guess, Win }
public record State(
int Errors,
Phase Phase,
string Word,
GuessResult GuessResult,
List<char> missedLetters,
string ImageUrl)
{
public bool Failed => Errors > 5;
}
private Phase CurrentPhase { get; set; }
private readonly HashSet<char> _incorrect = new();
private readonly HashSet<char> _correct = new();
private readonly HashSet<char> _remaining = new();
private readonly string _word;
private readonly string _imageUrl;
public HangmanGame(HangmanTerm term)
{
_word = term.Word;
_imageUrl = term.ImageUrl;
_remaining = _word
.ToLowerInvariant()
.Where(x => x.IsLetter())
.Select(char.ToLowerInvariant)
.ToHashSet();
}
public State GetState(GuessResult guessResult = GuessResult.NoAction)
=> new State(_incorrect.Count,
CurrentPhase,
CurrentPhase == Phase.Ended
? _word
: GetScrambledWord(),
guessResult,
_incorrect.ToList(),
CurrentPhase == Phase.Ended
? _imageUrl
: string.Empty);
private string GetScrambledWord()
{
Span<char> output = stackalloc char[_word.Length * 2];
for (var i = 0; i < _word.Length; i++)
{
var ch = _word[i];
if (ch == ' ')
output[i*2] = '';
if (!ch.IsLetter() || !_remaining.Contains(char.ToLowerInvariant(ch)))
output[i*2] = ch;
else
output[i*2] = '_';
output[i * 2 + 1] = ' ';
}
return new(output);
}
// todo lock
public State Guess(string guess)
{
if (CurrentPhase != Phase.Running)
return GetState(GuessResult.NoAction);
guess = guess.Trim();
if (guess.Length > 1)
{
if (guess.Equals(_word, StringComparison.InvariantCultureIgnoreCase))
{
CurrentPhase = Phase.Ended;
return GetState(GuessResult.Win);
}
return GetState(GuessResult.NoAction);
}
var charGuess = guess[0];
if (!char.IsLetter(charGuess))
return GetState(GuessResult.NoAction);
if (_incorrect.Contains(charGuess) || _correct.Contains(charGuess))
return GetState(GuessResult.AlreadyTried);
if (_remaining.Remove(charGuess))
{
if (_remaining.Count == 0)
{
CurrentPhase = Phase.Ended;
return GetState(GuessResult.Win);
}
return GetState(GuessResult.Guess);
}
_incorrect.Add(charGuess);
if (_incorrect.Count > 5)
{
CurrentPhase = Phase.Ended;
return GetState(GuessResult.Incorrect);
}
return GetState(GuessResult.Incorrect);
}
}
}

View File

@@ -1,17 +0,0 @@
using NadekoBot.Extensions;
namespace NadekoBot.Modules.Games.Common.Hangman
{
public class HangmanObject
{
public string Word { get; set; }
public string ImageUrl { get; set; }
public string GetWord()
{
var term = Word.ToTitleCase();
return $"[{term}](https://en.wikipedia.org/wiki/{term.Replace(' ', '_')})";
}
}
}

View File

@@ -0,0 +1,147 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Discord;
using Microsoft.Extensions.Caching.Memory;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Services;
using NadekoBot.Services;
namespace NadekoBot.Modules.Games.Hangman
{
public sealed class HangmanService : IHangmanService, ILateExecutor
{
private readonly ConcurrentDictionary<ulong, HangmanGame> _hangmanGames = new();
private readonly IHangmanSource _source;
private readonly IEmbedBuilderService _eb;
private readonly GamesConfigService _gcs;
private readonly ICurrencyService _cs;
private readonly IMemoryCache _cdCache;
private readonly object _locker = new();
public HangmanService(IHangmanSource source, IEmbedBuilderService eb, GamesConfigService gcs,
ICurrencyService cs, IMemoryCache cdCache)
{
_source = source;
_eb = eb;
_gcs = gcs;
_cs = cs;
_cdCache = cdCache;
}
public bool StartHangman(
ulong channelId,
string? category,
[NotNullWhen(true)] out HangmanGame.State? state)
{
state = null;
if (!_source.GetTerm(category, out var term))
return false;
var game = new HangmanGame(term);
lock (_locker)
{
var hc = _hangmanGames.GetOrAdd(channelId, game);
if (hc == game)
{
state = hc.GetState();
return true;
}
return false;
}
}
public ValueTask<bool> StopHangman(ulong channelId)
{
lock (_locker)
{
if (_hangmanGames.TryRemove(channelId, out var game))
{
return new(true);
}
}
return new(false);
}
public IReadOnlyCollection<string> GetHangmanTypes()
=> _source.GetCategories();
public async Task LateExecute(IGuild guild, IUserMessage msg)
{
if (_hangmanGames.ContainsKey(msg.Channel.Id))
{
if (string.IsNullOrWhiteSpace(msg.Content))
return;
if (_cdCache.TryGetValue(msg.Author.Id, out _))
return;
HangmanGame.State state;
long rew = 0;
lock (_locker)
{
if (!_hangmanGames.TryGetValue(msg.Channel.Id, out var game))
return;
state = game.Guess(msg.Content.ToLowerInvariant());
if (state.GuessResult == HangmanGame.GuessResult.NoAction)
return;
if (state.GuessResult == HangmanGame.GuessResult.Incorrect
|| state.GuessResult == HangmanGame.GuessResult.AlreadyTried)
{
_cdCache.Set(msg.Author.Id, string.Empty, new MemoryCacheEntryOptions()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3)
});
}
if (state.Phase == HangmanGame.Phase.Ended)
if (_hangmanGames.TryRemove(msg.Channel.Id, out _))
rew = _gcs.Data.Hangman.CurrencyReward;
}
if (rew > 0)
await _cs.AddAsync(msg.Author, "hangman win", rew, gamble: true);
await SendState((ITextChannel)msg.Channel, msg.Author, msg.Content, state);
}
}
private Task<IUserMessage> SendState(ITextChannel channel, IUser user, string content, HangmanGame.State state)
{
var embed = Games.HangmanCommands.GetEmbed(_eb, state);
if (state.GuessResult == HangmanGame.GuessResult.Guess)
embed.WithDescription($"{user} guessed the letter {content}!")
.WithOkColor();
else if (state.GuessResult == HangmanGame.GuessResult.Incorrect && state.Failed)
embed.WithDescription($"{user} Letter {content} doesn't exist! Game over!")
.WithErrorColor();
else if (state.GuessResult == HangmanGame.GuessResult.Incorrect)
embed.WithDescription($"{user} Letter {content} doesn't exist!")
.WithErrorColor();
else if (state.GuessResult == HangmanGame.GuessResult.AlreadyTried)
embed.WithDescription($"{user} Letter {content} has already been used.")
.WithPendingColor();
else if (state.GuessResult == HangmanGame.GuessResult.Win)
embed.WithDescription($"{user} won!")
.WithOkColor();
if (!string.IsNullOrWhiteSpace(state.ImageUrl)
&& Uri.IsWellFormedUriString(state.ImageUrl, UriKind.Absolute))
{
embed.WithImageUrl(state.ImageUrl);
}
return channel.EmbedAsync(embed);
}
}
}

View File

@@ -0,0 +1,8 @@
namespace NadekoBot.Modules.Games.Hangman
{
public sealed class HangmanTerm
{
public string Word { get; set; }
public string ImageUrl { get; set; }
}
}

View File

@@ -0,0 +1,14 @@
#nullable enable
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Hangman
{
public interface IHangmanService
{
bool StartHangman(ulong channelId, string? category, [NotNullWhen(true)] out HangmanGame.State? hangmanController);
ValueTask<bool> StopHangman(ulong channelId);
IReadOnlyCollection<string> GetHangmanTypes();
}
}

View File

@@ -0,0 +1,14 @@
#nullable enable
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using NadekoBot.Services;
namespace NadekoBot.Modules.Games.Hangman
{
public interface IHangmanSource : INService
{
public IReadOnlyCollection<string> GetCategories();
public void Reload();
public bool GetTerm(string? category, [NotNullWhen(true)] out HangmanTerm? term);
}
}

View File

@@ -1,8 +0,0 @@
namespace NadekoBot.Modules.Games.Common.Hangman
{
public enum Phase
{
Active,
Ended,
}
}

View File

@@ -1,50 +0,0 @@
using NadekoBot.Common;
using NadekoBot.Modules.Games.Common.Hangman.Exceptions;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Serilog;
namespace NadekoBot.Modules.Games.Common.Hangman
{
public class TermPool
{
const string termsPath = "data/hangman.json";
public IReadOnlyDictionary<string, HangmanObject[]> Data { get; } = new Dictionary<string, HangmanObject[]>();
public TermPool()
{
try
{
Data = JsonConvert.DeserializeObject<Dictionary<string, HangmanObject[]>>(File.ReadAllText(termsPath));
Data = Data.ToDictionary(
x => x.Key.ToLowerInvariant(),
x => x.Value);
}
catch (Exception ex)
{
Log.Warning(ex, "Error loading Hangman Term pool");
}
}
public HangmanObject GetTerm(string type)
{
type = type?.Trim().ToLowerInvariant();
var rng = new NadekoRandom();
if (type == "random")
{
type = Data.Keys.ToArray()[rng.Next(0, Data.Keys.Count())];
}
if (!Data.TryGetValue(type, out var termTypes) || termTypes.Length == 0)
throw new TermNotFoundException();
var obj = termTypes[rng.Next(0, termTypes.Length)];
obj.Word = obj.Word.Trim().ToLowerInvariant();
return obj;
}
}
}

View File

@@ -1,14 +0,0 @@
using System;
namespace NadekoBot.Modules.Games.Common.Hangman
{
[Flags]
public enum TermTypes
{
Countries = 0,
Movies = 1,
Animals = 2,
Things = 4,
Random = 8,
}
}

View File

@@ -1,13 +1,10 @@
#nullable enable
using Discord.Commands;
using NadekoBot.Extensions;
using System;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using NadekoBot.Common.Attributes;
using NadekoBot.Modules.Games.Common.Hangman;
using NadekoBot.Modules.Games.Services;
using NadekoBot.Modules.Games.Common.Hangman.Exceptions;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Hangman;
using NadekoBot.Services;
namespace NadekoBot.Modules.Games
@@ -15,136 +12,74 @@ namespace NadekoBot.Modules.Games
public partial class Games
{
[Group]
public class HangmanCommands : NadekoSubmodule<GamesService>
public class HangmanCommands : NadekoSubmodule<IHangmanService>
{
private readonly DiscordSocketClient _client;
private readonly ICurrencyService _cs;
private readonly GamesConfigService _gcs;
public HangmanCommands(DiscordSocketClient client, ICurrencyService cs, GamesConfigService gcs)
{
_client = client;
_cs = cs;
_gcs = gcs;
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Hangmanlist()
{
await SendConfirmAsync(Format.Code(GetText(strs.hangman_types(Prefix)) + "\n" + string.Join("\n", _service.TermPool.Data.Keys)));
await SendConfirmAsync(
GetText(strs.hangman_types(Prefix)),
_service.GetHangmanTypes().JoinWith('\n'));
}
private static string Draw(HangmanGame.State state)
{
return $@". ┌─────┐
.┃...............┋
.┃...............┋
.┃{(state.Errors > 0 ? ".............😲" : "")}
.{(state.Errors > 1 ? "............./" : "")} {(state.Errors > 2 ? "|" : "")} {(state.Errors > 3 ? "\\" : "")}
.{(state.Errors > 4 ? "............../" : "")} {(state.Errors > 5 ? "\\" : "")}
/-\";
}
public static IEmbedBuilder GetEmbed(IEmbedBuilderService eb, HangmanGame.State state)
{
if (state.Phase == HangmanGame.Phase.Running)
return eb.Create()
.WithOkColor()
.AddField("Hangman", Draw(state))
.AddField("Guess", Format.Code(state.Word))
.WithFooter(state.missedLetters.JoinWith(' '));
if (state.Phase == HangmanGame.Phase.Ended && state.Failed)
return eb.Create()
.WithErrorColor()
.AddField("Hangman", Draw(state))
.AddField("Guess", Format.Code(state.Word))
.WithFooter(state.missedLetters.JoinWith(' '));
else
{
return eb.Create()
.WithOkColor()
.AddField("Hangman", Draw(state))
.AddField("Guess", Format.Code(state.Word))
.WithFooter(state.missedLetters.JoinWith(' '));
}
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Hangman([Leftover]string type = "random")
public async Task Hangman([Leftover] string? type = null)
{
Hangman hm;
try
{
hm = new Hangman(type, _service.TermPool);
}
catch (TermNotFoundException)
if (!_service.StartHangman(ctx.Channel.Id, type, out var hangman))
{
await ReplyErrorLocalizedAsync(strs.hangman_running);
return;
}
if (!_service.HangmanGames.TryAdd(ctx.Channel.Id, hm))
{
hm.Dispose();
await ReplyErrorLocalizedAsync(strs.hangman_running).ConfigureAwait(false);
return;
}
hm.OnGameEnded += Hm_OnGameEnded;
hm.OnGuessFailed += Hm_OnGuessFailed;
hm.OnGuessSucceeded += Hm_OnGuessSucceeded;
hm.OnLetterAlreadyUsed += Hm_OnLetterAlreadyUsed;
_client.MessageReceived += _client_MessageReceived;
try
{
await SendConfirmAsync(GetText(strs.hangman_game_started) + $" ({hm.TermType})",
hm.ScrambledWord + "\n" + hm.GetHangman())
.ConfigureAwait(false);
}
catch { }
await hm.EndedTask.ConfigureAwait(false);
_client.MessageReceived -= _client_MessageReceived;
_service.HangmanGames.TryRemove(ctx.Channel.Id, out _);
hm.Dispose();
Task _client_MessageReceived(SocketMessage msg)
{
var _ = Task.Run(() =>
{
if (ctx.Channel.Id == msg.Channel.Id && !msg.Author.IsBot)
return hm.Input(msg.Author.Id, msg.Author.ToString(), msg.Content);
else
return Task.CompletedTask;
});
return Task.CompletedTask;
}
}
Task Hm_OnGameEnded(Hangman game, string winner, ulong userId)
{
if (winner is null)
{
var loseEmbed = _eb.Create().WithTitle($"Hangman Game ({game.TermType}) - Ended")
.WithDescription(Format.Bold("You lose."))
.AddField("It was", game.Term.GetWord())
.WithFooter(string.Join(" ", game.PreviousGuesses))
.WithErrorColor();
if (Uri.IsWellFormedUriString(game.Term.ImageUrl, UriKind.Absolute))
loseEmbed.WithImageUrl(game.Term.ImageUrl);
return ctx.Channel.EmbedAsync(loseEmbed);
}
var reward = _gcs.Data.Hangman.CurrencyReward;
if (reward > 0)
_cs.AddAsync(userId, "hangman win", reward, true);
var winEmbed = _eb.Create().WithTitle($"Hangman Game ({game.TermType}) - Ended")
.WithDescription(Format.Bold($"{winner} Won."))
.AddField("It was", game.Term.GetWord())
.WithFooter(string.Join(" ", game.PreviousGuesses))
.WithOkColor();
if (Uri.IsWellFormedUriString(game.Term.ImageUrl, UriKind.Absolute))
winEmbed.WithImageUrl(game.Term.ImageUrl);
return ctx.Channel.EmbedAsync(winEmbed);
}
private Task Hm_OnLetterAlreadyUsed(Hangman game, string user, char guess)
{
return SendErrorAsync($"Hangman Game ({game.TermType})", $"{user} Letter `{guess}` has already been used. You can guess again in 3 seconds.\n" + game.ScrambledWord + "\n" + game.GetHangman(),
footer: string.Join(" ", game.PreviousGuesses));
}
private Task Hm_OnGuessSucceeded(Hangman game, string user, char guess)
{
return SendConfirmAsync($"Hangman Game ({game.TermType})", $"{user} guessed a letter `{guess}`!\n" + game.ScrambledWord + "\n" + game.GetHangman(),
footer: string.Join(" ", game.PreviousGuesses));
}
private Task Hm_OnGuessFailed(Hangman game, string user, char guess)
{
return SendErrorAsync($"Hangman Game ({game.TermType})", $"{user} Letter `{guess}` does not exist. You can guess again in 3 seconds.\n" + game.ScrambledWord + "\n" + game.GetHangman(),
footer: string.Join(" ", game.PreviousGuesses));
var eb = GetEmbed(_eb, hangman);
eb.WithDescription(GetText(strs.hangman_game_started));
await ctx.Channel.EmbedAsync(eb);
}
[NadekoCommand, Aliases]
[RequireContext(ContextType.Guild)]
public async Task HangmanStop()
{
if (_service.HangmanGames.TryRemove(ctx.Channel.Id, out var removed))
if (await _service.StopHangman(ctx.Channel.Id))
{
await removed.Stop().ConfigureAwait(false);
await ReplyConfirmLocalizedAsync(strs.hangman_stopped).ConfigureAwait(false);
}
}

View File

@@ -4,7 +4,6 @@ using NadekoBot.Services;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Common;
using NadekoBot.Modules.Games.Common.Acrophobia;
using NadekoBot.Modules.Games.Common.Hangman;
using NadekoBot.Modules.Games.Common.Nunchi;
using NadekoBot.Modules.Games.Common.Trivia;
using Newtonsoft.Json;
@@ -12,7 +11,6 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@@ -40,10 +38,6 @@ namespace NadekoBot.Modules.Games.Services
//channelId, game
public ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>();
public ConcurrentDictionary<ulong, Hangman> HangmanGames { get; } = new ConcurrentDictionary<ulong, Hangman>();
public TermPool TermPool { get; } = new TermPool();
public ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>();
public Dictionary<ulong, TicTacToe> TicTacToeGames { get; } = new Dictionary<ulong, TicTacToe>();
public ConcurrentDictionary<ulong, TypingGame> RunningContests { get; } = new ConcurrentDictionary<ulong, TypingGame>();