I am building a little 2048 WinForms game just for fun.
Note that this is not about a 2048 AI. I am just trying to make a 2048 game that can be played by humans.
I first decided to use 0-17 to represent the tiles. 0 represents an empty tile. 1 represents a 2 tile. 2 represents a 4 tile. 3 represents a 8 tile, and so on.
Then I was thinking about how to calculate the resulting board, given the direction of movement and the board before the move. Here's what I thought about:
So I just need to figure out how to calculate the resulting board when the player moves left, then I can just figure out the rest of the directions by rotating the board, move left, and rotating back. I then came p with this quite bizarre algorithm for moving left.
char
. Now a back tick (`) represents an empty tile, a
represents a 2 tile, b
represents a 4 tile, and so on, al the way to p
.An example board might look like this:
aa``
````
```b
``cb
For each string,
([a-p])\1
and get the first match of the string.So this is how I evaluate each row:
int[] EvaluateRow(int[] row) {
// RowToString converts an int[] to a string like I said above
StringBuilder rowString = new StringBuilder(RowToString(row));
rowString.Replace("`", "");
var regex = new Regex("([a-p])\\1");
int lastIndex = -1;
while (true) {
var match = regex.Match(rowString.ToString(), lastIndex + 1);
if (match.Success) {
// newChar is the new tile after the merge
char newChar = (char)(match.Value[0] + 1);
rowString.Remove(match.Index, match.Length);
rowString.Insert(match.Index, newChar);
lastIndex = match.Index;
Score += // some calculation for score, irrelevant
} else {
break;
}
}
// StringToRow converts a string to an int[]
return StringToRow(rowString.ToString());
}
However, there is a really big problem with my current algorithm. This algorithm only tells me the final result of a move, but I don't know which picture box (I'm using picture boxes to show the tiles) I need to move, how many spaces should each picture box move, and which picture boxes need to show a new image. I really want to not use another solution and I want to just make some changes to my current solution.
Here are the things I need to get from each row (string):
List<(int x, int spaces)>
. Each element represents which tile needs to move (the x coordinate), and how many spaces it should move (spaces
).List<int>
. Each element represents the x coordinates of the tiles which is merged into.How can I get these information from a row string? Example:
The row string:
`a`a
will produce a list like [(1, 1), (3, 3)]
and another list like [1]
.
Keep it Cornered An easy way to beat the game is to keep your largest number in a corner. The way to do this is to keep the row or column filled with other numbers. This guarantees it will not move while you move around the other numbers.
Problem Explanation. Solving this game is an interesting problem because it has a random component. It's impossible to correctly predict not only where each new tile will be placed, but whether it will be a “2” or a “4”. As such, it is impossible to have an algorithm that will correctly solve the puzzle every time.
In this post, we'll answer that question by modeling the game of 2048 as a Markov chain and analyzing it to show that, no matter how well the player plays, the number of moves required to win the game is at least 938.8 on average.
Higher-scoring tiles emit a soft glow; the highest possible tile is 131,072.
I don't think the transformation to characters is really adding anything useful. If you stick with the number representation (0 = empty), then you can employ the following logic to find both the target configuration and which block went where. This is pseudo code (row
is given):
fromTo = [-1, -1, -1, -1];
result = [0, 0, 0, 0];
prevVal = 0;
pos = 0;
for (i = 0; i < 4; i++) {
if (row[i]) { // Not empty
if (row[i] == prevVal) {
result[pos-1]++; // merged
fromTo[i] = pos-1;
prevVal = 0; // avoid triple merge
} else {
result[pos] = row[i];
fromTo[i] = pos;
prevVal = row[i];
pos++;
}
}
}
Now the fromTo
array will indicate for each index, where the block at that original position went to. The result
will have the final values. From those two pieces of information you can also know which blocks were merged. A block at original position i is merged when result[fromTo[i]] != row[i]
. You also know the distance a block will travel: i - fromTo[i]
. In short, you have all information to set up an animation for each block.
row | fromTo | result
------------+----------------+-----------
[0,1,0,1] | [-1,0,-1,0] | [2,0,0,0]
[0,1,1,1] | [-1,0,0,1] | [2,1,0,0]
[1,1,1,1] | [0,0,1,1] | [2,2,0,0]
[1,2,2,3] | [0,1,1,2] | [1,3,3,0]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With