# rmunn's solution

## to Hangman in the F# Track

Published at Jul 13 2018 · 1 comment
Instructions
Test suite
Solution

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.

## Submitting Incomplete Solutions

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

### HangmanTest.fs

``````// This file was created manually and its version is 1.0.0.

module HangmanTest

open Xunit
open FsUnit.Xunit

open Hangman

[<Fact>]
let ``Initially 9 failures are allowed`` () =
let game = createGame "foo"
let states = statesObservable game

let mutable lastProgress = Busy 9
states.Add(fun state -> lastProgress <- state.progress) |> ignore

startGame game |> ignore

lastProgress |> should equal <| Busy 9

[<Fact(Skip = "Remove to run test")>]
let ``Initially no letters are guessed`` () =
let game = createGame "foo"
let states = statesObservable game

startGame game |> ignore

[<Fact(Skip = "Remove to run test")>]
let ``After 10 failures the game is over`` () =
let game = createGame "foo"
let states = statesObservable game

let mutable lastProgress = Busy 9
states.Add(fun state -> lastProgress <- state.progress) |> ignore

startGame game |> ignore

[for x in 1..10 do makeGuess 'x' game] |> ignore

lastProgress |> should equal Lose

[<Fact(Skip = "Remove to run test")>]
let ``Feeding a correct letter removes underscores`` () =
let game = createGame "foobar"
let states = statesObservable game

let mutable lastState = None
states.Add(fun state -> lastState <- Some state) |> ignore

startGame game |> ignore

makeGuess 'b' game |> ignore

lastState.Value.progress |> should equal <| Busy 9

makeGuess 'o' game |> ignore

lastState.Value.progress |> should equal <| Busy 9

[<Fact(Skip = "Remove to run test")>]
let ``Feeding a correct letter twice counts as a failure`` () =
let game = createGame "foobar"
let states = statesObservable game

let mutable lastState = None
states.Add(fun state -> lastState <- Some state) |> ignore

startGame game |> ignore

makeGuess 'b' game |> ignore

lastState.Value.progress |> should equal <| Busy 9

makeGuess 'b' game |> ignore

lastState.Value.progress |> should equal <| Busy 8

[<Fact(Skip = "Remove to run test")>]
let ``Getting all the letters right makes for a win`` () =
let game = createGame "hello"
let states = statesObservable game

let mutable lastState = None
states.Add(fun state -> lastState <- Some state) |> ignore

startGame game |> ignore

makeGuess 'b' game |> ignore

lastState.Value.progress |> should equal <| Busy 8

makeGuess 'e' game |> ignore

lastState.Value.progress |> should equal <| Busy 8

makeGuess 'l' game |> ignore

lastState.Value.progress |> should equal <| Busy 8

makeGuess 'o' game |> ignore

lastState.Value.progress |> should equal <| Busy 8

makeGuess 'h' game |> ignore

lastState.Value.progress |> should equal Win
``````module Hangman

// To install Gjallarhorn, add the next line to Hangman.fsproj:
// <PackageReference Include="Gjallarhorn" Version="1.2.3" />
// Then to resolve an FSharp.Core version mismatch:
// <PackageReference Include="FSharp.Core" Version="4.3.4" />

open Gjallarhorn

type GameProgress =
| NotStarted
| Busy of int  // Guesses remaining
| Lose
| Win

type GameState =
{
word : string
guesses : Set<char>
progress : GameProgress
}

word |> String.map (fun c ->
if guesses |> Set.contains c
then c
else '_' )

let createGame word =
{ word = word
guesses = Set.empty
progress = NotStarted } |> Mutable.create

let startGame (game : IMutatable<GameState>) =
game.Value <- { game.Value with progress = Busy 9 }

let statesObservable (game : ISignal<GameState>) = game

| Busy 0 -> Lose
| Busy n when n > 0 -> Busy (n-1)
| Busy n when n < 0 ->
printfn "Invalid game state: %d guesses" n
Lose
| other -> other

let makeGuessImpl guess isGood game =
let guesses' = game.guesses |> Set.add guess
let progress' =
if isGood && maskedWord' = game.word then Win
elif isGood then game.progress
{ game with
guesses = guesses'
progress = progress' }

let makeGuess guess (game : IMutatable<GameState>) =
let isGood =
game.Value.word |> Seq.contains guess &&
game.Value.guesses |> Seq.contains guess |> not
game.Value <- game.Value |> makeGuessImpl guess isGood``````

rmunn
Solution Author
commented 354 days ago

With Gjallarhorn and its Signals (whose main difference from Observables is that Signals are guaranteed to always have a current value, accessible by their .Value property), the unit tests of this can be improved to a better API. I'll work on a second version of this test at some point, with the unit tests rewritten to depend on the Gjallarhorn API.

### 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?