From 36e7ba200816a00f8f8ece17ca92d0c4fcca22c1 Mon Sep 17 00:00:00 2001 From: Mohsen Heidari Date: Fri, 12 Jun 2026 17:18:05 +0330 Subject: [PATCH] pathfinding_astar --- CMakeLists.txt | 38 ++++++++++ shaders/fragment.glsl | 8 ++ shaders/vertex.glsl | 8 ++ src/AStar.cpp | 104 ++++++++++++++++++++++++++ src/AStar.h | 44 +++++++++++ src/Grid.cpp | 105 +++++++++++++++++++++++++++ src/Grid.h | 47 ++++++++++++ src/Renderer.cpp | 148 +++++++++++++++++++++++++++++++++++++ src/Renderer.h | 42 +++++++++++ src/Shader.h | 87 ++++++++++++++++++++++ src/main.cpp | 165 ++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 796 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 shaders/fragment.glsl create mode 100644 shaders/vertex.glsl create mode 100644 src/AStar.cpp create mode 100644 src/AStar.h create mode 100644 src/Grid.cpp create mode 100644 src/Grid.h create mode 100644 src/Renderer.cpp create mode 100644 src/Renderer.h create mode 100644 src/Shader.h create mode 100644 src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..70de8ce --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.16) +project(PathfindingAStar VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Find packages +find_package(OpenGL REQUIRED) +find_package(glfw3 REQUIRED) +find_package(GLEW REQUIRED) +find_package(glm REQUIRED) + +# Source files +set(SOURCES + src/main.cpp + src/Grid.cpp + src/Renderer.cpp + src/AStar.cpp +) + +set(HEADERS + src/Grid.h + src/Renderer.h + src/AStar.h + src/Shader.h +) + +add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}) + +target_link_libraries(${PROJECT_NAME} PRIVATE + OpenGL::GL + glfw + GLEW::GLEW + glm::glm +) + +# Copy shaders to build directory +file(COPY ${CMAKE_SOURCE_DIR}/shaders DESTINATION ${CMAKE_BINARY_DIR}) diff --git a/shaders/fragment.glsl b/shaders/fragment.glsl new file mode 100644 index 0000000..8cbf882 --- /dev/null +++ b/shaders/fragment.glsl @@ -0,0 +1,8 @@ +#version 330 core +out vec4 FragColor; + +uniform vec3 color; + +void main() { + FragColor = vec4(color, 1.0); +} diff --git a/shaders/vertex.glsl b/shaders/vertex.glsl new file mode 100644 index 0000000..4c91528 --- /dev/null +++ b/shaders/vertex.glsl @@ -0,0 +1,8 @@ +#version 330 core +layout (location = 0) in vec2 aPos; + +uniform mat4 model; + +void main() { + gl_Position = model * vec4(aPos, 0.0, 1.0); +} diff --git a/src/AStar.cpp b/src/AStar.cpp new file mode 100644 index 0000000..73a6aeb --- /dev/null +++ b/src/AStar.cpp @@ -0,0 +1,104 @@ +#include "AStar.h" +#include +#include + +AStar::AStar(Grid& grid) : grid_(grid) {} + +float AStar::heuristic(int x1, int y1, int x2, int y2) const { + // Euclidean distance + float dx = static_cast(x1 - x2); + float dy = static_cast(y1 - y2); + return std::sqrt(dx * dx + dy * dy); +} + +void AStar::reset() { + running_ = false; + found_ = false; + + while (!openSet_.empty()) openSet_.pop(); + gScore_.clear(); + cameFrom_.clear(); +} + +bool AStar::findPath(int startX, int startY, int endX, int endY) { + reset(); + + endX_ = endX; + endY_ = endY; + + if (!grid_.isWalkable(startX, startY) || !grid_.isWalkable(endX, endY)) { + return false; + } + + running_ = true; + + Node start; + start.x = startX; + start.y = startY; + start.gCost = 0.0f; + start.hCost = heuristic(startX, startY, endX, endY); + + openSet_.push(start); + gScore_[startY * grid_.getWidth() + startX] = 0.0f; + + while (!openSet_.empty()) { + Node current = openSet_.top(); + openSet_.pop(); + + if (current.x == endX && current.y == endY) { + reconstructPath(endX, endY); + running_ = false; + found_ = true; + return true; + } + + // Mark as closed + if (grid_.getCell(current.x, current.y).type != CellType::Start) { + grid_.setCellType(current.x, current.y, CellType::Closed); + } + + auto neighbors = grid_.getNeighbors(current.x, current.y); + + for (auto* neighbor : neighbors) { + int nx = neighbor->x; + int ny = neighbor->y; + + float tentativeG = current.gCost + heuristic(current.x, current.y, nx, ny); + int key = ny * grid_.getWidth() + nx; + + if (gScore_.find(key) == gScore_.end() || tentativeG < gScore_[key]) { + cameFrom_[key] = {current.x, current.y}; + gScore_[key] = tentativeG; + + Node next; + next.x = nx; + next.y = ny; + next.gCost = tentativeG; + next.hCost = heuristic(nx, ny, endX, endY); + + openSet_.push(next); + + if (grid_.getCell(nx, ny).type != CellType::End) { + grid_.setCellType(nx, ny, CellType::Open); + } + } + } + } + + running_ = false; + return false; +} + +void AStar::reconstructPath(int endX, int endY) { + int key = endY * grid_.getWidth() + endX; + + while (cameFrom_.find(key) != cameFrom_.end()) { + auto [px, py] = cameFrom_[key]; + + if (grid_.getCell(px, py).type != CellType::Start) { + grid_.setCellType(px, py, CellType::Path); + } + + key = py * grid_.getWidth() + px; + } +} diff --git a/src/AStar.h b/src/AStar.h new file mode 100644 index 0000000..f1414ce --- /dev/null +++ b/src/AStar.h @@ -0,0 +1,44 @@ +#pragma once + +#include "Grid.h" +#include +#include +#include +#include + +struct Node { + int x, y; + float gCost = 0.0f; + float hCost = 0.0f; + float fCost() const { return gCost + hCost; } + + bool operator>(const Node& other) const { + return fCost() > other.fCost(); + } +}; + +class AStar { +public: + AStar(Grid& grid); + + bool findPath(int startX, int startY, int endX, int endY); + void visualizeStep(int startX, int startY, int endX, int endY); + bool isRunning() const { return running_; } + bool isFound() const { return found_; } + + void reset(); + +private: + float heuristic(int x1, int y1, int x2, int y2) const; + void reconstructPath(int endX, int endY); + + Grid& grid_; + bool running_ = false; + bool found_ = false; + + std::priority_queue, std::greater> openSet_; + std::unordered_map gScore_; + std::unordered_map> cameFrom_; + + int endX_, endY_; +}; diff --git a/src/Grid.cpp b/src/Grid.cpp new file mode 100644 index 0000000..4e6a8b5 --- /dev/null +++ b/src/Grid.cpp @@ -0,0 +1,105 @@ +#include "Grid.h" +#include + +Grid::Grid(int width, int height) : width_(width), height_(height) { + cells_.resize(width * height); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + cells_[y * width + x].x = x; + cells_[y * width + x].y = y; + } + } +} + +Cell& Grid::getCell(int x, int y) { + return cells_[y * width_ + x]; +} + +const Cell& Grid::getCell(int x, int y) const { + return cells_[y * width_ + x]; +} + +bool Grid::isValid(int x, int y) const { + return x >= 0 && x < width_ && y >= 0 && y < height_; +} + +bool Grid::isWalkable(int x, int y) const { + return isValid(x, y) && getCell(x, y).walkable; +} + +void Grid::setCellType(int x, int y, CellType type) { + if (!isValid(x, y)) return; + + auto& cell = getCell(x, y); + cell.type = type; + + switch (type) { + case CellType::Wall: + cell.walkable = false; + break; + case CellType::Start: + case CellType::End: + case CellType::Empty: + case CellType::Open: + case CellType::Closed: + case CellType::Path: + cell.walkable = true; + break; + } +} + +void Grid::reset() { + for (auto& cell : cells_) { + cell.type = CellType::Empty; + cell.walkable = true; + } +} + +void Grid::clearPath() { + for (auto& cell : cells_) { + if (cell.type == CellType::Open || + cell.type == CellType::Closed || + cell.type == CellType::Path) { + cell.type = CellType::Empty; + cell.walkable = true; + } + } +} + +glm::vec2 Grid::getCellPosition(int x, int y) const { + return glm::vec2( + static_cast(x) / static_cast(width_) * 2.0f - 1.0f, + static_cast(y) / static_cast(height_) * 2.0f - 1.0f + ); +} + +glm::vec3 Grid::getCellColor(CellType type) const { + switch (type) { + case CellType::Empty: return glm::vec3(0.15f, 0.15f, 0.15f); + case CellType::Wall: return glm::vec3(0.8f, 0.8f, 0.8f); + case CellType::Start: return glm::vec3(0.0f, 0.8f, 0.0f); + case CellType::End: return glm::vec3(0.8f, 0.0f, 0.0f); + case CellType::Open: return glm::vec3(0.0f, 0.6f, 0.6f); + case CellType::Closed: return glm::vec3(0.6f, 0.0f, 0.6f); + case CellType::Path: return glm::vec3(1.0f, 0.8f, 0.0f); + default: return glm::vec3(0.15f, 0.15f, 0.15f); + } +} + +std::vector Grid::getNeighbors(int x, int y) { + std::vector neighbors; + + const int dx[] = {-1, 1, 0, 0, -1, -1, 1, 1}; + const int dy[] = {0, 0, -1, 1, -1, 1, -1, 1}; + + for (int i = 0; i < 8; ++i) { + int nx = x + dx[i]; + int ny = y + dy[i]; + + if (isWalkable(nx, ny)) { + neighbors.push_back(&getCell(nx, ny)); + } + } + + return neighbors; +} diff --git a/src/Grid.h b/src/Grid.h new file mode 100644 index 0000000..3acc500 --- /dev/null +++ b/src/Grid.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +enum class CellType { + Empty = 0, + Wall = 1, + Start = 2, + End = 3, + Open = 4, + Closed = 5, + Path = 6 +}; + +struct Cell { + int x, y; + CellType type = CellType::Empty; + bool walkable = true; +}; + +class Grid { +public: + Grid(int width, int height); + + int getWidth() const { return width_; } + int getHeight() const { return height_; } + + Cell& getCell(int x, int y); + const Cell& getCell(int x, int y) const; + + bool isValid(int x, int y) const; + bool isWalkable(int x, int y) const; + + void setCellType(int x, int y, CellType type); + void reset(); + void clearPath(); + + glm::vec2 getCellPosition(int x, int y) const; + glm::vec3 getCellColor(CellType type) const; + + std::vector getNeighbors(int x, int y); + +private: + int width_, height_; + std::vector cells_; +}; diff --git a/src/Renderer.cpp b/src/Renderer.cpp new file mode 100644 index 0000000..1bb58ae --- /dev/null +++ b/src/Renderer.cpp @@ -0,0 +1,148 @@ +#include "Renderer.h" +#include + +Renderer::Renderer(int windowWidth, int windowHeight) + : windowWidth_(windowWidth), windowHeight_(windowHeight) {} + +Renderer::~Renderer() { + glDeleteVertexArrays(1, &VAO_); + glDeleteBuffers(1, &VBO_); + glfwTerminate(); +} + +bool Renderer::initialize() { + glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11); + if (!glfwInit()) { + std::cerr << "Failed to initialize GLFW" << std::endl; + return false; + } + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); + + window_ = glfwCreateWindow(windowWidth_, windowHeight_, "A* Pathfinding", NULL, NULL); + if (!window_) { + std::cerr << "Failed to create GLFW window" << std::endl; + glfwTerminate(); + return false; + } + + glfwMakeContextCurrent(window_); + glfwSwapInterval(1); + + if (glewInit() != GLEW_OK) { + std::cerr << "Failed to initialize GLEW" << std::endl; + return false; + } + + glViewport(0, 0, windowWidth_, windowHeight_); + glClearColor(0.05f, 0.05f, 0.05f, 1.0f); + + shader_ = std::make_unique("shaders/vertex.glsl", "shaders/fragment.glsl"); + setupQuad(); + + return true; +} + +void Renderer::setupQuad() { + float vertices[] = { + // positions + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + + glGenVertexArrays(1, &VAO_); + glGenBuffers(1, &VBO_); + + glBindVertexArray(VAO_); + + glBindBuffer(GL_ARRAY_BUFFER, VBO_); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} + +void Renderer::render(const Grid& grid) { + glClear(GL_COLOR_BUFFER_BIT); + + shader_->use(); + + for (int y = 0; y < grid.getHeight(); ++y) { + for (int x = 0; x < grid.getWidth(); ++x) { + const auto& cell = grid.getCell(x, y); + glm::vec3 color = grid.getCellColor(cell.type); + drawCell(x, y, color, grid); + } + } +} + +void Renderer::drawCell(int x, int y, const glm::vec3& color, const Grid& grid) { + float cellW = 2.0f / grid.getWidth(); + float cellH = 2.0f / grid.getHeight(); + + glm::mat4 model = glm::mat4(1.0f); + model = glm::translate(model, glm::vec3( + -1.0f + x * cellW + 0.005f, + -1.0f + y * cellH + 0.005f, + 0.0f + )); + model = glm::scale(model, glm::vec3( + cellW - 0.01f, + cellH - 0.01f, + 1.0f + )); + + shader_->setMat4("model", model); + shader_->setVec3("color", color.r, color.g, color.b); + + glBindVertexArray(VAO_); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glBindVertexArray(0); +} + +void Renderer::processInput() { + if (glfwGetKey(window_, GLFW_KEY_ESCAPE) == GLFW_PRESS) { + glfwSetWindowShouldClose(window_, true); + } +} + +bool Renderer::shouldClose() const { + return glfwWindowShouldClose(window_); +} + +void Renderer::swapBuffers() { + glfwSwapBuffers(window_); +} + +void Renderer::pollEvents() { + glfwPollEvents(); +} + +void Renderer::getMouseGridPos(int& gridX, int& gridY) const { + double mx, my; + glfwGetCursorPos(window_, &mx, &my); + + // Convert to NDC + float ndcX = (2.0f * mx / windowWidth_) - 1.0f; + float ndcY = 1.0f - (2.0f * my / windowHeight_); + + // Convert to grid coordinates + gridX = static_cast((ndcX + 1.0f) / 2.0f * gridWidth_); + gridY = static_cast((ndcY + 1.0f) / 2.0f * gridHeight_); +} + +bool Renderer::isMousePressed(int button) const { + return glfwGetMouseButton(window_, button) == GLFW_PRESS; +} + +bool Renderer::isKeyPressed(int key) const { + return glfwGetKey(window_, key) == GLFW_PRESS; +} diff --git a/src/Renderer.h b/src/Renderer.h new file mode 100644 index 0000000..241fda7 --- /dev/null +++ b/src/Renderer.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include "Grid.h" +#include "Shader.h" +#include + +class Renderer { +public: + Renderer(int windowWidth, int windowHeight); + ~Renderer(); + + bool initialize(); + void render(const Grid& grid); + void processInput(); + bool shouldClose() const; + void swapBuffers(); + void pollEvents(); + + GLFWwindow* getWindow() const { return window_; } + + // Mouse handling + void getMouseGridPos(int& gridX, int& gridY) const; + bool isMousePressed(int button) const; + bool isKeyPressed(int key) const; + +private: + void setupQuad(); + void drawCell(int x, int y, const glm::vec3& color, const Grid& grid); + + int windowWidth_, windowHeight_; + int gridWidth_ = 40; + int gridHeight_ = 30; + + GLFWwindow* window_ = nullptr; + std::unique_ptr shader_; + + GLuint VAO_ = 0, VBO_ = 0; +}; diff --git a/src/Shader.h b/src/Shader.h new file mode 100644 index 0000000..b10369c --- /dev/null +++ b/src/Shader.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#include +#include + +class Shader { +public: + GLuint ID; + + Shader(const char* vertexPath, const char* fragmentPath) { + std::string vertexCode; + std::string fragmentCode; + std::ifstream vShaderFile; + std::ifstream fShaderFile; + + vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); + fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + try { + vShaderFile.open(vertexPath); + fShaderFile.open(fragmentPath); + std::stringstream vShaderStream, fShaderStream; + vShaderStream << vShaderFile.rdbuf(); + fShaderStream << fShaderFile.rdbuf(); + vShaderFile.close(); + fShaderFile.close(); + vertexCode = vShaderStream.str(); + fragmentCode = fShaderStream.str(); + } catch (std::ifstream::failure& e) { + std::cerr << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl; + } + + const char* vShaderCode = vertexCode.c_str(); + const char* fShaderCode = fragmentCode.c_str(); + + GLuint vertex = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex, 1, &vShaderCode, NULL); + glCompileShader(vertex); + checkCompileErrors(vertex, "VERTEX"); + + GLuint fragment = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment, 1, &fShaderCode, NULL); + glCompileShader(fragment); + checkCompileErrors(fragment, "FRAGMENT"); + + ID = glCreateProgram(); + glAttachShader(ID, vertex); + glAttachShader(ID, fragment); + glLinkProgram(ID); + checkCompileErrors(ID, "PROGRAM"); + + glDeleteShader(vertex); + glDeleteShader(fragment); + } + + void use() { glUseProgram(ID); } + + void setVec3(const std::string& name, float x, float y, float z) const { + glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z); + } + + void setMat4(const std::string& name, const glm::mat4& mat) const { + glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]); + } + +private: + void checkCompileErrors(GLuint shader, std::string type) { + GLint success; + GLchar infoLog[1024]; + if (type != "PROGRAM") { + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { + glGetShaderInfoLog(shader, 1024, NULL, infoLog); + std::cerr << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << std::endl; + } + } else { + glGetProgramiv(shader, GL_LINK_STATUS, &success); + if (!success) { + glGetProgramInfoLog(shader, 1024, NULL, infoLog); + std::cerr << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << std::endl; + } + } + } +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..20f285f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,165 @@ +#include "Renderer.h" +#include "Grid.h" +#include "AStar.h" +#include +#include +#include + +const int GRID_W = 40; +const int GRID_H = 30; + +int main() { + std::srand(static_cast(std::time(nullptr))); + + Renderer renderer(800, 600); + if (!renderer.initialize()) { + return -1; + } + + Grid grid(GRID_W, GRID_H); + AStar astar(grid); + + // Default start and end positions + int startX = 5, startY = 5; + int endX = 34, endY = 24; + + grid.setCellType(startX, startY, CellType::Start); + grid.setCellType(endX, endY, CellType::End); + + // Generate random walls + for (int i = 0; i < 300; ++i) { + int wx = std::rand() % GRID_W; + int wy = std::rand() % GRID_H; + if ((wx != startX || wy != startY) && (wx != endX || wy != endY)) { + grid.setCellType(wx, wy, CellType::Wall); + } + } + + bool pathFound = false; + bool placingWalls = false; + bool removingWalls = false; + bool settingStart = false; + bool settingEnd = false; + + std::cout << "=== A* Pathfinding Controls ===" << std::endl; + std::cout << "Left Click : Place walls" << std::endl; + std::cout << "Right Click : Remove walls" << std::endl; + std::cout << "S + Click : Set start position" << std::endl; + std::cout << "E + Click : Set end position" << std::endl; + std::cout << "SPACE : Run A* algorithm" << std::endl; + std::cout << "R : Reset grid (keep walls)" << std::endl; + std::cout << "C : Clear all walls" << std::endl; + std::cout << "G : Generate new random walls" << std::endl; + std::cout << "ESC : Exit" << std::endl; + std::cout << "===============================" << std::endl; + + while (!renderer.shouldClose()) { + renderer.processInput(); + + int mx, my; + renderer.getMouseGridPos(mx, my); + + // Check modifier keys + settingStart = renderer.isKeyPressed(GLFW_KEY_S); + settingEnd = renderer.isKeyPressed(GLFW_KEY_E); + + // Mouse input handling + if (grid.isValid(mx, my)) { + if (renderer.isMousePressed(GLFW_MOUSE_BUTTON_LEFT) && !settingStart && !settingEnd) { + if (grid.getCell(mx, my).type != CellType::Start && + grid.getCell(mx, my).type != CellType::End) { + grid.setCellType(mx, my, CellType::Wall); + pathFound = false; + } + } + else if (renderer.isMousePressed(GLFW_MOUSE_BUTTON_RIGHT)) { + if (grid.getCell(mx, my).type != CellType::Start && + grid.getCell(mx, my).type != CellType::End) { + grid.setCellType(mx, my, CellType::Empty); + pathFound = false; + } + } + else if (renderer.isMousePressed(GLFW_MOUSE_BUTTON_LEFT) && settingStart) { + if (grid.getCell(mx, my).type != CellType::End && + grid.getCell(mx, my).type != CellType::Wall) { + grid.setCellType(startX, startY, CellType::Empty); + startX = mx; + startY = my; + grid.setCellType(startX, startY, CellType::Start); + pathFound = false; + } + } + else if (renderer.isMousePressed(GLFW_MOUSE_BUTTON_LEFT) && settingEnd) { + if (grid.getCell(mx, my).type != CellType::Start && + grid.getCell(mx, my).type != CellType::Wall) { + grid.setCellType(endX, endY, CellType::Empty); + endX = mx; + endY = my; + grid.setCellType(endX, endY, CellType::End); + pathFound = false; + } + } + } + + // Key actions (with simple debounce using static variable) + static bool spaceWasPressed = false; + static bool rWasPressed = false; + static bool cWasPressed = false; + static bool gWasPressed = false; + + bool spacePressed = renderer.isKeyPressed(GLFW_KEY_SPACE); + bool rPressed = renderer.isKeyPressed(GLFW_KEY_R); + bool cPressed = renderer.isKeyPressed(GLFW_KEY_C); + bool gPressed = renderer.isKeyPressed(GLFW_KEY_G); + + if (spacePressed && !spaceWasPressed) { + grid.clearPath(); + pathFound = astar.findPath(startX, startY, endX, endY); + if (pathFound) { + std::cout << "Path found!" << std::endl; + } else { + std::cout << "No path found!" << std::endl; + } + } + + if (rPressed && !rWasPressed) { + grid.clearPath(); + pathFound = false; + std::cout << "Path cleared." << std::endl; + } + + if (cPressed && !cWasPressed) { + grid.reset(); + grid.setCellType(startX, startY, CellType::Start); + grid.setCellType(endX, endY, CellType::End); + pathFound = false; + std::cout << "Grid cleared." << std::endl; + } + + if (gPressed && !gWasPressed) { + grid.reset(); + grid.setCellType(startX, startY, CellType::Start); + grid.setCellType(endX, endY, CellType::End); + for (int i = 0; i < 300; ++i) { + int wx = std::rand() % GRID_W; + int wy = std::rand() % GRID_H; + if ((wx != startX || wy != startY) && (wx != endX || wy != endY)) { + grid.setCellType(wx, wy, CellType::Wall); + } + } + pathFound = false; + std::cout << "New random walls generated." << std::endl; + } + + spaceWasPressed = spacePressed; + rWasPressed = rPressed; + cWasPressed = cPressed; + gWasPressed = gPressed; + + renderer.render(grid); + renderer.swapBuffers(); + renderer.pollEvents(); + } + + return 0; +}