
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. 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. 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 ? 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 : Here's my program. Could you help me. Thanks, Didier solution="HANGMAN" word="H-----N" letters=['A'..'Z'] hangman = do -- enter a letter putStrLn "Guess a letter (9 to end):" guess <- getChar -- process process_guess guess letters word process_guess pg pletters pword =do 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 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 toUpper c | isLower c = toEnum (fromEnum c - fromEnum 'a' + fromEnum 'A') | otherwise = c isLower c = c >= 'a' && c <= 'z'

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'

Hello, ok for the responses. However, about the strange behaviour of getChar, i forgot to say i'm running on Windows XP with GHci 6.10.4 It seems that getChar buffers all characters, including newline; so, typing A + enter is equivalent to typing 2 characters successively. To successfully solve the problem, i did the following : guessLetter :: IO Char guessLetter = do putStrLn "Guess a letter (9 to end): " cl <- getLine let c= extr cl -- c <- getChar -- putChar '\b' return (toUpper c) extr :: String -> Char extr (c:cs)=c Notice that, on my system, putChar has no visible effect. Seeking for info, i found this mail : http://old.nabble.com/How-to-getCh-on-MS-Windows-command-line--td20414545.ht... that talks about a similar issue on Windows Didier. Daniel Fischer a écrit :
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'

Am Montag 07 Dezember 2009 22:52:42 schrieb legajid:
Hello, ok for the responses. However, about the strange behaviour of getChar, i forgot to say i'm running on Windows XP with GHci 6.10.4
Voilà, c'est ton problème ;) Somehow, buffering (or rather not buffering) doesn't work properly on Windows. Ordinarily, in ghci, stdin is unbuffered, meaning each character you type is immediately passed to the application (for binaries, you'd have to "hSetBuffering stdin NoBuffering"), that seems not to be the case on Windows (http://hackage.haskell.org/trac/ghc/ticket/2189).
It seems that getChar buffers all characters, including newline; so, typing A + enter is equivalent to typing 2 characters successively. To successfully solve the problem, i did the following :
guessLetter :: IO Char guessLetter = do putStrLn "Guess a letter (9 to end): " cl <- getLine let c= extr cl -- c <- getChar -- putChar '\b' return (toUpper c)
extr :: String -> Char extr (c:cs)=c
Notice that, on my system, putChar has no visible effect.
Buffering again. Just to test what works, you can try a) ghci> :m +System.IO ghci> hSetBuffering stdout NoBuffering ghci> putChar 'A' (turning off buffering for stdout *may* work, even if it doesn't for stdin) if the next line you see is Aghci> it worked b) explicitly flush stdout do ... putChar 'X' hFlush stdout
Seeking for info, i found this mail : http://old.nabble.com/How-to-getCh-on-MS-Windows-command-line--td20414545.h tml that talks about a similar issue on Windows
Didier.
participants (2)
-
Daniel Fischer
-
legajid