# Happy Learn Haskell Tutorial Vol 1

Written and illustrated by GetContented.

Published on 2017-06-25.

## Contents

Previous chapter: 17. The People Book
18. Times-Table Train of Terror
... 18.1. Tuples or Pairs
... 18.2. Ranges and the zip function
... 18.3. Determining the Level Number
... 18.4. The game loop
... 18.5. Homework
Next chapter: 19. Skwak the Squirrel

# 18. Times-Table Train of Terror 🔗

We’re going to introduce you to a tiny toy educational game that introduces quite a few new functions and features of Haskell.

We won’t be using any special algebraic data types in this program. In particular, our main type will be:

``````
-- a game Level is simply a pair
-- of Integer values
type Level = (Integer, Integer)
``````

## 18.1. Tuples or Pairs 🔗

A `Level` is simply a pair that has two `Integer` values. As this is a simple math game, each level will be a pair of numbers that represent the two numbers for a multiplication question. (For example, a level value of `(7,9)` would represent a question something like “What is 7 times 9?”).

The next definition is where we build the levels for this game:

``````
levels :: [Level]
levels =
concat \$ map pairsForNum [3,5..12]
where
pairsForNum num = zip [2..12] \$ repeat num
``````

## 18.2. Ranges and the zip function 🔗

Wow, there is a lot packed into this value expression. Let’s pull it apart piece by piece.

The `levels` value is a list of `Level` values. It’s defined using the `(\$)` function, which takes a function on the left, and applies it to the value or expression on the right.

In our case here, the value expression on the right is `map pairsForNum [3,5..12]`. The main new thing in this expression for us is `[3,5..12]`, which is what’s called a range. The range of numbers between 1 and 100, for example, would be represented as `[1..100]`. The range above, though, is every number between 3 and 12, in odd numbers. (So, 3,5,7,9,11).

So what we’re doing, then, is mapping the `pairsForNum` function across this range. This function takes a number, calling it `num`, and “zips” the range `[2..12]` together with `repeat num`. Zipping is creating pairs from one item each of a number of lists as we’ll see. The `repeat` function gives an infinite list of whatever its argument is. The `zip` function takes two lists, and builds pairs out of those lists, taking one item from each as it does. So, `zip [1,2,3] [4,5,6]` will create `[(1,4),(2,5),(3,6)]`. However, the `zip` function will stop when the first list runs out of values, so zipping an infinite list of repeated items with another that has only a few is just fine, as we’re doing.

So what we’ll end up with by using `map pairsForNum [3,5,..12]` is a list of lists of pairs of `Integer` values. This basically means we’re combining each of the elements with each of the others. Then, we use `concat` to concatenate all the lists into one list. For example, `concat [[1],[2],[3]]` results in `[1,2,3]`. In math, this process of creating a big set out of two smaller sets is called a cartesian product. You don’t have to know this, but later we’ll see other, much simpler-looking, easier ways to do this. Unfortunately using them requires knowing more than we currently know, so that will have to wait.

The end result of this expression is that we have a list of 55 levels for our train of terror!

## 18.3. Determining the Level Number 🔗

Here’s a function we’ll use in our program to work out what number level the player is at:

``````
levelNumber :: [a] -> Int
levelNumber remainingLevels =
totalLevels - levelsLeft
where totalLevels = length levels + 1
levelsLeft  = length remainingLevels
``````

We take a list of remaining levels, then subtract the number of levels left (using the length function) from the number of levels we started with plus one. As the player “moves up” the train, we’ll be reducing the size of the list, so the remaining levels will get less, and the level number will go up. Notice from the type signature that we don’t care what the element type of the list passed into it is, as long as it’s a list.

The `main` function simply gives the player a greeting message, then starts the `trainLoop` function that handles all the game-play (by passing in the levels value).

``````
main :: IO ()
main = do
putStrLn "Suddenly, you wake up. Oh no, you're on..."
putStrLn "The Times-Table Train of Terror!"
putStrLn "Try to get to the end. We DARE you!"
trainLoop levels
``````

You can see we have our old friend the `do` block in action again, joining a whole bunch of IO actions together to form one.

In a moment, we come to the main game-play function, `trainLoop`.

## 18.4. The game loop 🔗

The function `trainLoop` has two definitions.

