/** Assignment 6 - Maze Runner, a program to parse a .maze text file and display it using SFML, and then solve it using either a breadth- or depth- first search algorithm. I also implemented both extra-credit pieces, displaying the final path and also showing added but not-yet searched nodes. @author Tyler Beckman (tyler_beckman@myriation.xyz) @date 12/4/24 */ #include #include #include #include #include #include using namespace sf; #include #include using namespace std; enum class State { Unexplored, Added, Explored, UsedInPath }; struct Node { char character; State state; Node* prevNode; }; int main(int argc, char** argv) { string filename; if (argc >= 2) { filename = argv[1]; } else { cout << "Please enter a maze filename to load: "; cin >> filename; } cout << "Loading maze file " << filename << endl; ifstream mazeFile(filename); if (mazeFile.fail()) { std::cout << "Failed to open specified file path " << filename << ", does it exist?" << std::endl; return 1; } int rows, cols; mazeFile >> rows >> cols; mazeFile.get(); // move cursor past newline to actual grid characters Node** grid = new Node*[rows]; pair startNode(-1, -1); for (int row = 0; row < rows; row++) { grid[row] = new Node[cols]; for (int col = 0; col < cols; col++) { grid[row][col].character = mazeFile.get(); grid[row][col].state = State::Unexplored; grid[row][col].prevNode = nullptr; // Save start as starting node and mark as visited if (grid[row][col].character == 'S') { startNode = make_pair(row, col); grid[row][col].state = State::Explored; }; } mazeFile.get(); // advance past newline } char searchType = '\0'; cout << "Would you like to use a [b]readth-first search or a [d]epth-first search? "; cin >> searchType; if (searchType != 'b' && searchType != 'B' && searchType != 'd' && searchType != 'D') { cout << "Search type '" << searchType << "' is unrecognized. Please enter a valid search type." << endl; return 1; } variant>, stack>> nodeList; // Create stack or queue with start node if (searchType == 'b' || searchType == 'B') { nodeList = queue>({startNode}); } else { nodeList = stack>({startNode}); } bool searching = true; // create a window RenderWindow window( VideoMode(15*cols, 15*rows), "Maze Runner" ); window.setVerticalSyncEnabled(true); // create an event object once to store future events Event event; // while the window is open while( window.isOpen() ) { // clear any existing contents window.clear(); ///////////////////////////////////// // BEGIN DRAWING HERE // Draw unsolved maze from grid by iterating over each column in each row for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { sf::RectangleShape rectangle; rectangle.setSize(Vector2f(15, 15)); rectangle.setPosition(col * 15, row * 15); switch (grid[row][col].character) { case 'S': rectangle.setFillColor(Color::Green); break; case 'E': rectangle.setFillColor(Color::Red); break; case '#': rectangle.setFillColor(Color::Black); break; case '.': rectangle.setFillColor(Color::White); break; } if (grid[row][col].character != 'S' && grid[row][col].character != 'E') { switch (grid[row][col].state) { case State::Added: rectangle.setFillColor(Color::Blue); break; case State::Explored: rectangle.setFillColor(Color::Magenta); break; case State::UsedInPath: rectangle.setFillColor(Color::Yellow); break; default: break; } } window.draw(rectangle); } } // Solve maze if (searching) { // Take a node off the stack or queue pair dequeuedNode; if (searchType == 'B' || searchType == 'b') { queue>& nodeQueue = get>>(nodeList); if (nodeQueue.size() == 0) { // Handle unsolvable mazes searching = false; cout << "Maze is unsolvable :c" << endl; continue; } dequeuedNode = nodeQueue.front(); nodeQueue.pop(); } else { stack>& nodeStack = get>>(nodeList); if (nodeStack.size() == 0) { // Handle unsolvable mazes searching = false; cout << "Maze is unsolvable :c" << endl; continue; } dequeuedNode = nodeStack.top(); nodeStack.pop(); } grid[dequeuedNode.first][dequeuedNode.second].state = State::Explored; // If end, print end and empty structure, then calculate true path if (grid[dequeuedNode.first][dequeuedNode.second].character == 'E') { cout << "End reached!" << endl; searching = false; // Empty stack/queue if (searchType == 'B' || searchType == 'b') { queue>& nodeQueue = get>>(nodeList); for (size_t i = 0; i < nodeQueue.size(); i++) { nodeQueue.pop(); } } else { stack>& nodeStack = get>>(nodeList); for (size_t i = 0; i < nodeStack.size(); i++) { nodeStack.pop(); } } // Mark all path-nodes as State::UsedInPath for (Node* node = &grid[dequeuedNode.first][dequeuedNode.second]; node != nullptr; node = node->prevNode) { node->state = State::UsedInPath; } } else { // Take dequeued node and process it pair nodesToAdd[4]; int nodesAdded = 0; // Add all adjacent nodes in South->East->North->West order, if not added already // South if ( dequeuedNode.first != (rows - 1) && grid[dequeuedNode.first + 1][dequeuedNode.second].state == State::Unexplored && grid[dequeuedNode.first + 1][dequeuedNode.second].character != '#' ) { nodesToAdd[nodesAdded] = make_pair(dequeuedNode.first + 1, dequeuedNode.second); nodesAdded++; } // East if ( dequeuedNode.second != (cols - 1) && grid[dequeuedNode.first][dequeuedNode.second + 1].state == State::Unexplored && grid[dequeuedNode.first][dequeuedNode.second + 1].character != '#' ) { nodesToAdd[nodesAdded] = make_pair(dequeuedNode.first, dequeuedNode.second + 1); nodesAdded++; } // North if ( dequeuedNode.first != 0 && grid[dequeuedNode.first - 1][dequeuedNode.second].state == State::Unexplored && grid[dequeuedNode.first - 1][dequeuedNode.second].character != '#' ) { nodesToAdd[nodesAdded] = make_pair(dequeuedNode.first - 1, dequeuedNode.second); nodesAdded++; } // West if ( dequeuedNode.second != 0 &&grid[dequeuedNode.first][dequeuedNode.second - 1].state == State::Unexplored && grid[dequeuedNode.first][dequeuedNode.second - 1].character != '#' ) { nodesToAdd[nodesAdded] = make_pair(dequeuedNode.first, dequeuedNode.second - 1); nodesAdded++; } // Mark all added notes as State::Added, and link to current node to later display path for (int i = 0; i < nodesAdded; i++) { grid[nodesToAdd[i].first][nodesToAdd[i].second].state = State::Added; grid[nodesToAdd[i].first][nodesToAdd[i].second].prevNode = &grid[dequeuedNode.first][dequeuedNode.second]; if (searchType == 'B' || searchType == 'b') { queue>& nodeQueue = get>>(nodeList); nodeQueue.push(nodesToAdd[i]); } else { stack>& nodeStack = get>>(nodeList); nodeStack.push(nodesToAdd[i]); } } // Sleep to avoid solving instantly sleep(milliseconds(50)); } } // END DRAWING HERE ///////////////////////////////////// // display the current contents of the window window.display(); ///////////////////////////////////// // BEGIN EVENT HANDLING HERE // check if any events happened since the last time checked while( window.pollEvent(event) ) { // if event type corresponds to pressing window X if (event.type == Event::Closed) { // tell the window to close window.close(); } // If event type corresponds to pressing Q or Esc if (event.type == Event::KeyPressed) { switch (event.key.code) { case Keyboard::Key::Q: case Keyboard::Key::Escape: // Tell the window to close window.close(); break; default: break; } } // check addition event types to handle additional events } // END EVENT HANDLING HERE ///////////////////////////////////// } return 0; }