A Star Search

profileJack Jonshen
Project1forVisualStudiousers.zip

Project 1 for Visual Studio users/.DS_Store

__MACOSX/Project 1 for Visual Studio users/._.DS_Store

Project 1 for Visual Studio users/main.cpp

Project 1 for Visual Studio users/main.cpp


/**********************************************************************************************************************/
/* This project is for students to practice search algorithms by appling them to solve the 9 puzzle problems.         */
/*                                                                                                                    */
/* Usage: The whole image is divided into 3-by-3 squares. There are 8 sub-images and 1 empty space.                   */
/*        These sub-images are placed in a random order, which makes the whole pattern look messy.                    */
/*        So the goal is to re-organize these sub-images to align them in the right order.                            */
/*        User can only move one sub-image at each time by left clicking it follwed by clicking the empty space.      */
/*        Each time, only neighboring sub-images around the empty space can be moved. At any time, even in the        */
/*        begining, if you do not want to continue playing the game, you can simply hit the keyboard - "s" key to     */
/*        obtain the solution. The system then automatically search for a possible path by using your implemented     */
/*        search algorithmns. So after you click the "s" key, what you should do is just to wait for the search       */
/*        result and see the sub-images move automatically followed by the output path until all the sub-images       */
/*        become perfectly aligned.                                                                                   */
/*                                                                                                                    */
/* Author: Ju Shen, Department of Computer Science, University of Dayton                                              */
/*                                                                                                                    */
/* Instructions:                                                                                                      */
/*              For this project, you only focus on implementing the search algorithms. They are the functions        */
/*              in the "SolutionSearch" class, which are marked as "Student Implementation:" in the comments.         */
/*              You can search the key words "Student Implementation" to find the places require you to write         */
/*              your code.                                                                                            */
/*                                                                                                                    */
/*              The GUI has already been implemted, so there is no need to do anything on it for this project.        */
/*              However, students are encouraged to get farmiliar with them, as you may need to implement them in     */
/*              the future projects.                                                                                  */
/*                                                                                                                    */
/**********************************************************************************************************************/


#include   < vector >         // std::vector
#include   < iostream >  
#include   < queue >
#include   < map >
#include   < opencv2 / core / core . hpp >
#include   < opencv2 / highgui / highgui . hpp >
#include   < opencv2 / imgproc / imgproc . hpp >
#include   < opencv / cv . h >
#include   "stdio.h"
#include   "math.h"

using   namespace  cv ;
using   namespace  std ;

#include   "RandomGenerator.h"
#include   "VisualDisplay.h"


void  printData ( int *  data )
{
    printf ( "%d, %d, %d\n %d, %d, %d\n %d, %d, %d\n" ,  data [ 0 ],  data [ 1 ],  data [ 2 ],
        data [ 3 ],  data [ 4 ],  data [ 5 ],  data [ 6 ],  data [ 7 ],  data [ 8 ]);
}




