#include #include #include #include /*для ошибок*/ #include #include /*мерит время*/ #include /*волшебная отрисовка*/ class cell{ private: int x, y; bool isWall; bool isExit; bool isStart; public: cell() {x=0; y=0; isWall=false; isExit=false; isStart = false;} cell(int x, int y, bool isWall, bool isExit, bool isStart) { this->x = x; this->y = y; this->isWall = isWall; this->isExit = isExit; this->isStart = isStart; } bool isPassable() {return !isWall;} void setStart(bool value) {isStart = value;} void setExit(bool value) {isExit = value;} void setX(int x) {this->x = x;} void setY(int y) {this->y = y;} void setIsWall(bool isWall) {this->isWall = isWall;} int getX() {return x;} int getY() {return y;} bool getIsWall() {return isWall;} bool getIsExit() {return isExit;} bool getIsStart() {return isStart;} }; class maze{ private: int width; int height; cell** matrix; cell* start; cell* exit; public: maze(int width, int height) { this->width = width; this->height = height; this->start = nullptr; this->exit = nullptr; matrix = new cell*[width]; for (int x = 0; x < width; ++x) { matrix[x] = new cell[height]; for (int y = 0; y < height; ++y) matrix[x][y] = cell(x, y, false, false, false); } } maze(int width, int height, int startX, int startY, int exitX, int exitY) { this->width = width; this->height = height; this->start = nullptr; this->exit = nullptr; matrix = new cell*[width]; for (int x = 0; x < width; ++x) { matrix[x] = new cell[height]; for (int y = 0; y < height; ++y) { matrix[x][y] = cell(x, y, false, false, false); } } matrix[startX][startY].setStart(true); matrix[exitX][exitY].setExit(true); start = &matrix[startX][startY]; exit = &matrix[exitX][exitY]; } ~maze() { for (int i = 0; i < width; ++i) delete[] matrix[i]; delete[] matrix; } cell* getCell(int x, int y) { if (x < 0 || x >= width || y < 0 || y >= height) return nullptr; return &matrix[x][y]; } void setCell(int x, int y, cell newCell) { if (x < 0 || x >= width || y < 0 || y >= height) return; matrix[x][y] = newCell; if (matrix[x][y].getIsStart()) start = &matrix[x][y]; if (matrix[x][y].getIsExit()) exit = &matrix[x][y]; } cell** getNeighbors(cell* current) { /*ДЕЛАТЬ delete[] neighbors; !!!!!!!!!!!!!!!*/ cell** neighbors = new cell*[5]; int count = 0; int x = current->getX(); int y = current->getY(); cell* up = getCell(x, y - 1); cell* down = getCell(x, y + 1); cell* left = getCell(x - 1, y); cell* right = getCell(x + 1, y); if (up != nullptr && up->isPassable()) { neighbors[count] = up; count++;} if (down != nullptr && down->isPassable()) { neighbors[count] = down; count++;} if (left != nullptr && left->isPassable()) { neighbors[count] = left; count++;} if (right != nullptr && right->isPassable()) { neighbors[count] = right; count++;} neighbors[count] = nullptr; return neighbors; } cell* getStart() {return start;} cell* getExit() {return exit;} int getWidth() {return width;} int getHeight() {return height;} }; class MazeBuilder { public: virtual maze* buildFromFile(const std::string& filename) = 0; virtual ~MazeBuilder() {} }; class TextFileMazeBuilder : public MazeBuilder { public: maze* buildFromFile(const std::string& filename) override { std::ifstream file(filename); if (!file.is_open()) throw std::runtime_error("Ошибка: Не удалось открыть файл!"); std::string line; int width = 0; int height = 0; // ДОБАВИЛ: первый проход по файлу. // Здесь узнаём ширину и высоту лабиринта. while (std::getline(file, line)) { if (height == 0) { width = line.length(); } else { if (line.length() != width) throw std::runtime_error("Ошибка: строки лабиринта разной длины!"); } height++; } if (width == 0 || height == 0) { throw std::runtime_error("Ошибка: файл пустой!"); } file.clear(); file.seekg(0); maze* labirint = new maze(width, height); bool hasStart = false; bool hasExit = false; int y = 0; while (std::getline(file, line)) { for (int x = 0; x < width; x++) { char ch = line[x]; bool isWall = false; bool isStart = false; bool isExit = false; switch(ch){ case '#': isWall = true; break; case ' ': isWall = false; break; case 'S': isStart = true; if (hasStart) throw std::runtime_error("Ошибка: в лабиринте больше одного старта!"); hasStart = true; break; case 'E': isExit = true; if (hasExit) throw std::runtime_error("Ошибка: в лабиринте больше одного выхода!"); hasExit = true; break; default: throw std::runtime_error("Ошибка: неизвестный символ в файле!"); break; } cell current(x, y, isWall, isExit, isStart); labirint->setCell(x, y, current); } y++; } file.close(); if (!hasStart) throw std::runtime_error("Ошибка: в лабиринте нет старта!"); if (!hasExit) throw std::runtime_error("Ошибка: в лабиринте нет выхода!"); return labirint; } // прочитать символы // создать клетки // вернуть готовый Maze } }; class PathFindingStrategy { public: virtual cell** findPath(maze* m, cell* start, cell* exit) = 0; virtual int getVisitedCells() = 0; /*для посещенных клеток*/ virtual ~PathFindingStrategy() {} }; class PathBuilder { public: static cell** buildPath(cell* start, cell* exit, cell*** parent) { int length = 0; cell* current = exit; while (current != nullptr) { length++; if (current == start) break; current = parent[current->getX()][current->getY()]; } cell** path = new cell*[length + 1]; current = exit; for (int i = length - 1; i >= 0; i--) { path[i] = current; if (current == start) break; current = parent[current->getX()][current->getY()]; } path[length] = nullptr; return path; } }; class BFSStrategy : public PathFindingStrategy { private: int visitedCells; public: BFSStrategy() {visitedCells = 0;} int getVisitedCells() override {return visitedCells;} cell** findPath(maze* m, cell* start, cell* exit) override { visitedCells = 0; int width = m->getWidth(); int height = m->getHeight(); bool** visited = new bool*[width]; cell*** parent = new cell**[width]; for (int x = 0; x < width; x++) { visited[x] = new bool[height]; parent[x] = new cell*[height]; for (int y = 0; y < height; y++) { visited[x][y] = false; parent[x][y] = nullptr; } } cell** deque = new cell*[width * height]; int head = 0; int tail = 0; deque[tail] = start; tail++; visited[start->getX()][start->getY()] = true; bool found = false; while (head < tail) { cell* current = deque[head]; head++; visitedCells++; if (current == exit) { //сравниваются указатели found = true; break; } cell** neighbors = m->getNeighbors(current); for (int i = 0; neighbors[i] != nullptr; i++) { cell* next = neighbors[i]; int nx = next->getX(); int ny = next->getY(); if (!visited[nx][ny]) { visited[nx][ny] = true; parent[nx][ny] = current; deque[tail] = next; tail++; } } delete[] neighbors; } cell** path; if (found) { path = PathBuilder::buildPath(start, exit, parent); } else { path = new cell*[1]; path[0] = nullptr; } delete[] deque; for (int x = 0; x < width; x++) { delete[] visited[x]; delete[] parent[x]; } delete[] visited; delete[] parent; return path; } }; class DFSStrategy : public PathFindingStrategy { private: int visitedCells; public: DFSStrategy() {visitedCells = 0;} int getVisitedCells() override {return visitedCells;} cell** findPath(maze* m, cell* start, cell* exit) override { visitedCells = 0; int width = m->getWidth(); int height = m->getHeight(); bool** visited = new bool*[width]; cell*** parent = new cell**[width]; for (int x = 0; x < width; x++) { visited[x] = new bool[height]; parent[x] = new cell*[height]; for (int y = 0; y < height; y++) { visited[x][y] = false; parent[x][y] = nullptr; } } cell** stack = new cell*[width * height]; int top = 0; stack[top] = start; top++; visited[start->getX()][start->getY()] = true; bool found = false; while (top > 0) { top--; cell* current = stack[top]; visitedCells++; if (current == exit) { //сравниваются указатели found = true; break; } cell** neighbors = m->getNeighbors(current); for (int i = 0; neighbors[i] != nullptr; i++) { cell* next = neighbors[i]; int nx = next->getX(); int ny = next->getY(); if (!visited[nx][ny]) { visited[nx][ny] = true; parent[nx][ny] = current; stack[top] = next; top++; } } delete[] neighbors; } cell** path; if (found) { path = PathBuilder::buildPath(start, exit, parent); } else { path = new cell*[1]; path[0] = nullptr; } delete[] stack; for (int x = 0; x < width; x++) { delete[] visited[x]; delete[] parent[x]; } delete[] visited; delete[] parent; return path; } }; class AStarStrategy : public PathFindingStrategy { private: int heuristic(cell* current, cell* exit) {return std::abs(current->getX() - exit->getX()) + std::abs(current->getY() - exit->getY());} int visitedCells; public: AStarStrategy() {visitedCells = 0;} int getVisitedCells() override {return visitedCells;} cell** findPath(maze* m, cell* start, cell* exit) override { visitedCells = 0; int width = m->getWidth(); int height = m->getHeight(); bool** closed = new bool*[width]; /*клетка [x][y] посещена (да/нет)*/ bool** inOpen = new bool*[width]; /*клетка [x][y] имеет потенциал к посещению (да/нет)*/ int** gScore = new int*[width]; /* до клетка [x][y] от старта gSchore[x][y] шагов*/ int** fScore = new int*[width]; /*f = h + g, где h - эвристика клетки[x][y]*/ cell*** parent = new cell**[width]; for (int x = 0; x < width; x++) { closed[x] = new bool[height]; inOpen[x] = new bool[height]; gScore[x] = new int[height]; fScore[x] = new int[height]; parent[x] = new cell*[height]; for (int y = 0; y < height; y++) { closed[x][y] = false; inOpen[x][y] = false; gScore[x][y] = width * height + 100000; /*тупо большое число чтоб было больше чем клеток в лаберинте*/ fScore[x][y] = width * height + 100000; parent[x][y] = nullptr; } } cell** open = new cell*[width * height]; /*клетки с потенциалом на посещение*/ int openCount = 0; /*это количество потенц клеток, а также индекс следующего незанятого места*/ int sx = start->getX(); int sy = start->getY(); gScore[sx][sy] = 0; fScore[sx][sy] = heuristic(start, exit); open[openCount] = start; openCount++; inOpen[sx][sy] = true; bool found = false; while (openCount > 0) { int bestIndex = 0; for (int i = 1; i < openCount; i++) { int ix = open[i]->getX(); int iy = open[i]->getY(); int bx = open[bestIndex]->getX(); int by = open[bestIndex]->getY(); if (fScore[ix][iy] < fScore[bx][by]) { bestIndex = i; /*(fSchore наименьший в [bestIndex])*/ } } cell* current = open[bestIndex]; if (current == exit) { found = true; visitedCells++; /*чтоб выход засчитывался*/ break; } int cx = current->getX(); int cy = current->getY(); open[bestIndex] = open[openCount - 1]; openCount--; inOpen[cx][cy] = false; closed[cx][cy] = true; visitedCells++; cell** neighbors = m->getNeighbors(current); for (int i = 0; neighbors[i] != nullptr; i++) { cell* next = neighbors[i]; int nx = next->getX(); int ny = next->getY(); if (closed[nx][ny]) { continue; } int tentativeG = gScore[cx][cy] + 1; if (tentativeG < gScore[nx][ny]) { parent[nx][ny] = current; gScore[nx][ny] = tentativeG; fScore[nx][ny] = gScore[nx][ny] + heuristic(next, exit); if (!inOpen[nx][ny]) { open[openCount] = next; openCount++; inOpen[nx][ny] = true; } } } delete[] neighbors; } cell** path; if (found) { path = PathBuilder::buildPath(start, exit, parent); } else { path = new cell*[1]; path[0] = nullptr; } delete[] open; for (int x = 0; x < width; x++) { delete[] closed[x]; delete[] inOpen[x]; delete[] gScore[x]; delete[] fScore[x]; delete[] parent[x]; } delete[] closed; delete[] inOpen; delete[] gScore; delete[] fScore; delete[] parent; return path; } }; class SearchStats { public: double timeMs; int visitedCells; int pathLength; SearchStats(double timeMs, int visitedCells, int pathLength) { this->timeMs = timeMs; this->visitedCells = visitedCells; this->pathLength = pathLength; } }; class MazeSolver{ private: maze* labirint; PathFindingStrategy* strategy; public: MazeSolver(maze* labirint) {this->labirint = labirint; this->strategy = nullptr;} void setStrategy(PathFindingStrategy* strategy){ this->strategy = strategy; } SearchStats solve(){ auto start = std::chrono::high_resolution_clock::now(); cell** path = strategy->findPath(labirint,labirint->getStart(),labirint->getExit()); auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration duration = end - start; int pathLength = 0; if (path[0] != nullptr) while (path[pathLength] != nullptr) {pathLength++;} int visitedCells = 0; visitedCells = strategy->getVisitedCells(); delete[] path; SearchStats stats(duration.count(), visitedCells, pathLength); return stats; } }; class Player{ private: cell* current; public: Player(cell* current) {this->current = current;} cell* getCurrent() {return current;} void moveTo(cell* cell) {this->current = cell;} }; class Direction{ private: int dx; int dy; public: Direction(int dx, int dy) { this->dx = dx; this->dy = dy; } int getDx() {return dx;} int getDy() {return dy;} }; class Command { public: virtual void execute() = 0; virtual void undo() = 0; virtual ~Command() {} }; class MoveCommand : public Command{ private: Player* player; Direction dir; cell* previousCell; maze* labirint; public: MoveCommand(Player* player, maze* labirint, Direction dir) : dir(dir) { this->player = player; this->labirint = labirint; this->previousCell = nullptr; } void execute() override { cell* currentCell = player->getCurrent(); int newX = currentCell->getX() + dir.getDx(); int newY = currentCell->getY() + dir.getDy(); cell* nextCell = labirint->getCell(newX, newY); if (nextCell != nullptr && nextCell->isPassable()) { previousCell = currentCell; player->moveTo(nextCell); } else { std::cout << "Нельзя сделать ход!" << std::endl; } } void undo() override { if (previousCell != nullptr) { player->moveTo(previousCell); previousCell = nullptr; } } }; class ConsolController{ private: maze* labirint; Player* player; Command* lastCommand; /*указатель на последнюю команду(на объект класса command)*/ bool running; public: ConsolController(maze* labirint, Player* player) { this->labirint = labirint; this->player = player; this->lastCommand = nullptr; this->running = false; } ~ConsolController() { if (lastCommand != nullptr) { delete lastCommand; } } void run() { running = true; while (running) { clearConsole(); drawMaze(); std::cout << "W/A/S/D - ход, Z - отмена, Q - выход" << std::endl; char ch; std::cin >> ch; handleInput(ch); } } void handleInput(char ch) { switch(ch){ case 'q': case 'Q': running = false; break; case 'z': case 'Z': undoLastMove(); break; default: handleMove(ch); break; } } void handleMove(char ch) { Direction dir(0, 0); switch (ch) { case 'w': case 'W': dir = Direction(0, -1); break; case 's': case 'S': dir = Direction(0, 1); break; case 'a': case 'A': dir = Direction(-1, 0); break; case 'd': case 'D': dir = Direction(1, 0); break; default: return; } if (lastCommand != nullptr) { delete lastCommand; lastCommand = nullptr; } lastCommand = new MoveCommand(player, labirint, dir); lastCommand->execute(); } void undoLastMove() { if (lastCommand != nullptr) { lastCommand->undo(); delete lastCommand; lastCommand = nullptr; /*можно отменить только одну команду назад, указатель делаем 0, чтобы не долбится в отмену уже отмененной команды*/ } } void clearConsole() { #ifdef _WIN32 system("cls"); #else system("clear"); #endif } void drawMaze() { for (int y = 0; y < labirint->getHeight(); y++) { for (int x = 0; x < labirint->getWidth(); x++) { cell* currentCell = labirint->getCell(x, y); if (currentCell == player->getCurrent()) std::cout << "P"; else if (currentCell->getIsWall()) std::cout << "#"; else if (currentCell->getIsStart()) std::cout << "S"; else if (currentCell->getIsExit()) std::cout << "E"; else std::cout << " "; } std::cout << std::endl; } } }; int main(){ }