The first is for an empty list, which for our program means the player has won, because they got to the end of the train without failing. In this case, we declare their victory to them.

The second definition comes into play when there are still levels passed in. This means the game is still in play. Each time the player gets done with a level, we remove it from the list, then pass the rest of the levels back into the trainLoop function and start it again.

``````
trainLoop :: [Level] -> IO ()
trainLoop [] =
putStrLn "You won! Well done."
trainLoop remainingLevels @ (currentLevel : levelsAfterThisOne) =
do
let currentLevelNumber =
levelNumber remainingLevels
(num1, num2) =
currentLevel
putStrLn \$
"You are in a Train Carriage "
++ show currentLevelNumber
++ " of " ++ (show \$ length levels)
putStrLn "Do you want to:"
putStrLn "1. Go to the next Carriage"
putStrLn "2. Jump out of the train"
putStrLn "3. Eat some food"
putStrLn "q. Quit"
activity <- getLine
case activity of
"1" ->
do
putStrLn \$ "You try to go to the next carriage."
++ " The door is locked."
putStrLn "Answer this question to unlock the door:"
putStrLn \$ "What is " ++ show num1
++ " times " ++ show num2 ++ "?"
if answer == (show \$ num1 * num2)
then do
putStrLn "The lock is opened!"
trainLoop levelsAfterThisOne
else do
putStrLn \$ "Wrong. You try to open the lock,"
++ " but it won't open."
trainLoop remainingLevels
"2" -> jumpingFutility
"3" -> eatingFutility
"q" -> putStrLn \$ "You decide to quit."
++ " Thanks for playing. Bah-Bye!"
_   -> do
putStrLn "That makes NO sense! Try again."
trainLoop remainingLevels
``````

The construction with an `@` symbol means the whole argument is matched as `remainingLevels`, and then, also, `currentLevel` is set to the first item of the list, and `levelsAfterThisOne` is set to the tail of the list. When we use the `@` symbol like this, it’s called an as pattern.

The `trainLoop` passes back an `IO` action, so we begin a very large `do` block. This `do` block has lots of different kinds of things in it, to get you familiar with more complicated looking `do` blocks.

Ideally, we would pull each of the sections into their own function. For now we’ll look at it like this because it serves our purposes quite nicely to show you a larger `do` block.

Remember, each of these expressions will evaluate to an `IO` action (because the final value of this function’s type is `IO ()`), and the `do` block simply connects them all up properly so they become one single `IO` action.

First, we set up some variables we’ll need later using `let`. A `let` expression is another way we can define some locally scoped definitions. In a `do` block, name defined in a `let` expression will be available for the remainder of the `do` block. One of these is `currentLevelNumber` which uses the `levelNumber` function we just looked at, and the next line is pattern matching the `currentLevel` variable into two number variables `num1` and `num2`. Remember, `currentLevel`’s type is `Level`, which is `(Integer, Integer)`, so `num1` and `num2` will both be `Integer` values.

Then we print out a message explaining that the player is on a certain train carriage, using show to turn the number values into strings so they can be fed into `(++)` with the other strings.

Next, a menu is described, and we receive a line of text from the player with `getLine`, which goes into the variable called `activity`.

After this, we have a `case` expression that matches on the player’s response. If they wrote 2, 3 or q, then we write a message and restart the whole game, or quit out (q quits the game).

If, however, they typed 1 in, they’re asked this level’s multiplication question. If they get it right, it unlocks the current level and they can proceed, which we do by starting the `trainLoop` again but with only the tail of the levels (by using the `levelsAfterThisOne` variable).

All that remains is to show the definitions for `jumpingFutility` and `eatingFutility`. These actions are executed if the player tries to jump or eat.

All they do is print a message, then start the game at the very beginning by returning trainLoop applied to the initial levels.

``````
jumpingFutility :: IO ()
jumpingFutility = do
putStrLn "You try to jump out of the train."
putStrLn "You fail and die."
trainLoop levels

eatingFutility :: IO ()
eatingFutility = do
putStrLn "You see a delicious looking cupcake."
putStrLn "You eat it. It's a time travel cupcake!"
trainLoop levels
``````

## 18.5. Homework 🔗

Write a program that prints your birthday on the screen. Write another program that prints two numbers added on the screen, then one for multiplied, then subtracted.