int  main ( int  arg ,   char **  argv )
{
     int  num_seq [ 9 ]   =   {   0 ,   1 ,   2 ,   3 ,   4 ,   5 ,   6 ,   7 ,   8   };   //These numbers are used to represent those sub-images (or pieces), 
                                                     // by default, the number 8 represents the empty space. 
                                                     // So when these numbers are in different order, it means the correseponding sum-images      
                                                     // are in different places. Initially, we make all the sub-images in the correct order.
                                                     // But they will be randomly reshuffled in the following code.
                                                     /*  For example: {0, 1, 2, 3, 4, 5, 6, 7, 8} in the GUI, its layout is:
                                                    0   1   2
                                                    3   4   5
                                                    6   7   8
                                                    which is the one we want.
                                                    */

     RandomGenerator  rg ;   //Used to generate different random orders
    rg . getRandomSeq ( num_seq ,   52 );   //The generated random order is stored in the "num_seq" array. The second parameter is an
                                   // integer which indicates how much degree you want to mess up the sub-images. The bigger
                                   // it is, the more messy it could be.
                                   /* For example, the random sequence after reshuffle can be: num_seq = {3, 8, 5, 2, 0, 4, 6, 7, 1}
                                  3   8   5
                                  2   0   4
                                  6   7   1
                                  */

     VisualDisplay  display ;   //The class for the GUI, which you do not need to do any change


     int  target_pos ;   //This stores the posit

                     /*Find the position of the empty space*/
     for   ( int  i  =   0 ;  i  <   9 ;  i ++ )
     {
         if   ( num_seq [ i ]   ==   8 )
            target_pos  =  i ;
     }


     /*For testing purpose, you can see the output of the generated random order in the console*/
    printf ( "The Current Pattern is :\n" );
    printData ( num_seq );



     /*******************************************   The two lines below are to show and run the game **************************************************/
     /*          Before you implement the code, if you run the program, and hit the "s" key, there is nother happened. Because you the "s"            */
     /*          is to trigger the search algorithm to find the possible solution. Since you have not done anything on it yet, it returns             */
     /*          an empty path, which you can see from the console.                                                                                   */
     /*                                                                                                                                               */
     /*          When you are testing your code or doing debug, you can simply comment out these two lines of codes. So you can focus on the          */
     /*          data and may output them to the console or use the debug tool to analyze them. After you are confident of your search code,          */
     /*          you can not uncomment these lines and enjoy the game.                                                                                */
     /*                                                                                                                                               */
    display . load ( const_cast < char *> ( "test.jpg" ),  num_seq ,  target_pos );   //The first parameter is the image file name. For fun, you can place any image you like, 
                                                    //no need to be square image, as long as it is close to square. 
                                                    //The second parameter is the generated random sequence.
                                                    //The third parameter is the detected empty space position in the array
    display . run ();

     return   1 ;
}

__MACOSX/Project 1 for Visual Studio users/._main.cpp

Project 1 for Visual Studio users/RandomGenerator.cpp

#include "RandomGenerator.h" #include <ctime> // std::time #include <algorithm> #include <iostream> RandomGenerator::RandomGenerator(void) { /***************** Pre-store the available moving direction or child of current tree ********/ /* 0 1 2 */ /* 3 4 5 */ /* 6 7 8 */ /***************** The order is left, bottom, right, top ***********************************/ moving_directions[0].push_back(3); //for each position, tell the piece-8 which one in the array that it can be swapped with. // So only two pieces are considered each time, when the piece-8 is moving. moving_directions[0].push_back(1); moving_directions[1].push_back(0); moving_directions[1].push_back(4); moving_directions[1].push_back(2); moving_directions[2].push_back(1); moving_directions[2].push_back(5); moving_directions[3].push_back(6); moving_directions[3].push_back(4); moving_directions[3].push_back(0); moving_directions[4].push_back(3); moving_directions[4].push_back(7); moving_directions[4].push_back(5); moving_directions[4].push_back(1); moving_directions[5].push_back(4); moving_directions[5].push_back(8); moving_directions[5].push_back(2); moving_directions[6].push_back(7); moving_directions[6].push_back(3); moving_directions[7].push_back(6); moving_directions[7].push_back(8); moving_directions[7].push_back(4); moving_directions[8].push_back(7); moving_directions[8].push_back(5); } RandomGenerator::~RandomGenerator(void) { } /*Generate the random sequence based on moving from goal status*/ void RandomGenerator::getRandomSeq(int* num, int steps) { int cur_pos = 8; //The position of the 8-piece int swap_pos; //The position that is used to be swapped with the 8-piece int temp; //Used for data swap int choices; //The number of choiece for different positions of 8-piece int x; //The choice randomly made from different choieces srand(time(NULL)); for(int i = 0; i < steps; i++) { int choices = moving_directions[cur_pos].size(); x = rand() % choices; swap_pos = moving_directions[cur_pos][x]; temp = num[cur_pos]; num[cur_pos] = num[swap_pos]; num[swap_pos] = temp; cur_pos = swap_pos; } } /*Purely generate random number, not gurantee it has a solution*/ void RandomGenerator::getRandomSeq2(int* num) { std::srand ( unsigned ( std::time(0) ) ); std::vector<int> myvector; for (int i = 0; i < 9; i++) myvector.push_back(i); // 0 1 2 3 4 5 6 7 8 // using built-in random generator: std::random_shuffle ( myvector.begin(), myvector.end() ); int idx = 0; for (std::vector<int>::iterator it=myvector.begin(); it!=myvector.end(); ++it) { num[idx] = *it; idx++; } };

