Wolfram Blog
Dan Fortunato

2048, Wolfram Style

May 9, 2014 — Dan Fortunato

If you’ve been anywhere on the internet these past few weeks, there’s little doubt that you’ve come across the game 2048 (made by Gabriele Cirulli). Based on the similar games 1024! (by Veewo Studio) and THREES (by Asher Vollmer), this game has a simple mechanic that can leave you puzzled for days—slide powers of two around a grid, and combine them to make higher powers of two. The goal is to get to 2048. It’s hard to explain just how fun and challenging this game is, so I recommend playing it for yourself.

So, as a tribute to this little game (and in honor of all games mathematical!), I thought it would be fun to demonstrate the power of the Wolfram Language by using it to make our own version of 2048. Let’s go!

The basic structure for the game board will be a 4X4 matrix, initialized with an empty element in each position:

4x4 matrix

Upon starting a new game, I will seed the board with two randomly placed tiles—either a 2 or a 4. I give a higher probability to 2, so that it will appear more often.

Seeding the board with randomly placed tiles

Time to make this board look a bit nicer. I can mimic the styling of the original game by scraping its CSS (Cascading Style Sheets). Here I’ve found the background and text colors for all the different tiles.

Scraping CSS from the original game

I now have a nice list of each number and its corresponding colors! I can encapsulate this further by making a function to look up the colors and convert them from hex (hexadecimal) to RGB (red-green-blue). I also define colors for the rest of the board, and some default colors just in case.

Converting hex to RGB

Now I’ll use this color information to make a function to draw the tiles. I have to be careful here to make the font size smaller when the number of digits in the number is larger.

Function to draw the lines

Here I’ve set the drawTile function so that it can easily be changed later…

drawTile function

To apply this styling to the original game board, I simply look at each element of the board and place its corresponding tile in the correct position.

Corresponding tile

Looking good! Now for some controls.

When the arrow keys are pressed, I want them to shift all the tiles on the board as far as possible in the specified direction, taking care to combine matching numbers along the way. I can use NotebookEventActions to listen for the key presses, and respond accordingly. The keys I want to listen for have the following codes:

keymap

Now let’s think about what happens when the tiles actually shift, say, to the left. I need to first worry about combining matches. Each row can be treated separately, since tiles won’t combine vertically for a horizontal shift. I want to look for a run of two identical numbers, possibly with some empty tiles between them, and replace them with their sum. The power of the Wolfram Language comes in handy here, as I can use pattern matching to do this easily.

Combining matches

After combining the matches, all I have to do now is add some extra empty tiles on the right to fill out the row. The procedure is the same for all rows of the board.

Adding extra tiles

Shifting to the right is similar, with one slight change—I want matches on the right to combine before matches on the left. Think about the row {$empty, 2, 2, 2}. Using the previous combineLeft function and then padding on the left would yield {$empty, $empty, 4, 2}, but in fact I want the rightmost 2s to combine first! Flipping the row, combining to the left, and then flipping it back fixes this problem.

Flipping to the right using combineRight

With these two functions in place, the up and down directions are easy! Shifting up on the board is the same as shifting to the left on the transposed board and then transposing back, and the same relationship holds for right and down.

Transposing for up and down

When a key is pressed, I only want to add a new tile to the board if the state of the board has changed—that is, if some tiles have moved or been combined. Keeping track of the previous state of the board takes care of this.

Let’s keep track of the score during the game. Whenever I combine two numbers, I Sow their sum, and then Reap after all the matching is done. I also display the highest tile reached so far.

Highest tile reached so far

Lastly, let’s add a check for a win or a loss. I’ve won if the highest tile on the board is greater than or equal to 2048, of course. I’ve lost if the board is full and there are no more matches available. Again, I can use pattern matching to determine if there are any matches.

Pattern matching to determine pattern

Using Dynamic, I can keep the board up to date when any changes are made. I can wrap all of this in a DynamicModule, using Initialization to set up the key handling and the game board. Putting this inside CreateDialog gives the game its own window.

Finally, the game is complete.

Game is complete

Game is complete

Initial screen shot of Wolfram 2048

Now, this looks nice and all, but here at Wolfram we like things a little more… Spikey. Let’s change the colors and tiles a bit.

Square Spikey tiles
Rounded Spikey tiles

I can switch between the two styles with buttons.

Switching between styles
Code for switching between styles continued
Switching between styles continued
Final screen shot of Wolfram 2048

Happy playing!

Download this post as a Computable Document Format (CDF) file.

Posted in: Design
Leave a Comment

9 Comments


Andrew Mullins

This is neat and all but you should really stop the madness of triggering a download without your users’ explicit consent. Simply visiting this page triggers the download of a 2048.cdf file.

Posted by Andrew Mullins    May 9, 2014 at 11:57 am
Tongxin.Zhang

The CurrentValue["EventKey"] return “None” when i press arrow keys on the keyboard.
so i have to change the “keymap” , before i enjoy the game. Maybe that’s a little bug.

Posted by Tongxin.Zhang    May 12, 2014 at 6:42 am
    Dan Fortunato

    Make sure that CurrentValue["EventKey"] is inside the “KeyDown” :> action of the NotebookEventActions of your notebook.

    Posted by Dan Fortunato    May 13, 2014 at 1:31 pm
stan wagon

One wonders whether the probabilities used by you for 2s and 4s agree with the ones in the actual game.

Interesting project now is to devise an algorithm that plays the game and see how well it does.

Posted by stan wagon    May 12, 2014 at 4:24 pm
Guest

Great example, though it would have been better to have implemented 3′s rather than the clone ’2048′. The game mechanics are simple, but the developer of 3′s deserves the advertising and kudos for implementing it first.

Paid original content developers should stick together:)

http://techcrunch.com/2014/03/24/clones-clones-everywhere-1024-2048-and-other-copies-of-popular-paid-game-threes-fill-the-app-stores/

Posted by Guest    May 14, 2014 at 8:47 pm
chen

Recently I wrote 2048 game in 3D form based onyour codes.But I encountered a serious problem I could not deal with it.When I press the arrow keys,it will be a significant delay.Could you give me some suggestions?My notebook is in here https://www.dropbox.com/s/v47umdef8a70ss3/2048%20game%EF%BC%882D%20and%203D%EF%BC%89and%20problem.nb .Thank you.

Posted by chen    June 8, 2014 at 11:50 am
    Dan Fortunato

    Hi Chen, looks cool! Here are a few things you could try for speed improvement:
    1. Use graphics multi-primitives whenever you can. This means instead of writing something like {Sphere[point1, r], Sphere[point2, r]}, use {Sphere[{point1, point2}, r]}. This works with many graphics objects.
    2. Use floating point arithmetic. This means instead of using 1/16 or Log[2, 5], use 1/16. or Log[2., 5.].
    You could also try to Rasterize the textures and numbers in advance.

    Posted by Dan Fortunato    June 10, 2014 at 5:00 pm


Leave a comment

Loading...

Or continue as a guest (your comment will be held for moderation):