Elm Plane - a Flappy Bird Clone in Elm

The really interesting topics are the ones you don’t really get at first. For me, reactive programming is such a topic - I understand enough to get by, but I don’t yet think in a functional reactive way. So, what better way to get into that state of mind than to try writing something in a language where there is no other choice but to write pure functional reactive code?

It’s been a while since I learned a new language. Sure, I read the odd blog post about Go or Rust and I can definitely recommend the book “Seven Languages in Seven Weeks” for a glimpse into some interesting concepts, but it wasn’t until I heard about Elm on the JS Jabber podcast that I thought, this is exactly what I need! (I later heard about PureScript in another JS Jabber episode and it sounds very interesting as well).

I wanted to quickly pick a small project to start working on and settled on a flappy bird clone. There are a lot of blog posts about Elm which helped me get started, so I’m not gonna talk about basic Elm stuff here as I feel it is pretty much covered (check the end of the post for links to a few of those posts). Instead I’m going to break down the code and describe some of the interesting bits in depth.

The pattern for writing games in Elm (or applications for that matter) breaks down the code into four parts:

The Model holds the state of the game which will include positions, velocity, score and other stuff like that. This is kind of like the application state in Flux/Redux. Like everything else in Elm this state record is immutable, which means that we’re not gonna modify that record, we’re gonna replace it with a new record every update.

The Input part defines the inputs for the game (keys, passage of time) and the Signal for those inputs.

The Update part defines what should be the next state of the game based on new inputs (just like a reducer). A few examples would be:

When space is pressed the spaceship should shoot

When the up arrow key is pressed the character should jump.

When a millisecond passes the plane’s vertical velocity decreases due to gravity.

The View part defines how to render a given game state. It is completely separate from the Update code.

Click the image to pause the animation.

Model

The Model.elm file holds the default game state, the one which every game starts with. The file also contains all of the relevant type definitions and a constants record that holds all sorts of variables that the game uses that, well, stay constant - size of things, value of gravity and speed etc.

Here’s what a game state for elm plane looks like:

type alias Game = { state : State --1 , backgroundX : Float --2 , y : Float --3 , vy : Float --4 , timeToPillar : Float --5 , pillars : Array . Array Pillar --6 , score : Int --7 }

The state in which the game is in, can be one of three: In the start screen, waiting for a user input to start a game

In an active game

In the game over screen, waiting for a user input to switch to the start screen The horizontal position of the background, used for scrolling the background image. The vertical position of the plane. The vertical velocity of the plane. The timespan left until the next coulmn spawn. The columns that are currently in the game. The player’s score.

Input

Flappy Bird Elm Plane is a pretty simple game right? the only inputs are one key (space) and the passage of time. So on the first iteration I defined the input type to contain a Bool property and a Time property, where delta is a Float Signal generated by the FPS ticker converted to seconds (delta between ticks).

delta = Signal . map inSeconds ( fps 45 ) type alias Input = { space : Bool , delta : Time }

I then defined the Input Signal to sample both on delta. This means that when there’s a event on the delta Signal, there will be a event on the Input signal with a value composed of the current state of the of the space key (whether it is pressed) and the current state of the Time Signal.

input : Signal Input input = Signal . sampleOn delta <| Signal . map2 Input Keyboard . space delta

This had an interesting effect. Instead of reacting to when the space key was pressed, every tick the game reacted to whether the space was pressed. So for example instead of “jumping” once when the space is pressed, every tick where the space key was down caused a “jump”. When I just kept the space key down it looked like this:

I'm needed elsewhere.

I realized what I actually need is a key down event and not whether space is pressed. Here is the revised Input Signal:

delta = timestamp <| Signal . map inSeconds ( fps 45 ) type Input = TimeDelta ( Time , Time ) | Space Bool

The Time Signal looks pretty much the same (I added a timestamp, but that’s only for a randomizer, the rest is basically the same). The Input Signal is now a Signal of a new union type which is either a (tuple of) Time or a Bool instead of a record with two properties.

Here is what the Input Signal looks like:

input : Signal Input input = Signal . mergeMany [ Signal . map TimeDelta delta , Signal . map Space Keyboard . space ]

Instead of sampling on time like the previous iteration, this signal will have a new event whenever there is a new tick or there is a change in the state of the the space key (down -> up or up -> down). This way I can handle discrete key presses.

The Update.elm file holds all the update functions. Following the refactor discussed in the previous section, there are two types of inputs - a time ‘tick’ and a keydown/keyup event. So I defined two types of functions like so:

type alias KeyUpdate = Bool -> Game -> Game type alias TimeUpdate = ( Time , Time ) -> Game -> Game

The main update function just calls all of the update functions according to the type of input:

update : Input -> Game -> Game update input game = case input of TimeDelta delta -> game |> updatePlayerY delta |> updateBackground delta |> applyPhysics delta |> checkFailState delta |> updatePillars delta |> updateScore delta Space space -> game |> transitionState space |> updatePlayerVelocity space

Most of them are pretty simple, so let’s look at one of each.

The applyPhysics function is a function of type TimeUpdate that is responsible for updating the the player’s vertical velocity:

applyPhysics : TimeUpdate applyPhysics delta game = { game | vy = if game . state == Play || game . state == GameOver && game . y > - gameHeight / 2 then game . vy - ( snd delta ) * constants . gravity else 0 }

There are two cases where we should apply gravity to the player’s velocity:

When the game is in Play state obviously.

state obviously. When the player just struck out and the plane falls to the bottom of the screen.

Otherwise, there is no need for vertical velocity, so we set it to zero on the next state.

Here is an example of a KeyUpdate function, the one that sets the player’s vertical velocity to the “jump” velocity once the space key is pressed:

updatePlayerVelocity : KeyUpdate updatePlayerVelocity space game = { game | vy = if game . state == Play && space then constants . jumpSpeed else game . vy }

When the game is in Play state and the space is pressed, set the velocity to the jumpSpeed constant, otherwise keep it unchanged. The space argument will be true only when the the state of the space key changes from false to true following the Input Signal refactor.

View

The View.elm defines how to construct a frame visually based on a game state. It is totally independent from the Update functions (and vice versa). I’m not gonna go into too much detail since the code basically just defines how to build forms for everything - the plane, the columns, the score etc.

The scrolling background is composed of just two identical images which move left using the backgroundX property of the game state:

The black outlined square is the game view that is actually visible. The images keep shifting left all the time until one leaves the game view entirely and is then placed to the right of the other image.

And that’s pretty much it! You can find the code on Github, please share any feedback or questions you have.

Here’s a bunch of articles I found useful:

Making Pong

Learning FP the Hard Way

Minesweeper - a brief journey from JavaScript/React to Elm

Writing Game of Life in Elm