Wrong Recursion for Hangman in Haskell

Viewed 46

I got a little problem with my haskell code:

import Prelude

hangman :: String -> IO ()
hangman [] = return ()
hangman s = do putStr ("Secret: " ++ text s ++ "\n")
               putStr "Enter a character: "
               charr <- getChar 
               function s charr (text s)

function :: String -> Char -> String -> IO ()
function [] _ _          = return ()
function (a:bc) charr (g:jt) = do putStr ("Secret: " ++ (textBack (a:bc) charr (g:jt)) ++ "\n")
                                  putStr "Enter a character: "
                                  charbaby <- getChar
                                  putStr "\n" 
                                  function (a:bc) charbaby (textBack (a:bc) charbaby (g:jt))
                                                  
text :: String -> String
text []     = ""
text (x:ys) = "*" ++ text ys

textBack :: String -> Char -> String -> String
textBack [] _ _              = ""
textBack (a:bc) charr (g:jt) = if charr == a
                                then charr : textBack bc charr jt 
                                else g : textBack bc charr jt

And here is the output:

*Main> hangman "hello"
Secret: *****
Enter a character: h
Secret: h****
Enter a character:
Secret: h****
Enter a character: e

Secret: he***
Enter a character:
Secret: he***
Enter a character: l

Secret: hell*
Enter a character:
Secret: hell*
Enter a character: o

Secret: hello
Enter a character:
Secret: hello
Enter a character:

And as you can see there are some lines which are called multiple times and I dont know why. I hope you can help me :)

Thank you,

Finn

1 Answers

I think you are making it yourself difficult by two things:

  1. you duplicate logic in hangman and function. This makes it hard to keep things in sync; and
  2. you are using unnecessary pattern matching making the function less elegant.

For the real problem, I think that this is due to the use of getChar: getChar takes a single character from the stdin, and if you write for example H and ENTER, then you thus enter two characters: 'h' and '\n'.

You thus better use getLine and then use only the first character, or ask to enter the input again when the user has written multiple characters. For example:

readGuess :: String -> Maybe Char
readGuess [x] = Just x
readGuess _ = Nothing

getGuess :: IO Char
getGuess = do
    putStrLn "Enter a character:"
    cs <- readGuess <$> getLine
    case cs of
        Just c -> pure c
        Nothing -> putStrLn "Invalid guess" >> getGuess

This thus will retrieve a guess with:

Prelude> getGuess 
Enter a character:

Invalid guess
Enter a character:
hello
Invalid guess
Enter a character:
foo
Invalid guess
Enter a character:
b
'b'

You can simplify hangman to:

hangman :: String -> IO ()
hangman s = hangman' s (text s)

hangman' :: String -> String -> IO ()
hangman' sa sb
    | sa == sb = putStrLn "You won"
    | otherwise = do
        putStr ("Secret: " ++ sb ++ "\n")
        g <- getGuess
        hangman' sa (textBack sa g sb)
Related