Shit-программирование: эволюция одноклеточных

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

Ребята здравствуйте, сегодня мы изучим принципы эволюции и напишем собственную модель мира с одноклеточными организмами. Использовать мы будем python 3.10 и библиотеку pygame.

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

Описание мира вокруг клетки

Приступим к созданию дивного мира для одноклеточных: Это будет матрица 64 на 64 (лист из 64 листов, в каждом 64 позиции). Так же у нас есть объекты классов Еда(Food), Мясо(Meat), Стена(Wall) и, собственно, Клетка(Cell). Стенами будет окружен этот мир + дополнительные стены будут внутри мира, чтобы создать препятствие.

Три объекта, с которыми Клетка будет взаимодействовать

Разница между классом Еда и Мясо заключается в том, что Еда появляется (растёт) в мире сама по себе, а Мясо - это трупы Клеток. Оба объекта содержат атрибут Энергия, которое указывает, сколько энергии получит Клетка, если съест еду. Так как растительная еда появляется в разы чаще, чем трупы клеток, то энергетическая ценность Мяса примерно в 1,5 раза выше.

Сама клетка

Объект класса Клетка

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

Атрибут gens является словарем, каждая пара ключ-значение описывает уникальный параметр Клетки. Они могут иметь цвет, могут решать когда помирать, когда делится, с каким шансом потомство должно мутировать, но основное внимание надо уделить на ключи pain_gen и gens.

Функция, которая генерирует гены Клетки

Ген — это набор из 7 цифр, которые указывают, что делать в зависимости от того, что клетка наблюдает своим органом зрения. По умолчанию клетка имеет 4 гена + ген реакции на боль. О том, как клетка решает что делать, вы прочитаете позже.

На каждый свой ход клетка тратит одну единицу энергии (однако если гены предписывают ничего не делать, то тогда она не тратит энергию). Чтобы выжить и оставить потомство, клетки должны есть еду максимально эффективно.

Pygame

Чтобы отрисовывать этот чудный мир, мы будем использовать библиотеку pygame.

Готовим и запускаем цикл для отрисовки и симуляции

Нарисуем простое окошко, которое в бесконечном(почти) цикле обрабатывает события. Событие pygame.QUIT нужно, чтобы симуляция смогла завершаться. А pygame.MOUSEBUTTONDOWN — для того, чтобы обрабатывать нажатия ЛКМ: если пользователь кликнет по ячейке, он переместит «курсор» на эту клетку, а если координаты клика не совпадают с ячейками мира, симуляция приостанавливается или продолжается (на самом деле из названия переменной понятно, что планировалась немного другая функция).

Рисуем мир в цикле и считаем количество еды, трупов и клеток

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

Функции объекта Клетка, которые возвращают положение глаза и цвет клетки

Клетка может иметь один из 8 разных цветов. Так же она имеет орган зрения в виде черной точки, который рисуется поверх квадрата клетки. Положение этой точки зависит от того, куда сейчас смотрит клетка.

Рисуем информацию с клетки, на которой стоит «курсор». Выводим кадр на экран

После идет код интерфейса пользователя, который позволяет просматривать информацию о каждой ячейке мира и вызов функции update у объекта окна из pygame.

Создание мира

Кусок кода, который создаёт мир

Всё начинается с создания той самой матрицы и окружения её стенами. Затем в мир по очереди помещается еда, мясо, стены и создаются Клетки со случайно сгенерированными параметрами. Проблема здесь заключается в том, что если за время спавна рандом выберет два раза одинаковые координаты, то объект пропадёт. Таким образом некоторые клетки при спавне могут пропасть.

Симуляция мира

Цикл симуляции мира

Сразу после отрисовки мира каждая клетка в мире делает свой ход. После этого к счёткику еды прибавляется значение, которое определяет, сколько еды должно появляться в мире. Затем еда, собственно, появляется. Также здесь в конце цикла вы можете увидеть условие, при котором симуляция останавливается. Не волнуйтесь, при стандартных настройках (про настройки позже) этого не должно произойти.

Логика клетки

Основная функция объекта Клетка

Алгоритм клетки довольно прост. Сначала она проверяет, если ли у неё вообще энергия и также не слишком старая ли она. Если хотя бы одно из этих условий не выполнится, клетка убъёт себя. Затем она проверяет, достаточно ли энергии у неё на размножение и есть ли впереди неё пустое место и если да, то она размножается. Далее она проверяет, больно ли ей. Если больно, то в функцию принятия решения self.brain() подается ген боли. Иначе в функцию подаётся текущий ген и она действует уже по нему. Каждый ген в геноме срабатывает по очереди.

Действия клетки

Функция принятия решения

На вход этой функции подаётся текущий ген. Ячейки гена могут иметь значение от 0 до 3, где 0 — ничего не делать, 1 — повернутся налево, 2 — повернутся направо и 3 — действие. Прежде, чем действовать, клетка смотрит, что перед ней. Так, например, если у клетки ген № 0 равен 3230311, то это значит, что если перед клеткой ничего нет, то клетка пройдёт вперёд. Если еда — повернётся направо. Мясо — съест. Клетка своего цвета — ничего не сделает. Клетка чужого цвета — атаковать (да да, поддерживается расизм). Стена — повернётся налево. И если клетка не может выполнить условия атаки (клетка слабее жертвы или недостаточно превосходит жертву по энергии) — также повернётся направо. Если действия клетки привели к достаточному росту запасов энергии, она приступает к размножению.

Размножение клетки

Функция размножения клетки

Как только клетка набирает достаточно энергии, она проходит вперёд и оставляет за собой своего потомка. Рандом решает, будет ли этот потомок с мутацией и если да, то одна случайная характеристика клетки меняется (собственно, это и является двигателем эволюции). Энергия родителя при этом делится пополам между родителем и потомком.

Стартовые настройки симуляции

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

Список констант

Вы можете сами менять настройки мира и смотреть, как клетки приспосабливаются к ним.

Результат работы

С самого старта программы появляется следующее окно

Создание мира

В окне можно увидеть сетку 64 на 64 ячейки, в каждой может быть ничего, стена, еда, труп или клетка. Справа от сетки - информация о клетке, обведённой белой рамкой (курсор). Внизу - основная информация о симуляции. Правой кнопкой мыши можно выбрать любую ячейку мира. Давайте выберем любую клетку и посмотрим на неё.

Статистика клетки

Здесь мы можем наблюдать основную статистику по клетке: её энергию, сколько ей надо энергии для деления, её возраст в количестве итераций, сколько она проживёт итераций, сколько всего она сделала за свою жизнь, шанс на мутацию генома, и, собственно, геном. Гены обозначаются строкой формата NFMSDWU, что означает None, Food, Meat, Same color cell, Different color cell, Wall и Unable to attack.

Этот же мир спустя 10000+ итераций

Оставив эту симуляцию, мы можем наблюдать, как из изначальных 229 клеток (а должно было быть 256) вообще неспособные есть и тратившие энергию впустую вымерли через 250-300 итераций и чуть позднее вымерли те, кто ел слишком мало еды или был атакован. Через примерно 1000 итераций начинает формироваться конкретный вид клеток, который появился из самых удачных клеток. Отличать виды можно по поведению и цвету. В симуляции со стандартными настройками спокойно может жить популяция из 40 - 60 клеток.

Вдохновение

Изначально на YouTube мне стали попадаться видео в рекомендациях про разнообразные симуляции эволюции одноклеточных и многоклеточных, в основном от авторства foo52ru. И изначальная версия моей симуляции должна была быть похожа на его симуляцию из его самого первого видео про генетические алгоритмы. Однако даже через большое количество поколений моим клеткам не удавалось сильно продвинутся по эволюции. Однако потом мне попалось видео «Симулятор эволюции ProtoPuddle» авторства ProtoPuddle. Моя симуляция основана как раз на его правилах с той лишь разницей, что у него клетки понимали, с какой стороны им больно; настройки можно менять на лету; можно сохранять и загружать миры и вообще симуляция была написана на Java.

Заключение

Итак, сегодня мы создали свой(нет) генетический алгоритм и симуляцию мира одноклеточных. Теперь можно бесконечно наблюдать за бегающими, едящими, кусающимися и умирающими клетками. Довольно много времени я потратил на эту статью и не думаю что многие её прочитают и вообще дойдут до конца. Однако тем, кто это сделал, я хочу сказать спасибо. Последняя версия моей симуляции будет находится на GitHub. Всем пока.