Загрузка данных


#include <ncurses.h>  // библиотека ncurses — ASCII-графика в терминале (initscr, getch, mvaddch, refresh, endwin, napms)
#include <stdio.h>    // стандартный ввод/вывод: scanf, getchar, fprintf, freopen
#include <stdlib.h>   // стандартная библиотека (общие функции)
#include <string.h>   // строковые/памятные функции: memcpy

#define HEIGHT 25       // высота поля — 25 строк (по ТЗ)
#define WIDTH 80        // ширина поля — 80 столбцов (по ТЗ)
#define DELAY_STEP 20   // шаг изменения задержки при нажатии A/Z, мс
#define MIN_DELAY 20    // минимальная задержка (макс. скорость) — 20 мс/тик
#define MAX_DELAY 1000  // максимальная задержка (мин. скорость) — 1000 мс/тик

// Функция count_neighbors: считает число живых соседей у клетки (y, x).
// Параметры: field — игровое поле, y, x — координаты клетки.
// Возвращает: количество живых соседей (0..8).
// Края замыкаются через % HEIGHT / % WIDTH — торроидальность.
static int count_neighbors(int field[HEIGHT][WIDTH], int y, int x) {
    int count = 0;                          // счётчик живых соседей
    for (int dy = -1; dy <= 1; dy++) {      // смещение по вертикали: -1, 0, +1
        for (int dx = -1; dx <= 1; dx++) {  // смещение по горизонтали: -1, 0, +1
            if (dy == 0 && dx == 0) continue;        // пропускаем саму клетку — она не сосед
            int ny = (y + dy + HEIGHT) % HEIGHT;     // координата соседа по Y с замыканием краёв
            int nx = (x + dx + WIDTH) % WIDTH;       // координата соседа по X с замыканием краёв
            count += field[ny][nx];                  // если сосед живой (1) — увеличиваем счётчик
        }
    }
    return count;                           // возвращаем число живых соседей
}

// Функция next_generation: вычисляет следующее поколение по правилам Конвея.
// Параметры: cur — текущее поколение (читаем), nxt — следующее (пишем).
// Правила: живая с 2-3 соседями выживает; мёртвая с 3 — рождается; остальные умирают/остаются мёртвыми.
static void next_generation(int cur[HEIGHT][WIDTH], int nxt[HEIGHT][WIDTH]) {
    for (int y = 0; y < HEIGHT; y++) {              // проходим все строки поля
        for (int x = 0; x < WIDTH; x++) {           // проходим все столбцы поля
            int n = count_neighbors(cur, y, x);     // считаем живых соседей у клетки (y, x)
            if (cur[y][x]) {                        // если клетка СЕЙЧАС живая
                nxt[y][x] = (n == 2 || n == 3) ? 1 : 0;  // выживает при 2 или 3 соседях, иначе умирает
            } else {                                // если клетка СЕЙЧАС мёртвая
                nxt[y][x] = (n == 3) ? 1 : 0;            // рождается ровно при 3 соседях, иначе мёртвая
            }
        }
    }
}

// Функция read_state: читает начальное состояние из stdin (файла через <).
// Формат: первая строка "25 80", далее матрица '#'/'.'.
// Возвращает: 0 — успех, 1 — ошибка (неверный размер, EOF).
static int read_state(int field[HEIGHT][WIDTH]) {
    int h = 0, w = 0;                          // размеры из файла
    if (scanf("%d %d", &h, &w) != 2) return 1; // читаем "25 80"; если не 2 числа — ошибка
    if (h != HEIGHT || w != WIDTH) return 1;   // проверяем, что размеры ровно 25×80 (по ТЗ)
    int ch;
    ch = getchar();                            // читаем символ после "80" — это '\n'
    while (ch != '\n' && ch != EOF) ch = getchar();  // добиваем до конца первой строки
    for (int y = 0; y < HEIGHT; y++) {         // по строкам поля
        for (int x = 0; x < WIDTH; x++) {      // по столбцам поля
            ch = getchar();                    // читаем очередной символ
            if (ch == EOF) return 1;           // файл кончился раньше — ошибка
            if (ch == '\n') {                  // если попали на перевод строки (конец строки матрицы)
                x--;                           // откатываем x, чтобы '\n' не считался клеткой
                continue;                      // перечитываем этот x заново
            }
            field[y][x] = (ch == '#') ? 1 : 0; // '#' → живая (1), иначе ('.') → мёртвая (0)
        }
    }
    return 0;                                  // успех
}

