Simple Hangman game

This commit is contained in:
Kwoth 2016-12-15 00:56:13 +01:00
parent 70f3177eaf
commit 35df37c418
7 changed files with 3477 additions and 0 deletions

View File

@ -0,0 +1,206 @@
using Discord;
using NadekoBot.Extensions;
using NadekoBot.Services;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Commands.Hangman
{
public class HangmanModel
{
public List<HangmanObject> All { get; set; }
public List<HangmanObject> Animals { get; set; }
public List<HangmanObject> Countries { get; set; }
public List<HangmanObject> Movies { get; set; }
public List<HangmanObject> Things { get; set; }
}
public class HangmanTermPool
{
public enum HangmanTermType
{
All,
Animals,
Countries,
Movies,
Things
}
const string termsPath = "data/hangman.json";
public static HangmanModel data { get; }
static HangmanTermPool()
{
try
{
data = JsonConvert.DeserializeObject<HangmanModel>(File.ReadAllText(termsPath));
data.All = data.Animals.Concat(data.Countries)
.Concat(data.Movies)
.Concat(data.Things)
.ToList();
}
catch (Exception ex) {
Console.WriteLine(ex);
}
}
public static HangmanObject GetTerm(HangmanTermType type)
{
var rng = new NadekoRandom();
switch (type)
{
case HangmanTermType.Animals:
return data.Animals[rng.Next(0, data.Animals.Count)];
case HangmanTermType.Countries:
return data.Countries[rng.Next(0, data.Countries.Count)];
case HangmanTermType.Movies:
return data.Movies[rng.Next(0, data.Movies.Count)];
case HangmanTermType.Things:
return data.Things[rng.Next(0, data.Things.Count)];
default:
return data.All[rng.Next(0, data.All.Count)];
}
}
}
public class HangmanGame
{
public IMessageChannel GameChannel { get; }
public HashSet<char> Guesses { get; } = new HashSet<char>();
public HangmanObject Term { get; private set; }
public uint Errors { get; private set; } = 0;
public uint MaxErrors { get; } = 6;
public uint MessagesSinceLastPost { get; private set; } = 0;
public string ScrambledWord => "`" + String.Concat(Term.Word.Select(c =>
{
if (!(char.IsLetter(c) || char.IsDigit(c)))
return $" {c}";
c = char.ToUpperInvariant(c);
if (c == ' ')
return " ";
return Guesses.Contains(c) ? $" {c}" : " _";
})) + "`";
public bool GuessedAll => Guesses.IsSupersetOf(Term.Word.ToUpperInvariant()
.Where(c => char.IsLetter(c) || char.IsDigit(c)));
public HangmanTermPool.HangmanTermType TermType { get; }
public event Action<HangmanGame> OnEnded;
public HangmanGame(IMessageChannel channel, HangmanTermPool.HangmanTermType type)
{
this.GameChannel = channel;
this.TermType = type;
}
public void Start()
{
this.Term = HangmanTermPool.GetTerm(TermType);
// start listening for answers when game starts
NadekoBot.Client.MessageReceived += PotentialGuess;
}
public async Task End()
{
NadekoBot.Client.MessageReceived -= PotentialGuess;
OnEnded(this);
var toSend = "Game ended. You **" + (Errors >= MaxErrors ? "LOSE" : "WIN") + "**!\n" + GetHangman();
var embed = new EmbedBuilder().WithTitle("Hangman Game")
.WithDescription(toSend)
.AddField(efb => efb.WithName("It was").WithValue(Term.Word))
.WithImage(eib => eib.WithUrl(Term.ImageUrl));
if (Errors >= MaxErrors)
await GameChannel.EmbedAsync(embed.WithColor(NadekoBot.ErrorColor).Build()).ConfigureAwait(false);
else
await GameChannel.EmbedAsync(embed.WithColor(NadekoBot.OkColor).Build()).ConfigureAwait(false);
}
private Task PotentialGuess(IMessage msg)
{
if (msg.Channel != GameChannel)
return Task.CompletedTask; // message's channel has to be the same as game's
if (msg.Content.Length != 1) // message must be 1 char long
{
if (++MessagesSinceLastPost > 10)
{
MessagesSinceLastPost = 0;
Task.Run(async () =>
{
try { await GameChannel.SendConfirmAsync("Hangman Game", ScrambledWord + "\n" + GetHangman()).ConfigureAwait(false); } catch { }
});
}
return Task.CompletedTask;
}
if (!(char.IsLetter(msg.Content[0]) || char.IsDigit(msg.Content[0])))// and a letter or a digit
return Task.CompletedTask;
var guess = char.ToUpperInvariant(msg.Content[0]);
// todo hmmmm
// how do i want to limit the users on guessing?
// one guess every 5 seconds if wrong?
Task.Run(async () =>
{
try
{
if (Guesses.Contains(guess))
{
++Errors;
if (Errors < MaxErrors)
await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author.Mention} Letter `{guess}` has already been used.\n" + ScrambledWord + "\n" + GetHangman()).ConfigureAwait(false);
else
await End().ConfigureAwait(false);
return;
}
Guesses.Add(guess);
if (Term.Word.ToUpperInvariant().Contains(guess))
{
if (GuessedAll)
{
try { await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author.Mention} guessed a letter `{guess}`!").ConfigureAwait(false); } catch { }
await End().ConfigureAwait(false);
return;
}
try { await GameChannel.SendConfirmAsync("Hangman Game", $"{msg.Author.Mention} guessed a letter `{guess}`!\n" + ScrambledWord + "\n" + GetHangman()).ConfigureAwait(false); } catch { }
}
else
{
++Errors;
if (Errors < MaxErrors)
await GameChannel.SendErrorAsync("Hangman Game", $"{msg.Author.Mention} Letter `{guess}` does not exist.\n" + ScrambledWord + "\n" + GetHangman()).ConfigureAwait(false);
else
await End().ConfigureAwait(false);
}
}
catch { }
});
return Task.CompletedTask;
}
public string GetHangman()
{
return
$@"\_\_\_\_\_\_\_\_\_
| |
| |
{(Errors > 0 ? "😲" : " ")} |
{(Errors > 1 ? "/" : " ")} {(Errors > 2 ? "|" : " ")} {(Errors > 3 ? "\\" : " ")} |
{(Errors > 4 ? "/" : " ")} {(Errors > 5 ? "\\" : " ")} |
/-\";
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Commands.Hangman
{
public class HangmanObject
{
public string Word { get; set; }
public string ImageUrl { get; set; }
}
}

View File

@ -0,0 +1,66 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Commands.Hangman;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games
{
public partial class Games
{
[Group]
public class HangmanCommands
{
private static Logger _log { get; }
//channelId, game
public static ConcurrentDictionary<ulong, HangmanGame> HangmanGames { get; } = new ConcurrentDictionary<ulong, HangmanGame>();
static HangmanCommands()
{
_log = LogManager.GetCurrentClassLogger();
}
string typesStr { get; } = "";
public HangmanCommands()
{
typesStr = $"`List of \"{NadekoBot.ModulePrefixes[typeof(Games).Name]}hangman\" term types:`\n" + String.Join(", ", Enum.GetNames(typeof(HangmanTermPool.HangmanTermType)));
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Hangmanlist(IUserMessage imsg)
{
await imsg.Channel.SendConfirmAsync(typesStr);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Hangman(IUserMessage imsg, HangmanTermPool.HangmanTermType type = HangmanTermPool.HangmanTermType.All)
{
var hm = new HangmanGame(imsg.Channel, type);
if (!HangmanGames.TryAdd(imsg.Channel.Id, hm))
{
await imsg.Channel.SendErrorAsync("Hangman game already running on this channel.").ConfigureAwait(false);
return;
}
hm.OnEnded += (g) =>
{
HangmanGame throwaway;
HangmanGames.TryRemove(g.GameChannel.Id, out throwaway);
};
hm.Start();
await imsg.Channel.SendConfirmAsync("Hangman game started", hm.ScrambledWord + "\n" + hm.GetHangman() + "\n" + hm.ScrambledWord);
}
}
}
}

View File

@ -17,6 +17,7 @@ using NadekoBot.TypeReaders;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using NadekoBot.Modules.Music; using NadekoBot.Modules.Music;
using NadekoBot.Services.Database.Models; using NadekoBot.Services.Database.Models;
using NadekoBot.Modules.Games.Commands.Hangman;
namespace NadekoBot namespace NadekoBot
{ {

View File

@ -2840,6 +2840,60 @@ namespace NadekoBot.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to hangman.
/// </summary>
public static string hangman_cmd {
get {
return ResourceManager.GetString("hangman_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Starts a game of hangman in the channel. Use `{0}hangmanlist` to see a list of available term types. Defaults to &apos;all&apos;..
/// </summary>
public static string hangman_desc {
get {
return ResourceManager.GetString("hangman_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0}hangman` or `{0}hangman movies`.
/// </summary>
public static string hangman_usage {
get {
return ResourceManager.GetString("hangman_usage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to hangmanlist.
/// </summary>
public static string hangmanlist_cmd {
get {
return ResourceManager.GetString("hangmanlist_cmd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shows a list of hangman term types..
/// </summary>
public static string hangmanlist_desc {
get {
return ResourceManager.GetString("hangmanlist_desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to `{0} hangmanlist`.
/// </summary>
public static string hangmanlist_usage {
get {
return ResourceManager.GetString("hangmanlist_usage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to #. /// Looks up a localized string similar to #.
/// </summary> /// </summary>

View File

@ -2745,4 +2745,22 @@
<data name="type_usage" xml:space="preserve"> <data name="type_usage" xml:space="preserve">
<value>`{0}type @someone`</value> <value>`{0}type @someone`</value>
</data> </data>
<data name="hangmanlist_cmd" xml:space="preserve">
<value>hangmanlist</value>
</data>
<data name="hangmanlist_desc" xml:space="preserve">
<value>Shows a list of hangman term types.</value>
</data>
<data name="hangmanlist_usage" xml:space="preserve">
<value>`{0} hangmanlist`</value>
</data>
<data name="hangman_cmd" xml:space="preserve">
<value>hangman</value>
</data>
<data name="hangman_desc" xml:space="preserve">
<value>Starts a game of hangman in the channel. Use `{0}hangmanlist` to see a list of available term types. Defaults to 'all'.</value>
</data>
<data name="hangman_usage" xml:space="preserve">
<value>`{0}hangman` or `{0}hangman movies`</value>
</data>
</root> </root>

File diff suppressed because it is too large Load Diff