简体   繁体   中英

How to use while more efficiently in C++?

I am trying to code 2048 game in C++, and I created some functions to add a new random number in a random place after detecting user presses the arrows to play. Those functions are about to find a place where no number has taken and then place the new number. I tried to ways to do this. One is kind of silly which is used a lot of cases, and the other one is just use while which works slow when I am running this game. I am new to C++. I hope someone can help me find a better solution. And here are those code:

bool add_new_number_when_up()
{
    srand(time(NULL));
    int n = rand() % 2 + 1;
    int newnumber = pow(2, n);
    while(true) {
        if(check_up_move() == 1) {
            loop:
            int a = rand() % 8;
            switch(a) {
            case 0:
                if(grid[2][0] == 0) {
                    grid[2][0] = newnumber;
                    return false;
                }
                else {
                    goto loop;
                }
                break;
            case 1:
                if(grid[2][1] == 0) {
                    grid[2][1] = newnumber;
                    return false;
                }
                else {
                    goto loop;
                }
                break;
            case 2:
                if(grid[2][2] == 0) {
                    grid[2][2] = newnumber;
                    return false;
                }
                else {
                    goto loop;
                }
                break;
            case 3:
                if(grid[2][3] == 0) {
                    grid[2][3] = newnumber;
                    return false;
                }
                else {
                    goto loop;
                }
                break;
            case 4:
                if(grid[3][0] == 0) {
                    grid[3][0] = newnumber;
                    return false;
                }
                else {
                    goto loop;
                }
                break;
            case 5:
                if(grid[3][1] == 0) {
                    grid[3][1] = newnumber;
                    return false;
                }
                else {
                    goto loop;
                }
                break;
            case 6:
                if(grid[3][2] == 0) {
                    grid[3][2] = newnumber;
                    return false;
                }
                else {
                    goto loop;
                }
                break;
            case 7:
                if(grid[3][3] == 0) {
                    grid[3][3] = newnumber;
                    return false;
                }
                else {
                    goto loop;
                }
                break;
            }
        }
        else {
            return false;
        }
    }
}

And this is the other one:

void test_if_zero(int row, int col) 
{
    srand(time(NULL));
    int n = rand() % 2 + 1;
    int newnumber = pow(2, n);
    if(grid[row][col] == 0) {
        grid[row][col] = newnumber;
    }
}

int check_empty()
{
    int flag = 0; 
    for(int i = 0; i < 4; i++) {
        for(int j = 0; j < 4; j++) {
            if(grid[i][j] == 0) {
                flag = 1;
                return flag;
            }
        }
    }
}

bool add_new_number(int num)
{
    Loop:
    int row, col;
    srand(time(NULL));
    switch(num) {
    case 1: //up
        row = rand() % 2 + 2;
        col = rand() % 4;
        break;
    case 2: //down
        row = rand() % 2;
        col = rand() % 4;
        break;
    case 3: //left
        row = rand() % 4;
        col = rand() % 2 + 2;
        break;
    case 4: //right
        row = rand() % 4;
        col = rand() % 2;
        break;
    }
    while(true) {
        if(check_empty() == 1) {
            if(grid[row][col] == 0) {
                test_if_zero(row, col);
                return false;
            }
            else {
                goto Loop;
            }
        }
        else {
            break;
        }
    }
}

Almost 50 years have passed since professor Edsger W. Dijkstra wrote his paper "Go To Statement Considered Harmful" ( Communications of the ACM 11, 3, March 1968, p. 147-148.) and still unjustified uses of that construct can be found.

OP's first snippet can be summerised as:

bool add_new_number_when_up()
{
    // ...
    while(true) {
        if( /* some unexplained condition */ ) {
            loop:                    // <- "loop" start
            int a = rand() % 8;
            switch(a) {
            case 0:
                if( /* something */ ) {
                    // ...
                    return false;
                }
                else {
                    goto loop;      // <- "loop" end
                }
                break;              // never reached...
            // many other cases...
            }
        }
        else {
            return false;
        }
    }
}

Which, besides the lack of any return true , could be (but really should not) rewritten as

bool add_new_number_when_up()
{
    // ...
    while(true) {
        if( /* some unexplained condition */ ) {
            while(true) {                    // <- nested loop!
                int a = rand() % 8;
                switch(a) {
                case 0:
                    if( /* something */ ) {
                        // ...
                        return false;        // true?
                    }
                    break;                   // <- restart the loop...
                // many other cases...
                }
            }            
        }
        else {
            return false;
        }
    }
}

Writing an infinite loop as loop: ... goto loop; instead of while(1) {...} doesn't make it magically faster, but it can surely obfuscate your code and it's more error prone.

The second snippet is even worse:

bool add_new_number(int num)
{
    Loop:                      // <- what kind of loop is this?
    // some expansive function which shouldn't be here...
    // ... 
    while(true) {
        if( /* call to another relatively expansive function */ ) {
            if( /* some condition */ ) {
                // call to another expansive function...
                return false;
            }
            else {
                goto Loop;          // <- end of loop?
            }
        }
        else {
            break;
        }
    }
}           // <- a return statement is missing!

The OP complains about the slowness of this code, which is unsurprising, given the overall design and the functions involved.

For example, there are multiple calls to srand , which are not only expansive, but also unnecessary as one call in the entire program, to seed the pseudo random number generator, is enough. The same would hold even for the "modern" <random> generators, like std::random_device and std::mt19937 .

Making any kind of micro optimization at this point is useless, though. It is far better to figure out a completely different algorithm.

Those functions are about to find a place where no number has taken and then place the new number.

My suggestion is to firstly determine which cells of the grid are empty. Just traverse half of the grid (which part, depends on the direction passed to the function) and add the empty ones (or their indeces) to a list (could be an array...) of candidates. There are 8 at most of those, it is a very small domain, it has to be a fast loop (or nested loops).

If there aren't any valid candidates, then return false, no number is added.

Otherwise, you can extract a random index between 0 and the number of candidates - 1 to choose between them the cell to be updated. You don't need to check again if it is empty, thanks to the previous step, just extract a number between 1 and 2 to use as new value and return true.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM