Skip to content

Instantly share code, notes, and snippets.

@pragdave
Last active March 16, 2021 00:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pragdave/9abb976d1fec651a4a7c53fa23b8f0b9 to your computer and use it in GitHub Desktop.
Save pragdave/9abb976d1fec651a4a7c53fa23b8f0b9 to your computer and use it in GitHub Desktop.
import Data.List (sort, intersperse, union)
import Data.Function ( (&) )
import System.Random (Random(randomRIO))
(|>) = (&) -- 'cos I'm too old to switch...
pickRandomWord :: [ String ] -> IO String
pickRandomWord words = do
rand <- randomRIO (0, length words - 1)
return (words !! rand)
displayLetter :: String -> Char -> Char
displayLetter guessed letter
| letter `elem` guessed = letter
| otherwise = '_'
summaryLine :: String -> String -> Int -> String
summaryLine secret guessed turnsLeft =
"\n" ++ wordTemplate ++ turnsLeftMsg ++ "\n" ++ alreadyGuessedMsg
where
wordTemplate =
secret
|> map (displayLetter guessed)
|> intersperse ' '
turnsLeftMsg =
" (turns left: " ++ show turnsLeft ++ ")"
alreadyGuessedMsg =
"Guessed so far: " ++ guessed |> sort |> intersperse ' '
scoreGuess :: String -> String -> Int -> Char -> IO ()
scoreGuess secret guessed turnsLeft guess
| guess `elem` guessed = do
putStrLn $ "You've already guessed " ++ show guess
playGame secret guessed turnsLeft
| guess `elem` secret = do
putStrLn "Good guess!"
playGame secret (guess:guessed) turnsLeft
| otherwise =
playGame secret (guess:guessed) (turnsLeft - 1)
playGame :: String -> String -> Int -> IO ()
playGame secret guessed turnsLeft
| turnsLeft == 0 = do
putStrLn $ "Sorry, the word was: " ++ secret
| guessed `union` secret == guessed = do
putStrLn $ "Congratulations! The word was " ++ secret
| otherwise = do
summaryLine secret guessed turnsLeft |> putStrLn
putStr "Your guess: "
guess <- getLine
scoreGuess secret guessed turnsLeft (head guess)
main :: IO ()
main = do
dictionary <- readFile "../assets/17kwords.txt"
let words = lines dictionary
secret <- pickRandomWord words
playGame secret [] 8
@pragdave
Copy link
Author

Feel free to comment here...

@Kleidukos
Copy link

The line 51 line do is redundant :)

@pragdave
Copy link
Author

The line 51 line do is redundant :)

Thanks for the comment. Why is that one not needed, but 48 and 54 are?

@Kleidukos
Copy link

Kleidukos commented Mar 15, 2021

Because I commented from my phone and didn't see the one at line 48, which should also be redundant.

Line 54's do-expression is desugarised as:

  | otherwise =  
      summaryLine secret guessed turnsLeft |> putStrLn 
        >> putStr "Your guess: "
        >> getLine
        >>= scoreGuess secret guessed turnsLeft (head guess)

Where the >> is monadic piping without passing a value, and >>= is the bind operator, where a monadic value (IO a for instance) is properly passed as a single value (a) to the next function that operates in the same monad.

@lachezar
Copy link

I think Haskell folks in general prefer function composition ((.), e.g. g . f) over this "pipe" operator.

E.g. intersperse ' ' . map (displayLetter guessed) $ secret

If you install HLint or use VSCode with the Haskell plugin, then you will get suggestions to improve the code (e.g. remove extensive braces)

There are several code formatters to chose from, but for me Ormolu does the best job and does not go overboard with the alignments. All of them would order your imports lexicographically.

You could assign a type alias as type Secret = String to specify even more the types used in the functions. Alternatively you could wrap these types with newtype like newtype Secret = Secret String, which will make it impossible to call your functions with wrong parameters (e.g. calling by accident playGame guessed secret turnsLeft instead of playGame secret guessed turnsLeft)

@pragdave
Copy link
Author

I know that this is an ongoing discussion in the community. I personally like reading code as a transformation, stuff starts on the left and travels through to the right.

I was using both VSCode and vim, both with the Haskell LS and linting, but didn't get brace warnings. I'll look into why not. And I'll check out Ormolu, too.

Thanks!

Dave

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment