Question

I decided to make a 2048 Command Line Edition but I'm having trouble getting the right tile movement...

My current structure is that the board is a 2D array (4x4) of ints. When an input is received, it will try to push every tile in that direction (ignoring tiles with value 0), if it notices a change it will start over (Because a tile on the bottom row will have to go all the way up, not just one step up). However, a side effect of this is the following problem:
[2][2][4] with the command -> should give [0][4][4] but since it starts over, the program will be able to merge 4 and 4 and get a [0][0][8] intead...
Another difficult problem is the [4][4][8][8] which should give [0][0][8][16] so I can't just stop after a merge.

The code below is my processCommand function. It takes a board and an input (which is "d", "u", "l", or "r". If the game notices gameover it will put "gameover" as input). It's not very pretty and I've tried to make a single for loop for moving tiles (Like if you write "l" a value horiz will be -1 and if you write "r" it will be 1 and then I move the tile horiz spots horizontal but I couldn't make that work).

Any ideas on how to do this (and critique on my programming) would be greatly appreciated!

func processCommand(board [][]int, input string) {

board_new := board

switch input {
    case "d":
        for i := 0; i < height - 1; i++ {
            for j := 0; j < width; j++ {

                if board[i][j] == 0 {
                    continue
                }

                if board[i + 1][j] == 0 || board[i + 1][j] == board[i][j] {
                    board_new[i + 1][j] = board[i + 1][j] + board[i][j]
                    board_new[i][j] = 0
                    i = 0
                    j = 0
                    change = true
                }
            }
        }

    case "u":
        for i := 1; i < height; i++ {
            for j := 0; j < width; j++ {

                if board[i][j] == 0 {
                    continue
                }

                if board[i - 1][j] == 0 || board[i - 1][j] == board[i][j] {
                    board_new[i - 1][j] = board[i - 1][j] + board[i][j]
                    board_new[i][j] = 0
                    i = 1
                    j = 0
                    change = true
                }
            }
        } 

    case "l":
        for i := 0; i < height; i++ {
            for j := 1; j < width; j++ {

                if board[i][j] == 0 {
                    continue
                }

                if board[i][j - 1] == 0 || board[i][j - 1] == board[i][j] {
                    board_new[i][j - 1] = board[i][j - 1] + board[i][j]
                    board_new[i][j] = 0
                    i = 0
                    j = 1
                    change = true
                }
            }
        }

    case "r":
        for i := 0; i < height; i++ {
            for j := 0; j < width - 1; j++ {

                if board[i][j] == 0 {
                    continue
                }

                if board[i][j + 1] == 0 || board[i][j + 1] == board[i][j] {
                    board_new[i][j + 1] = board[i][j + 1] + board[i][j]
                    board_new[i][j] = 0
                    i = 0
                    j = 0
                    change = true
                }
            }
        }

    case "gameover":
        gameOver = true

    default:
        processCommand(board, input)
}

board = board_new

}

Was it helpful?

Solution

First your merging problem

Currently you're always scanning your tiles top-to-bottom and left-to-right, independently of the player's move. For 2048, however, it is preferable to scan in the opposite direction of the player's move, since tiles will only be merged in that direction. For example, let's take the following scenario:

0   0   2   0   |
0   0   2   2   | Player move
0   2   4   8   v
2   32  4   2

Let's assume the player's move direction is towards the bottom, so we start scanning from the bottom towards the top. In the third column, we'd need to first merge 4+4 and then 2+2, i.e. bottom-to-top. Going in that direction allows you to merge 4+4 and then mark the bottom-most field of the column as merged, thus not allowing further merges (indicated by the parentheses around the number):

0   0   0   0   |
0   0   2   2   | Player move
0   2   2   8   v
2   32  (8) 2

After merging the bottom-most cells (if possible), we proceed with the cells above and so on...

0   0   0   0   |
0   0   0   2   | Player move
0   2   (4) 8   v
2   32  (8) 2

[...]

0   0   (0) 0   |
0   0   (0) 2   | Player move
0   2   (4) 8   v
2   32  (8) 2

When no more merges are possible the move ends and all "merged" markers are removed and we wait for the next turn. This approach fixes your multiple-merge problem.

Another example for the scan direction (numbers now represent how a loop would run through the fields):

Player move
    ---->
4   3   2   1
8   7   6   5
12  11  10  9
16  15  14  13

About your code

Looking at the code, one notices that there is lots of code duplication regarding your loops. For each case you do a separate nested for-loop, which is not really optimal. Instead, you might be able to do something like this:

for i := 1; i < height; i++ {
    for j := 0; j < width; j++ {
        if board[i][j] == 0 {
            continue
        }

        switch input {
            case "d":
                updateBoardDown(board, i, j)
            case "u":
                updateBoardUp(board, i, j)

            [...]
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top