// Функция draw_field: рисует игровое поле в терминале через ncurses.
// Параметр: field — поле для отрисовки.
// Строка 0 зарезервирована под статус, поэтому поле начинается с y+1.
static void draw_field(int field[HEIGHT][WIDTH]) {
    for (int y = 0; y < HEIGHT; y++) {         // по строкам поля
        for (int x = 0; x < WIDTH; x++) {      // по столбцам поля
            mvaddch(y + 1, x, field[y][x] ? '#' : '.');  // в (y+1, x) рисуем '#' (живая) или '.' (мёртвая)
        }
    }
}

// Функция draw_status: рисует верхнюю строку статуса (поколение, скорость, подсказки).
// Параметры: gen — номер поколения, delay_ms — текущая задержка в мс.
// refresh() обязателен — без него изменения не появятся на экране.
static void draw_status(int gen, int delay_ms) {
    move(0, 0);                                // курсор в левый верхний угол (строка 0, столбец 0)
    clrtoeol();                                // очистить строку до конца (убрать старый текст)
    mvprintw(0, 0, "Gen: %d  Speed: %d ms  [A]+  [Z]-  [Space] exit", gen, delay_ms);  // печатаем статус
    refresh();                                 // применить все изменения к экрану
}

// Функция handle_input: читает клавиатуру и обрабатывает A/Z/пробел.
// Параметр: delay_ms — указатель на задержку (меняем её в main).
// Возвращает: 1 — выйти из игры (пробел), 0 — продолжать.
// Цикл while дренирует ВЕСЬ буфер getch() за тик — пробел не теряется между тиками.
static int handle_input(int *delay_ms) {
    int quit = 0;                              // флаг "выйти из игры"
    int ch;
    while ((ch = getch()) != ERR) {            // читаем все нажатия за тик; ERR = нажатий нет
        if (ch == ' ') {                       // пробел — завершение игры
            quit = 1;
        } else if (ch == 'a' || ch == 'A') {   // A / a — ускорить
            *delay_ms -= DELAY_STEP;           // уменьшаем задержку
            if (*delay_ms < MIN_DELAY) *delay_ms = MIN_DELAY;  // не ниже 20 мс
        } else if (ch == 'z' || ch == 'Z') {   // Z / z — замедлить
            *delay_ms += DELAY_STEP;           // увеличиваем задержку
            if (*delay_ms > MAX_DELAY) *delay_ms = MAX_DELAY;  // не выше 1000 мс
        }
    }
    return quit;                               // 1 = выйти, 0 = продолжать
}

// Точка входа программы.
// Читает состояние из stdin, переоткрывает stdin на /dev/tty, запускает ncurses-цикл.
int main(void) {
    int cur[HEIGHT][WIDTH];                    // текущее поколение (матрица 25×80)
    int nxt[HEIGHT][WIDTH];                    // следующее поколение (буфер)
    if (read_state(cur) != 0) {                // читаем начальное состояние из stdin (файла)
        fprintf(stderr, "read_state failed\n");  // ошибка чтения
        return 1;                              // выход с кодом 1
    }
    if (freopen("/dev/tty", "r", stdin) == NULL) {  // переоткрываем stdin на терминал
        fprintf(stderr, "cannot reopen tty\n");     // чтобы getch() читал клавиатуру, а не файл
        return 1;                              // ошибка — выход с кодом 1
    }
    initscr();                                 // инициализация ncurses (переключение терминала в curses-режим)
    cbreak();                                  // отключает построчную буферизацию: символы доступны сразу
    noecho();                                  // не показывать нажатые символы на экране
    keypad(stdscr, TRUE);                      // включить распознавание спецклавиш (стрелки, F1...)
    nodelay(stdscr, TRUE);                     // getch() не блокируется: нет нажатий → возвращает ERR
    curs_set(0);                               // скрыть курсор (0 = невидимый)
    mousemask(0, NULL);                        // отключить обработку мыши (прокрутка колесом не ломает экран)
    int delay_ms = 200;                        // начальная задержка между тиками — 200 мс
    int gen = 0;                               // счётчик поколений
    int running = 1;                           // флаг работы главного цикла
    while (running) {                          // главный цикл игры
        draw_field(cur);                       // рисуем текущее поле
        draw_status(gen, delay_ms);            // рисуем строку статуса
        refresh();                             // применяем изменения на экране
        running = !handle_input(&delay_ms);    // читаем клавиши; пробел → running = 0
        if (!running) break;                   // выход, если пробел нажат
        napms(delay_ms);                       // пауза delay_ms мс (регулирует скорость)
        next_generation(cur, nxt);             // вычисляем следующее поколение в nxt
        memcpy(cur, nxt, sizeof(cur));         // копируем nxt в cur (cur = новое поколение)
        gen++;                                 // увеличиваем счётчик поколений
    }
    endwin();                                  // корректно завершаем ncurses (возвращаем терминал в обычный режим)
    return 0;                                  // успешный код возврата
}