2024-09-21 14:41:22 +12:00
|
|
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
|
|
|
using EllieBot.Common.ModuleBehaviors;
|
|
|
|
|
using EllieBot.Modules.Games.Services;
|
|
|
|
|
using System.Diagnostics.CodeAnalysis;
|
2025-03-28 21:13:53 +13:00
|
|
|
|
using EllieBot.Modules.Games.Quests;
|
2024-09-21 14:41:22 +12:00
|
|
|
|
|
|
|
|
|
namespace EllieBot.Modules.Games.Hangman;
|
|
|
|
|
|
|
|
|
|
public sealed class HangmanService : IHangmanService, IExecNoCommand
|
|
|
|
|
{
|
|
|
|
|
private readonly ConcurrentDictionary<ulong, HangmanGame> _hangmanGames = new();
|
|
|
|
|
private readonly IHangmanSource _source;
|
|
|
|
|
private readonly IMessageSenderService _sender;
|
|
|
|
|
private readonly GamesConfigService _gcs;
|
|
|
|
|
private readonly ICurrencyService _cs;
|
|
|
|
|
private readonly IMemoryCache _cdCache;
|
2025-03-28 21:13:53 +13:00
|
|
|
|
private readonly QuestService _quests;
|
2024-09-21 14:41:22 +12:00
|
|
|
|
private readonly object _locker = new();
|
|
|
|
|
|
|
|
|
|
public HangmanService(
|
|
|
|
|
IHangmanSource source,
|
|
|
|
|
IMessageSenderService sender,
|
|
|
|
|
GamesConfigService gcs,
|
|
|
|
|
ICurrencyService cs,
|
2025-03-28 21:13:53 +13:00
|
|
|
|
IMemoryCache cdCache,
|
|
|
|
|
QuestService quests)
|
2024-09-21 14:41:22 +12:00
|
|
|
|
{
|
|
|
|
|
_source = source;
|
|
|
|
|
_sender = sender;
|
|
|
|
|
_gcs = gcs;
|
|
|
|
|
_cs = cs;
|
|
|
|
|
_cdCache = cdCache;
|
2025-03-28 21:13:53 +13:00
|
|
|
|
_quests = quests;
|
2024-09-21 14:41:22 +12:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool StartHangman(ulong channelId, string? category, [NotNullWhen(true)] out HangmanGame.State? state)
|
|
|
|
|
{
|
|
|
|
|
state = null;
|
2025-04-03 15:50:06 +13:00
|
|
|
|
if (!_source.GetTerm(category, out var termData))
|
2024-09-21 14:41:22 +12:00
|
|
|
|
return false;
|
|
|
|
|
|
2025-04-03 15:50:06 +13:00
|
|
|
|
var game = new HangmanGame(termData.Value.Term, termData.Value.Category);
|
2024-09-21 14:41:22 +12:00
|
|
|
|
lock (_locker)
|
|
|
|
|
{
|
|
|
|
|
var hc = _hangmanGames.GetOrAdd(channelId, game);
|
|
|
|
|
if (hc == game)
|
|
|
|
|
{
|
|
|
|
|
state = hc.GetState();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ValueTask<bool> StopHangman(ulong channelId)
|
|
|
|
|
{
|
2025-03-15 11:13:52 +13:00
|
|
|
|
if (_hangmanGames.TryRemove(channelId, out _))
|
|
|
|
|
return new(true);
|
2024-09-21 14:41:22 +12:00
|
|
|
|
|
|
|
|
|
return new(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IReadOnlyCollection<string> GetHangmanTypes()
|
|
|
|
|
=> _source.GetCategories();
|
|
|
|
|
|
|
|
|
|
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
|
|
|
|
{
|
2025-03-15 11:13:52 +13:00
|
|
|
|
if (!_hangmanGames.ContainsKey(msg.Channel.Id))
|
2024-09-21 14:41:22 +12:00
|
|
|
|
{
|
2025-03-15 11:13:52 +13:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(msg.Content))
|
|
|
|
|
return;
|
|
|
|
|
|
2025-03-16 15:47:56 +13:00
|
|
|
|
if (_cdCache.TryGetValue("hangman:" + msg.Author.Id, out _))
|
2025-03-15 11:13:52 +13:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
HangmanGame.State state;
|
|
|
|
|
long rew = 0;
|
|
|
|
|
lock (_locker)
|
|
|
|
|
{
|
|
|
|
|
if (!_hangmanGames.TryGetValue(msg.Channel.Id, out var game))
|
2024-09-21 14:41:22 +12:00
|
|
|
|
return;
|
|
|
|
|
|
2025-03-15 11:13:52 +13:00
|
|
|
|
state = game.Guess(msg.Content.ToLowerInvariant());
|
|
|
|
|
|
|
|
|
|
if (state.GuessResult == HangmanGame.GuessResult.NoAction)
|
2024-09-21 14:41:22 +12:00
|
|
|
|
return;
|
|
|
|
|
|
2025-03-15 11:13:52 +13:00
|
|
|
|
if (state.GuessResult is HangmanGame.GuessResult.Incorrect or HangmanGame.GuessResult.AlreadyTried)
|
2024-09-21 14:41:22 +12:00
|
|
|
|
{
|
2025-03-16 15:47:56 +13:00
|
|
|
|
_cdCache.Set("hangman:" + msg.Author.Id,
|
2025-03-15 11:13:52 +13:00
|
|
|
|
string.Empty,
|
|
|
|
|
new MemoryCacheEntryOptions
|
|
|
|
|
{
|
|
|
|
|
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3)
|
|
|
|
|
});
|
2024-09-21 14:41:22 +12:00
|
|
|
|
}
|
|
|
|
|
|
2025-03-15 11:13:52 +13:00
|
|
|
|
if (state.Phase == HangmanGame.Phase.Ended)
|
|
|
|
|
{
|
|
|
|
|
if (_hangmanGames.TryRemove(msg.Channel.Id, out _))
|
|
|
|
|
rew = _gcs.Data.Hangman.CurrencyReward;
|
|
|
|
|
}
|
2024-09-21 14:41:22 +12:00
|
|
|
|
}
|
2025-03-15 11:13:52 +13:00
|
|
|
|
|
|
|
|
|
if (rew > 0)
|
|
|
|
|
await _cs.AddAsync(msg.Author, rew, new("hangman", "win"));
|
2025-03-28 21:13:53 +13:00
|
|
|
|
|
|
|
|
|
if (state.GuessResult == HangmanGame.GuessResult.Win)
|
|
|
|
|
await _quests.ReportActionAsync(msg.Author.Id, QuestEventType.GameWon, new() { { "game", "hangman" } });
|
2025-03-15 11:13:52 +13:00
|
|
|
|
|
|
|
|
|
await SendState((ITextChannel)msg.Channel, msg.Author, msg.Content, state);
|
2024-09-21 14:41:22 +12:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Task<IUserMessage> SendState(
|
|
|
|
|
ITextChannel channel,
|
|
|
|
|
IUser user,
|
|
|
|
|
string content,
|
|
|
|
|
HangmanGame.State state)
|
|
|
|
|
{
|
|
|
|
|
var embed = Games.HangmanCommands.GetEmbed(_sender, 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 _sender.Response(channel).Embed(embed).SendAsync();
|
|
|
|
|
}
|
|
|
|
|
}
|