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.
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;
}
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;
}
Related
I have the following loop in a class method:
vector<PuzzleImpl> PuzzleImpl::performMove()
{
//! Remove a single move from mMoves and store it in aMove.
MovesImpl aMove = mMoves.top();
mMoves.pop();
//! Cached local variables.
vector<size> soln = aMove.getSoln();
size RID = aMove.getRowID();
size CID = aMove.getColID();
size BID = aMove.getBlockID();
bool solvable = true;
//! Return Vector, will store all possible moves from aMove.
vector<PuzzleImpl> neighbours;
//! Build all Boards and update Moves Queue for a move from top of mMoves.
while (!soln.empty()) {
//! Store local copies to update without being destructive to original.
// Also reset updatedMoves and updatedBoard for next iteration.
fib_heap tempHeap = mMoves;
fib_heap updatedMoves;
Board<size> updatedBoard = mBoard;
//! Get a <value>, remove it form <soln>.
size value = soln.back();
soln.pop_back();
//! Update Board with the move.
updatedBoard.set(RID, CID, value);
//! Iterate through the mMoves queue and update for the removed move.
while (!tempHeap.empty()) {
//! Get an element, remove it from the heap.
MovesImpl temp = tempHeap.top();
tempHeap.pop();
//! If the temp object shares the same RID/CID/BID remove <value>.
if ((temp.getRowID() == RID) || (temp.getColID() == CID)
|| (temp.getBlockID() == BID)) {
temp.removeMove(value);
}
//! Check if we can solve the puzzle form this position. If you have
// a blank position left but there are have no moves from that
// position, a solution is unattainable. - HALT.
if (temp.getSolnSize() == 0) {
solvable = false;
break;
}
//! Add the Moves object to the updatedMoves Heap.
updatedMoves.push(temp);
}
//! If the puzzle is solvable from this position with the current move,
// generate new PuzzleImpl object using the Board and Moves Vec.
neighbours.push_back(PuzzleImpl(updatedBoard, updatedMoves, solvable));
}
//! Return the Vector containing all possible states from this move.
return neighbours;
}
The problem I have is in the line:
MovesImpl temp = tempHeap.top();
I get an access violation (Access violation reading location 0x0000000C.) stating that <src>'s memory can't be read and <this> is set to random values in memory. MovesImpl doesn't have any heap allocation, its stack based and I thus use the default assignment operator. I have specified the copy ctor.
Any ideas? Input greatly appreciated.
/Thanks!
i am developing a kid´s game.
It consists in
3 Tags
6 sacks
Each sack has one secret fruit inside:Bananas or Tomatoes
The first step is to reveal one sack, for example imagine we reveal sack 2 and it is a T(Tomato)
Now the rule is that you have to assign Tags to Sacks , following the rule that no of to sacks has above a correct Tag
So we start to assign tags to sacks.
And finally:
I don´t know how to develop a recursive function that checks the validity of each movement. I have tried a lot of functions but it ´s becoming impossible.
I think the best option is to create
Recursive function
Checks if any other Tag could be a possible solution of the sacks revealed.If it finds another tag that fit in the sacks (with future solution possible of course) is Game Over.
Which is the pseudo code of this function?Or you can imagine another way to develop this code??
I am really stuck and I ask here because you are awesome
Don´t forget that the game is looking for any other possible movement to return game over, he wants you to fail!
Here I've got one possible solution without recursion. I know that you requested pseudo code, but I did it in JAVA just to make sure that is working. At least it will give you another direction where to look at.
Let's assume that you have an array that represent all your possible combinations, e.g:
"00" => Apple-Apple
"01" => Apple-Banana
"10" => Banana-Apple
"11" => Banana-Banana
So, let's do the validation to find out if the MOVEMENT is valid.
public class TestCode {
public static String[] stuff = new String[] { "00", "01", "10", "11" };
public static Boolean isMySearchValid = false;
public static void main(String[] args) {
String first = "01"; // my first guess was "01" which is Apple-Banana
isMySearchValid = isValid(first, "11"); // find out if 10 is valid and
// also has not been taken
// already
System.out.println(isMySearchValid);
}
public static Boolean isValid(final String firstGuess, final String newGuess) {
Boolean response = false;
if (firstGuess == newGuess) {
response = false; // Game Over, because the newGuess was already
// taken
//System.out.println(response + " 1");
} else {
for (int i = 0; i < stuff.length; i++) {
if (!stuff[i].contains(firstGuess)) {
if (stuff[i].contains(newGuess)) {
response = true;
System.out.println("VALID: because was found available in emelement : " + i);
break;
}
}
}
}
return response;
}
}
2nd Approach: (Pseudo Code)
#define VALID_MOVE = 1;
#define TAG_ALREADY_EXIST = 2;
#define NOT_FOUND_OR_NOT_VALID = 3;
int isTagValid(sring[] ArrayOfTags, string tagToFind) {
string[] tempArray = NULL;
int response;
if(tempArray != NULL) {
foreach(string str in tempArray) {
if (str == tagToFind) {
response = TAG_ALREADY_EXIST;
}
}
} else {
foreach(string str in ArrayOfTags) {
if (str == tagToFind) {
tempArray.add(tagToFind);
response = VALID_MOVE;
} else {
response = NOT_FOUND_OR_NOT_VALID;
}
}
}
return response;
}
Notice that if you proposes another card to a sack as a gameover situation, the next movements should be able to solve the game succesfully. Is not enough to check the validity of 1 movement, you need to check until the game ends.
You assign the 3rd card (Banana| Tomato) to the 3-4 sacks cause is the only one which satisfies the 2 rules (fruits are the same in sacks/card & card not above sacks).L
Later, we do another move .We move the 1st card (Tomato| Tomato to the 3rd sack because we have to satisfy that the rule of "correct card is never above sacks". If the algorithm is one step only checking, it will says that the correct one is the other possibility=>card 2 (Banana|Banana), so it will show us game over. But what the algorithm doesn´t know is that the game became unsolvable because we have a card above the only sack pending to solve (Tomato|Tomato)
I'm going to assume that you have a fixed, small set of fruits, each with an associated id. The simplest way to implement this would be an enum:
enum Fruit {
APPLE,
PEAR,
BANANA
};
This way, APPLE has the value 0, PEAR is 1, and BANANA is 2. Now you can represent any sack pair or card by an array telling how many occurrences there is of each fruit (each element would tell the number of occurrences of the fruit that corresponds to the index). For example, a card with two apples would be {2, 0, 0}, and a sack pair with apples and bananas would be {1, 0, 1}. For each sack pair, you would also keep track of the "visible" fruits: if the aforementioned sack pair hasn't been opened, we have {0, 0, 0}, and when we have only revealed the apples, we would have {1, 0, 0}.
Now, all you need to do to figure out whether a given card is compatible with what we know about a given sack pair is to check each pair of elements and see if all elements from the sack pair's array is less than or equal to the corresponding element of the card's array. No recursion needed:
bool isCompatible(const vector<int> & card, const vector<int> & sack) {
for (int i = 0; i < card.size(); i++) {
if (card[i] < sack[i])
return false;
}
return true;
}
bool isLegalMove(const vector<int> & movedCard, const vector<vector<int> > & otherCards, const vector<int> & sack) {
if (!isCompatible(movedCard, sack))
return false;
for (int i = 0; i < otherCards.size(); i++) {
if (isCompatible(otherCards[i], sack))
return false;
}
return true;
}
Edit: I still don't quite understand the mechanics of your game, but here's the general approach for simulating possible game moves until the game ends. The code assumes that any move will always make the game progress closer to the end, i.e. that it's not possible to go around in circles (if it were, you'd need additional code to keep track of which game states you've tried out).
I'm assuming that all of your game state is wrapped up in a class called GameState, and that a move can be represented by Move. In your case, you might have two subclasses of Move, namely OpenSack (containing the sack index) and MoveCard (containing the original and final positions of the card being moved). You need a function vector<Move *> generateMoves(GameState &) that can generate the moves that are possible in a given game state (note that if the game state is a "game over", this function should return an empty vector). Move needs an abstract function GameState perform(const GameState &), to be implemented by each subclass, that will actually perform the move and create a new GameState (rather than modifying the existing state) that represents the game state after the move. (In a game with a larger state, it would be better to actually modify the state, and to have a reverse() method that would undo the effects of a move.) Then, the simulation can be implemented like this:
bool isSolvable(const GameState & currentState) {
if (currentState.isValidFinalState())
return true;
vector<Move *> moves = generateMoves(currentState);
for (int i = 0; i < moves.size(); i++) {
GameState stateAfterMove = moves[i]->perform(currentState);
if (isSolvable(stateAfterMove))
return true;
}
return false;
}
This will play out all possible moves (this is feasible only because it's such a "small" game), and if any sequence of moves leads to a valid solution, the function will return true.
I am currently writing an A* pathfinding algorithm for a game and came across a very strange performance problem regarding priority_queue's.
I am using a typical 'open nodes list', where I store found, but yet unprocessed nodes. This is implemented as an STL priority_queue (openList) of pointers to PathNodeRecord objects, which store information about a visited node. They are sorted by the estimated cost to get there (estimatedTotalCost).
Now I noticed that whenever the pathfinding method is called, the respective AI thread gets completely stuck and takes several (~5) seconds to process the algorithm and calculate the path. Subsequently I used the VS2013 profiler to see, why and where it was taking so long.
As it turns out, the pushing to and popping from the open list (the priority_queue) takes up a very large amount of time. I am no expert in STL containers, but I never had problems with their efficiency before and this is just weird to me.
The strange thing is that this only occurs while using VS's 'Debug' build configuration. The 'Release' conf. works fine for me and the times are back to normal.
Am I doing something fundamentally wrong here or why is the priority_queue performing so badly for me? The current situation is unacceptable to me, so if I cannot resolve it soon, I will need to fall back to using a simpler container and inserting it to the right place manually.
Any pointers to why this might be occuring would be very helpful!
.
Here is a snippet of what the profiler shows me:
http://i.stack.imgur.com/gEyD3.jpg
.
Code parts:
Here is the relevant part of the pathfinding algorithm, where it loops the open list until there are no open nodes:
// set up arrays and other variables
PathNodeRecord** records = new PathNodeRecord*[graph->getNodeAmount()]; // holds records for all nodes
std::priority_queue<PathNodeRecord*> openList; // holds records of open nodes, sorted by estimated rest cost (most promising node first)
// null all record pointers
memset(records, NULL, sizeof(PathNodeRecord*) * graph->getNodeAmount());
// set up record for start node and put into open list
PathNodeRecord* startNodeRecord = new PathNodeRecord();
startNodeRecord->node = startNode;
startNodeRecord->connection = NULL;
startNodeRecord->closed = false;
startNodeRecord->costToHere = 0.f;
startNodeRecord->estimatedTotalCost = heuristic->estimate(startNode, goalNode);
records[startNode] = startNodeRecord;
openList.push(startNodeRecord);
// ### pathfind algorithm ###
// declare current node variable
PathNodeRecord* currentNode = NULL;
// loop-process open nodes
while (openList.size() > 0) // while there are open nodes to process
{
// retrieve most promising node and immediately remove from open list
currentNode = openList.top();
openList.pop(); // ### THIS IS, WHERE IT GETS STUCK
// if current node is the goal node, end the search here
if (currentNode->node == goalNode)
break;
// look at connections outgoing from this node
for (auto connection : graph->getConnections(currentNode->node))
{
// get end node
PathNodeRecord* toNodeRecord = records[connection->toNode];
if (toNodeRecord == NULL) // UNVISITED -> path record needs to be created and put into open list
{
// set up path node record
toNodeRecord = new PathNodeRecord();
toNodeRecord->node = connection->toNode;
toNodeRecord->connection = connection;
toNodeRecord->closed = false;
toNodeRecord->costToHere = currentNode->costToHere + connection->cost;
toNodeRecord->estimatedTotalCost = toNodeRecord->costToHere + heuristic->estimate(connection->toNode, goalNode);
// store in record array
records[connection->toNode] = toNodeRecord;
// put into open list for future processing
openList.push(toNodeRecord);
}
else if (!toNodeRecord->closed) // OPEN -> evaluate new cost to here and, if better, update open list entry; otherwise skip
{
float newCostToHere = currentNode->costToHere + connection->cost;
if (newCostToHere < toNodeRecord->costToHere)
{
// update record
toNodeRecord->connection = connection;
toNodeRecord->estimatedTotalCost = newCostToHere + (toNodeRecord->estimatedTotalCost - toNodeRecord->costToHere);
toNodeRecord->costToHere = newCostToHere;
}
}
else // CLOSED -> evaluate new cost to here and, if better, put back on open list and reset closed status; otherwise skip
{
float newCostToHere = currentNode->costToHere + connection->cost;
if (newCostToHere < toNodeRecord->costToHere)
{
// update record
toNodeRecord->connection = connection;
toNodeRecord->estimatedTotalCost = newCostToHere + (toNodeRecord->estimatedTotalCost - toNodeRecord->costToHere);
toNodeRecord->costToHere = newCostToHere;
// reset node to open and push into open list
toNodeRecord->closed = false;
openList.push(toNodeRecord); // ### THIS IS, WHERE IT GETS STUCK
}
}
}
// set node to closed
currentNode->closed = true;
}
Here is my PathNodeRecord with the 'less' operator overloading to enable sorting in priority_queue:
namespace AI
{
struct PathNodeRecord
{
Node node;
NodeConnection* connection;
float costToHere;
float estimatedTotalCost;
bool closed;
// overload less operator comparing estimated total cost; used by priority queue
// nodes with a higher estimated total cost are considered "less"
bool operator < (const PathNodeRecord &otherRecord)
{
return this->estimatedTotalCost > otherRecord.estimatedTotalCost;
}
};
}
std::priority_queue<PathNodeRecord*> openList
I think the reason is that you have a priority_queue of pointers to PathNodeRecord.
and there is no ordering defined for the pointers.
try changing it to std::priority_queue<PathNodeRecord> first, if it makes a difference then all you need is passing on your own comparator that knows how to compare pointers to PathNodeRecord, it will just dereference the pointers first and then do the comparison.
EDIT:
taking a wild guess about why did you get an extremely slow execution time, I think the pointers were compared based on their address. and the addresses were allocated starting from one point in memory and going up.
and so that resulted in the extreme case of your heap (the heap as in data structure not the memory part), so your heap was actually a list, (a tree where each node had one children node and so on).
and so you operation took a linear time, again just a guess.
You cannot expect a debug build to be as fast as a release optimized one, but you seems to do a lot of dynamic allocation that may interact badly with the debug runtime.
I suggest you to add _NO_DEBUG_HEAP=1 in the environment setting of the debug property page of your project.
I have a pointer that is set to 0, then later on in the same function, inside some loops/conditions I try to re assign it.. (please read the comments)
for(Entity* tile : _originalFloorTiles)
{
for(Turns turn : pointsUpLeftDownRight)
{
if(tile->GetCollisionRect().ContainsPoint(turn.first.x, turn.first.y)){
turn.second = tile; //everything looks fine here, turn.second is still null and tile is a valid pointer
assert(turn.second); //turn.second is definitely assigned the value of tile here.
}
HAPI->DebugText("check pointsUpLeftDownRight now");//!Here's where it gets weird,
// If i hover over turn and inspect it in visual studio now, turn.second is still completely valid
// (with the assigned value of tile).
// But hovering over pointsUpLeftDownRight shows its contents for each turn..
// and inside there the current turn is a NULL pointer for turn.second!
}
}
So one moment i have assignd my pointer no problem, and the next moment the pointer doesn't seem to have changed at all.
To clarify, Turns is a lazy typedef for std::pair<Vect, Entity*> , apologies if that makes my code harder to read, it's some quickly thrown together enemy ai. I'll post the complete function below.
I'm really stumped here and not sure if i'm being an idiot or something weird is going on, would really appreciate anyone taking the time to look.
//looks for turns that the ghost can take.
void IceGhostNPC::RespondToTimePassed()
{
//Entity* t = _originalFloorTiles[0];
//test if enough time has passed since ghost last decided to look for turns
if(_lastTimeTurned < timeGetTime() - _timeBeforeSearchingForTurns)
{
//store points surrounding ghost in a way that we can associate them with a valid floor tile to move onto
std::vector<Turns> pointsUpLeftDownRight;
pointsUpLeftDownRight.push_back(
Turns(Vect(GetCenterXPos(), GetCenterYPos() - floorTileHeight), 0)); //point above
pointsUpLeftDownRight.push_back(
Turns(Vect(GetCenterXPos() - floorTileWidth, GetCenterYPos()), 0)); //point left
pointsUpLeftDownRight.push_back(
Turns(Vect(GetCenterXPos(), GetCenterYPos() + floorTileHeight), 0)); //point down
pointsUpLeftDownRight.push_back(
Turns(Vect(GetCenterXPos() + floorTileWidth, GetCenterYPos()), 0)); //point right
//look through original floor tiles,
for(Entity* tile : _originalFloorTiles)
{
//see if its possible to take a turn
for(Turns turn : pointsUpLeftDownRight)
{
if(tile->GetCollisionRect().ContainsPoint(turn.first.x, turn.first.y)){
turn.second = tile;
assert(turn.second);
}
HAPI->DebugText("check pointsUpLeftDownRight now");
}
}
//Now to make the behaviour more interesting we have the ghost randomly take one of the turns,
// ( we use associated tile to check the turn is possible, and we can also change that tile to an icy patch )
bool turnTaken = false;
do{
int turnChoice = rand() % 4;
if(pointsUpLeftDownRight[turnChoice].second == 0)
continue; //go back to top of loop if that turn had a null tile
else
{
switch(turnChoice){
case 0: //turn upwards
_moveable->SetYDirection(Controller::UP);
_moveable->SetXDirection(Controller::NONE);
break;
case 1: //turn leftwards
_moveable->SetYDirection(Controller::NONE);
_moveable->SetXDirection(Controller::LEFT);
break;
case 2: //turn downwards
_moveable->SetYDirection(Controller::DOWN);
_moveable->SetXDirection(Controller::NONE);
break;
case 3: //turn right
_moveable->SetYDirection(Controller::NONE);
_moveable->SetXDirection(Controller::RIGHT);
break;
}
turnTaken = true;
_lastTimeTurned = timeGetTime();
//ice tile up baby
}
}while(turnTaken = false);
}
FinishResponding(timeGetTime());
}
Check this line:
for(Turns turn : pointsUpLeftDownRight)
You are iterating over copies of the elements in pointsUpLeftDownRight. Whatever value you assign to that copy will be lost when the copy is destroyed (at the end of the for body). Your assignment changes a temporary.
Try with this instead:
for(Turns& turn : pointsUpLeftDownRight)
Here is my code for checking if future move is legal, I have assumed its legal and copied move into mySquares array. I then call this method in the game cycle set in the form and in the timer handler which is:
canvas->drawGrid();
testBlock->drawBlock();
testBlock->moveDown();//this method has checkBounds for when hit sides, top & bottom
if(newBlock->canMoveDown()==false)
{
newBlock->addMySelfToGameBoard();
mainGameBoard->updateGrid();
}
//timer1 handler finish
bool TTetrisBlock::canMoveDown()
{
array<Point>^ temporaryCopy = gcnew array<Point>(4);
bool canGoDown = true;
for(int i=0;i<mySquares->Length;i++)
{
//Set future move
temporaryCopy[i].X = mySquares[i].X;
temporaryCopy[i].Y = mySquares[i].Y+1;
}
//Check if future move cells are full, if not assign values to mySquares
//Check if future move is legal
for(int j=0;j<temporaryCopy->Length;j++)
{
if(gameBoard->isCellOccupied(temporaryCopy[j].X,temporaryCopy[j].Y) == true)
{
mySquares[j].X = temporaryCopy[j].X;
mySquares[j].Y = temporaryCopy[j].Y;
}
}
return canGoDown;
}
//end of moveDown
in my gameboard class i have the method which checks if TCell is occupied or not. TGameBoar holds an array of TCells which has a color and bool isOccupied = false;
bool TGameBoard::isCellOccupied(int c,int r)
{
//Checks if TCell is occupied
return myGrid[c,r]->getIsOccupied();
}
It Crashes and indicates here was the problem, Im currently learning C++ at school. I would appreciate some help. I am also struggling with the Keydown for moving left and right using e->KeyData == Keys::Left) etc. and creating a newblock when gone through loop.
I have my project rar if you want to check it out. I have all the classes done, its just putting it together is the hard bit.
Project Tetris
I see three problems.
First you should only move mySquares when isCellOccupied returns false (not true as you currently have it). I suspect this is the cause of your crash as it looks like you will be moving a block into a cell that is already occupied.
Second, when isCellOccupied returns true you should set canGoDown to false and break out of your for loop (or better yet, make canGoDown (==true) an additional condition of your for loop i.e. j < temporaryCopy->Length && canGoDown). As it is, your function always return true because it is never set to false and that can't be right.
Just making an assumption here, but don't all mySquares consist of 4 elements? You are initializing temporaryCopy with 4 elements but it isn't clear whether mySquares has 4 elements. If not, this could be dangerous as in your first loop you are looping on mySquares->Length and addressing temporaryCopy with that index value, which could be out of range. And then later doing the opposite. It might be better to use a constant (4) in all all loops or better yet, always use mySquares->Length (especially when creating the temporaryCopy array) to ensure that both arrays contain the same number of elements.