forked from EllieBotDevs/elliebot
112 lines
3 KiB
C#
112 lines
3 KiB
C#
|
#nullable disable
|
|||
|
|
|||
|
namespace EllieBot.Modules.Games.Hangman;
|
|||
|
|
|||
|
public sealed class HangmanGame
|
|||
|
{
|
|||
|
public enum GuessResult { NoAction, AlreadyTried, Incorrect, Guess, Win }
|
|||
|
|
|||
|
public enum Phase { Running, Ended }
|
|||
|
|
|||
|
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 => char.IsLetter(x)).Select(char.ToLowerInvariant).ToHashSet();
|
|||
|
}
|
|||
|
|
|||
|
public State GetState(GuessResult guessResult = GuessResult.NoAction)
|
|||
|
=> new(_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 (!char.IsLetter(ch) || !_remaining.Contains(char.ToLowerInvariant(ch)))
|
|||
|
output[i * 2] = ch;
|
|||
|
else
|
|||
|
output[i * 2] = '_';
|
|||
|
|
|||
|
output[(i * 2) + 1] = ' ';
|
|||
|
}
|
|||
|
|
|||
|
return new(output);
|
|||
|
}
|
|||
|
|
|||
|
public State Guess(string guess)
|
|||
|
{
|
|||
|
if (CurrentPhase != Phase.Running)
|
|||
|
return GetState();
|
|||
|
|
|||
|
guess = guess.Trim();
|
|||
|
if (guess.Length > 1)
|
|||
|
{
|
|||
|
if (guess.Equals(_word, StringComparison.InvariantCultureIgnoreCase))
|
|||
|
{
|
|||
|
CurrentPhase = Phase.Ended;
|
|||
|
return GetState(GuessResult.Win);
|
|||
|
}
|
|||
|
|
|||
|
return GetState();
|
|||
|
}
|
|||
|
|
|||
|
var charGuess = guess[0];
|
|||
|
if (!char.IsLetter(charGuess))
|
|||
|
return GetState();
|
|||
|
|
|||
|
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);
|
|||
|
}
|
|||
|
|
|||
|
_correct.Add(charGuess);
|
|||
|
return GetState(GuessResult.Guess);
|
|||
|
}
|
|||
|
|
|||
|
_incorrect.Add(charGuess);
|
|||
|
if (_incorrect.Count > 5)
|
|||
|
{
|
|||
|
CurrentPhase = Phase.Ended;
|
|||
|
return GetState(GuessResult.Incorrect);
|
|||
|
}
|
|||
|
|
|||
|
return GetState(GuessResult.Incorrect);
|
|||
|
}
|
|||
|
|
|||
|
public record State(
|
|||
|
int Errors,
|
|||
|
Phase Phase,
|
|||
|
string Word,
|
|||
|
GuessResult GuessResult,
|
|||
|
List<char> MissedLetters,
|
|||
|
string ImageUrl)
|
|||
|
{
|
|||
|
public bool Failed
|
|||
|
=> Errors > 5;
|
|||
|
}
|
|||
|
}
|