Лесные пожары – явление столь же древнее, сколь и сама жизнь на суше. Величественные и одновременно ужасающие, они способны за считанные часы превратить гектары зеленого массива в выжженную пустыню, неся угрозу экосистемам, человеческим поселениям и климату планеты. Ежегодно новости пестрят сообщениями о новых очагах возгорания, о борьбе стихии и человека. Но что если мы попытаемся заглянуть в самое сердце этого хаотичного, на первый взгляд, процесса? Что если мы сможем не просто наблюдать, а моделировать, предсказывать и даже экспериментировать с распространением огня, не выходя из-за своего компьютера?

К концу этого материала вы не просто поймете, как из простых локальных правил рождается сложное глобальное поведение, но и напишете код, который позволит вам воочию наблюдать за виртуальным лесным пожаром и изменять его параметры. Мы будем использовать популярные библиотеки, такие как NumPy для эффективных вычислений и Pygame для наглядной визуализации нашего цифрового леса и бушующего в нем пламени.

Прежде чем мы бросимся в огонь (пусть и виртуальный), давайте разберемся с нашим основным инструментом моделирования – клеточными автоматами. Звучит немного футуристично, не правда ли? На самом деле, это удивительно простая и изящная математическая концепция, способная описывать невероятно сложные системы.

Представьте себе мир, разделенный на множество одинаковых ячеек или "клеток", образующих сетку (чаще всего двумерную, как шахматная доска, но она может быть и одномерной, и трехмерной, и даже более сложной). Каждая такая клетка в любой момент времени может находиться в одном из нескольких предопределенных состояний. Например, клетка может быть "живой" или "мертвой", "пустой" или "заполненной", "здоровой" или "больной".

Самое интересное начинается, когда мы вводим правила. Состояние каждой клетки на следующем шаге времени (или в следующем поколении) определяется исключительно ее текущим состоянием и состояниями ее ближайших соседей. Эти правила "локальны" – клетка "смотрит" только на свое непосредственное окружение, не имея представления о глобальном состоянии всей системы. Время в мире клеточных автоматов течет дискретными шагами: вся система обновляется одновременно, клетка за клеткой, согласно заданным правилам.

И вот тут-то и происходит магия! Из этих простых, локальных взаимодействий, повторяющихся снова и снова, могут возникать удивительно сложные и разнообразные глобальные паттерны и поведения. Система "эволюционирует" во времени, порождая структуры, которые невозможно было бы предсказать, глядя только на отдельные правила.

