Идея вынашивалась довольно давно и сегодня я хочу рассказать о процессе создания симуляции экосистемы под рабочим названием "NewLife", которая моделирует взаимодействие между травой, мирными клетками и хищниками. Идея симуляции, как понятно из названия статьи, родилась по мотивам игры Life из доисторической компьютерной эпохи.
Идея и постановка задачи
Целью задачи является создать простую, но, прежде всего, наглядную модель экосистемы, способную заинтересовать ребенка, где три типа сущностей взаимодействуют друг с другом:
Трава — растет случайным образом и служит пищей для мирных клеток.
Мирные клетки — питаются травой, размножаются и служат пищей для хищников.
Хищники — охотятся на мирные клетки и размножаются.
Постановка задачи требует простоты, наглядности, случайности начальных условий. Максимальная упрощенность условий позволит нам в дальнейшем развивать начальную задумку посредством введения дополнительных "законов взаимодействия", живых и мертвых сущностей подверженных разнообразным ограничениям поведения.
Визуальная часть основана на возможностях pygame
Итак, переходим к кодингу.
Начало разработки
Инициализация Pygame
import pygame import random from collections import deque
Инициализация Pygame
Первым шагом инициализируем Pygame и создание окна для отображения симуляции. Мы задали размеры окна, параметры клеток и цвета для каждого типа сущностей.
pygame.init() width, height = 200, 200 cell_size = 4 simulation_height = height * cell_size graph_height = 400 window_size = (width * cell_size, simulation_height + graph_height) screen = pygame.display.set_mode(window_size) pygame.display.set_caption("NewLife Simulation")
Создание классов сущностей
Для каждой сущности (трава, мирные клетки, хищники) создадим отдельный класс. Каждый класс содержит координаты и методы для движения, размножения и взаимодействия с другими сущностями. Такой подход позволит нам добавлять сущности в количествах, ограниченных только производительностью системы. Вопрос оптимизации оставим за скобками
class Grass: def __init__(self, x, y): self.x = x self.y = y class Peaceful: def __init__(self, x, y): self.x = x self.y = y self.energy = 100 def move_towards_grass(self, grass_list): if not grass_list: return closest_grass = min(grass_list, key=lambda g: (self.x - g.x) ** 2 + (self.y - g.y) ** 2) if self.x < closest_grass.x: self.x += 1 elif self.x > closest_grass.x: self.x -= 1 if self.y < closest_grass.y: self.y += 1 elif self.y > closest_grass.y: self.y -= 1 def reproduce(self, peaceful_list): if self.energy >= 30 and len(peaceful_list) < max_cells_per_cycle: for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: new_x, new_y = self.x + dx, self.y + dy if 0 <= new_x < width and 0 <= new_y < height: if not any(p.x == new_x and p.y == new_y for p in peaceful_list): peaceful_list.append(Peaceful(new_x, new_y)) self.energy /= 2 break class Predator: def __init__(self, x, y): self.x = x self.y = y self.energy = 100 def move_towards_peaceful(self, peaceful_list): if not peaceful_list: return closest_peaceful = min(peaceful_list, key=lambda p: (self.x - p.x) ** 2 + (self.y - p.y) ** 2) if self.x < closest_peaceful.x: self.x += 1 elif self.x > closest_peaceful.x: self.x -= 1 if self.y < closest_peaceful.y: self.y += 1 elif self.y > closest_peaceful.y: self.y -= 1 def reproduce(self, predator_list): if self.energy >= 50 and len(predator_list) < max_cells_per_cycle: for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: new_x, new_y = self.x + dx, self.y + dy if 0 <= new_x < width and 0 <= new_y < height: if not any(pd.x == new_x and pd.y == new_y for pd in predator_list): predator_list.append(Predator(new_x, new_y)) self.energy /= 2 breakОсновной цикл симуляции
Основной цикл симуляции
Основной цикл игры будет у нас заниматься обновлением сост��яния симуляции и отрисовку всех элементов. В каждом цикле:
Растет трава.
Мирные клетки двигаются, едят траву и размножаются.
Хищники охотятся на мирные клетки и размножаются.
Состояние системы сохраняется для отображения на графике.
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 not paused: # Логика игры # Трава растет for _ in range(grass_growth_per_cycle): if len(grass) < width * height: x, y = random.randint(0, width - 1), random.randint(0, height - 1) if not any(g.x == x and g.y == y for g in grass): grass.append(Grass(x, y)) # Мирные клетки двигаются и едят траву for p in peaceful[:]: # Используем копию списка для безопасного удаления p.energy -= 1 if p.energy <= 0: peaceful.remove(p) continue p.move_towards_grass(grass) for g in grass[:]: # Используем копию списка для безопасного удаления if p.x == g.x and p.y == g.y: grass.remove(g) p.energy = 100 break p.reproduce(peaceful) # Хищные клетки двигаются и едят мирных for pd in predator[:]: # Используем копию списка для безопасного удаления pd.energy -= 1 if pd.energy <= 0: predator.remove(pd) continue pd.move_towards_peaceful(peaceful) for p in peaceful[:]: # Используем копию списка для безопасного удаления if pd.x == p.x and pd.y == p.y: peaceful.remove(p) pd.energy = 100 break pd.reproduce(predator) # Ограничение размножения if len(peaceful) > max_cells_per_cycle: peaceful = peaceful[:max_cells_per_cycle] if len(predator) > max_cells_per_cycle: predator = predator[:max_cells_per_cycle] # Сохранение истории history.append((len(grass), len(peaceful), len(predator))) # Отрисовка draw_cells() graph_surface = draw_graph() screen.blit(graph_surface, (0, simulation_height)) # Отображение информации font = pygame.font.SysFont("Arial", 18) text = font.render(f"Cycle: {cycle}, Grass: {len(grass)}, Peaceful: {len(peaceful)}, Predator: {len(predator)}", True, (255, 255, 255)) screen.blit(text, (10, 10)) pygame.display.flip() clock.tick(1) cycle += 1
Проблемы и их решение
Проблема 1: Утечка памяти из-за большого количества сущностей
На начальном этапе количество сущностей (особенно травы) росло слишком быстро, что приводило к утечке памяти и замедлению работы программы. Для решения этой проблемы были введены ограничения на максимальное количество сущностей и скорость роста травы.
max_cells_per_cycle = 500 grass_growth_per_cycle = 200
Проблема 2: Нереалистичное поведение хищников
Хищники могли бесконечно преследовать мирные клетки, даже если те находились далеко. Это приводило к неестественному поведению. Для улучшения реалистичности было добавлено уменьшение энергии хищников с каждым шагом, что позволило дать шанс мирным клеткам на выживание.
pd.energy -= 1 if pd.energy <= 0: predator.remove(pd) continue
Проблема 3: Отрисовка графика
Я пробовал сначала отрисовывать график с использование mathplotlib, но оказалось, что это кушает ресурсы необъятных количествах, да и вообще не очень наглядно. В результате остановился на варианте с выводом графика снизу поля симуляции. НЕ очень пафосно, зато работает :-)
Выводы и заключение
Создание симуляции «NewLife» оказалось весьма любопытным и показательным для юных обучающихся языку python. В процессе разработки удалось порешать различные проблемы, связанные с производительностью, реалистичностью поведения сущностей и визуализацией данных. В результате получилась простая, но наглядная модель экосистемы, которая может быть использована для изучения базовых принципов взаимодействия в природе. При этом, с указанными значениями стартовых параметров удалось получить «экосистему», дожившую до 2000+ цикла (дальше не хватило терпения ждать).
Код проекта доступен на GitHub и я буду рад, если он вдохновит вас на создание собственных симуляций или улучшение существующих.
