Skip to content

Instantly share code, notes, and snippets.

@ygrenzinger
Created December 27, 2016 20:55
Show Gist options
  • Save ygrenzinger/1961032c0ef4c4f88ae8e3be3400cce2 to your computer and use it in GitHub Desktop.
Save ygrenzinger/1961032c0ef4c4f88ae8e3be3400cce2 to your computer and use it in GitHub Desktop.
Hangman Haskell
module Main where
import Control.Monad (forever)
import Data.Char (toLower)
import Data.Maybe (isJust, fromMaybe)
import Data.List (intersperse)
import System.Exit (exitSuccess)
import System.Random (randomRIO)
newtype WordList = WordList [String] deriving (Eq, Show)
minWordLength :: Int
minWordLength = 4
maxWordLength :: Int
maxWordLength = 7
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 - 1)
return $ wl !! randomIndex
randomWord' :: IO String
randomWord' = gameWords >>= randomWord
data Puzzle = Puzzle String [Maybe Char] [Char]
instance Show Puzzle where
show (Puzzle _ discovered guessed) =
intersperse ' ' $ fmap renderPuzzleChar discovered ++ " Guessed so far: " ++ guessed
freshPuzzle :: String -> Puzzle
freshPuzzle s = Puzzle s (map (const Nothing) s) []
charInWord :: Puzzle -> Char -> Bool
charInWord (Puzzle word _ _) c = c `elem` word
alreadyGuessed :: Puzzle -> Char -> Bool
alreadyGuessed (Puzzle _ _ alreadyGuessed) c = c `elem` alreadyGuessed
renderPuzzleChar :: Maybe Char -> Char
renderPuzzleChar = fromMaybe '_'
fillInCharacter :: Puzzle -> Char -> Puzzle
fillInCharacter (Puzzle word filledInSoFar s) c =
Puzzle word newFilledInSoFar (c : s)
where zipper guessed wordChar guessChar =
if wordChar == guessed then Just wordChar else guessChar
newFilledInSoFar = zipWith (zipper c) word filledInSoFar
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)
gameOver :: Puzzle -> IO ()
gameOver (Puzzle wordToGuess _ guessed) =
if length guessed > 7 then
do putStrLn "You lose!"
putStrLn $ "The word was: " ++ wordToGuess
exitSuccess
else return ()
gameWin :: Puzzle -> IO ()
gameWin (Puzzle _ filledInSoFar _) =
if all isJust filledInSoFar
then do putStrLn "You win!"
exitSuccess
else return ()
runGame :: Puzzle -> IO ()
runGame puzzle = forever $ do
gameOver puzzle
gameWin puzzle
putStrLn $ "Current 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