- PVSM.RU - https://www.pvsm.ru -
Идея вынашивалась довольно давно и сегодня я хочу рассказать о процессе создания симуляции экосистемы под рабочим названием "NewLife", которая моделирует взаимодействие между травой, мирными клетками и хищниками. Идея симуляции, как понятно из названия статьи, родилась по мотивам игры Life из доисторической компьютерной эпохи.
Целью задачи является создать простую, но, прежде всего, наглядную модель экосистемы, способную заинтересовать ребенка, где три типа сущностей взаимодействуют друг с другом:
Трава — растет случайным образом и служит пищей для мирных клеток.
Мирные клетки — питаются травой, размножаются и служат пищей для хищников.
Хищники — охотятся на мирные клетки и размножаются.
Постановка задачи требует простоты, наглядности, случайности начальных условий. Максимальная упрощенность условий позволит нам в дальнейшем развивать начальную задумку посредством введения дополнительных "законов взаимодействия", живых и мертвых сущностей подверженных разнообразным ограничениям поведения.
Визуальная часть основана на возможностях pygame
Итак, переходим к кодингу.
import pygame
import random
from collections import deque
Первым шагом инициализируем 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
На начальном этапе количество сущностей (особенно травы) росло слишком быстро, что приводило к утечке памяти и замедлению работы программы. Для решения этой проблемы были введены ограничения на максимальное количество сущностей и скорость роста травы.
max_cells_per_cycle = 500
grass_growth_per_cycle = 200
Хищники могли бесконечно преследовать мирные клетки, даже если те находились далеко. Это приводило к неестественному поведению. Для улучшения реалистичности было добавлено уменьшение энергии хищников с каждым шагом, что позволило дать шанс мирным клеткам на выживание.
pd.energy -= 1
if pd.energy <= 0:
predator.remove(pd)
continue
Я пробовал сначала отрисовывать график с использование mathplotlib, но оказалось, что это кушает ресурсы необъятных количествах, да и вообще не очень наглядно. В результате остановился на варианте с выводом графика снизу поля симуляции. НЕ очень пафосно, зато работает :-)
GitHub [1]
Создание симуляции «NewLife» оказалось весьма любопытным и показательным для юных обучающихся языку python. В процессе разработки удалось порешать различные проблемы, связанные с производительностью, реалистичностью поведения сущностей и визуализацией данных. В результате получилась простая, но наглядная модель экосистемы, которая может быть использована для изучения базовых принципов взаимодействия в природе. При этом, с указанными значениями стартовых параметров удалось получить «экосистему», дожившую до 2000+ цикла (дальше не хватило терпения ждать).
Код проекта доступен на GitHub [1] и я буду рад, если он вдохновит вас на создание собственных симуляций или улучшение существующих.
Автор: ilyasch
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/409598
Ссылки в тексте:
[1] GitHub: https://github.com/shilvlad/NewLifeSimulation
[2] Источник: https://habr.com/ru/articles/878892/?utm_source=habrahabr&utm_medium=rss&utm_campaign=878892
Нажмите здесь для печати.