__MACOSX/Project 1 for Visual Studio users/._RandomGenerator.cpp

Project 1 for Visual Studio users/RandomGenerator.h

#pragma once #include <vector> class RandomGenerator { private: std::vector<int> moving_directions[9]; public: RandomGenerator(void); ~RandomGenerator(void); void getRandomSeq2(int *num); /*Purely generate random number, not gurantee it has a solution*/ void getRandomSeq(int *num, int steps); /*Generate the random sequence based on moving from goal status*/ };

__MACOSX/Project 1 for Visual Studio users/._RandomGenerator.h

Project 1 for Visual Studio users/SolutionSearch.cpp

#include "SolutionSearch.h" SolutionSearch::SolutionSearch(void) { } SolutionSearch::~SolutionSearch(void) { } /* Student Implementation: here you need to implement the Breadth First Search Method */ /* */ /* Input: the first parameter is the random order of the 9 numbers, which you need to */ /* re-organize to make them into the correct order. */ /* the second parameter is actually an output. It returns or stores the moving */ /* path of the "empty space" that it is involved to make all the sub-images in */ /* the correct position. The integer seqence varible "solution" should store */ /* all the steps along the path */ /* */ /* For example: */ /* Input: data = {8, 4, 1, 3, 0, 2, 6, 7, 5 }; */ /* to make it into the correct order {0, 1, 2, 3, 4, 5, 6, 7, 8} */ /* you need to make the following changes on the number 8, since the */ /* number 8 represents the empty space, moving 8 to its neigboring */ /* numbers equals to moving the corresponding number to the empty space. */ /* Below it shows a demo of the steps: */ /* */ /* 8 4 1 swap with 4 8 0 1 swap with 1 8 1 0 swap with 2 8 1 2 swap with 5 0 1 2 */ /* 3 0 2 ------------> 3 4 2 -------------> 3 4 2 --------------> 3 4 8 --------------> 3 4 5 ---> End */ /* 6 7 5 6 7 5 6 7 5 6 7 5 6 7 8 */ /* */ /* So from this example, the right path should be {1, 2, 5, 8}. */ /* WHY? You may thought it was {4, 1, 2, 5}, since the number 8 has swapped with them in this order. */ /* That is true. However, we do not care which number it swapped with, but which position the number 8 */ /* has gone through. As the numbers can be in any positions during different time, which give no hint */ /* about where the number 8 is. In contrast, the positions are fixed. So we assume the positions are */ /* in the same order as an increasing sequence: */ /* */ /* [0] [1] [2] */ /* Fixed Position = [3] [4] [5] */ /* [6] [7] [8] */ /* */ /* Here, I use "[]" to distinguish the positions from the numbers. So now you can see, the number 8 starts */ /* from position [4], then swapped with number 4, which goes to the position [1]; then swapped with number */ /* 1, which goes to the position [2]; then swapped with number 2, which goes to the position [5]; finally, */ /* swapped with number 5, which goes to the position [8]. So the path you should assign to the parameter */ /* "&solution" with the path sequence {1, 2, 5, 8}. */ bool SolutionSearch::AStarSearch(int *data, vector<int> &solution) { return 1; }

__MACOSX/Project 1 for Visual Studio users/._SolutionSearch.cpp

Project 1 for Visual Studio users/SolutionSearch.h

/* To make the code compatible, different search algorithms have the same input and output parameters */ #pragma once #include <vector> // std::vector using namespace std; class SolutionSearch { public: SolutionSearch(void); ~SolutionSearch(void); bool AStarSearch(int *data, vector<int> &solution); //Student Implementation: you need to implement this function };

__MACOSX/Project 1 for Visual Studio users/._SolutionSearch.h

Project 1 for Visual Studio users/test.jpg

__MACOSX/Project 1 for Visual Studio users/._test.jpg

Project 1 for Visual Studio users/VisualDisplay.cpp

