Please Buy now at Leanpub
Written and illustrated by GetContented.
Published on 2024-05-24.
This is one of the simplest horror games ever. The original idea was by Peter Halasz of https://becauseofgames.com who created it in the programming language C when he was learning how to program.
We’re going to use this little game to learn how to get some input from the user, to sequence chunks of IO code together, and also to see a way to make a program continue until the user wants to stop.
Here is the listing. We explain it below.
main :: IO ()
main = do
putStrLn "You are in a fridge. What do you want to do?"
putStrLn "1. Try to get out."
putStrLn "2. Eat."
putStrLn "3. Die."
command <- getLine
case command of
"1" ->
putStrLn "You try to get out. You fail. You die."
"2" ->
do
putStrLn "You eat. You eat some more."
putStrLn "Damn, this food is tasty!"
putStrLn "You eat so much you die."
"3" ->
putStrLn "You die."
_ ->
putStrLn "Did not understand."
putStrLn "Play again? write y if you do."
playAgain <- getLine
if playAgain == "y"
then main
else putStrLn "Thanks for playing."
This program uses a do
block, which is something we haven’t seen before. This allows you to join many actions together into one single action. There are two main things to know about do
blocks, which we’ll cover below.
Firstly, all of the actions in an IO
do block are executed in the order they’re written. That is, they’re sequenced together. Secondly, using “<-
”, you can connect an inner value from an IO
action on its right to a variable on its left. It’s worth noting that you can only use this <-
syntax within a do
block, though.
You’ll also notice that we have a case
expression. We can use any Haskell expressions we like in a do
block as long as they result in an action of the same type as the do
block’s type.
Anyway, let’s see these things in action with two tiny example programs, one for do
blocks combining and sequencing IO
actions, and one for gathering input from the user.
main :: IO ()
main =
do
putStrLn "Hello"
putStrLn "There"
Ok, so this program will first print Hello
on the screen, then it will print There
on the next line. The do block takes one or more actions, and packs them into a single action. The type of that do block above is IO ()
, just like main
, and in IO
, these actions will be sequenced one after the other as we’ve written them down the page.
And now, getting some input from the user:
main :: IO ()
main =
do
putStrLn "What is your name? "
theName <- getLine
putStrLn ("You said your name is " ++ theName)
Ok here we’re sequencing three actions together. We can get input from the user in Haskell with getLine
. When this program is run, once the first line has been output, it will wait for the user to put some text in and press return. Once they do that, it will have bound that text into the getLine
action, pulled that value out as theName
, and printed out the last line which includes that text the user entered!
If we look back at our original program, and look at the line command <- getLine
, you’ll see <-
is there to pull the String
from the getLine
action and set the variable “command
” to its value. The type of getLine
is IO String
, which means it’s an IO
action that “contains” a String
when it’s executed. When we use <-
, we can think about it like it’s extracting the String
value from the action (note that this applies only when we’re in an IO
action, though).
Notice, also, that you can put do
blocks within do
blocks. This is called nesting. We’re doing this by having another do
block in the “2
” branch of the case
expression.
This game has two sections. First it tells the user their options and asks for their input with getLine
, and then depending on what they wrote, it tells them what happened. Next, it asks if they want to play again, and if they do, it runs the whole thing again by calling main
. This is recursion of the whole program. The program is an IO
action, and do
blocks allow us to compose IO
actions, so it’s perfectly fine to have the whole program at the end recursively.
One last thing to note about do
blocks, though, is that they must always end with the same type as the whole do
block. So, becuase ours ends with an if
expression whose resultant type is IO ()
, which is the type of the main
function itself, it will work just fine. We’ll see more examples of do
blocks in later chapters.
Homework is to go for a walk with a pad and pen and write a program to add up a few of the numbers on number plates of cars in your street (assuming there are lots of cars around, if not, pick something else where there are lots of numbers). Add them up using the (+)
function, and use either print
or putStrLn
with show
. Do this as many times as you need to so that you know how to do it without looking it up. You will probably need to do this in a few different sessions to fully anchor it in your memory. Also, write a function that prints out a greeting.
You’ll notice that we’re giving you lots of repeated homework with putStrLn
, print
, show
and the other basics. That’s because we want to make sure you can do it very well. Varied repetition, or practice, is the key to getting very good with a skill.
If you’ve enjoyed reading this, please consider purchasing a copy at Leanpub today
Please follow us, and check out our videos Follow @HappyLearnTutes
Also, Volume 2 is now in beta and being written! Show your support and register your interest at its Leanpub site.