I am trying to write an A* search Algorithm program that solves the classic 8 Puzzle problem. However after many attempts and research I have found myself stuck implementing the algorithm.
The program is required to return the shortest path as a string that represents the actions required to take to reach the goal state from the starting state, for example "UDLRUDLRRDD" U = up, D = down, L = left and R = right.
I am trying to follow the algorithm given by geeksforgeeks(https://www.geeksforgeeks.org/a-search-algorithm/). I Have added a reproducible example below.
Struct to store board states
#include <ctime>
#include <string>
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <bits/stdc++.h>
enum heuristicFunction{misplacedTiles, manhattanDistance};
struct State{ // state struct to store states of board
int cost = 0, hCost = -1, gCost = 0; // total cost F(n) = H(n) + G(n)
int state[3][3]; // Board, the blank tile is represented as 0
int x, y; // 0 pos
string action; // stores the action the state took
State *parent = NULL;
// default state constructor
State(const string element){
int n;
n = 0;
for(int i=0; i < 3; i++){
for(int j=0; j < 3; j++){
state[i][j] = element[n] - '0';
if(state[i][j] == 0){
x = j;
y = i;
}
n++;
}
}
}
// copy constructor
State(State *s){
for(int i=0; i<3; i++){
for(int j=0; j<3; j++){
this->state[i][j] = s->state[i][j];
}
}
this->x = s->x;
this->y = s->y;
this->cost = s->cost;
this->hCost = s->hCost;
this->gCost = s->gCost;
this->action = s->action;
this->parent = s->parent;
}
};
A* search class that implements the struct above
class AStarSearch{
private:
struct Cost_Compare{ // struct containing operator for cost organize Q
bool operator()(State* const& n1, State* const& n2) {
return n1->cost > n2->cost; // return true if n1 cost is greater
}
};
// Containers
priority_queue<State*, vector<State*>, Cost_Compare> Queue; // Q container
vector<State*> visitedList; // visited list
// start and end states
State *initial_state;
State *final_state;
// heuristic enum
heuristicFunction heuristic;
public:
// constructor
AStarSearch(const string init, const string final, heuristicFunction heuristic){
this->initial_state = new State(init); // set init state
this->final_state = new State(final); // set final state
this->Queue.push(this->initial_state); // push init state to queue
this->heuristic = heuristic;
}
State *getInit(){
return this->initial_state;
}
State *getFinal(){
return this->final_state;
}
// Cost Functions
State* Calculate_H(State *n){
int sum = 0;
switch(heuristic){
case misplacedTiles:
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
if(n->state[i][j] != final_state->state[i][j]){
sum++;
}
}
}
n->hCost = sum;
break;
case manhattanDistance:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if(n->state[i][j] != 0){
for (int k = 0; k < 3; k++) {
for (int l = 0; l < 3; l++) {
if (n->state[i][j] == final_state->state[k][l]) {
sum += abs(i-k) + abs(j-l);
}
}
}
}
}
}
n->hCost = sum;
break;
}
return n;
}
int calculate_cost(State *n){
int cost;
cost = n->hCost + n->gCost;
return cost;
}
void swap(int *a, int *b){
int temp = *a;
*a = *b;
*b = temp;
}
// State Actions
State* move_left(State* current){
State *curr = new State(current);
// Move Blank tile
swap(&curr->state[curr->y][curr->x], &curr->state[curr->y][curr->x-1]);
curr->x = curr->x - 1;
curr = Calculate_H(curr); // calculate hCost
curr->gCost = curr->gCost + 1; // add depth
curr->cost = calculate_cost(curr); // calc total tile cost
curr->action = "L";
curr->parent = current; // assign parent node
return curr;
}
State* move_right(State* current){
State *curr = new State(current);
// Move Blank tile
swap(&curr->state[curr->y][curr->x], &curr->state[curr->y][curr->x+1]);
curr->x = curr->x + 1;
curr = Calculate_H(curr); // calculate hCost
curr->gCost = curr->gCost + 1; // add depth
curr->cost = calculate_cost(curr); // calc total tile cost
curr->action = "R";
curr->parent = current; // assign parent node
return curr;
}
State* move_down(State* current){
State *curr = new State(current);
// Move Blank tile
swap(&curr->state[curr->y][curr->x], &curr->state[curr->y+1][curr->x]);
curr->y = curr->y + 1;
curr = Calculate_H(curr); // calculate hCost
curr->gCost = curr->gCost + 1; // add depth
curr->cost = calculate_cost(curr); // calc total tile cost
curr->action = "D";
curr->parent = current; // assign parent node
return curr;
}
State* move_up(State* current){
State *curr = new State(current);
// Move Blank tile
swap(&curr->state[curr->y][curr->x], &curr->state[curr->y-1][curr->x]);
curr->y = curr->y - 1;
curr = Calculate_H(curr); // calculate hCost
curr->gCost = curr->gCost + 1; // add depth
curr->cost = calculate_cost(curr); // calc total tile cost
curr->action = "U";
curr->parent = current; // assign parent node
return curr;
}
bool isEqual(State *s1, State *s2){
for(int i=0; i<3; i++)
for(int j=0; j<3; j++)
if(s1->state[i][j] != s2->state[i][j])
return false;
return true;
}
bool isPresentInVisited(State *curr){
bool flag1 = false;
for(int i=0; i<visitedList.size(); i++){
if(isEqual(curr, visitedList[i])){
return true;
}
}
return false;
}
bool isPresentInQueue(State *s1, priority_queue<State*, vector<State*>, Cost_Compare> q){
while(!q.empty()){
State *curr = q.top();
q.pop();
if(isEqual(s1, curr)){
return true;
}
}
return false;
}
void search(){
State *current = Queue.top();
if(current->hCost == 0){
visitedList.push_back(current);
return;
}
while(!Queue.empty()){
State* next_state; // next state
next_state = Queue.top(); // assign to top of Q
Queue.pop(); // pop element off Q
visitedList.push_back(next_state); // add to visited list
vector<State*> successors;
if(next_state->hCost == 0){ // check goal
return;
} // else expand state
// expansions
else if(next_state->x == 0 && next_state->y == 0){
/*
0 1 2 0->down
3 4 5 0->right
6 7 8
*/
State *down = move_down(next_state);
State *right = move_right(next_state);
successors.push_back(down);
successors.push_back(right);
}
else if(next_state->x == 0 && next_state->y == 1){
/*
1 0 2 0->down
3 4 5 0->right
6 7 8 0->left
*/
State *down = move_down(next_state);
State *right = move_right(next_state);
State *left = move_left(next_state);
successors.push_back(down);
successors.push_back(right);
successors.push_back(left);
}
else if(next_state->x == 0 && next_state->y == 2){
/*
1 2 0 0->down
3 4 5 0->left
6 7 8
*/
State *down = move_down(next_state);
State *left = move_left(next_state);
successors.push_back(down);
successors.push_back(left);
}
else if(next_state->x == 1 && next_state->y == 0){
/*
1 2 3 0->down
0 4 5 0->left
6 7 8 0->up
*/
State *down = move_down(next_state);
State *left = move_left(next_state);
State *up = move_up(next_state);
successors.push_back(down);
successors.push_back(left);
successors.push_back(up);
}
else if(next_state->x == 1 && next_state->y == 1){
/*
1 2 3 0->down
4 0 5 0->left
6 7 8 0->up, 0-right
*/
State *down = move_down(next_state);
State *left = move_left(next_state);
State *right = move_right(next_state);
State *up = move_up(next_state);
successors.push_back(down);
successors.push_back(left);
successors.push_back(right);
successors.push_back(up);
}
else if(next_state->x == 1 && next_state->y == 2){
/*
1 2 3 0->down
4 5 0 0->left
6 7 8 0->up
*/
State *down = move_down(next_state);
State *left = move_left(next_state);
State *up = move_up(next_state);
successors.push_back(down);
successors.push_back(left);
successors.push_back(up);
}
else if(next_state->x == 2 && next_state->y == 0){
/*
1 2 3
4 5 6 0->right
0 7 8 0->up
*/
State *right = move_right(next_state);
State *up = move_up(next_state);
successors.push_back(right);
successors.push_back(up);
}
else if(next_state->x == 2 && next_state->y == 1){
/*
1 2 3 0->right
4 5 6 0->left
7 0 8 0->up
*/
State *left = move_left(next_state);
State *right = move_right(next_state);
State *up = move_up(next_state);
successors.push_back(left);
successors.push_back(right);
successors.push_back(up);
}
else if(next_state->x == 2 && next_state->y == 2){
/*
1 2 3 0->up
4 5 6 0->left
7 8 0
*/
State *up = move_up(next_state);
State *left = move_left(next_state);
successors.push_back(up);
successors.push_back(left);
}
for(int i=0; i<successors.size(); i++){
if(successors[i]->hCost==0){
visitedList.push_back(successors[i]);
return;
}
else if(!isPresentInVisited(successors[i]) && !isPresentInQueue(successors[i], Queue)){
Queue.push(successors[i]);
}
}
}
return;
}
priority_queue<State*, vector<State*>, Cost_Compare> getQueue(){
return this->Queue;
}
vector<State*> getVisited(){
return this->visitedList;
}
};
This is where the class is implemented. The initial state and goal state are strings that follow this format: initialState = "042158367" and goalState = "012345678"
string aStar_search(string const initialState, string const goalState, heuristicFunction heuristic){
string path; // final path
// a star start
State *state;
vector<string> pathVector;
AStarSearch search = AStarSearch(initialState, goalState, heuristic);
search.search(); // begin search
state = search.getVisited()[search.getVisited().size()-1]; // assign last state this should the goal state
// debug expected goal state of the board
cout << endl << "Goal State: "<< endl;
for(int i=0; i < 3; i++){
for(int j=0; j < 3; j++){
cout << search.getFinal()->state[i][j];
}
cout << endl;
}
// debug the initial state of the board
cout << endl << "init State: "<< endl;
for(int i=0; i < 3; i++){
for(int j=0; j < 3; j++){
cout << search.getInit()->state[i][j];
}
cout << endl;
}
// debug this should be the same as goal state
cout << endl << "Goal State from visitedList: "<< endl;
for(int i=0; i < 3; i++){
for(int j=0; j < 3; j++){
cout << state->state[i][j];
}
cout << endl;
}
while(state->parent){ // iter back through parents of states
pathVector.push_back(state->action); // push actions to vector
state = state->parent; // next parent
}
for(int i = pathVector.size()-1; i >= 0; i--){ // need to iter backwards because we follow the perant states
path += pathVector[i]; // add to path
}
// aStar exit
cout << endl << "Path To Goal: " << path << endl;
return path;
}
I believe my implementation of the search function is flawed as when I print the goal state from the visited list I get a completely different state than the expected goal state and hence this returns the incorrect path. For example when I set initialState = "042158367" and goalState = "012345678" I get the following output in the console
Goal State:
012
345
678
init State:
042
158
367
Goal State from visitedList:
514
082
367
Path To Goal: DRULDRRUR
I was wondering if someone could take a look at my code and tell me where I am going wrong in the implementation of the algorithm. Any help would be greatly appreciated.
Thanks