1

I've implemented 2048 game in C++, github link : 2048

For implementing undo operation, i.e. going back to previous state of game, I'm maintaining a matrix for previous board configuration, but if I'm allowing many undo operations consecutively, I can't maintain that number of matrices.

What can be a way to improve this approach?

One way I thought was maintaining only the previous moves(up, down, left or right), but only this information can't help to regenerate the previous state, if I'm missing something in this approach or it can be extended, please suggest a way to do this.

Mandeep Singh
  • 198
  • 2
  • 3
  • 16
  • 1
    *"I can't maintain that number of matrices."* A 4x4 matrix of numbers is just a few bytes large. – HolyBlackCat Feb 10 '18 at 14:27
  • That's not the point, I'm just trying to reduce space, your help will be much appreciated. – Mandeep Singh Feb 10 '18 at 14:32
  • A copy of each board state is the most compact representation of the history of a 2048 game. Not only would you have to store the direction of move, you would have to store how far each element moved, including pairs of elements that merged. – Caleth Feb 13 '18 at 13:14
  • 1
    Once you deem that your game has reached a "good enough" state, I suggest you submit it to [CodeReview](https://codereview.stackexchange.com/) to get feedback :) – Quentin Feb 13 '18 at 13:39

2 Answers2

0

You can store the board's current state into a stack, so every time the user makes a move, which will change the board state, just put it into a stack so you get a stack full of with matrix of board's current state of user's moves and ordered from recent one being on the top. So when you want to undo their latest move just pop from stack which will give you their latest operation.

...
std::stack<std::array<int, 16>> boardStates;
std::array<16, int> currentBoardState;
// whenever user makes a move
makeMove(currentBoardState)

//when they want to undo    
tempBoardState = undoMove();
if(tempBoardState != nullptr)
{
   currentBoardState = tempBoardState;
}
else
{
   std::cout << "No previous move available" << std::endl
}
...

void makeMove(std::array<int, 16> currentState)
{
   boardStates.push(currentState);
}

std::array<int, 16> undoMove()
{
    if(!boardStates.empty())
    {
        return boardStates.pop();
    }

    return nullptr;
}
Onur A.
  • 2,938
  • 3
  • 20
  • 35
0

Implement an history, to store subsequent board status changes.

//maximum history size (can be whatever reasonable value)
const int MAX_UNDO = 2048;

//give a shorter name to the type used to store the board status
typedef std::array<int, 16> snapshot; 

//use a double ended queue to store the board statuses
std::deque<snapshot> history;

//this function saves the current status, has to be called each time 
//the board status changes, i.e. after the board initialization 
//and after every player move
void save()
{
    //make a copy of g, the current status
    snapshot f;
    std::copy(&g[0][0], &g[0][0] + 16, f.begin());

    //push the copy on top
    history.push_back(f); 

    //check history size
    if(history.size() > MAX_UNDO)
    {
        //remove one element at the bottom end
        history.pop_front();
    }
}

bool undo()
{
    //history must hold at least one element 
    //other than the current status copy
    if(history.size() > 1)
    {
        //the top element of the queue always holds a copy of the 
        //current board status: remove it first
        history.pop_back(); 

        //now the top element is the previous status copy
        snapshot f = history.back();

        //copy it back to g
        std::copy(f.begin(), f.end(), &g[0][0]);

        //undo operation succedeed
        return true;
    }

    //undo operation failed
    return false;
}
p-a-o-l-o
  • 8,939
  • 2
  • 17
  • 34
  • I implemented it using `stack > >`, your approach is useful in case I'm considering undo operations till a `MAX_UNDO_LIMIT`, is there any way such that previous state can be regenerated without storing complete board information? – Mandeep Singh Feb 12 '18 at 11:39
  • As it states in Effective C++, it's a good practice to prefer const and inline to #define – Onur A. Feb 13 '18 at 12:49
  • Ah ah ah ... thanks. While you're here, watch your code 'cause it doesn't compile (std::stack::pop is void) and the logic is plainly broken (on undo, the board status will not change). But, hey, no `#define`'s ... – p-a-o-l-o Feb 13 '18 at 12:54
  • 2
    You're not a fan of criticism, are you :p -- I don't see what's DV-worthy here though. Additional note: if OP makes the switch to `std::array`, pushing and popping will be `history.push_back(g)` and `g = history.back()`, which is much more comfy. – Quentin Feb 13 '18 at 13:13
  • @Quentin In this context, I find funny to see working code downvoted by someone who posted broken code. Anyway, I obeyed the critic and replaced the #define :) – p-a-o-l-o Feb 13 '18 at 13:24
  • @p-a-o-l-o I don't think competition is a good drive on SO anyway. That said, note that the "broken" answer has no activity -- whether or not your well-commented and ready-to-use code is more pertinent than a longer explanation with draft code, it *is* better received right now. – Quentin Feb 13 '18 at 13:32
  • Really? Because I'm on the verge of deleting it! :) I agree about competition being potentially disastrous here. – p-a-o-l-o Feb 13 '18 at 13:36
  • 1
    @p-a-o-l-o I just shared a good practice from Scott Meyer's and at least indicated why I downvoted it instead of downvoting and doesn't mentioning why I have downvoted. Thanks for the warning on pop() though – Onur A. Feb 13 '18 at 14:40