Avatar of frankjwitte

frankjwitte's solution

to Hangman in the C# Track

Published at Jul 15 2018 · 0 comments
Instructions
Test suite
Solution

Note:

This exercise has changed since this solution was written.

Implement the logic of the hangman game using functional reactive programming.

Hangman is a simple word guessing game.

Functional Reactive Programming is a way to write interactive programs. It differs from the usual perspective in that instead of saying "when the button is pressed increment the counter", you write "the value of the counter is the sum of the number of times the button is pressed."

Implement the basic logic behind hangman using functional reactive programming. You'll need to install an FRP library for this, this will be described in the language/track specific files of the exercise.

Hints

This exercise requires you to work with events. For more information, see [this page] (https://docs.microsoft.com/en-us/dotnet/articles/csharp/programming-guide/events/) .

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

HangmanTest.cs

using Xunit;

public class HangmanTest
{
    [Fact]
    public void Initially_9_failures_are_allowed()
    {
        var game = new HangmanGame("foo");

        HangmanState lastState = null;
        game.StateChanged += (sender, state) => lastState = state;

        game.Start();

        Assert.Equal(HangmanStatus.Busy, lastState.Status);
        Assert.Equal(9, lastState.RemainingGuesses);
    }

    [Fact(Skip = "Remove to run test")]
    public void Initially_no_letters_are_guessed()
    {
        var game = new HangmanGame("foo");
        
        HangmanState lastState = null;
        game.StateChanged += (sender, state) => lastState = state;

        game.Start();

        Assert.Equal("___", lastState.MaskedWord);
    }

    [Fact(Skip = "Remove to run test")]
    public void After_10_failures_the_game_is_over()
    {
        var game = new HangmanGame("foo");
        
        HangmanState lastState = null;
        game.StateChanged += (sender, state) => lastState = state;

        game.Start();

        for (var i = 0; i < 10; i++)
        {
            game.Guess('x');
        }

        Assert.Equal(HangmanStatus.Lose, lastState.Status);
    }

    [Fact(Skip = "Remove to run test")]
    public void Feeding_a_correct_letter_removes_underscores()
    {
        var game = new HangmanGame("foobar");

        HangmanState lastState = null;
        game.StateChanged += (sender, state) => lastState = state;

        game.Start();

        game.Guess('b');

        Assert.Equal(HangmanStatus.Busy, lastState.Status);
        Assert.Equal(9, lastState.RemainingGuesses);
        Assert.Equal("___b__", lastState.MaskedWord);

        game.Guess('o');

        Assert.Equal(HangmanStatus.Busy, lastState.Status);
        Assert.Equal(9, lastState.RemainingGuesses);
        Assert.Equal("_oob__", lastState.MaskedWord);
    }

    [Fact(Skip = "Remove to run test")]
    public void Feeding_a_correct_letter_twice_counts_as_a_failure()
    {
        var game = new HangmanGame("foobar");
        
        HangmanState lastState = null;
        game.StateChanged += (sender, state) => lastState = state;

        game.Start();

        game.Guess('b');

        Assert.Equal(HangmanStatus.Busy, lastState.Status);
        Assert.Equal(9, lastState.RemainingGuesses);
        Assert.Equal("___b__", lastState.MaskedWord);

        game.Guess('b');

        Assert.Equal(HangmanStatus.Busy, lastState.Status);
        Assert.Equal(8, lastState.RemainingGuesses);
        Assert.Equal("___b__", lastState.MaskedWord);
    }

    [Fact(Skip = "Remove to run test")]
    public void Getting_all_the_letters_right_makes_for_a_win()
    {
        var game = new HangmanGame("hello");
        
        HangmanState lastState = null;
        game.StateChanged += (sender, state) => lastState = state;

        game.Start();

        game.Guess('b');

        Assert.Equal(HangmanStatus.Busy, lastState.Status);
        Assert.Equal(8, lastState.RemainingGuesses);
        Assert.Equal("_____", lastState.MaskedWord);

        game.Guess('e');

        Assert.Equal(HangmanStatus.Busy, lastState.Status);
        Assert.Equal(8, lastState.RemainingGuesses);
        Assert.Equal("_e___", lastState.MaskedWord);

        game.Guess('l');

        Assert.Equal(HangmanStatus.Busy, lastState.Status);
        Assert.Equal(8, lastState.RemainingGuesses);
        Assert.Equal("_ell_", lastState.MaskedWord);

        game.Guess('o');

        Assert.Equal(HangmanStatus.Busy, lastState.Status);
        Assert.Equal(8, lastState.RemainingGuesses);
        Assert.Equal("_ello", lastState.MaskedWord);

        game.Guess('h');

        Assert.Equal(HangmanStatus.Win, lastState.Status);
        Assert.Equal("hello", lastState.MaskedWord);
    }
}
using System.Collections.Generic;
using System.Linq;

public class HangmanState
{
    public HangmanState()
    {
        Guesses = new HashSet<char>();
    }
    public HangmanStatus Status { get; set; }
    public int RemainingGuesses { get; set; }
    public string MaskedWord { get; set; }
    public HashSet<char> Guesses { get; set; }
}

public enum HangmanStatus
{
    Busy,
    Win,
    Lose
}

public class HangmanGame
{
    public delegate void StateChangedHandler(object sender, HangmanState state);
    public event StateChangedHandler StateChanged;

    private readonly HangmanState _state = new HangmanState();
    private readonly string _word;

    public HangmanGame(string word)
    {
        _word = word;
        _state.Status = HangmanStatus.Busy;
        _state.RemainingGuesses = 9;
        _state.MaskedWord = string.Concat(Enumerable.Repeat("_", word.Length));
    }

    public void Start()
    {
        OnStateChanged(_state);
    }

    public void Guess(char c)
    {
        if (!AlreadyGuessedLetter(c))
        {
            _state.Guesses.Add(c);
            if (WordContainsLetter(c))
            {
                RecreateMaskedWord();
                if (GuessedWord())
                {
                    _state.Status = HangmanStatus.Win;
                }
            }
            else
            {
                _state.RemainingGuesses--;
            }
        }
        else
        {
            _state.RemainingGuesses--;
        }

        if (_state.RemainingGuesses == 0)
        {
            _state.Status = HangmanStatus.Lose;
        }
        OnStateChanged(_state);
    }

    private bool WordContainsLetter(char c)
    {
        return _word.Contains(c);
    }

    private void RecreateMaskedWord()
    {
        _state.MaskedWord = string.Empty;
        foreach (var letter in _word)
        {
            if (_state.Guesses.Contains(letter))
            {
                _state.MaskedWord += letter;
            }
            else
            {
                _state.MaskedWord += "_";
            }
        }
    }

    private bool GuessedWord()
    {
        return !_state.MaskedWord.Contains("_");
    }

    private bool AlreadyGuessedLetter(char c)
    {
        return _state.Guesses.Contains(c);
    }

    void OnStateChanged(HangmanState state)
    {
        StateChanged?.Invoke(this, state);
    }

}

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?