Last active
October 5, 2016 10:13
-
-
Save BillyBadBoy/345259f8afbb3939c4653c637cd95184 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
animal :: IO () | |
animal = do | |
putStrLn "Animal guessing game..." | |
newGame dbInit | |
--------------------------------------------------------------------------- | |
newGame :: [(String, Int)] -> IO () | |
newGame db = do | |
putStrLn "\nThink of an animal and I'll guess it." | |
play db 0 | |
--------------------------------------------------------------------------- | |
playAgain :: [(String, Int)] -> IO () | |
playAgain db = do | |
putStr "\nPlay again ? (y/n): " | |
ans <- getChar | |
putStrLn "" | |
if ans == 'y' then newGame db else putStrLn "Goodbye.\n" | |
--------------------------------------------------------------------------- | |
-- database of animal info | |
dbInit :: [(String, Int)] | |
dbInit = [ ("Is it a bird ?",1) | |
, ("a penguin",0) | |
, ("Is it a fish ?",3) | |
, ("a shark",0) | |
, ("Does it have a trunk ?",5) | |
, ("an elephant",0) | |
, ("Is it an insect ?",7), | |
("an ant",0),("a cobra",0) ] | |
--------------------------------------------------------------------------- | |
handleResults :: [(String, Int)] -> Bool -> Int -> IO () | |
handleResults db correct pos = do | |
if correct | |
then putStrLn "\n\nHey! I got it right !" | |
else putStrLn "\n\nOops! You beat me !" | |
if not correct then add db pos else playAgain db | |
--------------------------------------------------------------------------- | |
-- play game until a final guess is made | |
play :: [(String, Int)] -> Int -> IO () | |
play db pos = | |
if snd (db !! pos) == 0 -- we're ready to guess | |
then do | |
putStrLn "\nI think I know the answer!" | |
putStr ("Is it " ++ fst (db !! pos) ++ "? (y/n): ") | |
ans <- getChar | |
handleResults db (ans == 'y') pos | |
else do | |
putStr ("\n" ++ fst (db !! pos) ++ " (y/n): ") -- we have more questions | |
ans <- getChar | |
let pos' = snd (db !! pos) + if ans == 'y' then 0 else 1 | |
play db pos' | |
--------------------------------------------------------------------------- | |
-- add new question/animal to db and return updated db | |
add :: [(String, Int)] -> Int -> IO () | |
add db pos = do | |
putStr "\nPlease add your animal to my database.\nYour animal was a/an: " | |
s <- getLine | |
let a1 = fst (db !! pos) | |
let a2 = addArticle s | |
putStrLn ("Enter a question that distinguishes " ++ a1 ++ " and " ++ a2 ++ ":") | |
s' <- getLine | |
let qu = addQu s' | |
putStrLn ("\nIf you're thinking of " ++ a2 ++ ", and I ask: \'" ++ qu ++ "\'") | |
putStr "What would you answer ? (y/n): " | |
ans <- getChar | |
if ans == 'y' then updateDb db pos qu a2 a1 | |
else updateDb db pos qu a1 a2 | |
--------------------------------------------------------------------------- | |
updateDb :: [(String, Int)] -> Int -> String -> String -> String -> IO () | |
updateDb db pos qu animal1 animal2 = | |
let | |
prefix = take pos db | |
suffix = drop (pos + 1) db | |
question = (qu, length db) | |
guess_yes = (animal1, 0) | |
guess_no = (animal2, 0) | |
newDb = prefix ++ [question] ++ suffix ++ [guess_yes] ++ [guess_no] | |
in | |
playAgain newDb | |
--------------------------------------------------------------------------- | |
addArticle :: String -> String | |
addArticle s = (if head s `elem` "aeiou" then "an " else "a ") ++ s | |
--------------------------------------------------------------------------- | |
addQu :: String -> String | |
addQu s = if not ('?' `elem` s) then s ++ " ?" else s |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
(this comment is literate haskell, you can copy it as is into file.lhs and run; well, if github does not mess with formatting..)
Interesting program, but i want to say a few things.
IO
. And, moreover, theyreturn
IO ()
. Thus, it's hard to test and debug. Say, i want to see howyou add a new entry to db, but i can't, because
add
returns nothing(useful). And pure function is easier to test programmatically, then one
with side-effects (in monad).
play
->handleResults
->add
->updateDb
->playAgain
.. I can't do one action at a time in e.g.ghci
, and, thus, again i can't debug one function.at least for me. May be because i didn't do that a long time.
understand why, but, it seems, there are several places, where support is
missing and i've failed to figure out the quick fix.
certainly not: you have a question and an answer (or may be even two) - two
properties, which constitute a single item. But these items and distinction
between question and answer can't be derived from your structure type:
[(String, Int)]
. It's buried down somewhere in values.additional question leads to strange results. I guess, you can't make a
proper branching without proper data types.
navigation. I don't want to argue about editors here, but, really, don't do
this :)
If i understand correctly, you want the second answer to be used, when user
says "no" for the first. So, first, let's change the db type:
I've used type synonym (just another name for the type). The type is still not
perfect, but much better, because now i may try to guess where is the question
and where are the answers. I've used list for storing answers, so i may have
many answers.. well, that because i don't want to decide, which answer to
replace in
add
- it's simpler to collect them all. Then let's make anupdateDb
pure (it really does not need any IO - it just updates db, as itname says):
I've removed
pos
, because i don't think, that it's possible to implementbranching properly without proper types (i looked over week 3 titles, and, it
seems, that the
data
types should be introduced there..). So, hereupdateDb
either replaces existing element or inserts new, if there is nosuch element yet.
Now, since i often ask user 'yes' or 'no', let's write a separate function for
that:
It displays a prompt, asks the answer and returns a Bool. Then comes the
add
:I take an old db, maybe a question to update and return a new db. This add
covers two cases (similar to
updateDb
): when user answers to all questions'no' and he may just want to add a new question, and when user answers to some
question 'yes', but i haven't guessed an animal (and user may want to add his
answer).
Now, it's time for
play
. Note, that now i have two lists: list of answersand list of questions (with answers), which i want to traverse. But i want to
traverse them differently: list of answers i want to traverse until i'll find
the right one or reach the end of the list. so function with type
findM :: (a -> IO Bool) -> [a] -> IO (Maybe a)
may generalize answers traversal: i have a predicate of type (a -> IO Bool),
which gets an element from list, does something, and tells
findM
to continueto next element or not.
But the list of questions i want to traverse.. well, also until i'll find the
right question, but then the right question may not have the right answers. I
still want to stop a list traversal at that point, but i need to know what was
with answers. Thus, i need an additional information to be passed from
predicate, which checks list element (the question here), - whether answer was
correct or not. So the type of function becomes
findM :: b -> (a -> IO (Bool, b)) -> [a] -> IO (Maybe a, b)
Look, it first takes the default answer state (first argument of type b): it
will be no answer. Then it tries each question in turn, until user will
confirm the question. Then i want to stop list traversal (return True as first
element of tuple) and i want to know whether i guessed or not (that will be
the second tuple element). Let's try to write it!
Then, let's write the predicate for guessing answers:
and the predicate for asking questions:
So, i've used two bools: first Bool is used by
findM
to determine whether tocontinue list traversal, second Bool is telling outer function, whether i
guess the answer or not. Then, finally, this outer function:
Now, let's see what we have:
(question + answers).
ghci
with some input and see how it works.easy to try and test every element alone.
it's a great improvement :)
There're still many-many ways to further rewrite this code, make it more
concise or more complex or more interesting or just nicer. I think.