Пишем симуляцию по мотивам игры Life

от автора

Идея вынашивалась довольно давно и сегодня я хочу рассказать о процессе создания симуляции экосистемы под рабочим названием «NewLife», которая моделирует взаимодействие между травой, мирными клетками и хищниками. Идея симуляции, как понятно из названия статьи, родилась по мотивам игры Life из доисторической компьютерной эпохи.

Идея и постановка задачи

Целью задачи является создать простую, но, прежде всего, наглядную модель экосистемы, способную заинтересовать ребенка, где три типа сущностей взаимодействуют друг с другом:

  1. Трава — растет случайным образом и служит пищей для мирных клеток.

  2. Мирные клетки — питаются травой, размножаются и служат пищей для хищников.

  3. Хищники — охотятся на мирные клетки и размножаются.

Постановка задачи требует простоты, наглядности, случайности начальных условий. Максимальная упрощенность условий позволит нам в дальнейшем развивать начальную задумку посредством введения дополнительных «законов взаимодействия», живых и мертвых сущностей подверженных разнообразным ограничениям поведения.

Визуальная часть основана на возможностях 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Основной цикл симуляции 

Основной цикл симуляции

Основной цикл игры будет у нас заниматься обновлением состояния симуляции и отрисовку всех элементов. В каждом цикле:

  1. Растет трава.

  2. Мирные клетки двигаются, едят траву и размножаются.

  3. Хищники охотятся на мирные клетки и размножаются.

  4. Состояние системы сохраняется для отображения на графике.

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, но оказалось, что это кушает ресурсы необъятных количествах, да и вообще не очень наглядно. В результате остановился на варианте с выводом графика снизу поля симуляции. НЕ очень пафосно, зато работает 🙂

GitHub

Выводы и заключение

Создание симуляции «NewLife» оказалось весьма любопытным и показательным для юных обучающихся языку python. В процессе разработки удалось порешать различные проблемы, связанные с производительностью, реалистичностью поведения сущностей и визуализацией данных. В результате получилась простая, но наглядная модель экосистемы, которая может быть использована для изучения базовых принципов взаимодействия в природе. При этом, с указанными значениями стартовых параметров удалось получить «экосистему», дожившую до 2000+ цикла (дальше не хватило терпения ждать).

Код проекта доступен на GitHub и я буду рад, если он вдохновит вас на создание собственных симуляций или улучшение существующих.


ссылка на оригинал статьи https://habr.com/ru/articles/878892/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *