2048 - A complete clone of the classic game

2048 is one of my favorite games to play. When I first learned Elm, 2048 was also one of the first games I tried to clone. My efforts were shared here and this is what it looked like:

A little more than 4 years has past since my first attempt and I gained so much experience with Elm over those years. I wanted to challenge myself to overcome all the hurdles I previously encountered while building the app. The major one being how to handle all those animations. I’m happy to report that I figured out how to make it all work and that’s why I’m so excited to share the fruits of my labor.

I present to you all 2048 (source code). Have fun!

25 Likes

This is really well-made.

I just played your old version yesterday and was wondering if one could create a complete clone of the original in elm.

This is the perfect example. Playing through it, it really felt authentic.

3 Likes

dude this is pro level elm, so pretty and organized. everything. please I beg you to go on YouTube and share some good tips or general idea how you build an elm application or even this game.

5 Likes

@ViniciusAtaide Thanks for the glowing review.

I won’t be able to do that anytime soon but I can give you a quick run down of how I approached building this particular application and then you’d have to read the code to understand the rest for now.

Here goes.

My biggest unknown was how to structure the code to handle the animations. So I started by trying to understand how the animations worked in the original version. Then, I tried to recreate them so they worked in Elm. That’s what the experiments folder is about.

Experiments

The code in the experiments folder answers all the animation related questions I had.

  • How to make the appear, move/merge, score and game over animations work in Elm?
  • How to implement the move algorithm so that the animations work as expected?

Basically, I found out that CSS transitions/animations would do for almost everything except the move/merge and score animations.

The move/merge animation

The problem with the move/merge animation is that it is order dependent, i.e. if I don’t output the tiles in the correct order (where correct order isn’t well defined in all cases) then the animation goes haywire.

I tested the order dependency by hard coding the before and after configuration of the tiles. Then, by changing the order of the after configuration I was able to see how the animation was affected.

To play with the move/merge animation you can do the following:

$ nix-shell
$ ./experiments/merge/bin/build.sh
$ ./experiments/merge/bin/serve.sh

Then, open localhost:8000 in your browser to test it out.

Basically, the results of the experiment got me 99% percent there. I figured out how to structure the code and what would work and what wouldn’t work. To get 100% of the way I had to do the following:

  1. Use mgold/elm-animation to translate the tiles.
  2. Use Html.Keyed nodes.

Another thing.

The duration of the tile translation affects when the other animations occur. Since the tile translation was happening in Elm I wanted Elm to be the source of truth for the value of the duration. However, I wanted the CSS to remain the source of truth for the other animations. To accomplish this I used a combination of CSS variables and a style node.

--grid-tile-move-duration: 100ms;
--tile-appear-duration: 200ms;
--tile-appear-delay: var(--grid-tile-move-duration);
--tile-pop-duration: 200ms;
--tile-pop-delay: var(--grid-tile-move-duration);
--tile-disappear-duration: 0s;
--tile-disappear-delay: calc(var(--grid-tile-move-duration) + var(--tile-pop-duration));
H.node "style" []
  [ H.text <| ":root { --grid-tile-move-duration: " ++ String.fromFloat moveDuration ++ "ms;" ++ " }" ]

The score animation

The most interesting thing with this is how all updates to the score are shown. The animation doesn’t get cut. If you play the original JS game you’d notice that if you have back to back scores the animation for the first score gets removed and the new score is animated. In my version I store all the changes to the score, which I call deltas, and only when a delta is done animating (which I determine by setting an animationend event on the delta) I remove it.

To play with the score experiment you can do the following:

$ nix-shell
$ ./experiments/score/bin/build.sh
$ ./experiments/score/bin/serve.sh

Then, open localhost:8000 in your browser.

Prototype

With the experiments out of the way I started to work on everything else.

A new process I’ve been trying out recently is to first build all my views in HTML & Sass. I predominantly use BEM to help me in this process. During this process I pick a block and figure out the structure of the HTML in all its configurations. Then, I figure out how style each configuration.

Once I’m done with this process translating to Elm is trivial. Furthermore, when I encounter structural or visual problems with the block it’s the prototype I return to to fix the issue.

For example, here’s what the prototype for the grid block (the most complicated block in the application) looks like:

To play with the prototype you can do the following:

$ nix-shell
$ ./prototype/bin/build.sh
$ ./prototype/bin/serve.sh

Then, open localhost:8000 in your browser.

Elm Views

Building the prototype was a bottom-up approach for me. I started with the smallest blocks until I had a prototype of the entire application. Then, I translated the prototype to elm/html. A trivial process.

The game logic

The part where Elm really shines. I wrote the game logic to be independent of the views. It can be found here. I hope when you read it, it screams: 2048. I made extensive use of opaque types and tried to model the domain so that impossible states remained impossible.

Tech

Nix, bash scripts, Caddy, Sass, elm-format, elm-review, elm-optimize-level-2.

12 Likes

This looks and works amazing. Also, thank you for mentioning Threes there!

1 Like

this is awesome, thanks for sharing! I played a whole lot of 2048 during college

1 Like

Thats some great work.

It reminded me of my old project in elm in 2020 :wink:
Looking at my old code, I feel I have come a long way since then.

Here are the links if anyone wants to know how an elm noob coded in 2020.

https://elm-2048.surge.sh/

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.