Created
December 11, 2017 21:47
-
-
Save erasmas/78483ec9cded2af746acbb57e234249b to your computer and use it in GitHub Desktop.
Hangman exercise from Haskell Programming book
This file contains hidden or 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
module Main where | |
import Control.Monad (forever) | |
import Data.Char (toLower) | |
import Data.List (intersperse) | |
import Data.Maybe (isJust, isNothing) | |
import System.Exit (exitSuccess) | |
import System.Random (randomRIO) | |
newtype WordList = | |
WordList [String] | |
deriving (Eq, Show) | |
minWordLength :: Int | |
minWordLength = 5 | |
maxWordLength :: Int | |
maxWordLength = 9 | |
allWords :: IO WordList | |
allWords = do | |
dict <- readFile "data/dict.txt" | |
return $ WordList (lines dict) | |
gameWords :: IO WordList | |
gameWords = do | |
(WordList aw) <- allWords | |
return $ WordList (filter gameLength aw) | |
where | |
gameLength w = | |
let l = length (w :: String) | |
in l >= minWordLength && l < maxWordLength | |
randomWord :: WordList -> IO String | |
randomWord (WordList wl) = do | |
randomIndex <- randomRIO (0, length wl) | |
return $ wl !! randomIndex | |
randomWord' :: IO String | |
randomWord' = gameWords >>= randomWord | |
data Puzzle = | |
Puzzle String | |
[Maybe Char] | |
String | |
renderPuzzleChar :: Maybe Char -> Char | |
renderPuzzleChar (Just c) = c | |
renderPuzzleChar Nothing = '_' | |
instance Show Puzzle where | |
show (Puzzle _ discovered guessed) = | |
intersperse ' ' (fmap renderPuzzleChar discovered) ++ | |
" Guessed so far: " ++ guessed | |
freshPuzzle :: String -> Puzzle | |
freshPuzzle word = Puzzle word discovered [] | |
where | |
discovered = map (const Nothing) [0 .. ((pred . length) word)] | |
charInWord :: Puzzle -> Char -> Bool | |
charInWord (Puzzle word _ _) c = c `elem` word | |
alreadyGuessed :: Puzzle -> Char -> Bool | |
alreadyGuessed (Puzzle _ _ guessed) c = c `elem` guessed | |
fillInCharacter :: Puzzle -> Char -> Puzzle | |
fillInCharacter (Puzzle word discovered guessed) c = | |
Puzzle word newDiscovered (c : guessed) | |
where | |
zipper guessed' wordChar guessChar = | |
if wordChar == guessed' | |
then Just wordChar | |
else guessChar | |
newDiscovered = zipWith (zipper c) word discovered | |
handleGuess :: Puzzle -> Char -> IO Puzzle | |
handleGuess puzzle guess = do | |
putStrLn $ "Your guess was: " ++ [guess] | |
case (charInWord puzzle guess, alreadyGuessed puzzle guess) of | |
(_, True) -> do | |
putStrLn "You already guessed that character, pick something else!" | |
return puzzle | |
(True, _) -> do | |
putStrLn | |
"This character was in the word, filling in the word accordingly." | |
return (fillInCharacter puzzle guess) | |
(False, _) -> do | |
putStrLn "This character wasn't in the word, try again." | |
return (fillInCharacter puzzle guess) | |
wrongGuesses :: Puzzle -> Int | |
wrongGuesses (Puzzle _ discovered guesses) = | |
length guesses - rightGuesses | |
where | |
rightGuesses = length $ filter (\c -> Just c `elem` discovered) guesses | |
gameOver :: Puzzle -> IO () | |
gameOver puzzle @ (Puzzle wordToGuess _ _) = | |
let maxAttempts = (pred . length) wordToGuess | |
wrongAttempts = wrongGuesses puzzle | |
attemptsLeft = maxAttempts - wrongAttempts | |
in if (wrongAttempts == maxAttempts) | |
then do | |
putStrLn "You lose!" | |
putStrLn $ "The word was: " ++ wordToGuess | |
exitSuccess | |
else do | |
putStrLn $ "Attempts left: " ++ (show attemptsLeft) | |
return () | |
gameWin :: Puzzle -> IO () | |
gameWin (Puzzle _ discovered _) = | |
if all isJust discovered | |
then do | |
putStrLn "You win!" | |
exitSuccess | |
else return () | |
runGame :: Puzzle -> IO () | |
runGame puzzle = | |
forever $ do | |
gameOver puzzle | |
gameWin puzzle | |
putStrLn $ "\nCurrent puzzle is: " ++ show puzzle | |
putStr "Guess a letter: " | |
guess <- getLine | |
case guess of | |
[c] -> handleGuess puzzle c >>= runGame | |
_ -> putStrLn "Your guess must be a single character" | |
main :: IO () | |
main = do | |
word <- randomWord' | |
let puzzle = freshPuzzle (fmap toLower word) | |
runGame puzzle |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment