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.
In the beginning, only the length of the text and the spaces between its words are shown.
Only a single letter may be guessed in each turn.
Every letter that is present in the text becomes visible at every position where it can be found.
All the missed guesses are noted.
The games ends, if:
The player has managed to guess all the letters of the riddle, with 5 misses at maximum.
The player could not solve the riddle with less than 5 misses.
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.
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
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
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:
If the guessed letter is not present in the specified alphabet, signal an error with error
.
If the guessed letter is already present in the list of previously guessed ones, the state should stay unchanged.
If the guessed letter is in the text, prepend it to the list of the letters that were guessed right.
If the guessed letter is not in the text, prepend it to the list of the letters that were guessed wrong.
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
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"
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", [])
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
Define a function that determines if the current state implies the end of the game. A game is over if:
The player completed the riddle and there were no more than 5 wrong guesses.
The player guessed wrong more than 5 times.
isGameOver :: State -> Bool
For example:
isGameOver ("SOS", [], []) == False
isGameOver ("SOS", [], "LKHJIG") == True
isGameOver ("SAVE OUR SOULS", "SAVEOURL", "KZTW") == True
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"
Define a function that plays a game with a state and a list of characters specified. The play shall end if:
Any of the previous end criteria is met.
The list of characters has depleted.
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")
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\")"