Elm: v0.14 Last updated: 03 Mar, 2015

This is the second primer article in the series “Developing Games in Elm” in which I discuss the two main fundamental differences between elm and more traditional programming languages. If you haven’t read the previous article yet I recommend you do that first.

Elm is not just a functional language, it is a functional reactive language. It gives your game the ability to react to signals from the outside world; to player inputs and time.

In game development one of the most important concepts is the event. An event represents a thing that happened during the cycle of the game loop, for example mouse movement. The various modules in the code can attach a listener to the ‘mouse move’ event so that when it is triggered the listener function is run to modify some object(s).

events.listen('mousemove', function(x, y) {
    world.scroll(x, y);
});

This is by it’s very nature a side effect, which as you’ll remember from the previous article is a bad idea.

Instead we want to be able to react to events in a pure functional way. We can do this with elm’s signals.

Time

A signal is a value that changes over time. The simplest example is time itself. Let’s say we have a signal of milliseconds that updates every second:

import Time (every, second)
import Signal (Signal)

time : Signal Int
time =
    every second

Since the value of the signal only changes each second, if we sample it multiple times within the same second we will get the same value back. The value isn’t fired to us in a listener when it changes, instead we sample it from the signal.

Now we need a way to use this signal, since we can’t listen to it and change some other object directly we have to map it into the game’s state and handle the value from there.

As is common, elm uses the main function as it’s entry point. This function should return an element which can be rendered:

import Text (fromString, leftAligned)
import Graphics.Element (Element)

main : Element
main =
    "Hello, World!"
        |> fromString
        |> leftAligned

But that is just static content, if we want dynamic content we output a signal of elements:

import Text (asText)
import Time (every, second)
import Signal (Signal, map)
import Graphics.Element (Element)

main : Signal Element
main =
    every second
        |> map asText

The value of the signal is mapped to the Text.asText function which passes it as a string into an element.

But we don’t typically want to just blindly get the time since 1970. When making a game you need to get the exact delta since the last frame to ensure you can scale your time based calculations appropriately. This scaling prevents your game logic running at erratic speeds if your fps varies.

This is nice and simple in in elm, here we just output the delta every frame at a target of 60 frames per second:

import Text (asText)
import Time (fps)
import Signal (Signal, map)
import Graphics.Element (Element)

main : Signal Element
main =
    fps 60
        |> map asText

Input

As well as time, we can get inputs from the player. Continuing with our mouse move example; instead of listening for mouse move events, with elm we map the Mouse.position to our game logic:

import Text (asText)
import Mouse
import Signal (Signal, map)
import Graphics.Element (Element)

main : Signal Element
main =
    Mouse.position
        |> map asText

Few games only take one input, so we can merge a second or more signals together.

Signal.merge : Signal a -> Signal a -> Signal a
Signal.mergeMany : List (Signal a) -> Signal a

If multiple signals change at the same time priority is given to the left most signal. In the following example if the mouse is moving at the same time as it is clicked the position signal is given priority over isDown:

import Text (asText)
import Mouse
import Signal (Signal, map, merge)
import Graphics.Element (Element)

main : Signal Element
main =
    merge
         (Mouse.position |> map asText)
         (Mouse.isDown |> map asText)

Foldp

Now we have access to various input signals we want to apply these to our game state over time. Elm has this wonderful concept of folding over the past.

Signal.foldp : (a -> state -> state) -> state -> Signal a -> Signal state

This takes a function which transforms the state with the value of the input signal and then outputs a signal with the new value of the state.

In this example we get the maximum position the mouse has been moved to, i.e. how far to the bottom right of the window.

import Text (asText)
import Mouse
import Signal (Signal, map, foldp)
import Graphics.Element (Element)

main : Signal Element
main =
    maxMousePos
        |> map asText

maxMousePos : Signal (Int, Int)
maxMousePos =
    let update (x,y) (mx,my) =
            ( if x > mx then x else mx
            , if y > my then y else my
            )
    in foldp update (0,0) Mouse.position

Channels

We are now able to communicate with the outside world, but what about communications between different parts of our game internally? For this we can use custom channels. A channel is what a signal is sent on and can be subscribed to by another piece of the game to receive updates to the value. All the in-built examples we’ve seen so far are channels.

To send our own signals we create a channel that can receive values of a set type and takes a default value. To get the signal from this channel we subscribe to it and can then handle it as before.

import Text (asText)
import Signal (Signal, Channel, map, channel, subscribe)
import Graphics.Element (Element)

main : Signal Element
main =
    mySubscriber
        |> map asText

-- create a channel which takes values of type String and has an initial value of "foobar"
myChannel : Channel String
myChannel =
    channel "foobar"

-- subscribe to the channel creating a signal of string values
mySubscriber : Signal String
mySubscriber =
    subscribe myChannel

Next

So we’ve now looked into the two most important differences between traditional languages and elm. Functional programming provides a more descriptive way to write code, and signals allow your game to react to time and inputs.

The next article in this series will delve into architecting a space invaders clone. With multiple modules, inputs, graphics and aliens it will demonstrate a successful approach to creating games in elm.

While you wait, why not follow me on twitter and streak club to hear updates about my own game’s progress and other development bits and bobs.

Updated [02 Mar, 2015]: Added a lead note indicating using elm v0.14. Updated [05 Mar, 2015]: Added a section on custom channels.