Cloning 2048

Making my own clone of the popular browser game.

I caught 2048 bug just like everyone else last week. The game is certainly a lot of fun, but I wanted to modify it in certain ways: increase the board from 4x4 to 5x5, increase the pace, etc.

I cloned the game and posted my version at https://glebbahmutov.com/2048/

This is a description of the few steps I took right after cloning the original github source. The steps are really basic techniques I use an ALL my projects. They are necessary to establish minimum quality base for a JavaScript / HTML project. The baseline is important because it allows automating all build steps.

Cloning

The basic step is to grab my own copy of the source, just like almost 2000 people have done. Press the "fork" button at the original github project and wait a few seconds to have a copy of the repo in your own space. For me, it is bahmutov/2048.

After bringing the code to local machine using git clone [email protected]:bahmutov/2048.git I got to work. Each step is tagged using semver, and you can inspect each milestone by looking at the releases

0.0.0 - npm, grunt

I needed a systematic way to run certain command line tools: jshint, css preprocessor, maybe some others. I picked grunt because it is widely used, supports every other tool as a plugin, and I like it a lot.

In order to keep track of grunt plugins to install, I initialized an NPM package using npm init command and answering a few questions.

I created Gruntfile.js to run jshint and nice-package. JShint will statically check JavaScript code for style and obvious syntax errors (like misspelled variables, etc). grunt-nice-package is my own Grunt plugin that checks the package.json structure to make sure everything is specified correctly. It is a good plugin to use whenever you need to open source your JavaScript code.

0.0.1 - fixing jshint warnings

Even a lenient .jshintrc file included with the project generated a few style warnings. I fixed the few found ones.

0.0.2 - sass to css

The game style is defined in style/main.css and probably is generated from the included style/main.scss file. To compile SASS file into CSS file I added grunt-contrib-sass plugin to package:

npm install grunt-contrib-sass --save-dev

I added sass step to Gruntfile.

1
2
3
4
5
6
7
8
9
10
11
12
sass: {
dist: {
options: {
// do not minify
style: 'expanded'
},
files: {
// output filename: input filename
'style/main.css': 'style/main.scss'
}
}
}

I wanted a larger board size, 4x4 felt too small. So I increased the number of tiles from 4 to 5, and added corresponding HTML nodes to index.html. There was also field size passed around in JavaScript code. You can see the changes here.

0.0.3 - text changes

I changed the text on the game page to explain how 5x5 clone is different from the original 2048 and pointed back at my github project. The game at this point works, but the project has not been deployed to github pages yet.

0.0.4 - fast pace

I felt the original game started too slow. I could play for 5 minutes moving tiles one by one before real fun began. I increased the number of tiles appearing at each turn from 1 to 2. I also tweaked the probabilities for different new numbers: 2 is the most common, 4 is less common, and 8 very rare.

1
2
3
4
5
6
7
8
9
10
11
12
// js/game_manager.js
// Adds a tile in a random position
GameManager.prototype.addRandomTile = function () {
var value = Math.random() < 0.75 ? 2 : (Math.random() < 0.95 ? 4 : 8);
var tile = new Tile(this.grid.randomAvailableCell(), value);
...
}
// later in move
if (moved) {
this.addRandomTile();
this.addRandomTile(); // add second new tile
}

The JavaScript code is nicely separated into individual files, although I feel attaching everything to the window's global variable is bad. Ultimately I decided to not to restructure the code or even concatenate it at this point.

The diff source

0.0.5 - github pages

2048 is a simple game, all code could be included in a single static HTML file if needed. I decided to use free github pages to host it. For a github-hosted project it means created a special branch gh-pages, merging only the desired file (no Gruntfile, package.json, etc). Doing this manually is a pain, so I installed grunt-gh-pages and added gh-pages step to Gruntfile

1
2
3
4
5
6
7
8
9
'gh-pages': {
src: [
'favicon.ico',
'index.html',
'js/*.js',
'style/*.css',
'meta/*.*'
]
}

Only files necessary to play the game are included; no Gruntfile, no SASS input, no README.md. You can see the included files in the gh-pages and this is what gets served from https://glebbahmutov.com/2048/.

While testing the game from a mobile phone, I noticed wrong tile locations and incorrect number of tiles. So I found that number of tiles (4) is harcoded in two places in the SASS file. Desktop and mobile styles have separate number of tiles, see the SASS file. Because the file is long, I did not see the second variable. I would defnitely refactor this at first opportunity.

The diff source

0.0.6 - offline mode

The game worked great on my phone, but whenever I had bad connection, I could not open the url. So I added offline mode by utilizing HTML5 app cache manifest.

I added cache attribute to the index.html

1
<html manifest="cache.manifest">

and created a file cache.manifest that lists files that should be cached by the browser indefinitely. I also added a variable to this file that can be replaced with the current timestamp using grunt-replace plugin. Browser checks if the cache file has been updated to invalidate the cached files. So by changing a commented out timestamp, I can clean the cached game version in the future.

The cache.manifest file lists all files used by the game that should be cached, it closesly mirrors what gh-pages brunch has

CACHE MANIFEST
# @@timestamp: Sun Mar 23 2014 23:03:21

CACHE:
index.html
js/animframe_polyfill.js
js/application.js
js/game_manager.js
js/grid.js
js/html_actuator.js
js/keyboard_input_manager.js
js/local_score_manager.js
js/tile.js
style/main.css
meta/apple-touch-icon.png

You can see the latest version of all code at github, and play the game from desktop or smart phone even without a connection. Happy hacking!