I'm writing an A* algorithm for Rush Hour (game) in order to learn the basics. For that I wrote a class named Board
, which instances also act as the 'nodes' for the A* search.
For the algorithm to work, I generate (generate_moves()
) the possible moves as new Board
's and add them to the original board as children (std::priority_queue
named children
).
For some reason, the moment I decide to obtain a Board
from mystd::priority_queue children
container withtop()
, the pointer it has towards its parent (Board* parent
), which is assigned previously during generation, turns into a pointer towards itself.
- Abbreviated
generate_moves()
function:
"for each possible move do"
...
Board newBoard = Board();
...
newBoard.parent = this; // I understand 'this' to always point towards the function caller.
children.push(newBoard); // Priority queue of the current board
...
The following images display the debugging process in the a_star()
function (which takes the calling Board
as root) after a single node expansion from root, while grabbing the very first 'child':
before top()
As you see, every element inside the open
priority queue has the root parent, whose own parent is 'nullptr' (this is correct, root node has no parent).
custom score-comparing class Compare
for the priority queues
As you can see, everything is messed up already. From the very first comparison, the parent'parent
pointer on every child now goes towards itself, instead of nullptr
.
after top() (Board currNode)
After the priority queue exits top()
, The node is now messed up, having a self-referencing parent.
after top() (priority_queue open)
The same goes for every Board
inside the open
priority queue, having a self-referencing parent.
I've been dabbling around this issue for a long time now. Any help would be deeply appreciated. If you require any additional context please let me know.
I've tried:
- Replacing
priority_queue
with my own type, but I end up dealing with the issues of 'const'-required values, for which I end up creating what's basically the same class. - Giving the parent pointer as a constructor parameter. Didn't change a thing.
- Instead of using a copy of the OPEN priority_queue for some comparisons that require popping (I don't want to do that to the original), I also tried popping the original and then pushing the nodes back in, as the 'copying' bit could've been the issue. Didn't change a thing.
- I changed the Compare function from comparing reference values (
Board&
) to normal values (Board
), as the issue seems to be happening during comparison. Didn't change a thing.
EDIT: Here's my attempt at a "minimal reproducible example". Not sure if consistent due to use of rand() to simplify some functions but it showcases the exact same issue:
#pragma once
#include <iostream>
#include <bitset>
#include <vector>
#include <queue>
#include <stdexcept>
using namespace std;
class SimplifiedBoard {
public:
bitset<36> board;
class Compare {
public:
bool operator() (SimplifiedBoard& a, SimplifiedBoard& b) {
return (a.fscore < b.fscore);
}
};
int fscore;
priority_queue<SimplifiedBoard, vector<SimplifiedBoard>, Compare> children;
SimplifiedBoard* parent;
SimplifiedBoard() {
fscore = 0;
parent = nullptr;
}
// Move generator
void generateMoves() {
int arbitraryChildren = 5;
for (int i = 0; i < arbitraryChildren; i++) {
SimplifiedBoard newBoard = SimplifiedBoard();
newBoard.fscore = (int)rand() / 100;
// Assign parent to child
newBoard.parent = this;
// Add new child
children.push(newBoard);
}
}
// A*
vector<SimplifiedBoard> aStar(SimplifiedBoard& start) {
priority_queue<SimplifiedBoard, vector<SimplifiedBoard>, Compare> open = {};
vector<SimplifiedBoard> closed = {};
start.fscore = 0;
start.parent = nullptr;
open.push(start);
// Loop
while (!open.empty()) {
// ***BUG HAPPENS here (2nd cycle, after root node has been expanded and closed)****
SimplifiedBoard currNode = open.top(); open.pop();
if (currNode.parent != nullptr &&
currNode.parent->parent != nullptr &&
currNode.parent == currNode.parent->parent) {
cout << "Self-referential bug!" << endl;
}
// *** Here, in 2nd cycle, currNode.parent suddenly references a node which references itself ***
// Child generation. f,g,h scores and 'parent' assigned inside function
currNode.generateMoves();
// For each child check OPEN/CLOSE and and see if I add to OPEN
auto childrenCopy = currNode.children;
for (int i = 0; i < (int)currNode.children.size(); i++) {
bool isWorse = false;
SimplifiedBoard currChildNode = childrenCopy.top(); childrenCopy.pop();
// Victory?
if (currChildNode.isVictory()) {
closed.push_back(currChildNode);
// Path reconstruction
vector<SimplifiedBoard> path = {};
SimplifiedBoard currPathNode = currChildNode;
// Ciclo child->parent
while (currPathNode.parent != nullptr) {
path.push_back(currPathNode);
currPathNode = *currPathNode.parent;
}
// Insert root
path.push_back(currPathNode);
// Return path
return path;
}
// OPEN
auto openCopy = open;
for (int j = 0; j < (int)open.size(); j++) {
SimplifiedBoard currOpenNode = openCopy.top(); openCopy.pop();
if (currChildNode.fscore <= currOpenNode.fscore) {
isWorse = true; break;
}
}
if (isWorse) { continue; }
// CLOSED
for (int j = 0; j < (int)closed.size(); j++) {
;
if (currChildNode.fscore <= closed[j].fscore) {
isWorse = true; break;
}
}
if (isWorse) { continue; }
//
open.push(currChildNode);
}
// Close the node
closed.push_back(currNode);
}
// return empty if can't find goal
return vector<SimplifiedBoard>();
}
// Check victory
bool isVictory() {
if ((int)rand() % 50 == 5) { return true; }
else { return false; }
}
};
int main() {
// Debug_example
srand((int)time(NULL));
SimplifiedBoard b = SimplifiedBoard();
cout << "If it gets stuck the bug is happening:" << endl;
vector<SimplifiedBoard> res = b.aStar(b);
for (int i = 0; i < (int)res.size(); i++) {
cout << res[i].fscore << endl;
}
}