[Haskell-beginners] Keyboard input

Daniel Fischer daniel.is.fischer at web.de
Sat Dec 5 18:24:48 EST 2009

Am Samstag 05 Dezember 2009 23:20:22 schrieb legajid:
> Hello,
> in order to get familiar with keyboard data entry, i'm writing a program
> for the well-known Hangman game.
> Each entry must change several lists (letters found and remaining letters).
> I have three main difficulties with data entry :
> First When i enter a letter (A) , i get a good response (displaying old
> word and letters, message "guess ok") and an unattended one that says
> "guess false", thus displaying the result from my entry.
> I don't understand why the process seems to execute twice, the fisrt one
> being correct, the second not.

I don't get that behaviour, for me, after each guess, it displays only one message.

> Second difficulty : the  "procedure"  for data entry is written twice
> ("guess a letter ..." prompt). I think it would be a good idea to write
> it once as a "function" that would display the prompt then get a
> character from the keyboard.

Also, the entered letter appears before the word, which is not nice.
You can either

import System.IO

and at the beginning of hangman

 hSetEcho stdin False

and at the end

do putStrLn "Done"
   hSetEcho stdin True

or, without futzing with the echo setting:

import Data.Char (toUpper)
import Data.List (delete)

guessLetter :: IO Char
guessLetter = do
    putStrLn "Guess a letter (9 to end): "
    c <- getChar
    putChar '\b'
    return (toUpper c)

> Third difficulty : in both cases (guess ok or false), i have to ask for
> a new entry; to avoid writing this twice again, i write it before the
> recursive calls of process_guess. Writing a function would perhaps allow
> to call it directly as a parameter of process_guess ?

It would be cleaner to not have the guess as a parameter for the game loop.

> Being new to haskell,  recursive functions now seem clear to me but i
> feel that IOs make writing a program more difficult. And i've not yet
> tried data base access...
> Please, say me if the way i wrote my program is a good or bad approach.
> Below an example of a run :

Forgot that, didn't you?

> Here's my program.
> Could you help me.
> Thanks,
> Didier
> solution="HANGMAN"
> word="H-----N"
> letters=['A'..'Z']

It would be good to have a list of letters to be guessed,

wletters = "AGMN"

and check whether the guess appears there (-> Guess OK)
or not (-> Guess false :()
and remove correctly guessed letters from this, so when there are no more letters to be 
guessed you can detect it and

putStrLn "Congratulations"

to end the game.

The list of letters as allowed guesses is unnecessary, just check whether

'A' <= g && g <= 'Z'

where g is toUpper (entered letter), or don't care whether what is entered is a letter or 
something else.

> hangman = do
>   -- enter a letter
>   putStrLn "Guess a letter (9 to end):"
>   guess <- getChar
>   -- process
>   process_guess guess letters word

hangman = hangloop wletters word

> process_guess pg pletters pword =do

hangloop "" pword = do
    putStrLn pword
    putStrLn "Congratulations, you've solved it!"
hangloop pletters pword = do
    -- best to display the word first
    putStrLn pword
    g <- guessLetter
    if g == '9'
      then putStrLn "Bye"
      else do
            -- check whether the guess is good
            if g `notElem` pletters
              then do
                putStrLn "Wrong guess"
                hangloop pletters pword
              else do
                putStrLn "Good guess"
                let nletters = delete g pletters
                    nword = newword pword g solution
                hangloop nletters nword

>   let pguess = toUpper pg
>   putStrLn pword
>   putStrLn pletters
>   if pguess == '9'
>     then do putStrLn "Done"
>     else do
>       if pguess `elem` pletters
>         then do putStrLn "Guess OK \n"
>         else do putStrLn "Guess false \n"
>       -- Enter a new letter before going on, in both cases
>       putStrLn "Guess a letter (9 to end):"
>       guess <- getChar
>       if pguess `elem` pletters
>         -- guess ok -> remove guess from available letters then add
> guess to word
>         then do process_guess guess (newletters pletters pguess)
> (newword pword pguess solution)
>         -- guess false -> remove guess from available letters, leave
> word unchanged
>         else do process_guess guess (newletters pletters pguess) pword
> newletters l g =
>   filter (/= g) l

if every letter appears only once in l, Data.List.delete g l is perhaps better.

> newword [] _ _ = []
> newword (w:ws) g (s:ss) =
>   if w == '-'
>     then
>       if s == g
>         then g : newword ws g ss
>         else w : newword ws g ss
>     else
>       w : newword ws g ss

better pattern-match:

newword "" _ _ = ""
newword ('-':ws) g (s:ss)
    | g == s    = g:newword ws g ss
newword (w:ws) g (s:ss) = w:newword ws g ss

or, for the nonempty case:

newword (w:ws) g (s:ss)
    | w == '-' && s == g  = g:newword ws g ss
    | otherwise = w:newword ws g ss


-- these are in Data.Char

> toUpper c
>   | isLower c = toEnum (fromEnum c - fromEnum 'a' + fromEnum 'A')
>   | otherwise = c
> isLower c = c >= 'a' && c <= 'z'

More information about the Beginners mailing list