#include "VisualDisplay.h" #define STANDARD_SIZE 600 #define CONTAINER_SIZE 640 #define GAP 10 //gap between each piece #define PIECE_SIZE 200 #define MOVING_SPEED 20 void onMouse( int event, int x, int y, int flags, void* param ) { VisualDisplay* vd = (VisualDisplay*) param; int cur_selection = -1; switch( event ) { case CV_EVENT_LBUTTONDOWN: //find selected position for(int i = 0; i < 9; i++) { if(vd->piece_positions[i].top_left.x <= x && x <= vd->piece_positions[i].bottom_right.x && vd->piece_positions[i].top_left.y <= y && y <= vd->piece_positions[i].bottom_right.y) { if(i == vd->target_pos) { //Make sure there has already been a candiate selected before swaping the pieces; otherwise no actions are taken if(!(vd->selected_piece == -1)) { printf("Make a swap with %d!\n", vd->selected_piece); vd->swap(vd->selected_piece); vd->selected_piece = -1; } } else //if current selection is not the target pos, check whether current position is in the neighborhood of the target position { for(int nb_idx = 0; nb_idx < (int) vd->moving_directions[vd->target_pos].size(); nb_idx++) { if(i == vd->moving_directions[vd->target_pos][nb_idx]) { cur_selection = i; break; } } } } } vd->selected_piece = cur_selection; break; case CV_EVENT_RBUTTONDOWN: vd->selected_piece = -1; break; case CV_EVENT_LBUTTONUP: break; case CV_EVENT_RBUTTONUP: break; } } VisualDisplay::VisualDisplay(void) { cv::namedWindow("window", 1); cv::setMouseCallback("window", onMouse, this); //specify the posi }on of each piece on the container for(int i = 0; i < 9; i++) { int row_idx = i / 3; int col_idx = i % 3; piece_positions[i].top_left.x = col_idx * GAP + 10 + PIECE_SIZE * col_idx; piece_positions[i].top_left.y = row_idx * GAP + 10 + PIECE_SIZE * row_idx; piece_positions[i].bottom_right.x = piece_positions[i].top_left.x + PIECE_SIZE; piece_positions[i].bottom_right.y = piece_positions[i].top_left.y + PIECE_SIZE; } /***************** The order is left, bottom, right, top ***********************************/ moving_directions[0].push_back(3); //for each position, tell the empty space which one in the array that it can be swapped with. // So only two pieces are considered each time, when the empty space is moving. moving_directions[0].push_back(1); moving_directions[1].push_back(0); moving_directions[1].push_back(4); moving_directions[1].push_back(2); moving_directions[2].push_back(1); moving_directions[2].push_back(5); moving_directions[3].push_back(6); moving_directions[3].push_back(4); moving_directions[3].push_back(0); moving_directions[4].push_back(3); moving_directions[4].push_back(7); moving_directions[4].push_back(5); moving_directions[4].push_back(1); moving_directions[5].push_back(4); moving_directions[5].push_back(8); moving_directions[5].push_back(2); moving_directions[6].push_back(7); moving_directions[6].push_back(3); moving_directions[7].push_back(6); moving_directions[7].push_back(8); moving_directions[7].push_back(4); moving_directions[8].push_back(7); moving_directions[8].push_back(5); //Initialize Mouse data selected_piece = -1; } VisualDisplay::~VisualDisplay(void) { } /* Draw the container by putting all the 8 sub-images based on their orders*/ void VisualDisplay::paintContainer() { container = cv::Mat(CONTAINER_SIZE, CONTAINER_SIZE, CV_8UC3, cv::Scalar(255, 255, 255));//make the container white color //put all the pieces into container for(int i = 0; i < 9; i++) { int cur_idx = current_states[i]; if(cur_idx != 8) //piece-8 does not paint { if(selected_piece == i) { cv::Mat high_light = cv::Mat(PIECE_SIZE, PIECE_SIZE, CV_8UC3, cv::Scalar(100, 100, 100)); cv::Mat temp = high_light + piece[cur_idx]; temp.copyTo(container(cv::Range(piece_positions[i].top_left.y, piece_positions[i].bottom_right.y), cv::Range(piece_positions[i].top_left.x, piece_positions[i].bottom_right.x))); } else piece[cur_idx].copyTo(container(cv::Range(piece_positions[i].top_left.y, piece_positions[i].bottom_right.y), cv::Range(piece_positions[i].top_left.x, piece_positions[i].bottom_right.x))); } } } /*Make a swap between the selected candiate piece and the target piece*/ void VisualDisplay::swap(int candidate) { //do the motion generateVisualMotion(candidate, target_pos); //change the current state int temp = current_states[candidate]; current_states[candidate] = current_states[target_pos]; current_states[target_pos] = temp; //change the target_pos target_pos = candidate; } /*Generate the motion from the selected piece to the target position*/ void VisualDisplay::generateVisualMotion(int candidate, int target) { cv::Point top_left; //store the transition position of the moving piece cv::Point bottom_right; top_left.y = piece_positions[candidate].top_left.y; top_left.x = piece_positions[candidate].top_left.x; bottom_right.y = piece_positions[candidate].bottom_right.y; bottom_right.x = piece_positions[candidate].bottom_right.x; cv::Mat white_piece = cv::Mat(PIECE_SIZE, PIECE_SIZE, CV_8UC3, cv::Scalar(255, 255, 255)); //used to clear last position of the moving piece int cur_idx = current_states[candidate]; int num_steps = PIECE_SIZE / MOVING_SPEED; //check the direction first if(candidate < target) { //moving right if((target - candidate) == 1) { printf("moving right\n"); //Gradually copy the candidate piece to the transiation position for(int step_idx = 1; step_idx <= num_steps; step_idx++) { top_left.x = piece_positions[candidate].top_left.x + MOVING_SPEED * step_idx; bottom_right.x = piece_positions[candidate].bottom_right.x + MOVING_SPEED * step_idx; //clear last position white_piece.copyTo(container(cv::Range(piece_positions[candidate].top_left.y, piece_positions[candidate].bottom_right.y), cv::Range(piece_positions[candidate].top_left.x, piece_positions[candidate].bottom_right.x))); piece[cur_idx].copyTo(container(cv::Range(top_left.y, bottom_right.y), cv::Range(top_left.x, bottom_right.x))); int delay = 300/num_steps; cv::waitKey(delay); imshow("window", container); } } else //moving down { printf("moving down\n"); //Gradually copy the candidate piece to the transiation position for(int step_idx = 1; step_idx <= num_steps; step_idx++) { top_left.y = piece_positions[candidate].top_left.y + MOVING_SPEED * step_idx; bottom_right.y = piece_positions[candidate].bottom_right.y + MOVING_SPEED * step_idx; //clear last position white_piece.copyTo(container(cv::Range(piece_positions[candidate].top_left.y, piece_positions[candidate].bottom_right.y), cv::Range(piece_positions[candidate].top_left.x, piece_positions[candidate].bottom_right.x))); piece[cur_idx].copyTo(container(cv::Range(top_left.y, bottom_right.y), cv::Range(top_left.x, bottom_right.x))); int delay = 300/num_steps; cv::waitKey(delay); imshow("window", container); } } } else { if((candidate - target) == 1) //moving left { printf("moving left\n"); //Gradually copy the candidate piece to the transiation position for(int step_idx = 1; step_idx <= num_steps; step_idx++) { top_left.x = piece_positions[candidate].top_left.x - MOVING_SPEED * step_idx; bottom_right.x = piece_positions[candidate].bottom_right.x - MOVING_SPEED * step_idx; //clear last position white_piece.copyTo(container(cv::Range(piece_positions[candidate].top_left.y, piece_positions[candidate].bottom_right.y), cv::Range(piece_positions[candidate].top_left.x, piece_positions[candidate].bottom_right.x))); piece[cur_idx].copyTo(container(cv::Range(top_left.y, bottom_right.y), cv::Range(top_left.x, bottom_right.x))); int delay = 300/num_steps; cv::waitKey(delay); imshow("window", container); } } else //moving top { printf("moving top\n"); //Gradually copy the candidate piece to the transiation position for(int step_idx = 1; step_idx <= num_steps; step_idx++) { top_left.y = piece_positions[candidate].top_left.y - MOVING_SPEED * step_idx; bottom_right.y = piece_positions[candidate].bottom_right.y - MOVING_SPEED * step_idx; //clear last position white_piece.copyTo(container(cv::Range(piece_positions[candidate].top_left.y, piece_positions[candidate].bottom_right.y), cv::Range(piece_positions[candidate].top_left.x, piece_positions[candidate].bottom_right.x))); piece[cur_idx].copyTo(container(cv::Range(top_left.y, bottom_right.y), cv::Range(top_left.x, bottom_right.x))); int delay = 300/num_steps; cv::waitKey(delay); imshow("window", container); } } } } /*Load necessary information into the class: image name, original sequence, and the position of empty space*/ void VisualDisplay::load(char* file_name, int* data, int pos) { cv::Mat temp = cv::imread(file_name); int height = temp.rows; int width = temp.cols; cv::Mat sub_img; if(width > height) { int start_col = (width - height)/2; int end_col = start_col + height; sub_img = temp(cv::Range(0, height), cv::Range(start_col, end_col)); } else { int start_row = (height - width)/2; int end_row = start_row + width; sub_img = temp(cv::Range(start_row, end_row), cv::Range(0, width)); } cv::resize(sub_img, image, cv::Size(STANDARD_SIZE, STANDARD_SIZE)); //Assign different parts of the image into different pieces for(int i = 0; i < 9; i++) { int col_idx = i % 3; int row_idx = i / 3; image(cv::Range(row_idx * PIECE_SIZE, (row_idx + 1) * PIECE_SIZE), cv::Range(col_idx * PIECE_SIZE, (col_idx + 1) * PIECE_SIZE)).copyTo(piece[i]); } original_states = data; //Load the data sequence current_states = data; target_pos = pos; //store the current target position } /*Run the game: showing the container of multiple sub-images and enabling user to re-organize the sub-images */ void VisualDisplay::run() { while(1) { paintContainer(); //draw the whole pattern by putting all the sub-images into the container imshow("window", container); char c = cv::waitKey(1); if(c == 27) break; else if(c == 's' || c == 'S') //At any time, when the user press the "s" or "S" key, it automatically finds the solution { showSolution(); //Use your implemented search algorithm to find the solution and show it } } } /*Student Implementation: actually you do not need to implement anything here. Just for testing purpose, you may test different search algorithms here. But be aware, only one algorithm is called everytime. */ void VisualDisplay::showSolution() { path.clear(); /*Only one of the following search function is called each time, so comment the others*/ solution_finder.AStarSearch(current_states, path); //use A-star to find the solution showPath(); //show the path on the GUI } /* Show the solution on the GUI by moving those sub-images step by step until all the sub-image get to the right order */ void VisualDisplay::showPath() { //Printout the solution on the console printf("The solution path is: \n"); for(int i = 0; i < (int)path.size(); i++) { printf("%d, ", path[i]); } printf("\n"); //Show the path visually selected_piece = -1; for(int i = 0; i < (int)path.size(); i++) { swap(path[i]); paintContainer(); imshow("window", container); cv::waitKey(800); } }

