In this post I will be creating a small game, 2048 in Elm. 2048 is a game created by gabrielecirulli which went viral in 2014.

I chose 2048 because the game mechanics are simple which will help us understand Elm clearly. Also, I didn’t want to make another todo app.

You can play here.

## The Game Mechanics?#

• There are some number tiles on the board (4x4). The number on the tile will be a power of 2 with minimum being 2. See above image.
• You can press arrow keys: up, left, down, and right. This will trigger “merge” in the direction of the key-press. “merge” is the process of merging same number tiles into one and doubling its value. See below gif.
• The score is calculated by sum of number on tiles on the board, so you play to maximise your score by merging as many tiles as possible.
• A new tile (number 2) is introduced in the game at any random empty space with every key-press
• Game is over when no new tiles can be added, thats your final score (highest).

Simple eh!

## The Elm Architecture#

Every Elm application follows the Elm Architecture, which is pretty straightforward.

• Model — the state of your application
• View — a way to turn your state into HTML
• Update — a way to update your state based on messages

We won’t deep dive into each of them, you can read more about Elm here.

Lets dive into building the game.

## Building the game in Elm#

### Shaping Elm Architecture#

Based on the game’s functioning, let’s start defining the architecture of our game from Elm’s perspective:

Model
We have to store every tile’s data (number on the tiles and position of the tile). To store these tiles, we can introduce a matrix of `N`x`N` (`N` being the number of rows/columns, here 4). We can also store tiles as a list but matrix will be easy to render and visualize.

View
Since we kept our Model close to our View, in View we have to render this matrix into a matrix, with CSS.

Update
In Update function we will regenerate the Model (state) depending upon the message it receives. Let’s look at different kind of messages we must have:

• `LeftMove`: To enable merging leftwards
• `RightMove`: To enable merging rightwards
• `UpMove`: To enable merging upwards
• `DownMove`: To enable merging downwards
• `Reset`: For resetting the grid to the initial state
• `AddTile`: To add a tile at any random available space

The structure of the game looks good, now we have to implement the handling of these messages.

### Diving into our messages#

• `LeftMove`

`LeftMove` will merge same number tiles on a row in left direction. Since the tiles to be merged are in same list, we can do map-reduce for rows and merge 2 tiles with the same number into one and double the number on the tile. Lets look at the code below which merges tiles in a list and returns the list:

`mergeAndFillRow`

``````mergeAndFillRow : List Cell -> List Cell
mergeAndFillRow list =
let
updatedRow =
mergeRow list
in
List.concat
(updatedRow
:: [ EmptyCell
|> List.repeat
((list |> List.length) - (updatedRow |> List.length))
]
)
``````

`mergeRow`

``````mergeRow : List Cell -> List Cell
mergeRow list =
case list of
[] ->
[]

x1 :: xs ->
if x1 == EmptyCell then
mergeRow xs

else
case xs of
[] ->
[ x1 ]

x2 :: xs2 ->
if x1 == x2 then
mergeCell ( x1, x2 ) :: mergeRow xs2

else
x1 :: mergeRow (x2 :: xs2)
``````

In `mergeRow`, we use recursion to reduce the list, and then in `mergeAndFillRow` we fill the list with empty tiles. Functional programming is fun!

• `RightMove`

`RightMove` is `LeftMove` in the opposite direction (Thanks, I think). So we will reuse `mergeAndFillRow`. How? By reversing the `List` before and after calling this function.

• `UpMove`

For `RightMove`, and `LeftMove`, the merging operation is fairly easy since row elements are in a single list and therefore we can use `List` methods to reduce and merge tiles, but in `UpMove` case, the tiles are in multi columns, meaning items are in different lists. Here, elementary Mathematics comes to our rescue. We can transpose the matrix and then call the function `mergeAndFillRow` to implement `UpMove`.
Since Elm is a functional programming language there are no looping (for/while) constructs, and this makes transpose little challenging.

You can refer to the code below, uses recursion:

`transposeMap`

``````transposeMap : Grid -> Grid -> Grid
transposeMap grid2 grid1 =
case Array.get 0 grid1 of
Just e ->
transposeMap (grid2 |> transposeForIdx 0 e) (grid1 |> Array.slice 1 (grid1 |> Array.length))

Nothing ->
grid2
``````

`transposeForIdx`

``````transposeForIdx : Int -> Array Cell -> Grid -> Grid
transposeForIdx idx list grid2 =
case Array.get 0 list of
Just e ->
transposeForIdx (idx + 1)
(list |> Array.slice 1 (list |> Array.length))
(case grid2 |> Array.get idx of
Just l ->
grid2 |> Array.set idx (l |> Array.push e)

Nothing ->
grid2 |> Array.push (Array.fromList [ e ])
)

Nothing ->
grid2
``````
• `DownMove`

Using the same technique as we did with `RightMove`, we can: reverse the list, transpose, call `mergeAndFillRow`, transpose again, and finally reverse.

• `AddTile`

For this will create a list of available positions of tiles by traversing the matrix, and then randomly adding number tile 2 on any one of available positions.

• `Reset`

Reset is initializing the matrix and sending `AddTile` message.

### Merging cells:#

`mergeCell` is a common function which merges 2 tiles (Cell) and returns one tile:

``````swap : ( Cell, Cell ) -> ( Cell, Cell )
swap ( cell1, cell2 ) =
( cell2, cell1 )

mergeCell : ( Cell, Cell ) -> Cell
mergeCell ( cell1, cell2 ) =
case ( cell1, cell2 ) of
( Tile val1, Tile val2 ) ->
Tile (val1 + val2)

( Tile val1, EmptyCell ) ->
Tile val1

_ ->
mergeCell (swap ( cell1, cell2 ))
``````

Note: The above `mergeCell` method fails when arguments are `EmptyCell` and `EmptyCell`, it gets stuck in infinite recursion. But since we never have that case, it never crashes.

### Subscriptions#

For integrating key-presses and touch coordinates, we will use subscriptions.

Game is ready, now time for icing on the cake.