15

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:

  • To move up, it's just rotating the board counterclockwise by 90 degrees, move left, then rotate the board back
  • To move right, it's just rotating the board clockwise by 180 degrees, move left, then rotate back
  • To move down, it's just rotating the board clockwise by 90 degrees, move left, then rotate back.

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.

  • Convert each of the initial board's integers into characters by adding 96 and casting to 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.
  • Concatenate the characters to form 4 strings, each representing a row of the board.
  • An example board might look like this:

    aa``
    ````
    ```b
    ``cb
    
  • For each string,

    • Remove all the back ticks
    • Use the regex (yes I'm using a regex in a 2048 game) ([a-p])\1 and get the first match of the string.
    • replace the first match with the new tile
    • match the rest of the string which hasn't been matched yet until no more matches is found.
  • Pad the string to the right if it has fewer than 4 characters.
  • Turn each string back to an array of integers by subtracting 96

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):

  • A List<(int x, int spaces)>. Each element represents which tile needs to move (the x coordinate), and how many spaces it should move (spaces).
  • A 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].

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • See my solution at following posting. I create my own array of buttons (you can use a picture box instead). I create a class that inherits the standard button class and then add properties like row number and column number so I can easily tell which button was pressed : https://stackoverflow.com/questions/37165402/c-sharp-adding-button-with-value-at-runtime – jdweng Feb 19 '18 at 20:28
  • @jdweng I have no trouble figuring out which picture box corresponds to which row and column. I just can't figure out which tiles are moved when a move in a particular direction is performed. – Sweeper Feb 19 '18 at 20:30
  • You can add properties to the inherited class to better identify each picture box. You can add the name of the picture or anything else that will give you more info. In your case the tile name may help. – jdweng Feb 19 '18 at 20:42

1 Answers1

7

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.

Examples

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]
trincot
  • 317,000
  • 35
  • 244
  • 286
  • Can you show an example? What would `fromTo` and `result` be if the row were `[0,1,0,1]` and why? – Sweeper Feb 19 '18 at 21:33
  • I added a few examples. The first one will have `[-1,0,-1,0]` for `fromTo`. The -1 are default values which are not touched when there is no block at that initial position. The two zeroes indicate that the blocks at positions 1 and 3 will both go to position 0 (where they will merge). `result` will reflect this merge in the first position: `[2,0,0,0]`. The other zeroes are default values indicating that that position will be empty. – trincot Feb 19 '18 at 21:40