diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4723eec --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.dll +*.exe \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f8a4c40 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# Maze Path Finder + +screenshot + +### Just 3 steps to get started: + 1) **Install [C++ SFML Library](https://www.sfml-dev.org/download.php).** + 2) **In the "build.bat" file, specify your path to SFML.** + 3) **Run "run.bat".** + +### Author's libraries used by this project: +- [athm.h](https://github.com/ZERDICORP/athm-lib.git) +- [file.h](https://github.com/ZERDICORP/file-lib.git) diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..b6346bf --- /dev/null +++ b/build.bat @@ -0,0 +1,15 @@ +@echo off + +REM ↓↓↓↓↓↓↓↓↓↓↓↓ Path to SFML ↓↓↓↓↓↓↓↓↓↓↓↓ + +SET sfmlPath="C:/Program Files/SFML-2.5.1" + +REM ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ + +SET includePath=%sfmlPath%/include +SET libPath=%sfmlPath%/lib + +g++ ./src/implementation/*.cpp -o ./build/main.exe -O3 -O2 -O1 -DSFML_STATIC -I %includePath% -I "./src/headers" -L %libPath% -lsfml-graphics -lsfml-window -lsfml-system + +echo Press any button.. +pause > nul \ No newline at end of file diff --git a/build/.cfg b/build/.cfg new file mode 100644 index 0000000..ea25150 --- /dev/null +++ b/build/.cfg @@ -0,0 +1 @@ +squareWidth = 10 \ No newline at end of file diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..c346af9 --- /dev/null +++ b/run.bat @@ -0,0 +1,3 @@ +@echo off +call build.bat +call start.bat \ No newline at end of file diff --git a/screenshots/s1.png b/screenshots/s1.png new file mode 100644 index 0000000..5552203 Binary files /dev/null and b/screenshots/s1.png differ diff --git a/src/headers/config.h b/src/headers/config.h new file mode 100644 index 0000000..963ea56 --- /dev/null +++ b/src/headers/config.h @@ -0,0 +1,12 @@ +#include "include.h" +#include "macros.h" + +#ifndef MAIN_CONFIG + #define MAIN_CONFIG + enum class EVENT_CODE + { + NONE, + CLOSE, + RESTART, + }; +#endif \ No newline at end of file diff --git a/src/headers/include.h b/src/headers/include.h new file mode 100644 index 0000000..3234a91 --- /dev/null +++ b/src/headers/include.h @@ -0,0 +1,6 @@ +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/src/headers/macros.h b/src/headers/macros.h new file mode 100644 index 0000000..5c2d3c3 --- /dev/null +++ b/src/headers/macros.h @@ -0,0 +1,4 @@ +#define mWH 400 +#define mWW 400 +#define mTitle "maze generation" +#define mConfigPath ".cfg" \ No newline at end of file diff --git a/src/headers/tools.h b/src/headers/tools.h new file mode 100644 index 0000000..814ee43 --- /dev/null +++ b/src/headers/tools.h @@ -0,0 +1,18 @@ +#include "config.h" + +struct Node +{ + bool bCollapse = false; + bool bTopWall = true; + bool bLeftWall = true; +}; + +void mazeGen(std::vector>& nodes, int iRows, int iCols); +void displayConsoleInformation(std::map& cfg); + +std::map readConfig(std::string sConfigPath); + +EVENT_CODE eventListener(sf::RenderWindow& window); + +int init(sf::RenderWindow& window); +int main(); \ No newline at end of file diff --git a/src/implementation/main.cpp b/src/implementation/main.cpp new file mode 100644 index 0000000..ff925f6 --- /dev/null +++ b/src/implementation/main.cpp @@ -0,0 +1,200 @@ +#include "config.h" +#include "tools.h" + +int loop(sf::RenderWindow& window, std::map& cfg) +{ + bool bNeedToUpdateConsole = true; + bool bFinding = true; + bool bRollback = false; + + int iSqW = cfg["squareWidth"]; + int iRows = mWH / iSqW; + int iCols = mWW / iSqW; + + sf::Vector2i startPos(0, 0); + sf::Vector2i finishPos(zer::athm::rand_int(10, iCols - 1), zer::athm::rand_int(10, iRows - 1)); + sf::Vector2i currentPos = startPos; + + std::vector> nodes(iRows, std::vector(iCols, Node())); + + mazeGen(nodes, iRows, iCols); + + for (int iRow = 0; iRow < iRows; ++iRow) + for (int iCol = 0; iCol < iCols; ++iCol) + nodes[iRow][iCol].bCollapse = false; + + std::vector history; + history.push_back(currentPos); + + std::vector vectors({ + sf::Vector2i(0, -1), + sf::Vector2i(0, 1), + sf::Vector2i(-1, 0), + sf::Vector2i(1, 0) + }); + + sf::RectangleShape rectStart(sf::Vector2f(iSqW, iSqW)); + rectStart.setFillColor(sf::Color::Yellow); + rectStart.setPosition(0, 0); + + sf::RectangleShape rectFinish(sf::Vector2f(iSqW, iSqW)); + rectFinish.setFillColor(sf::Color::Red); + rectFinish.setPosition(finishPos.x * iSqW, finishPos.y * iSqW); + + sf::VertexArray pathLine(sf::LinesStrip, 2); + pathLine[0].color = sf::Color(15, 255, 252); + pathLine[1].color = sf::Color(15, 255, 252); + + sf::VertexArray mazeLine(sf::LineStrip, 2); + mazeLine[0].color = sf::Color::White; + mazeLine[1].color = sf::Color::White; + + while (window.isOpen()) + { + window.clear(); + + /* + Maze path finder algorithm body. + */ + if (bFinding) + { + if (currentPos.y == finishPos.y && currentPos.x == finishPos.x) + { + bFinding = false; + rectFinish.setFillColor(sf::Color::Green); + continue; + } + + std::vector vectorVariants({0, 1, 2, 3}); + + while (true) + { + int iRandomVectorVariantIndex = zer::athm::rand_int(vectorVariants.size()); + int iRandomVectorIndex = vectorVariants[iRandomVectorVariantIndex]; + + sf::Vector2i tempPos = currentPos + vectors[iRandomVectorIndex]; + + if (zer::athm::inRange2D(iRows, iCols, tempPos.y, tempPos.x)) + { + bool bWall = ((vectors[iRandomVectorIndex].y < 0 && nodes[currentPos.y][currentPos.x].bTopWall) || + (vectors[iRandomVectorIndex].y > 0 && nodes[tempPos.y][tempPos.x].bTopWall) || + (vectors[iRandomVectorIndex].x < 0 && nodes[currentPos.y][currentPos.x].bLeftWall) || + (vectors[iRandomVectorIndex].x > 0 && nodes[tempPos.y][tempPos.x].bLeftWall)); + + /* + If there are no walls in the direction and we have not been there yet. + */ + if (!bWall && !nodes[tempPos.y][tempPos.x].bCollapse) + { + if (bRollback) + { + history.push_back(currentPos); + bRollback = false; + } + + currentPos = tempPos; + + history.push_back(currentPos); + + nodes[currentPos.y][currentPos.x].bCollapse = true; + + break; + } + else + vectorVariants.erase(vectorVariants.begin() + iRandomVectorVariantIndex); + } + else + vectorVariants.erase(vectorVariants.begin() + iRandomVectorVariantIndex); + + if (vectorVariants.size() == 0) + { + currentPos = history[history.size() - 1]; + + history.erase(history.end() - 1); + + bRollback = true; + + break; + } + } + } + + /* + Drawing start & finish. + */ + window.draw(rectStart); + window.draw(rectFinish); + + /* + Drawing path. + */ + if (history.size() > 1) + { + for (int i = 1; i < history.size() - 1; ++i) + { + pathLine[0].position = sf::Vector2f(history[i - 1].x * iSqW + (iSqW / 2), history[i - 1].y * iSqW + (iSqW / 2)); + pathLine[1].position = sf::Vector2f(history[i].x * iSqW + (iSqW / 2), history[i].y * iSqW + (iSqW / 2)); + + window.draw(pathLine); + } + + pathLine[0].position = sf::Vector2f( + history[history.size() - 2].x * iSqW + (iSqW / 2), history[history.size() - 2].y * iSqW + (iSqW / 2)); + pathLine[1].position = sf::Vector2f( + history[history.size() - 1].x * iSqW + (iSqW / 2), history[history.size() - 1].y * iSqW + (iSqW / 2)); + + window.draw(pathLine); + } + + /* + Drawing maze. + */ + for (int r = 0; r < iRows; ++r) + { + for (int c = 0; c < iCols; ++c) + { + if (nodes[r][c].bTopWall) + { + mazeLine[0].position = sf::Vector2f(c * iSqW, r * iSqW); + mazeLine[1].position = sf::Vector2f(c * iSqW + iSqW, r * iSqW); + + window.draw(mazeLine); + } + if (nodes[r][c].bLeftWall) + { + mazeLine[0].position = sf::Vector2f(c * iSqW, r * iSqW); + mazeLine[1].position = sf::Vector2f(c * iSqW, r * iSqW + iSqW); + + window.draw(mazeLine); + } + } + } + + window.display(); + + if (bNeedToUpdateConsole) + { + displayConsoleInformation(cfg); + bNeedToUpdateConsole = false; + } + + switch (eventListener(window)) + { + case EVENT_CODE::CLOSE: + window.close(); + break; + + case EVENT_CODE::RESTART: + init(window); + break; + } + } + return 0; +} + +int init(sf::RenderWindow& window) +{ + std::map cfg = readConfig(mConfigPath); + + return loop(window, cfg); +} \ No newline at end of file diff --git a/src/implementation/tools.cpp b/src/implementation/tools.cpp new file mode 100644 index 0000000..1f0cf8e --- /dev/null +++ b/src/implementation/tools.cpp @@ -0,0 +1,127 @@ +#include "tools.h" + +void mazeGen(std::vector>& nodes, int iRows, int iCols) +{ + sf::Vector2f point(0, 0); + + std::vector history; + std::vector vectors; + vectors.push_back(sf::Vector2f(0, -1)); + vectors.push_back(sf::Vector2f(0, 1)); + vectors.push_back(sf::Vector2f(-1, 0)); + vectors.push_back(sf::Vector2f(1, 0)); + + std::vector vectorVariants({0, 1, 2, 3}); + + for (int r = 0; r < iRows; ++r) + nodes[r][0].bLeftWall = false; + + for (int c = 0; c < iCols; ++c) + nodes[0][c].bTopWall = false; + + nodes[point.y][point.x].bCollapse = true; + + while (true) + { + int iRandomVectorVariantIndex = zer::athm::rand_int(vectorVariants.size()); + int iRandomVectorIndex = vectorVariants[iRandomVectorVariantIndex]; + + sf::Vector2f temp = point + vectors[iRandomVectorIndex]; + + if (zer::athm::inRange2D(iRows, iCols, temp.y, temp.x) && !nodes[temp.y][temp.x].bCollapse) + { + point = temp; + + if (vectors[iRandomVectorIndex].y < 0) + nodes[point.y - vectors[iRandomVectorIndex].y][point.x - vectors[iRandomVectorIndex].x].bTopWall = false; + else if (vectors[iRandomVectorIndex].y > 0) + nodes[point.y][point.x].bTopWall = false; + else if (vectors[iRandomVectorIndex].x < 0) + nodes[point.y - vectors[iRandomVectorIndex].y][point.x - vectors[iRandomVectorIndex].x].bLeftWall = false; + else if (vectors[iRandomVectorIndex].x > 0) + nodes[point.y][point.x].bLeftWall = false; + + vectorVariants = std::vector({0, 1, 2, 3}); + + history.push_back(point); + + nodes[point.y][point.x].bCollapse = true; + } + else + vectorVariants.erase(vectorVariants.begin() + iRandomVectorVariantIndex); + + if (vectorVariants.size() == 0) + { + if (!history.size()) + break; + + vectorVariants = std::vector({0, 1, 2, 3}); + + point = history[history.size() - 1]; + + history.erase(history.end() - 1); + } + } +} + +void displayConsoleInformation(std::map& cfg) +{ + system("cls"); + + std::cout << "# " << mTitle << " #" << std::endl; + std::cout << "\n[!] keyboard buttons for control:" << std::endl; + std::cout << "\t [ ESC ] - exit;" << std::endl; + std::cout << "\t [ R ] - restart;" << std::endl; + std::cout << "\n[!] note: visit a \"" << mConfigPath << "\" file to change configuration;" << std::endl; + std::cout << "\n[!] current configuration:" << std::endl; + + for (std::map::iterator p = cfg.begin(); p != cfg.end(); p++) + std::cout << "\t" << p -> first << " = " << p -> second << ";" << std::endl; +} + +std::map readConfig(std::string sConfigPath) +{ + std::map cfg; + + zer::File file(sConfigPath); + file.read({zer::file::Modifier::lines}); + + for (int i = 0; i < file.linesLen(); ++i) + { + std::string sLine = file.lineAt(i); + if (sLine.find(" = ") != std::string::npos) + { + std::vector lineParts = zer::athm::split(sLine, " = "); + cfg[lineParts[0]] = stof(lineParts[1]); + } + } + + return cfg; +} + +EVENT_CODE eventListener(sf::RenderWindow& window) +{ + sf::Event event; + while (window.pollEvent(event)) + { + if (event.type == sf::Event::Closed) + return EVENT_CODE::CLOSE; + if (event.type == sf::Event::KeyPressed) + { + if (event.key.code == sf::Keyboard::Escape) + return EVENT_CODE::CLOSE; + if (event.key.code == sf::Keyboard::R) + return EVENT_CODE::RESTART; + } + } + return EVENT_CODE::NONE; +} + +int main() +{ + zer::athm::rand_init(); + + sf::RenderWindow window(sf::VideoMode(mWW, mWH), mTitle); + + return init(window); +} \ No newline at end of file diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..cf48754 --- /dev/null +++ b/start.bat @@ -0,0 +1,9 @@ +@echo off + +SET root=%cd% + +cd "%cd%\build" + +call main + +cd %root% \ No newline at end of file