<commit_before>#nullable disable
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(_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);
    }

    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);<commit_msg>Small bugfix in hangman, thx ala<commit_after>﻿#nullable disable
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(_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);
    }

    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);
            }

            _correct.Add(charGuess);
            return GetState(GuessResult.Guess);
        }

        _incorrect.Add(charGuess);
        if (_incorrect.Count > 5)
        {
            CurrentPhase = Phase.Ended;
            return GetState(GuessResult.Incorrect);
{'fraction_non_alphanumeric': 0.09845288326300984, 'fraction_numerical': 0.003750586029067042, 'mean_word_length': 1.7210884353741496, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7318064', 'n_tokens_mistral': 1891, 'n_tokens_neox': 1798, 'n_words': 383}