Lab Assignment 4B: Famous
Goal & Purpose
In this assignment you will implement a small guessing game, where the computer tries to guess which famous person you are thinking about. Watch this short video (there is no audio) to find out how the game is supposed to work.
The game maintains some knowledge about famous people (in the form of a decision tree). When the computer fails to guess which famous persion you are thinking about, it asks you for some information that it can use to improve its knowledge. The accumulated knowledge is stored in a file, so that it is retained even if you stop the game and start it again.
Although this is a fairly small and simple the game, it will teach you something about working with IO as well as recursive data types. And, unlike the previous assignments, this time you write the complete program yourself, there are no ready-made modules for you to download, and no step-by-step instructions for how to structure the program, providing a different kind of challenge from the previous assignments.
Must be submitted before Friday, October 25 at midnight (2019).
The submission instructions are similar to those for Lab 4A.
Requirements & Recommendations
Instead of a list of subtasks that need to be completed in the order given, in this assignment we just give you some overall requirement, recommendations and hints for implementing the game. Read them all before you start writing any code.
“Question-Answer” Decision Trees
You will need to have a data type (call it
QA) to represent “Question-Answer” decision trees like the following:
The smallest trees just give the name of a person. Larger trees are built with a question together with two sub-trees, one for the yes answer and one for the no answer. You can assume that all questions are yes-no so you do not need to represent the “yes” and “no” explicitly.
deriving (Show, Read); The
show function will be used when you store a decision tree into a file. The
Read class will give you a function
read :: String -> QA which will help you when you want to build instructions to read a decision tree back from that file.
Use a representation of the decision tree pictured above as your default tree, to be used when there is no file (
Playing a round of the game
Once we have a decision tree (either from the file or using the default tree), we start playing a round of the game by walking down the tree, asking questions as we go along. Once you get to a person then the guess has been decided.
So suppose we have played the default decision tree as follows:
Is she from Europe? yes
Is she a scientist? no
My guess: Is it Queen Elisabeth II? no
OK - you won this time.
At this point the game will continue (the text in bold face is typed by the player):
Just curious: Who was your famous person? Theresa May
Give me a question for which the answer for "Theresa May" is "yes"
and the answer for "Queen Elisabeth II" is "no".
Is she a politician?
At this point the game should construct an extended decision tree, as pictured below:
We recommend that this process is implemented using a recursive function
play returns the new decision tree (which will be unchanged if the computer guessed the right answer) then it can be stored back into the file.
If we use
readFile :: FileName -> IO String and the file does not exist then there will be an error and the program will crash. This is not what we want! Here you should use
tryIOError :: IO a -> IO (Either IOError a)
from standard module
tryIOError (readFile ...), we turn an IO instruction that possibly generates an error into an IO instruction that never generates any error, but instead returns a value that tells us whether or not there was an error. Look at the documentation for the datatypes Either and IOError for more information.
About buffered output
Most operating systems do not write things directly on the screen when the program tells them to. Instead, they wait until a whole line is printed to the screen, and then they actually print it. This can be a nuisance when a question is printed on the screen, and the user has to type the answer on the same line (as we want in this game). To avoid the question not being printed on the screen, we tell the operating system to “flush” the output of the program, i.e. print everything on the screen that we want to print on the screen, not waiting for the end of the current line. A haskell instruction
hFlush stdout will do this for us, so use it after using
putStr ... to make sure things get printed right away. You will need to include module
System.IO for this.
Most of this program will be IO instructions, but that does not mean that you should not think about defining useful functions which you can reuse in several places, and which help to give your program some nice structure.
As a hint, think about defining a function
question :: String -> IO String
which asks a question and returns the answer typed by the user. Using this, you can define
yesNoQuestion :: String -> IO Bool
which asks a yes-no question (repeatedly if needed) until it gets either answer “yes” or “no”, and it returns
True when the answer was yes, and
False when the answer was no. This can be used in several places in your program.
The whole game should be defined as
Then you can run the game from the terminal by typing
runhaskell Famous (which does the same as loading the file in
ghci and typing
main). You can also compile your program into a standalone executable file.
You must submit using the Fire system in groups of 3.
You should only submit: your version of
Famous.hs. Do not upload any other files.
Before you submit your code,
spend some time on cleaning up your code; make it simpler,
remove unneccessary things, etc.
We will reject your solution if it is not clean. Clean code:
Feel free to use the
program to help with many of these issues and other haskell style issues.
If a the suggestion from hlint looks like it assumes knowledge about things
we haven't covered yet in the lectures, you can ignore it
(or possibly include it in a comment in your code).
- Does not have long lines (< 78 characters)
- Has a consistent layout (avoid using TAB characters in your code)
- Has type signatures for all top-level functions
that you are asked to write
- Has good comments
- Has no junk (junk is unused code, commented code, unneccessary comments)
- Has no overly complicated function definitions
- Does not contain any repetitive code (copy-and-paste programming)
To the Fire System
- 2019-09-12 Minor revisions for 2019 (Thomas Hallgren)
- 2018-10-18 First version (Dave Sands)