An installer for GHC can be downloaded from its home page or as part of the Haskell Platform. For reference, the full documentation is available that can be searched through Hoogle.

This exercise is about implementing a hangman game. During the game, the player is presented a hidden text, a riddle, and the goal is to solve the riddle by figuring out all of its letters with a limited number of guesses.

The rules of the game are as follows.

The games ends, if:

First, we assign an alphabet for the riddle. The riddle may only use the letters of the alphabet.

type Alphabet = [Char]

alphabet :: Alphabet
alphabet = ['A'..'Z']

The current state of the game (State) is a 3-tuple that consists of the hidden text (Riddle), the guessed letters that were right (RightGuesses), and the letters that were missed (WrongGuesses). A common invariant of this state is that it can only contain uppercase letters. This is going to be warranted by the implemented functions.

Is the Letter Valid?

Each of the guessed letters may be both lowercase and uppercase ones, or even others. Define a function that checks if a given letter is member of an alphabet in a case-insensitive way.

Hint. For handling case insensitivity, use the toUpper and toLower functions from Data.Char.

isValidLetter :: Char -> Alphabet -> Bool

For example:

isValidLetter 'a' alphabet == True
isValidLetter 'X' alphabet == True
isValidLetter ' ' alphabet == False
isValidLetter '$' "*]-$><" == True
isValidLetter 'E' ['a'..'z'] == True

The Start State

Define a function that returns the start state, but only if the riddle is built up from the letters of the specified alphabet, raise an error otherwise with the error function. The resulting 3-tuple has the riddle with uppercase letters, and two empty lists that correspond to the letters that were guessed right or wrong.

startState :: Alphabet -> String -> State

For example:

startState alphabet "" == ([], [], [])
startState alphabet "SOS" == ("SOS", [], [])
startState alphabet "Save Our Souls" == ("SAVE OUR SOULS", [], [])
startState alphabet "Save Our Souls!" --> exception

Guessing Letters

Define a function that modifies the state of the game by the guessed letter. There are the following cases depending on the value of the guessed letter:

guessLetter :: Alphabet -> Char -> State -> State

For example:

guessLetter alphabet 'a' (startState alphabet "Save Our Souls") == ("SAVE OUR SOULS", "A", [])
guessLetter alphabet 'A' (startState alphabet "Save Our Souls") == ("SAVE OUR SOULS", "A", [])
guessLetter alphabet 'k' (startState alphabet "Save Our Souls") == ("SAVE OUR SOULS", [], "K")
guessLetter alphabet 'a' (guessLetter alphabet 'a' (startState alphabet "Save Our Souls")) == ("SAVE OUR SOULS", "A", [])
guessLetter alphabet 'K' (guessLetter alphabet 'k' (startState alphabet "Save Our Souls")) == ("SAVE OUR SOULS", [], "K")
guessLetter alphabet 'v' ("SAVE OUR SOULS", "A", []) == ("SAVE OUR SOULS", "VA", [])
guessLetter alphabet 'k' ("SAVE OUR SOULS", "VA", []) == ("SAVE OUR SOULS", "VA", "K")
guessLetter alphabet 'รก' ("SAVE OUR SOULS", "VA", []) --> exception

Showing the Riddle

Define a function that displays the current state of the game for the player. The resulting string should contain all the guessed characters at the respective positions, and an _ (underscore) character at the others. Mind the spaces between the words in the riddle as well.

showRiddle :: State -> String

For example:

showRiddle ("SAVE OUR SOULS", [], []) == "____ ___ _____"
showRiddle ("SAVE OUR SOULS", "AL", []) == "_A__ ___ ___L_"
showRiddle ("SAVE OUR SOULS", "SAL", []) == "SA__ ___ S__LS"
showRiddle ("SAVE OUR SOULS", "SALO", []) == "SA__ O__ SO_LS"
showRiddle ("SOS", "SALO", []) == "SOS"

Showing the State

Define a function that maps the current state of the game to a state that could be presented to the player.

showState :: State -> State

For example:

showState ("SAVE OUR SOULS", [], []) == ("____ ___ _____", [], [])
showState ("SAVE OUR SOULS", "SAL", []) == ("SA__ ___ S__LS", "SAL", [])
showState ("SAVE OUR SOULS", "SALO", []) == ("SA__ O__ SO_LS", "SALO", [])

Is the Riddle Complete?

Define a function that determines if the player has managed to complete the riddle based on the current state of the game. A riddle is considered complete if all of its letters have been explored by the guesses.

Remark. The limit on the number of wrong guesses shall not be checked yet.

isRiddleComplete :: State -> Bool

For example:

isRiddleComplete ("SOS", [], []) == False
isRiddleComplete ("SOS", "SALO", []) == True
isRiddleComplete ("SOS", "ALO", []) == False
isRiddleComplete ("SOS", [], "LKHJIG") == False
isRiddleComplete ("SAVE OUR SOULS", "SAVEOURL", "KZTW") == True

Is the Game Over?

Define a function that determines if the current state implies the end of the game. A game is over if:

isGameOver :: State -> Bool

For example:

isGameOver ("SOS", [], []) == False
isGameOver ("SOS", [], "LKHJIG") == True
isGameOver ("SAVE OUR SOULS", "SAVEOURL", "KZTW") == True

Getting Available Letters

Define a function that determines which letters have not yet been used for guessing.

getAvailableLetters :: Alphabet -> State ->  [Char]

For example:

getAvailableLetters alphabet ("SAVE OUR SOULS", "SAVEOURL", "KZTW") == "BCDFGHIJMNPQXY"
getAvailableLetters alphabet ("SOS", [], "LKHJIG") == "ABCDEFMNOPQRSTUVWXYZ"
getAvailableLetters alphabet ("SOS", [], []) == "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

Playing a Game

Define a function that plays a game with a state and a list of characters specified. The play shall end if:

For displaying the result of the game at the end, use the showState function.

play :: Alphabet -> [Char] -> State -> State

For example:

play alphabet "SAkZVETWOURL" (startState alphabet "Save Our Souls") == ("SAVE OUR SOULS", "LRUOEVAS", "WTZK")
play alphabet "PjiSAKZVETWOURL" (startState alphabet "Save Our Souls") == ("SAVE ___ S___S", "EVAS", "TZKIJP")
play alphabet "WOUrL" (startState alphabet "Save Our Souls") == ("____ OUR _OUL_", "LRUO", "W")
play alphabet "ESAKIVTLZVSETSWVOUYRL" (startState alphabet "Save Our Souls") == ("SAVE OU_ SOULS", "UOLVASE", "YWZTIK")
play alphabet "EpSAKIVTLZVSETSWVOUYRL" (startState alphabet "Save Our Souls") == ("SAVE ___ S__LS", "LVASE", "WZTIKP")

Evaluating a Play

With the help of the previous function, define a function that plays a game and displays its result as a string. There can be three outcomes: the player managed to solve the riddle or not, or the game was not finished.

Hints. Use the show function for displaying the full state. For the exact messages for each for the cases mentioned above, see the tests.

evaluatePlay :: Alphabet -> [Char] -> State -> String

For example:

evaluatePlay alphabet "SAKZVETWOURL" (startState alphabet "Save Our Souls") == "Congratulations! The solutions was: SAVE OUR SOULS"
evaluatePlay alphabet "PjiSAKZVETWOURL" (startState alphabet "Save Our Souls") == "Game Over.  Try again."
evaluatePlay alphabet "PSK" (startState alphabet "SOS") == "The game is pending.  The current state is: (\"S_S\",\"S\",\"KP\")"
evaluatePlay alphabet "PSAKZVETW" (startState alphabet "Save Our Souls") == "The game is pending.  The current state is: (\"SAVE ___ S___S\",\"EVAS\",\"WTZKP\")"
evaluatePlay alphabet "PSAKZVE" (startState alphabet "Save Our Souls") == "The game is pending.  Current state is: (\"SAVE ___ S___S\",\"EVAS\",\"ZKP\")"