Загрузка данных
#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; // успешный код возврата
}