2026-rff_mp/SolovevDS/docs/data/data_for_task2/task2.cpp

855 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <fstream>
#include <iostream>
#include <string>
#include <stdexcept> /*для ошибок*/
#include <cmath>
#include <chrono> /*мерит время*/
#include <cstdlib> /*волшебная отрисовка*/
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<double, std::milli> 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(){
}