// Example of world building, display, and successor computation for the artificial // intelligence path-finding lab // // Author: Didier LIME // Adapted by : Jovian HERSEMEULE // Date: 2018-10-03 #include #include #include #include #include #include #include using namespace std; // Default parameters #define DEFAULT_LENGTH 50 #define DEFAULT_HEIGHT 15 #define DEFAULT_PROBABILITY 0.2 #define DEFAULT_ANIMATION true #define DEFAULT_ANIMATION_DELAY 50000 // us #define DEFAULT_COLOR_ENABLE true // Tile codes #define FREE 0 #define WALL 1 #define ORIGIN 2 #define TARGET 3 #define DISCOVERED 4 #define TRACE 5 unsigned int identifyTile(unsigned int y, unsigned int x, unsigned int l) { return y * l + x; } void moveCursorUp(const unsigned int& h) { cout << "\033[" << h << 'A'; } class World { private: // Number of columns unsigned int l; // Number of lines unsigned int h; // Size of the array const unsigned int size; // Unidimensional array for tiles int* board; // Number of empty tiles unsigned int tileQuantity; public: // Constructor World(unsigned int l_, unsigned int h_, double p) :l(l_), h(h_), size(l_ * h_), tileQuantity(l_ * h_) { board = new int[size](); // Add walls to the first and last columns for (unsigned int i = 0; i < h; i++) { board[i * l] = WALL; board[i * l + l - 1] = WALL; tileQuantity -= 2; } // Add walls to the first and last lines for (unsigned int j = 0; j < l; j++) { board[j] = WALL; board[(h - 1) * l + j] = WALL; tileQuantity -= 2; } for (unsigned int i = 0; i < h; i++) { for (unsigned int j = 0; j < l; j++) { // add a wall in this tile with probability p and provided that it is neither // the starting tile nor the goal tile if ((double) rand() / RAND_MAX < p && !(i == 1 && j == 1) && !(i == h - 2 && j == l - 2)) { board[i * l + j] = WALL; tileQuantity --; } } } } // Copy constructor World(const World& other) :l(other.l), h(other.h), size(other.size), tileQuantity(other.tileQuantity) { board = new int[size](); for (int k(0); k < size; k++) { board[k] = other.board[k]; } } // Display the world void display() { for (unsigned int i = 0; i < h; i++) { for (unsigned int j = 0; j < l; j++) { switch (board[identifyTile(i, j, l)]) { case FREE: cout << " "; break; case WALL: if (DEFAULT_COLOR_ENABLE) cout << "\033[0;43m"; cout << "#"; break; case ORIGIN: if (DEFAULT_COLOR_ENABLE) cout << "\033[1;33m"; cout << "o"; break; case TARGET: if (DEFAULT_COLOR_ENABLE) cout << "\033[1;33m"; cout << "T"; break; case DISCOVERED: if (DEFAULT_COLOR_ENABLE) cout << "\033[0;34m"; cout << ":"; break; case TRACE: if (DEFAULT_COLOR_ENABLE) { cout << "\033[0;32m"; cout << "\033[1;42m"; cout << ":"; } else cout << "+"; break; } if (DEFAULT_COLOR_ENABLE) cout << "\033[0;30m"; } cout << endl; } } // compute the successors of tile number i in world w // we return the number n of valid successors // the actual list is in array r where only the first n // elements are significant unsigned int successors(unsigned int i, unsigned int r[4]) { unsigned int n = 0; if (i >= 0 && i < size && board[i] != WALL) { // if i is a correct tile number (inside the array and not on a wall) // look in the four adjacent tiles and keep only those with no wall const unsigned int moves[] = { i - 1, i + 1, i - l, i + l}; for (unsigned int k = 0; k < 4; k++) { if (board[moves[k]] != WALL) { r[n] = moves[k]; n++; } } } return n; } // Mark a list of points in the world void markAll(const list& path, int value = TRACE) { for (auto tile : path) { markOne(tile, value); } } // Mark a point in the world void markOne(unsigned int tile, int value = ORIGIN) { board[tile] = value; } // Depth-first search // starting from tile number origin, find a path to tile number t // return true if such a path exists, false otherwise // if it exists the path is given in variable path (hence the reference &) const bool dfs(unsigned int origin, unsigned int target, list& path, list& discovered) { bool targetIsReached = false; bool explored[size]; unsigned int previous[size]; for (unsigned int k(0); k < size; k ++) { explored[k] = false; previous[k] = k; } explored[origin] = true; stack open; open.push(origin); int current; int neighbour; unsigned int succs[4]; unsigned int nbSuccs; do { current = open.top(); open.pop(); nbSuccs = successors(current, succs); for (unsigned int i(0); i < nbSuccs; i++) { neighbour = succs[i]; if (!explored[neighbour]) { open.push(neighbour); explored[neighbour] = true; previous[neighbour] = current; } } // Current tile is now processed discovered.push_back(current); // Stop if target found targetIsReached = (current == target); } while (!targetIsReached && !open.empty()); // Remove origin and target if (!discovered.empty()) { discovered.remove(origin); discovered.remove(target); } // Build path if (targetIsReached) { do { path.push_back(current); current = previous[current]; } while (current != origin); path.pop_front(); } return targetIsReached; } // Breadth-first search // starting from tile number origin, find a path to tile number t // return true if such a path exists, false otherwise // if it exists the path is given in variable path (hence the reference &) const bool bfs(unsigned int origin, unsigned int target, list& path, list& discovered) { bool targetIsReached = false; bool explored[size]; unsigned int previous[size]; for (unsigned int k(0); k < size; k ++) { explored[k] = false; previous[k] = k; } explored[origin] = true; queue open; open.push(origin); int current; int neighbour; unsigned int succs[4]; unsigned int nbSuccs; do { current = open.front(); open.pop(); nbSuccs = successors(current, succs); for (unsigned int i(0); i < nbSuccs; i++) { neighbour = succs[i]; if (!explored[neighbour]) { open.push(neighbour); explored[neighbour] = true; previous[neighbour] = current; } } // Current tile is now processed discovered.push_back(current); // Stop if target found targetIsReached = (current == target); } while (!targetIsReached && !open.empty()); // Remove origin and target if (!discovered.empty()) { discovered.remove(origin); discovered.remove(target); } // Build path if (targetIsReached) { do { path.push_back(current); current = previous[current]; } while (current != origin); path.pop_front(); } return targetIsReached; } void animate(bool exitFound, const list& discovered, const list& path) { for (unsigned int tile : discovered) { markOne(tile, DISCOVERED); display(); usleep(DEFAULT_ANIMATION_DELAY); moveCursorUp(h); } if (exitFound) for (unsigned int tile : path) { markOne(tile, TRACE); display(); usleep(DEFAULT_ANIMATION_DELAY); moveCursorUp(h); } } void showResults(bool exitFound, const list& discovered, const list& path) { markAll(discovered, DISCOVERED); if (!path.empty()) { markAll(path, TRACE); } display(); // Display DFS results if (exitFound) cout << "SUCCESS !" << endl; else cout << "FAILURE ..." << endl; const unsigned int discoveryRate(100 * discovered.size() / tileQuantity); cout << discovered.size() << " tiles discovered (" << discoveryRate << "%);" << endl; cout << path.size() << " path length." << endl; } void showProperties() { cout << "Length : " << l << endl; cout << "Height : " << h << endl; cout << "Number of floor tiles : " << tileQuantity << endl; } }; int main() { // Initialise the random number generator srand(time(0)); // Create a world const unsigned int l(DEFAULT_LENGTH), h(DEFAULT_HEIGHT); const double wallProbability(DEFAULT_PROBABILITY); World w(l, h, wallProbability); unsigned int start(identifyTile(1, 1, l)); unsigned int end(identifyTile(h - 2, l - 2, l)); // Display it cout << endl << "Generated world" << endl; w.showProperties(); w.markOne(start, ORIGIN); w.markOne(end, TARGET); w.display(); const bool animation(DEFAULT_ANIMATION); // 1 cout << endl << "Depth-First Search" << endl; World dfsWorld(w); list dfsPath; list dfsDiscovered; bool dfsExitFound = dfsWorld.dfs(start, end, dfsPath, dfsDiscovered); if (animation) dfsWorld.animate(dfsExitFound, dfsDiscovered, dfsPath); dfsWorld.showResults(dfsExitFound, dfsDiscovered, dfsPath); // 2 cout << endl << "Breadth-First Search" << endl; World bfsWorld(w); list bfsPath; list bfsDiscovered; bool bfsExitFound = bfsWorld.bfs(start, end, bfsPath, bfsDiscovered); if (animation) bfsWorld.animate(bfsExitFound, bfsDiscovered, bfsPath); bfsWorld.showResults(bfsExitFound, bfsDiscovered, bfsPath); // End return 0; }