diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..621fb8d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: [push, pull_request] + +defaults: + run: + shell: bash + +jobs: + build: + name: ${{ matrix.platform.name }} ${{ matrix.config.name }} + runs-on: ${{ matrix.platform.os }} + + strategy: + fail-fast: false + matrix: + platform: + - { name: Windows VS2019, os: windows-2019 } + - { name: Windows VS2022, os: windows-2022 } + - { name: Linux GCC, os: ubuntu-latest } + - { name: Linux Clang, os: ubuntu-latest, flags: -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ } + - { name: macOS, os: macos-latest } + config: + - { name: Shared, flags: -DBUILD_SHARED_LIBS=TRUE } + - { name: Static, flags: -DBUILD_SHARED_LIBS=FALSE } + + steps: + - name: Install Linux Dependencies + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install libxrandr-dev libxcursor-dev libudev-dev libopenal-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev + + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure + run: cmake -B build ${{matrix.platform.flags}} ${{matrix.config.flags}} + + - name: Build + run: cmake --build build --config Release diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1cd063e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.28) +project(Project3) + +set(CMAKE_CXX_STANDARD 17) +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) + +include_directories(src) + + +include(FetchContent) +FetchContent_Declare(SFML + GIT_REPOSITORY https://github.com/SFML/SFML.git + GIT_TAG 2.6.x + GIT_SHALLOW ON + EXCLUDE_FROM_ALL + SYSTEM) +FetchContent_MakeAvailable(SFML) + +add_executable(Project3 + src/maze.cpp + src/maze.h + main.cpp + src/dfs.cpp + src/dfs.h + src/bfs.cpp + src/bfs.h) +target_link_libraries(Project3 PRIVATE sfml-graphics) +target_compile_features(Project3 PRIVATE cxx_std_17) +include_directories(${SFML_INCLUDE_DIRS}) + +if(WIN32) + add_custom_command( + TARGET Project3 + COMMENT "Copy OpenAL DLL" + PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${SFML_SOURCE_DIR}/extlibs/bin/$,x64,x86>/openal32.dll $ + VERBATIM) +endif() \ No newline at end of file diff --git a/main.cpp b/main.cpp index 39889a3..4bd96f7 100644 --- a/main.cpp +++ b/main.cpp @@ -1,142 +1,81 @@ +#include "bfs.h" +#include "Maze.h" +using namespace std; + +// maze size, will be changed later but is 20x20 for ease of use +const int width = 50; +const int height = 50; + #include #include #include #include #include #include +#include using namespace std; -// maze size, will be changed later but is 20x20 for ease of use -const int width = 50; -const int height = 50; - // directions to move in vector> directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; -// random num gen credit: https://stackoverflow.com/questions/7560114/random-number-c-in-some-range -// -random_device rd; -mt19937 gen(rd()); -uniform_int_distribution<> distr(0, 3); - -// VISITABLE CELL must check 3 co -bool visitableCell(int currX, int currY, const vector>& maze) { - //make sure the DFS doesn't run into a wall - if (currX <= 0 || currX >= width - 1 || currY <= 0 || currY >= height - 1) { - return false; - } - int numWalls = 0; - //make sure the cell is surrounded by walls - for (int i = 0; i < 4; i++) { - int newX = currX + directions[i].first; - int newY = currY + directions[i].second; - - if (maze[newX][newY] == 0 && newX >= 0 && newX < width && newY >= 0 && newY < height) { - numWalls++; - } - } - if (numWalls >= 3) { - return true; - } - else { - return false; - } - -} - -string generateMaze() { - - //TODO: loop to continue while stack is not empty, - // pop the top of the stack and check if it is a dead end, if it is, - // remove the wall and add the adjacent cell to the stack, if it is not, - //add the adjacent cell to the stack +// // random num gen credit: https://stackoverflow.com/questions/7560114/random-number-c-in-some-range - // 0 is a wall, 1 is a path - vector> maze(width, vector(height, 0)); - // stringstream to print the maze - stringstream ss; - // initialize the maze with walls - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - maze[x][y] = 0; - } - } - - // start and end points - int middleY = height / 2; - maze[0][middleY] = 1; - maze[width - 1][middleY] = 1; - - // stack to keep track of current position - stack> currPos; - pair startCell = {0, middleY}; - currPos.push(startCell); - - // Choose a random direction. Call visitableCell() on the cell in that direction. - // If visitableCell returns true, then we add it to the stack, remove the wall and the while loop repeats. - // It becomes our new currPosStack. - // If it doesn't, we choose a different random direction & call visitableCell on it. - // We do this until - // - while (!currPos.empty()) { - int currX = currPos.top().first; - int currY = currPos.top().second; - - currPos.pop(); - bool directions_guessed[4] = {false, false, false, false}; - bool allTried = false; - while (!allTried) { - int dirIndex = distr(gen); - directions_guessed[dirIndex] = true; - - int newX = currX + directions[dirIndex].first; - int newY = currY + directions[dirIndex].second; - - if (visitableCell(newX, newY, maze)) { - maze[newX][newY] = 1; - - currPos.push({newX, newY}); - break; - } - - allTried = true; - for (int i = 0; i < 4; i++) { - if (!directions_guessed[i]) { - allTried = false; - break; - } +int main() { + //create a window with text about what this is with + // string maze = generateMaze(); + // cout << maze; + Maze m = Maze(width, height); + m.generate_maze(); + m.remove_wall(m.height-1,m.width-1,2); + auto window = sf::RenderWindow({(int)WINDOW_WIDTH, (int)WINDOW_HEIGHT}, "CMake SFML Project"); + m.show(window); + + window.setFramerateLimit(144); + int frame = 0; + dfs d = dfs(&m); + bfs b = bfs(&m); + + sf::Text text; + sf::Font font; + font.loadFromFile("src/NotoSans-VariableFont_wdth,wght.ttf"); + text.setFont(font); + text.setCharacterSize(40); + text.setFillColor(sf::Color::Black); + + while (window.isOpen()) { + for (auto event = sf::Event(); window.pollEvent(event);) + { + if (event.type == sf::Event::Closed) + { + window.close(); + } + + if(event.type == sf::Event::MouseButtonPressed) { + // m.reset(); + // m.generate_maze(); + // m.remove_wall(m.height-1,m.width-1,2); + //std :: cout << b.step_forward() << std::endl; + frame++; + } } - } - } - - + if(frame != 0) { + b.step_forward(); + d.step_forward(); - // print the maze - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - if (maze[x][y] == 1) { - ss << " "; - } else { - ss << "██"; - } } - ss << "\n"; + m.show(window); + text.setString(to_string(b.steps_taken)); + text.setPosition(sf::Vector2f(1200,50)); + window.draw(text); + text.setString(to_string(d.steps_taken)); + text.setPosition(sf::Vector2f(1400,50)); + window.draw(text); + window.display(); } - - // return the maze - return ss.str(); -} - - - -int main() { - string maze = generateMaze(); - cout << maze; - return 0; } diff --git a/src/Maze.cpp b/src/Maze.cpp new file mode 100644 index 0000000..1a2170a --- /dev/null +++ b/src/Maze.cpp @@ -0,0 +1,205 @@ +#include "Maze.h" + +Maze::Maze(int width, int height) { + this->width = width; + this->height = height; + + this->vertices = vector >(height, vector(width, 15)); // start with all walls. + this->vertex_colors = vector >(height, vector(width, sf::Color::White)); + this->vertex_colors2 = vector >(height, vector(width, sf::Color::White)); +} + +random_device rd; +mt19937 gen(rd()); +uniform_int_distribution<> distr(0, 3); + +bool Maze::visitable_vertex(int x, int y) { + if (x < 0 || x >= this->width || y < 0 || y >= this->height) { + return false; + } + int num_walls = 0; + + for (int i = 0; i < 4; i++) { + if (check_if_wall(y, x, i)) num_walls++; + } + + if (num_walls >= 3) return true; + return false; +} + + +void Maze::generate_maze() { + stack > vertex_stack; + vertex_stack.push(make_pair(0, 0)); + remove_wall(0, 0, 0); + + vector > visited(height, vector(width, 0)); + + bool first_move = true; + + while (!vertex_stack.empty()) { + int x = vertex_stack.top().first; + int y = vertex_stack.top().second; + vector directions_guessed(4, false); + int guess = 0; + bool has_moved = false; + while (guess < 4) { + int dirIndex = distr(gen); + + if (first_move) { + dirIndex = 1; + first_move = false; + } + + if (directions_guessed[dirIndex]) { + continue; + } + + directions_guessed[dirIndex] = true; + guess++; + + int new_x = x + direction_list[dirIndex].first; + int new_y = y + direction_list[dirIndex].second; + + if (visitable_vertex(new_x, new_y) && visited[new_y][new_x] == 0) { + remove_wall(y, x, dirIndex); + vertex_stack.push(make_pair(new_x, new_y)); + visited[new_y][new_x] = 1; + has_moved = true; + break; + } + } + if (!has_moved) { + vertex_stack.pop(); + } + } +} + +bool Maze::check_if_wall(int row, int col, int direction) { + return (vertices[row][col] & ((char) pow(2, direction))) == ((char) pow(2, direction)); +} + +void Maze::place_wall(int row, int col, int direction) { + int x = std::pow(2, direction); + this->vertices[row][col] |= (char) pow(2, direction); + int other_row = row + direction_list[direction].second; + int other_col = col + direction_list[direction].first; + if (other_row < 0 || other_col < 0 || other_row >= height || other_col >= width) return; + direction += 2; + direction %= 4; + this->vertices[other_row][other_col] |= (char) pow(2, direction); +} + +void Maze::remove_wall(int row, int col, int direction) { + this->vertices[row][col] &= ~(char) pow(2, direction); + int other_row = row + direction_list[direction].second; + int other_col = col + direction_list[direction].first; + if (other_row < 0 || other_col < 0 || other_row >= height || other_col >= width) return; + direction += 2; + direction %= 4; + this->vertices[other_row][other_col] &= ~(char) pow(2, direction); +} + +void Maze::place_walls(int row, int col, int directions[4]) { + for (int i = 0; i < 4; i++) { + if (directions[i] == 1) { + place_wall(row, col, i); + } + } +} + +void Maze::place_walls_for_directions(int row, int col, int from_direction, int to_direction) { + int walls[] = {1, 1, 1, 1}; + + walls[to_direction] = 0; + + from_direction += 2; // invert to place the walls + from_direction %= 4; // + walls[from_direction] = 0; + + place_walls(row, col, walls); +} + + +void Maze::print_maze() { + std::string output; + for (int i = 0; i < this->height; i++) { + for (int j = 0; j < this->width; j++) { + output += std::to_string(((int) this->vertices[i][j])); + } + output += "\n"; + } + std::cout << output << std::endl; +} + +void Maze::show(sf::RenderWindow &window) { + window.clear(sf::Color::White); + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + float line_width = MAZE_PIXEL_WIDTH / width; + float line_height = MAZE_PIXEL_HEIGHT / height; + //float start_x = (WINDOW_WIDTH - MAZE_PIXEL_WIDTH) / 2; + float start_x = 20; + float start_y = (WINDOW_HEIGHT - MAZE_PIXEL_HEIGHT) / 2; + + sf::RectangleShape rectangle(sf::Vector2f(line_width, line_height)); + rectangle.setPosition(start_x + line_width * (float) col, start_y + line_height * (float) row); + + if (vertex_colors[row][col] != sf::Color::White) { + rectangle.setFillColor(vertex_colors[row][col]); + window.draw(rectangle); + } + + if (vertex_colors2[row][col] != sf::Color::White) { + rectangle.setFillColor(vertex_colors2[row][col]); + window.draw(rectangle); + } + + if (check_if_wall(row, col, 0)) { + sf::Vector2f pos = sf::Vector2f(start_x + line_width * (float) col, + start_y + line_height * (float) row); + sf::Vertex line[] = { + sf::Vertex(pos, sf::Color::Black), + sf::Vertex(pos + sf::Vector2f(line_width, 0), sf::Color::Black), + }; + window.draw(line, 2, sf::Lines); + } + if (check_if_wall(row, col, 1)) { + sf::Vector2f pos = sf::Vector2f(start_x + line_width * ((float) col + 1), + start_y + line_height * (float) row); + sf::Vertex line[] = { + sf::Vertex(pos, sf::Color::Black), + sf::Vertex(pos + sf::Vector2f(0, line_height), sf::Color::Black), + }; + window.draw(line, 2, sf::Lines); + } + if (check_if_wall(row, col, 2)) { + sf::Vector2f pos = sf::Vector2f(start_x + line_width * (float) col, + start_y + line_height * ((float) row + 1)); + sf::Vertex line[] = { + sf::Vertex(pos, sf::Color::Black), + sf::Vertex(pos + sf::Vector2f(line_width, 0), sf::Color::Black), + }; + window.draw(line, 2, sf::Lines); + } + if (check_if_wall(row, col, 3)) { + sf::Vector2f pos = sf::Vector2f(start_x + line_width * (float) col, + start_y + line_height * (float) row); + sf::Vertex line[] = { + sf::Vertex(pos, sf::Color::Black), + sf::Vertex(pos + sf::Vector2f(0, line_height), sf::Color::Black), + }; + window.draw(line, 2, sf::Lines); + } + } + } + //window.display(); +} + +void Maze::reset() { + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + this->vertices[i][j] = 15; + } + } +} diff --git a/src/Maze.h b/src/Maze.h new file mode 100644 index 0000000..cffb06d --- /dev/null +++ b/src/Maze.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +constexpr float WINDOW_WIDTH = 1920.0f; +constexpr float WINDOW_HEIGHT = 1000.0f; +constexpr float MAZE_PIXEL_WIDTH = 950.0f; +constexpr float MAZE_PIXEL_HEIGHT = 950.0f; + +using namespace std; +class Maze { + std::vector> vertices; //1 byte first 4 bits represent walls + vector> direction_list = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}}; //up, right, down, left +public: + vector> vertex_colors; + vector> vertex_colors2; + int width; + int height; + Maze(int width, int height); + bool check_if_wall(int row, int col, int direction); //right up left down for the bits + void place_wall(int row, int col, int direction); + void remove_wall(int row, int col, int direction); + void place_walls(int row, int col, int directions[4]); + void place_walls_for_directions(int row, int col, int from_direction, int to_direction); + void print_maze(); + void show(sf::RenderWindow& window); + void generate_maze(); + bool visitable_vertex(int x, int y); + void update_nodes_counter(); // for sf + void reset(); +}; + + + + diff --git a/src/NotoSans-VariableFont_wdth,wght.ttf b/src/NotoSans-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000..ceb7b2f Binary files /dev/null and b/src/NotoSans-VariableFont_wdth,wght.ttf differ diff --git a/src/bfs.cpp b/src/bfs.cpp new file mode 100644 index 0000000..14d86e0 --- /dev/null +++ b/src/bfs.cpp @@ -0,0 +1,65 @@ +#include "bfs.h" + + +bfs::bfs(Maze *maze){ + this->visited = std::vector>(maze->height, std::vector(maze->width, 0)); + this->parents = std::vector>>(maze->width, std::vector>(maze->width, make_pair(0,0))); + this->maze = maze; + vertex_queue = std::queue>(); + vertex_queue.emplace(make_pair(0,0)); + start_time = std::chrono::steady_clock::time_point::min(); + end_time = std::chrono::steady_clock::time_point::min(); +} + +bool bfs::can_visit_vertex(int x, int y, int direction) { + int new_x = x + direction_list[direction].first; + int new_y = y + direction_list[direction].second; + if(new_x < 0 || new_y < 0 || new_x >= maze->width || new_y >= maze->height) return false; + if(visited[new_y][new_x] == 1) return false; + + return !maze->check_if_wall(y,x, direction); +} + +int bfs::step_forward() { + + if (start_time == std::chrono::steady_clock::time_point::min()) { + start_time = std::chrono::steady_clock::now(); + } + + if(vertex_queue.empty()) return 0; + if(finished) { + maze->vertex_colors[current_backtrack.first][current_backtrack.second] = {10,200,150, 100}; + if(current_backtrack.first == 0 && current_backtrack.second == 0) { + return 1; + } + current_backtrack = parents[current_backtrack.first][current_backtrack.second]; + return 1; + } + int x = vertex_queue.front().first; + int y = vertex_queue.front().second; + if(maze->vertex_colors[y][x] == sf::Color::White) { + //maze->vertex_colors[y][x] = {0,0,0}; + } + if(y == maze->height - 1 && x == maze->width-1) { + finished = true; + current_backtrack.first = y; + current_backtrack.second = x; + return 1; + } + + maze->vertex_colors[y][x] = {10,100,200, 100}; + steps_taken++; + + vertex_queue.pop(); + + for(int i = 0; i < 4; i ++) { + if(can_visit_vertex(x,y,i)) { + int new_x = x + direction_list[i].first; + int new_y = y + direction_list[i].second; + visited[new_y][new_x] = 1; + vertex_queue.push(make_pair(new_x,new_y)); + parents[new_y][new_x] = make_pair(y,x); + } + } + return -1; +} diff --git a/src/bfs.h b/src/bfs.h new file mode 100644 index 0000000..416c282 --- /dev/null +++ b/src/bfs.h @@ -0,0 +1,25 @@ +#pragma once +#include +#include +#include +#include +#include + + +class bfs { + std::vector> visited; + std::vector>> parents; + Maze *maze; + std::queue> vertex_queue; + vector> direction_list = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}}; //up, right, down, left + bool finished = false; + int step = 0; + pair current_backtrack; + chrono::time_point start_time; + chrono::time_point end_time; +public: + int steps_taken = 0; + bfs(Maze* maze); + bool can_visit_vertex(int x, int y, int direction); + int step_forward(); +}; diff --git a/src/dfs.cpp b/src/dfs.cpp new file mode 100644 index 0000000..597e2ed --- /dev/null +++ b/src/dfs.cpp @@ -0,0 +1,77 @@ +#include "dfs.h" +#include +//#include <__filesystem/path.h> + +// std::chrono::steady_clock::time_point start_time, end_time; + +dfs::dfs(Maze *maze){ + this->visited = std::vector>(maze->height, std::vector(maze->width, 0)); + this->parents = std::vector>>(maze->width, std::vector>(maze->width, make_pair(-1,-1))); + this->maze = maze; //stack update + vertex_stack = std::stack>(); //stack + vertex_stack.emplace(make_pair(0,0)); //starting point +} + +bool dfs::can_visit_vertex(int x, int y, int direction) { + int new_x = x + direction_list[direction].first; + int new_y = y + direction_list[direction].second; + if(new_x < 0 || new_y < 0 || new_x >= maze->width || new_y >= maze->height) return false; + if(visited[new_y][new_x] == 1) return false; + + return !maze->check_if_wall(y,x, direction); +} + +int dfs::step_forward() { + + if (!finished && vertex_stack.empty()) { + + //start_time = std::chrono::steady_clock::now(); + } + + if (finished) { + //ending here + //end_time = std::chrono::steady_clock::now(); + //auto elapsed_time = std::chrono::duration_cast(end_time - start_time); + //std::cout << "Elapsed time: " << elapsed_time.count() << " milliseconds" << std::endl; + } + + if(vertex_stack.empty()) return 0; + if(finished) { + maze->vertex_colors2[current_backtrack.first][current_backtrack.second] = {200,10,50, 100}; + if(current_backtrack.first == 0 && current_backtrack.second == 0) { + return 1; + } + current_backtrack = parents[current_backtrack.first][current_backtrack.second]; + return 1; + } + int x = vertex_stack.top().first; + int y = vertex_stack.top().second; + if(y == maze->height - 1 && x == maze->width-1) { + finished = true; + current_backtrack.first = y; + current_backtrack.second = x; + return 1; + } + std::cout << y << " " << x << std::endl; + + maze->vertex_colors2[y][x] = {200,130,5, 100}; + + for(int i = 0; i < 4; i ++) { // checks each direction to go in + if(can_visit_vertex(x,y,i)) { + int new_x = x + direction_list[i].first; + int new_y = y + direction_list[i].second; + visited[new_y][new_x] = 1; + vertex_stack.push(make_pair(new_x,new_y));; + //maze->vertex_colors[new_y][new_x] ={200,130,5, 200}; + parents[new_y][new_x] = make_pair(y,x); + return -1; + } + } + + maze->vertex_colors2[y][x] *= {255,255,255,100}; + steps_taken++; + + vertex_stack.pop(); + + return -1; +} diff --git a/src/dfs.h b/src/dfs.h new file mode 100644 index 0000000..9daeab9 --- /dev/null +++ b/src/dfs.h @@ -0,0 +1,25 @@ +#pragma once +#include +#include +#include +#include +#include + +using namespace std; +class dfs { + std::vector> visited; + std::vector>> parents; + Maze *maze; + std::stack> vertex_stack; + vector> direction_list = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}}; //up, right, down, left + bool finished = false; + int step = 0; + pair current_backtrack; + std::chrono::steady_clock::time_point start_time, end_time; + +public: + int steps_taken = 0; + dfs(Maze* maze); + bool can_visit_vertex(int x, int y, int direction); + int step_forward(); // make changes to update to the newest +};