__MACOSX/Project 1 for Visual Studio users/._VisualDisplay.cpp

Project 1 for Visual Studio users/VisualDisplay.h

/* This class is used to visually show the puzzle game. All the code have already been provided. So you do not need */ /* to do any moidification here. The only thing you may touch is in the "showSolution()" function. As you may test */ /* different search functions by comment or uncomment a couple of lines there. */ #pragma once #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv/cv.h> using namespace cv; #include "SolutionSearch.h" struct Square{ //this structure define a square region on the container (image) by using two points cv::Point top_left; cv::Point bottom_right; }; class VisualDisplay { private: int *original_states; int *current_states; vector<int> path; //the path for solution int target_pos; //Store the position of the 8-piece cv::Mat image; cv::Mat piece[9]; cv::Mat container; //the image that actually show the pieces and board Square piece_positions[9]; //define the position on the container that each piece will lay out. //here it has the order of 0, 1, 2, ..., 8. So initially, it is assign the position value in the constructor SolutionSearch solution_finder; //the most important component that is used to find the possible solutions //Below are used for mouse actions int selected_piece; //whether there is any piece has been selected -1 means no selection, 0-7 means a particular piece is selected std::vector<int> moving_directions[9]; public: VisualDisplay(void); ~VisualDisplay(void); void paintContainer(); //draw all the sub-images onto a single image void load(char* file_name, int* data, int pos); //load the image file to the image object void run(); //the main function of the class that shows different pieces of the image and support user's interaction friend void onMouse( int event, int x, int y, int flags, void* param ); void swap(int candidate); void generateVisualMotion(int candidate, int target); void showSolution(); void showPath(); };

__MACOSX/Project 1 for Visual Studio users/._VisualDisplay.h

__MACOSX/._Project 1 for Visual Studio users