Идея вынашивалась довольно давно и сегодня я хочу рассказать о процессе создания симуляции экосистемы под рабочим названием "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 и я буду рад, если он вдохновит вас на создание собственных симуляций или улучшение существующих.