Идея клеточных автоматов не нова. Одними из первых ее исследовали математики Джон фон Нейман и Станислав Улам еще в 1940-х годах, размышляя о возможности создания самовоспроизводящихся машин. Однако настоящую популярность клеточные автоматы обрели благодаря британскому математику Джону Хортону Конвею, который в 1970 году придумал игру "Жизнь" (Conway's Game of Life). Это, пожалуй, самый известный пример клеточного автомата. На простой сетке клетки могут быть либо "живыми", либо "мертвыми", а правила их рождения, выживания и смерти зависят лишь от количества живых соседей. Несмотря на простоту правил, "Жизнь" порождает невероятное разнообразие движущихся, пульсирующих, взаимодействующих и даже самовоспроизводящихся структур, завораживая исследователей и энтузиастов по сей день.

С тех пор клеточные автоматы нашли применение в самых разных областях:

  • Физика: моделирование роста кристаллов, распространения жидкостей и газов, фазовых переходов.

  • Биология: изучение роста популяций, распространения эпидемий, формирования паттернов на шкурах животных.

  • Химия: моделирование химических реакций и диффузии.

  • Социология и экономика: исследование распространения мнений, транспортных потоков, развития городов.

  • Информатика и компьютерные игры: генерация процедурного контента (ландшафтов, текстур), создание искусственного интеллекта для игровых персонажей.

Почему же клеточные автоматы так хорошо подходят для моделирования лесных пожаров? Во-первых, лес можно естественным образом представить в виде сетки, где каждая ячейка – это участок определенного типа (например, с деревом или без). Во-вторых, процесс распространения огня по своей природе локален: дерево загорается от соседнего горящего дерева или от искры, прилетевшей с близкого расстояния. Глобальная картина пожара – это результат множества таких локальных взаимодействий. Клеточные автоматы позволяют элегантно и относительно просто описать эти взаимодействия и наблюдать за тем, как из них рождается сложная динамика огненной стихии. Именно этим мы и займемся в следующих разделах, переходя от теории к практике.

Проектируем нашу модель лесного пожара

Прежде чем погрузиться в код, давайте четко определим, как будет устроена наша модель. Это важный этап, который поможет нам структурировать мысли и код.

Состояния клеток: Четыре стихии нашего леса

Каждая ячейка на нашей виртуальной карте леса может находиться в одном из четырех состояний:

  1. ПУСТО (EMPTY), будем обозначать цифрой 0: Это участок земли без какой-либо растительности. Он не может гореть и не участвует в распространении огня. Представьте себе голую землю, скалы или водоем.

  2. ДЕРЕВО (TREE), обозначаем 1: Участок, покрытый деревьями или другой горючей растительностью. Именно эти клетки могут загореться и способствовать распространению пожара.

  3. ГОРИТ (FIRE), обозначаем 2: Клетка, которая в данный момент охвачена огнем. Это активная фаза пожара, и именно такие клетки являются источником его дальнейшего распространения.

  4. СГОРЕЛО (BURNT), обозначаем 3: Участок, где пожар уже прошел. Деревья сгорели, остался лишь пепел. Такие клетки больше не могут гореть и не участвуют в распространении огня (по крайней мере, в нашей базовой модели).

Правила перехода: Как огонь танцует по лесу

Теперь самое интересное – правила, по которым клетки будут менять свои состояния. Эти правила будут применяться на каждом шаге нашей симуляции.

  • Рождение огня (спонтанное возгорание):

    • Клетка ДЕРЕВО может самопроизвольно перейти в состояние ГОРИТ с очень маленькой вероятностью (назовем ее PROB_LIGHTNING). Это имитирует случайные события, такие как удар молнии или неосторожное обращение с огнем.

  • Распространение огня:

    • Клетка ДЕРЕВО переходит в состояние ГОРИТ, если хотя бы одна из ее соседних клеток (мы будем рассматривать 8 соседей – по горизонтали, вертикали и диагоналям) находится в состоянии ГОРИТ. Чтобы сделать процесс более реалистичным и менее детерминированным, мы введем вероятность распространения огня (PROB_FIRE_SPREAD). То есть, даже если рядом горит сосед, наше дерево загорится не со 100% вероятностью, а лишь с вероятностью PROB_FIRE_SPREAD.

  • Выгорание:

    • Клетка ГОРИТ не может гореть вечно. Через определенное количество шагов времени (назовем этот параметр FIRE_DURATION) она переходит в состояние СГОРЕЛО.

  • Стабильные состояния:

    • Клетки в состоянии ПУСТО и СГОРЕЛО не меняют своего состояния в ходе симуляции.

Представление сетки: Наш цифровой лес

Весь наш лес будет представлен в виде двумерной сетки (матрицы). Для эффективной работы с такими структурами в Python идеально подходит библиотека NumPy. Каждая ячейка матрицы будет хранить числовое значение, соответствующее ее состоянию (0, 1, 2 или 3).

Реализация на Python: Шаг за шагом к пылающему лесу

Наконец-то мы добрались до самой интересной части – написания кода! Мы будем использовать Python и несколько популярных библиотек: NumPy для работы с нашей сеткой и Pygame для создания интерактивной визуализации. Если вы предпочитаете Matplotlib, его тоже можно адаптировать для визуализации, но Pygame даст нам больше гибкости для интерактивного управления симуляцией.

Полный код симуляции:

import pygame
import numpy as np
import random
import time

# --- Константы --- 
# Состояния клеток
EMPTY = 0
TREE = 1
FIRE = 2
BURNT = 3

# Цвета для визуализации (RGB)
COLOR_EMPTY = (139, 69, 19)  # Коричневый (земля)
COLOR_TREE = (0, 100, 0)     # Темно-зеленый
COLOR_FIRE = (255, 0, 0)     # Красный
COLOR_BURNT = (105, 105, 105) # Серый (пепел)

# Размеры сетки
GRID_WIDTH = 100  # Количество клеток по горизонтали
GRID_HEIGHT = 100 # Количество клеток по вертикали
CELL_SIZE = 6    # Размер каждой клетки в пикселях

# Параметры симуляции
PROB_INITIAL_TREE = 0.65  # Начальная вероятность того, что клетка является деревом
PROB_FIRE_SPREAD = 0.35   # Вероятность распространения огня на соседнее дерево
PROB_LIGHTNING = 0.00005 # Вероятность того, что дерево загорится само (молния)
FIRE_DURATION = 10        # Сколько шагов клетка остается в огне, прежде чем сгорит

# Размеры окна Pygame
WINDOW_WIDTH = GRID_WIDTH * CELL_SIZE
WINDOW_HEIGHT = GRID_HEIGHT * CELL_SIZE
FPS = 10 # Кадров в секунду для симуляции

# --- Основная симуляция --- 
def main():
    pygame.init()
    screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
    pygame.display.set_caption("Симуляция Лесного Пожара")
    clock = pygame.time.Clock()

    # Инициализация сетки
    grid = np.zeros((GRID_HEIGHT, GRID_WIDTH), dtype=int)
    for r in range(GRID_HEIGHT):
        for c in range(GRID_WIDTH):
            if random.random() < PROB_INITIAL_TREE:
                grid[r, c] = TREE
            else:
                grid[r, c] = EMPTY

    fire_start_times = {} # Словарь для отслеживания времени начала пожара в клетке (r,c) -> time_step
    
    # Установка нескольких начальных очагов возгорания
    for _ in range(max(1, int(GRID_WIDTH * GRID_HEIGHT * 0.001))): # Например, 0.1% клеток
        while True:
            r_fire_init, c_fire_init = random.randint(0, GRID_HEIGHT - 1), random.randint(0, GRID_WIDTH - 1)
            if grid[r_fire_init,c_fire_init] == TREE:
                grid[r_fire_init,c_fire_init] = FIRE
                fire_start_times[(r_fire_init,c_fire_init)] = 0 # Пожар начинается на временном шаге 0
                break
    
    running = True
    time_step = 0
    paused = False

    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE: # Пауза/Возобновление
                    paused = not paused
                if event.key == pygame.K_r: # Сброс симуляции
                    grid = np.zeros((GRID_HEIGHT, GRID_WIDTH), dtype=int)
                    for r_init in range(GRID_HEIGHT):
                        for c_init in range(GRID_WIDTH):
                            if random.random() < PROB_INITIAL_TREE:
                                grid[r_init, c_init] = TREE
                            else:
                                grid[r_init, c_init] = EMPTY
                    fire_start_times = {}
                    for _ in range(max(1, int(GRID_WIDTH * GRID_HEIGHT * 0.001))):
                        while True:
                            r_f, c_f = random.randint(0, GRID_HEIGHT - 1), random.randint(0, GRID_WIDTH - 1)
                            if grid[r_f,c_f] == TREE:
                                grid[r_f,c_f] = FIRE
                                fire_start_times[(r_f,c_f)] = 0
                                break
                    time_step = 0
                    paused = False # Сбрасываем паузу при рестарте
            
            if not paused and event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1: # Левая кнопка мыши - поджечь дерево
                    mx, my = pygame.mouse.get_pos()
                    r_click, c_click = my // CELL_SIZE, mx // CELL_SIZE
                    if 0 <= r_click < GRID_HEIGHT and 0 <= c_click < GRID_WIDTH:
                        if grid[r_click,c_click] == TREE:
                            grid[r_click,c_click] = FIRE
                            fire_start_times[(r_click,c_click)] = time_step
                elif event.button == 3: # Правая кнопка мыши - посадить дерево / потушить (если горит)
                    mx, my = pygame.mouse.get_pos()
                    r_click, c_click = my // CELL_SIZE, mx // CELL_SIZE
                    if 0 <= r_click < GRID_HEIGHT and 0 <= c_click < GRID_WIDTH:
                        if grid[r_click,c_click] == EMPTY or grid[r_click,c_click] == BURNT:
                            grid[r_click,c_click] = TREE
                        elif grid[r_click,c_click] == FIRE: # Тушим огонь
                             grid[r_click,c_click] = TREE # Восстанавливаем до дерева
                             if (r_click,c_click) in fire_start_times:
                                 del fire_start_times[(r_click,c_click)] # Удаляем из горящих

        if not paused:
            new_grid = np.copy(grid) # Работаем с копией, чтобы изменения не влияли на текущий шаг
            
            # Клетки, которые горят в данный момент (копируем ключи, чтобы избежать ошибок при изменении словаря во время итерации)
            # current_fire_coords = list(fire_start_times.keys()) 
            # Этот список не используется напрямую в логике ниже, но может быть полезен для отладки или статистики

            for r in range(GRID_HEIGHT):
                for c in range(GRID_WIDTH):
                    current_state = grid[r,c]

                    if current_state == TREE:
                        # Проверка на удар молнии
                        if random.random() < PROB_LIGHTNING:
                            new_grid[r,c] = FIRE
                            fire_start_times[(r,c)] = time_step
                            continue # Переходим к следующей клетке, т.к. эта уже загорелась
                        
                        # Проверка на распространение от соседей (8 соседей)
                        # Обходим всех 8 соседей
                        for dr in [-1, 0, 1]:
                            for dc in [-1, 0, 1]:
                                if dr == 0 and dc == 0:
                                    continue # Пропускаем саму клетку
                                
                                nr, nc = r + dr, c + dc # Координаты соседа
                                
                                # Проверка границ сетки
                                if 0 <= nr < GRID_HEIGHT and 0 <= nc < GRID_WIDTH:
                                    if grid[nr, nc] == FIRE and random.random() < PROB_FIRE_SPREAD:
                                        new_grid[r,c] = FIRE
                                        fire_start_times[(r,c)] = time_step
                                        break # Дерево загорелось от одного из соседей, выходим из цикла проверки соседей
                            if new_grid[r,c] == FIRE: # Если дерево загорелось, переходим к следующей клетке в основной сетке
                                break 
                    
                    elif current_state == FIRE:
                        # Проверка, не пора ли клетке сгореть
                        # Используем .get() с значением по умолчанию, чтобы избежать ошибки, если ключ был удален (например, при тушении)
                        if (r,c) in fire_start_times and (time_step - fire_start_times.get((r,c), time_step)) >= FIRE_DURATION:
                            new_grid[r,c] = BURNT
                            # Удаляем из словаря, так как клетка сгорела
                            if (r,c) in fire_start_times: # Дополнительная проверка перед удалением
                                del fire_start_times[(r,c)]
            
            grid = new_grid # Обновляем основную сетку результатами текущего шага
            time_step += 1

        # Отрисовка
        screen.fill(COLOR_EMPTY) # Фон по умолчанию - земля (или цвет для EMPTY)
        for r in range(GRID_HEIGHT):
            for c in range(GRID_WIDTH):
                color = COLOR_EMPTY # По умолчанию
                if grid[r,c] == TREE:
                    color = COLOR_TREE
                elif grid[r,c] == FIRE:
                    color = COLOR_FIRE
                elif grid[r,c] == BURNT:
                    color = COLOR_BURNT
                pygame.draw.rect(screen, color, (c * CELL_SIZE, r * CELL_SIZE, CELL_SIZE, CELL_SIZE))
        
        pygame.display.flip() # Обновляем весь экран
        clock.tick(FPS) # Ограничиваем FPS

    pygame.quit()

if __name__ == "__main__":
    main()

Разбор кода:

  1. Импорт библиотек и константы:

    • pygame: для графики и интерактивности.

    • numpy: для работы с сеткой (массивом).

    • random: для случайных событий (начальное распределение деревьев, молнии, распространение огня).

    • time: не используется напрямую в этой версии, но может быть полезен для отладки или задержек.

    • Константы EMPTYTREEFIREBURNT определяют числовые значения для состояний клеток.

    • Цвета COLOR_... задают RGB-значения для каждого состояния.

    • Размеры сетки (GRID_WIDTHGRID_HEIGHTCELL_SIZE) и параметры симуляции (PROB_INITIAL_TREEPROB_FIRE_SPREADPROB_LIGHTNINGFIRE_DURATION) легко настраиваются.

  2. Функция main():

    • pygame.init(): инициализирует все модули Pygame.

    • screen = pygame.display.set_mode(...): создает окно для отображения.

    • pygame.display.set_caption(...): устанавливает заголовок окна.

    • clock = pygame.time.Clock(): используется для контроля FPS.

  3. Инициализация сетки:

    • grid = np.zeros(...): создает NumPy массив, заполненный нулями (состояние EMPTY).

    • Затем в цикле проходим по каждой клетке и с вероятностью PROB_INITIAL_TREE устанавливаем ее состояние в TREE.

    • fire_start_times = {}: этот словарь будет хранить время (шаг симуляции), когда каждая конкретная клетка загорелась. Это нужно, чтобы знать, когда клетка должна перейти в состояние BURNT.

    • Несколько случайных деревьев поджигаются в самом начале, чтобы запустить процесс.

  4. Основной цикл симуляции (while running):

    • Обработка событий:

      • pygame.event.get(): получает все события (нажатия клавиш, мыши, закрытие окна).

      • event.type == pygame.QUIT: если пользователь закрыл окно, running становится False, и цикл завершается.

      • event.key == pygame.K_SPACE: пауза/возобновление симуляции.

      • event.key == pygame.K_r: сброс симуляции к начальному состоянию.

      • Обработка кликов мыши (подробнее в следующем разделе).

    • Логика симуляции (если не на паузе):

      • new_grid = np.copy(grid)Очень важный момент! Все изменения на текущем шаге должны производиться на копии сетки. Если изменять оригинальную сетку grid напрямую, то состояние клетки, обновленное в начале текущего шага, повлияет на расчет состояния ее соседей на этом же шаге, что некорректно. Мы должны рассчитать все новые состояния на основе старых, а затем разом обновить всю сетку.

      • Вложенные циклы for r in range(GRID_HEIGHT): for c in range(GRID_WIDTH): обходят каждую клетку.

      • Если клетка TREE:

        • Проверяем на самовозгорание (PROB_LIGHTNING).

        • Если не загорелась сама, проверяем 8 соседей. Если хотя бы один сосед FIRE и случайное число меньше PROB_FIRE_SPREAD, то текущая клетка становится FIRE в new_grid, и в fire_start_times записывается текущий time_step.

      • Если клетка FIRE:

        • Проверяем, не пора ли ей сгореть. Если разница между текущим time_step и временем начала горения этой клетки (fire_start_times.get((r,c), time_step)) больше или равна FIRE_DURATION, то клетка становится BURNT в new_grid, и удаляется из fire_start_times.

      • grid = new_grid: после обхода всех клеток обновляем основную сетку.

      • time_step += 1: увеличиваем счетчик времени.

    • Отрисовка:

      • screen.fill(COLOR_EMPTY): очищаем экран (заливаем цветом земли).

      • Вложенные циклы обходят сетку, и для каждой клетки рисуется прямоугольник (pygame.draw.rect) соответствующего цвета.

      • pygame.display.flip(): обновляет содержимое всего экрана, чтобы показать нарисованное.

      • clock.tick(FPS): делает паузу, чтобы поддерживать заданный FPS.

    • pygame.quit(): корректно завершает работу Pygame при выходе из цикла.

      Наша симуляция уже работает, но настоящее веселье начинается, когда мы можем с ней взаимодействовать! Pygame предоставляет для этого все необходимые инструменты.

Управление симуляцией:

В основном цикле, в блоке обработки событий, мы уже добавили:

  • Пауза/Возобновление (Пробел):

    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_SPACE:
            paused = not paused
    

    Это позволяет остановить симуляцию в любой момент, чтобы рассмотреть текущее состояние, а затем продолжить.

  • Сброс симуляции (Клавиша R):

    if event.key == pygame.K_r:
        # ... (код для реинициализации сетки и fire_start_times)
        time_step = 0
        paused = False # Сбрасываем паузу при рестарте

    Это возвращает лес к первоначальному случайному состоянию с новыми очагами возгорания.

Взаимодействие с пользователем (мышью):

Мы можем позволить пользователю самому влиять на ход пожара:

  • Поджечь дерево (Левая кнопка мыши):

    if not paused and event.type == pygame.MOUSEBUTTONDOWN:
        if event.button == 1: # Левая кнопка мыши
            mx, my = pygame.mouse.get_pos()
            r_click, c_click = my // CELL_SIZE, mx // CELL_SIZE # Преобразуем координаты мыши в индексы сетки
            if 0 <= r_click < GRID_HEIGHT and 0 <= c_click < GRID_WIDTH: # Проверка, что клик внутри сетки
                if grid[r_click,c_click] == TREE: # Если кликнули по дереву
                    grid[r_click,c_click] = FIRE # Поджигаем его
                    fire_start_times[(r_click,c_click)] = time_step # Запоминаем время возгорания
    
  • Посадить дерево / Потушить огонь (Правая кнопка мыши):

    elif event.button == 3: # Правая кнопка мыши
        mx, my = pygame.mouse.get_pos()
        r_click, c_click = my // CELL_SIZE, mx // CELL_SIZE
        if 0 <= r_click < GRID_HEIGHT and 0 <= c_click < GRID_WIDTH:
            if grid[r_click,c_click] == EMPTY or grid[r_click,c_click] == BURNT: # Если пусто или сгорело
                grid[r_click,c_click] = TREE # Сажаем дерево
            elif grid[r_click,c_click] == FIRE: # Если горит
                 grid[r_click,c_click] = TREE # Тушим (превращаем обратно в дерево)
                 if (r_click,c_click) in fire_start_times:
                     del fire_start_times[(r_click,c_click)] # Удаляем из списка горящих
    

Теперь вы можете активно вмешиваться в процесс: создавать новые очаги пожара, чтобы посмотреть, как они будут распространяться, или, наоборот, пытаться остановить огонь, превращая горящие участки обратно в деревья (или в сгоревшие, если хотите более реалистичного тушения).

Улучшение графики (идеи):

Хотя наши цветные квадраты функциональны, визуализацию можно сделать и поприятнее:

  • Более естественные цвета: Поэкспериментируйте с оттенками зеленого для деревьев, оранжевого и желтого для огня, темно-серого для пепла.

  • Простые спрайты: Вместо заливки цветом можно рисовать небольшие иконки (спрайты) для каждого состояния. Pygame позволяет легко загружать и отображать изображения.

  • Статистика на экране: Можно выводить на экран количество горящих клеток, процент сгоревшей территории и т.д. Для этого понадобится использовать функции Pygame для рендеринга текста (pygame.font).

Обсуждение возможностей и ограничений модели:

Наша модель, несмотря на свою относительную простоту, позволяет увидеть и понять некоторые ключевые аспекты распространения лесных пожаров:

  • Пороговый характер: Если плотность деревьев слишком мала или вероятность распространения огня низкая, пожар может быстро затухнуть сам по себе. Если же эти параметры выше определенного порога, огонь будет распространяться лавинообразно.

  • Роль связности: Огонь распространяется только по связанным участкам леса. Большие пустые пространства могут остановить пожар.

  • Формирование фронта: Можно наблюдать, как образуются и движутся фронты пламени.

Конечно, наша модель имеет и ограничения: мы не учитывали ветер, рельеф, влажность, разные типы растительности, возможность тушения и многие другие факторы, влияющие на реальные лесные пожары. Однако прелесть моделирования в том, что мы всегда можем усложнить нашу модель, добавляя новые параметры и правила.

Заключение

Мы с вами проделали большой путь: от знакомства с концепцией клеточных автоматов до создания собственной интерактивной симуляции лесного пожара на Python. Надеюсь, это путешествие было для вас не только познавательным, но и увлекательным. Вы увидели, как из простых правил могут рождаться сложные и динамичные системы, и научились воплощать эти идеи в коде.

Моделирование – это мощный инструмент для понимания окружающего нас мира. Оно позволяет нам экспериментировать, проверять гипотезы и получать наглядное представление о процессах, которые в реальности могут быть слишком масштабными, опасными или труднодоступными для прямого изучения. Наша симуляция лесного пожара – это лишь один из бесчисленных примеров того, как программирование помогает нам исследовать и открывать новое.

Комментарии (3)


  1. samsergey
    14.05.2025 10:43

    Благодарю за хорошую статью! Ею вы побудили меня довести до публикации черновик с рассказом о самоорганизующейся критичности в котором анализируется такая же модель лесных пожаров, но уже с позиций теории динамических систем.


  1. Affdey
    14.05.2025 10:43

    Неплохо! В физике это называется автоволновой процесс (см. гугл), там ещё есть свои законы взаимодействия и можно получить более интересные картины, например двухзаходную спираль. У меня была в 2004 курсовая, также моделировал.

    Сейчас лес горит, если убрать рандомайз, в виде квадрата 3х3, 5х5, .... подумайте, как реализовать круг. потому что так будет более естественно.


  1. wmlab
    14.05.2025 10:43

    у вас используется прямоугольная сетка, самая простая. но в ней расстояние между соседями - либо 1 либо корень из 2 (~1.4), это нормально для шахмат, а вот для моделирования физических процессов на плоскости - не очень. обычно используют гексагональную сетку. в ней расстояния между соседями одинаковы
    см Amit’s Thoughts on Grids