2048, Wolfram Style
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:
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.
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.
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.
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.
Here I’ve set the drawTile function so that it can easily be changed later…
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.
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:
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.
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.
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.
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.
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.
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.
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.
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.
I can switch between the two styles with buttons.
Happy playing!
Download this post as a Computable Document Format (CDF) file.
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.
Make sure that CurrentValue[“EventKey”] is inside the “KeyDown” :> action of the NotebookEventActions of your notebook.
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.
The probabilities are indeed the same (I checked the javascript source code).
An AI would be a very interesting project! There is a great discussion about building one for 2048 here: http://stackoverflow.com/questions/22342854/what-is-the-optimal-algorithm-for-the-game-2048/22389702#22389702
And someone wrote a great AI that has about a 90% success rate: http://ov3y.github.io/2048-AI/
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/
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.
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.