/
s-evg
/
cpps
Обзор
Документация
Войти
/
s-evg
/
cpps
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
diplom/main.cpp
552 строки
24 KB
s-evg
diplom: внесены правки по результатам проверки эксперта
07 май 2026, 16:46
07 май 2026, 16:46
b1787ce
Код
Авторство
О чём код?
#include <cstdlib> #include <ctime> #include <unistd.h> #include <filesystem> #include <fstream> #include <iostream> #include <string> namespace game_of_life { // Максимальное количество строк игрового поля (без учёта границ). const int MAX_ROWS = 35; // Максимальное количество столбцов игрового поля (без учёта границ). const int MAX_COLS = 35; // Количество строк с учётом граничных клеток (верхняя + нижняя границы). const int ROWS_WITH_BORDERS = MAX_ROWS + 2; // Количество столбцов с учётом граничных клеток (левая + правая границы). const int COLS_WITH_BORDERS = MAX_COLS + 2; // Максимальное количество поколений, после которого симуляция останавливается. const int MAX_GENERATIONS = 100; // Заменить, если отображаются кракозябры // true = эмодзи, false = ASCII (* и -) const bool USE_EMOJI_RENDERING = true; /** * Структура, представляющая игровую вселенную (поле) в игре "Жизнь". * * Хранит двумерный массив клеток, где каждая клетка может быть: * -1 – граничная клетка (не используется в расчётах), * 0 – мёртвая клетка, * 1 – живая клетка. * Поле включает дополнительные граничные клетки для упрощения подсчёта соседей. * * @var cells Двумерный массив размером [ROWS_WITH_BORDERS][COLS_WITH_BORDERS]. * @var rows Количество строк игрового поля (без границ). * @var cols Количество столбцов игрового поля (без границ). */ struct Universe { int cells[ROWS_WITH_BORDERS][COLS_WITH_BORDERS]; int rows; int cols; }; /** * Инициализирует граничные клетки и очищает внутреннюю область вселенной. * * Устанавливает значения -1 в клетки, образующие границу поля (верхний и нижний ряды, * левый и правый столбцы). Внутренние клетки (игровое поле) заполняются нулями. * Функция должна вызываться после установки rows и cols в структуре Universe. * * @param universe Ссылка на структуру Universe, которую нужно инициализировать. */ void initializeBorders(Universe& universe) { for (int c = 0; c < COLS_WITH_BORDERS; ++c) { universe.cells[0][c] = -1; universe.cells[MAX_ROWS + 1][c] = -1; } for (int r = 0; r < ROWS_WITH_BORDERS; ++r) { universe.cells[r][0] = -1; universe.cells[r][MAX_COLS + 1] = -1; } for (int r = 1; r <= universe.rows; ++r) { for (int c = 1; c <= universe.cols; ++c) { universe.cells[r][c] = 0; } } } /** * Ищет в текущей директории ровно один файл с расширением .txt. * * Сканирует текущую рабочую директорию, подсчитывает количество .txt файлов. * Если файлов нет или их больше одного, выводит сообщение об ошибке и возвращает false. * Если найден ровно один .txt файл, записывает его имя в outFilename и возвращает true. * * @param outFilename Ссылка на строку, в которую будет записано имя найденного файла. * @return true, если найден ровно один .txt файл, иначе false. */ bool findSingleTxtFile(std::string& outFilename) { std::string foundFile = ""; int txtCount = 0; for (const auto& entry : std::filesystem::directory_iterator(".")) { if (entry.is_regular_file() && entry.path().extension() == ".txt") { ++txtCount; foundFile = entry.path().filename().string(); } } if (txtCount == 0) { std::cerr << "Ошибка: в текущей директории нет .txt файлов\n"; return false; } if (txtCount > 1) { std::cerr << "Ошибка: в директории найдено " << txtCount << " .txt файлов. Оставьте ровно один.\n"; return false; } outFilename = foundFile; return true; } /** * Загружает начальную конфигурацию вселенной из текстового файла и выполняет валидацию. * * Формат файла: * - Первая строка: два целых числа rows cols (размеры поля без границ) * - Последующие строки: пары координат row col (живые клетки) * * Функция проверяет: * - Возможность открытия файла * - Корректность формата первой строки * - Соответствие размеров поля ограничениям MAX_ROWS и MAX_COLS * - Валидность координат (в пределах поля) * - Отсутствие дубликатов координат (предупреждение) * - Наличие хотя бы одной живой клетки * * В случае успеха инициализирует структуру Universe, устанавливает границы * и заполняет живые клетки. * * @param universe Ссылка на структуру Universe, которую нужно заполнить. * @param filename Имя файла с конфигурацией. * @return true, если файл успешно загружен и прошёл все проверки, иначе false. */ bool loadAndValidateFile(Universe& universe, const std::string& filename) { std::ifstream file(filename); if (!file.is_open()) { std::cerr << "Ошибка: не удалось открыть файл " << filename << "\n"; return false; } int inputRows = 0; int inputCols = 0; if (!(file >> inputRows >> inputCols)) { std::cerr << "Ошибка: неверный формат (первая строка должна содержать два числа)\n"; return false; } // Ограничиваем размеры файла максимальными допустимыми значениями if (inputRows <= 0) inputRows = 1; if (inputCols <= 0) inputCols = 1; if (inputRows > MAX_ROWS) inputRows = MAX_ROWS; if (inputCols > MAX_COLS) inputCols = MAX_COLS; universe.rows = inputRows; universe.cols = inputCols; initializeBorders(universe); int row = 0; int col = 0; int validCells = 0; while (file >> row >> col) { if (row < 0 || row >= universe.rows || col < 0 || col >= universe.cols) { std::cerr << "Предупреждение: координаты (" << row << ", " << col << ") за пределами поля\n"; continue; } if (universe.cells[row + 1][col + 1] == 1) { std::cerr << "Предупреждение: дубликат координат (" << row << ", " << col << ")\n"; continue; } universe.cells[row + 1][col + 1] = 1; ++validCells; } if (validCells == 0) { std::cerr << "Ошибка: в файле не задано ни одной живой клетки\n"; return false; } file.close(); return true; } /** * Загружает начальную конфигурацию из файла start.txt (простой режим). * * Вызывает loadAndValidateFile с фиксированным именем файла "start.txt". * Используется в режиме выбора 2 (простой режим). * * @param universe Ссылка на структуру Universe для заполнения. * @return true, если файл успешно загружен, иначе false. */ bool loadSimple(Universe& universe) { return loadAndValidateFile(universe, "start.txt"); } /** * Загружает начальную конфигурацию из единственного .txt файла в текущей директории (продвинутый режим). * * Сначала вызывает findSingleTxtFile для поиска подходящего файла, * затем загружает его с помощью loadAndValidateFile. * Используется в режиме выбора 3 (продвинутый режим). * * @param universe Ссылка на структуру Universe для заполнения. * @return true, если файл найден и успешно загружен, иначе false. */ bool loadAdvanced(Universe& universe) { std::string filename = ""; if (!findSingleTxtFile(filename)) { return false; } return loadAndValidateFile(universe, filename); } /** * Генерирует случайное начальное состояние вселенной (режим случайного заполнения). * * Устанавливает размер поля на максимально допустимый (MAX_ROWS x MAX_COLS), * инициализирует границы, затем случайным образом размещает живые клетки. * Количество живых клеток выбирается равномерно из диапазона [minLive, maxLive]. * Координаты каждой клетки выбираются случайно внутри игрового поля. * * @param universe Ссылка на структуру Universe, которую нужно заполнить. * @param minLive Минимальное количество живых клеток. * @param maxLive Максимальное количество живых клеток. */ void generateRandomLiveCells(Universe& universe, int minLive, int maxLive) { universe.rows = MAX_ROWS; universe.cols = MAX_COLS; initializeBorders(universe); int targetCount = minLive + std::rand() % (maxLive - minLive + 1); for (int i = 0; i < targetCount; ++i) { int row = 1 + std::rand() % MAX_ROWS; int col = 1 + std::rand() % MAX_COLS; universe.cells[row][col] = 1; } } /** * Подсчитывает количество живых клеток во вселенной. * * Обходит внутреннюю область игрового поля (без граничных клеток) * и считает клетки со значением 1. * * @param universe Константная ссылка на структуру Universe. * @return Количество живых клеток. */ int countLiveCells(const Universe& universe) { int liveCount = 0; for (int r = 1; r <= universe.rows; ++r) { for (int c = 1; c <= universe.cols; ++c) { if (universe.cells[r][c] == 1) { ++liveCount; } } } return liveCount; } /** * Выводит текущее состояние вселенной в консоль. * * Отображает номер поколения, количество живых клеток, * а затем графическое представление поля с использованием эмодзи: * 🟩 – живая клетка, * ⬛️ – мёртвая клетка. * * @param universe Константная ссылка на структуру Universe. * @param generation Номер текущего поколения. */ void printUniverse(const Universe& universe, int generation) { int liveCount = countLiveCells(universe); std::cout << "Поколение: " << generation << "\n"; std::cout << "Живых клеток: " << liveCount << "\n"; for (int r = 1; r <= universe.rows; ++r) { for (int c = 1; c <= universe.cols; ++c) { // if (universe.cells[r][c] == 1) { // std::cout << "🟩"; // } else { // std::cout << "⬛️"; // } if (universe.cells[r][c] == 1) { std::cout << (USE_EMOJI_RENDERING ? "🟩" : "*"); } else { std::cout << (USE_EMOJI_RENDERING ? "⬛️" : "-"); } } std::cout << "\n"; } } /** * Подсчитывает количество живых соседей у заданной клетки. * * Рассматривает восемь соседних клеток (окрестность Мура) вокруг клетки (row, col). * Игнорирует саму клетку. Использует граничные клетки со значением -1, * которые никогда не являются живыми, поэтому не влияют на подсчёт. * * @param universe Константная ссылка на структуру Universe. * @param row Координата строки клетки (уже с учётом границ, т.е. от 1 до rows). * @param col Координата столбца клетки (уже с учётом границ, т.е. от 1 до cols). * @return Количество живых соседей (от 0 до 8). */ int countLiveNeighbors(const Universe& universe, int row, int col) { int count = 0; for (int dr = -1; dr <= 1; ++dr) { for (int dc = -1; dc <= 1; ++dc) { if (dr == 0 && dc == 0) { continue; } if (universe.cells[row + dr][col + dc] == 1) { ++count; } } } return count; } /** * Вычисляет следующее поколение вселенной по правилам игры "Жизнь". * * Правила: * - Живая клетка выживает, если у неё 2 или 3 живых соседа, иначе погибает. * - Мёртвая клетка оживает, если у неё ровно 3 живых соседа. * * Функция инициализирует граничные клетки в структуре next значением -1, * затем для каждой внутренней клетки вычисляет количество живых соседей * и определяет её состояние в следующем поколении. * * @param current Константная ссылка на текущее состояние вселенной. * @param next Ссылка на структуру Universe, в которую будет записано следующее состояние. */ void computeNextGeneration(const Universe& current, Universe& next) { for (int c = 0; c < COLS_WITH_BORDERS; ++c) { next.cells[0][c] = -1; next.cells[MAX_ROWS + 1][c] = -1; } for (int r = 0; r < ROWS_WITH_BORDERS; ++r) { next.cells[r][0] = -1; next.cells[r][MAX_COLS + 1] = -1; } for (int r = 1; r <= current.rows; ++r) { for (int c = 1; c <= current.cols; ++c) { int neighbors = countLiveNeighbors(current, r, c); if (current.cells[r][c] == 1) { next.cells[r][c] = (neighbors == 2 || neighbors == 3) ? 1 : 0; } else { next.cells[r][c] = (neighbors == 3) ? 1 : 0; } } } } /** * Копирует состояние вселенной из src в dst. * * Копирует весь двумерный массив cells (включая граничные клетки), * а также поля rows и cols. * * @param src Константная ссылка на исходную вселенную. * @param dst Ссылка на целевую вселенную, которая будет перезаписана. */ void copyUniverse(const Universe& src, Universe& dst) { for (int r = 0; r < ROWS_WITH_BORDERS; ++r) { for (int c = 0; c < COLS_WITH_BORDERS; ++c) { dst.cells[r][c] = src.cells[r][c]; } } dst.rows = src.rows; dst.cols = src.cols; } /** * Проверяет, является ли конфигурация стабильной (не изменяется за одно поколение). * * Сравнивает внутренние клетки текущего и следующего поколений. * Если все клетки совпадают, конфигурация считается стабильной. * * @param current Константная ссылка на текущее состояние вселенной. * @param next Константная ссылка на следующее состояние вселенной. * @return true, если конфигурация стабильна, иначе false. */ bool isStable(const Universe& current, const Universe& next) { for (int r = 1; r <= current.rows; ++r) { for (int c = 1; c <= current.cols; ++c) { if (current.cells[r][c] != next.cells[r][c]) { return false; } } } return true; } // Кроссплатформенная очистка экрана void clearScreen() { #ifdef _WIN32 std::system("cls"); #else std::system("clear"); #endif } } // namespace game_of_life /** * Главная функция программы "Игра Жизнь". * * Предоставляет пользователю циклическое меню с выбором режима инициализации поля: * 0. Выход из программы * 1. Случайное заполнение (30–100 живых клеток) * 2. Простой режим (загрузка из файла start.txt) * 3. Продвинутый режим (автопоиск единственного .txt файла в текущей директории) * 4. Справка по режимам и правилам игры * * После успешной инициализации запускает симуляцию поколений с визуализацией в консоли. * Симуляция продолжается до тех пор, пока: * - Все клетки не погибнут, * - Не будет достигнут лимит поколений (MAX_GENERATIONS), * - Конфигурация не станет стабильной (не изменяется за одно поколение). * * Каждое поколение выводится на экран с задержкой 1 секунда. * По завершении выводится итоговое состояние, причина остановки и пауза для возврата в меню. * * @return 0 при успешном завершении, 1 при критической ошибке ввода. */ int main() { while (true) { std::cout << "Выберите режим запуска:\n"; std::cout << "0. Выход\n"; std::cout << "1. Случайное заполнение\n"; std::cout << "2. Простой режим (файл start.txt)\n"; std::cout << "3. Продвинутый режим (автопоиск .txt + валидация)\n"; std::cout << "4. Справка\n"; std::cout << "Введите номер: "; int choice = 0; if (!(std::cin >> choice)) { std::cin.clear(); std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); std::cerr << "Ошибка: введено не число. Попробуйте снова.\n"; continue; } if (choice == 0) { std::cout << "Выход из программы.\n"; break; } if (choice == 4) { std::cout << "Справка по игре \"Жизнь\" и режимам работы:\n"; std::cout << " 1. Генерация случайного поля 20x30 с 30-100 живыми клетками.\n"; std::cout << " 2. Загрузка конфигурации из файла start.txt.\n"; std::cout << " 3. Автопоиск единственного .txt файла с полной проверкой формата.\n"; std::cout << " Правила: ровно 3 соседа - рождение, 2 или 3 - жизнь, иначе смерть.\n"; std::cout << " Визуализация: 🟩 (живая), ⬛️ (мёртвая). Лимит: 100 поколений.\n"; continue; } game_of_life::Universe current = {}; game_of_life::Universe next = {}; bool initSuccess = false; switch (choice) { case 1: game_of_life::generateRandomLiveCells(current, 30, 100); initSuccess = true; break; case 2: initSuccess = game_of_life::loadSimple(current); break; case 3: initSuccess = game_of_life::loadAdvanced(current); break; default: std::cerr << "Неверный выбор. Попробуйте снова.\n"; continue; } if (!initSuccess) { std::cerr << "Ошибка инициализации поля.\n"; continue; } next.rows = current.rows; next.cols = current.cols; game_of_life::initializeBorders(next); std::srand(static_cast<unsigned int>(std::time(nullptr))); int generation = 1; std::string reason = ""; while (true) { game_of_life::clearScreen(); game_of_life::printUniverse(current, generation); if (game_of_life::countLiveCells(current) == 0) { reason = "Все клетки погибли"; break; } if (generation >= game_of_life::MAX_GENERATIONS) { reason = "Достигнут лимит поколений"; break; } game_of_life::computeNextGeneration(current, next); // if (game_of_life::isStable(current, next)) { // reason = "Стабильная конфигурация"; // game_of_life::copyUniverse(next, current); // break; // } if (game_of_life::isStable(current, next)) { reason = "Стабильная конфигурация"; game_of_life::copyUniverse(next, current); ++generation; // Стабильное состояние считается новым поколением break; } game_of_life::copyUniverse(next, current); ++generation; sleep(1); } game_of_life::clearScreen(); game_of_life::printUniverse(current, generation); std::cout << "\nИгра завершена. Причина: " << reason << "\n"; std::cout << "Нажмите Enter для возврата в меню..."; std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); std::cin.get(); } return 0; }