Dilawar Singh Being Dilawar/ Tools/

Hangman in Haskell

Rule of Hangman

You play this game with a man called Hangman. The job of hangman is to kill you. Hangman is fair and have some rules. He is abided by these rules.

Functions we need

Obviously, it requires input-output to system. When hangman inputs the word, you should not see. For this purpose, we have to disable echo at stdin.

= import System.IO @ Now we have to read the word from terminal. For this purpose we create a function `getWord`. \= getWord :: IO String getWord = do c <- getChar if c == '\\n' then return "" else do l <- getWord return (c:l) @ ##### Checking if a letter is in word To do this, first we check if a given letter is in word by using function `posChar`. It returns a list of `Bool` specifying if given letter exists in the word or not. For example, give a word 'gabbar' and a letter b, `posChar` returns a list of _\[False, False, True, True, False, False\]_. += posChar :: \[Char\] -> Char -> \[Bool\] posChar \[\] char = \[\] posChar (x:xs) char = (x==char) : (posChar xs char) It would be useful sometimes, just to see if a letter exists in given word or not. We can simply create another function `ifContain` using `foldr`. The idea is to use logical `or` operation on the returned list from `posChar`. += ifContain :: \[Char\] -> Char -> Bool ifContain word c = foldr (||) False (posChar word c) ##### Hangman constructs word from guesses Now if the guessed letter exists in the word then hangman has to show the word with guessed letter unmasked. We will use letter `_`to mask the hidden letter. At the beginning, hangman creates a word and a masked copy of it. Both of them must have the same size. We need a function to create such a masked copy. To make such a copy, we write a lambda expression on the fly, foldr(\\x ->('\_':))\[\] word does the job nicely. Now the job of hangman is to maintain the mask word and display its unmasked letter to the player. To maintain it, he needs a function `addGuessToWord`. += addGuessToWord :: \[Char\] -> \[Char\] -> Char -> \[Char\] addGuessToWord w w1 c | length(w) /= length(w1) = error "Length mismatch" -- just for safety. | ifContain w c = buildW1 w1 c (posChar w c) | otherwise = w1 buildW1 :: \[Char\] -> Char -> \[Bool\] -> \[Char\] buildW1 (x:\[\]) c (p:\[\]) | x == '\_' && p == True = c:\[\] | x /= '\_' = x:\[\] | otherwise = '\_':\[\] buildW1 (x:xs) c (p:ps) | x == '\_' && p == True = c:buildW1 xs c ps | x /= '\_' = x:buildW1 xs c ps | otherwise = '\_' : buildW1 xs c ps This function is slightly complicated. It uses extensive pattern matching. At the heart of this function is another function `buildW1` which unmasks (or rather put) correct guessed letter into masked-word. ## Put things together Now we have all the pieces to play hangman. Now we have to maintain no of guesses a player is allowed to make and `callHangman` at each step. We do it by, += callHangman :: Int -> \[Char\] -> \[Char\] -> Char -> IO () {- No attempt left, you are dead -} callHangman 0 wrd wrd1 c = do putStrLn "Dead" {- Attempts left, play on! -} callHangman n wrd wrd1 c | ifContain wrd c = do let newWrd = addGuessToWord wrd wrd1 c putStrLn (" --> " ++ (show newWrd)) if wrd == newWrd then do putStrLn "Well done!" else do putStrLn ("Guess again. Left " ++ (show n)) guess <- getChar callHangman n wrd newWrd guess | not (ifContain wrd c) = do putStrLn ("Guess again. Left " ++ (show (n-1))) guess <- getChar callHangman (n-1) wrd wrd1 guess | otherwise = error "Something wrong in logic." ## The main function \= main :: IO () main = do hSetEcho stdin False -- echo off to get the word from hangman. putStrLn "I am going to give you a work to guess." word <- getWord hSetEcho stdin True -- Now, echo on so player can play. let n = (length word) putStrLn (show n) let emptyWrd = foldr(\\x->('\_':)) \[\] word --create a masked copy of word. putStrLn emptyWrd callHangman (n+1) word emptyWrd ' ' -- dummy call to start the game. putStrLn "Game over" -- make sure you are alive ;-) ## Program structure <\*>= __ __ _
_ ##### Index