В данном посте я расскажу о своем опыте написания игры-симулятора эволюции одноклеточных организмов на прямоугольной плоскости. Кроме разработки алгоритмов симулятора, речь пойдет о проблемах с которыми я столкнулся при разработке данного проекта на C#, а также о его портировании для работы в браузере. В конце статьи будет ссылка на готовую к игре версию и на все исходники. Если я вас заинтересовал — прошу под кат.

Вдохновение


Перед тем как мы приступим — я хочу коротко рассказать о тех проектах, которые вдохновили меня на написание данного симулятора. Первую аналогию вы могли провести еще по скриншоту в шапке поста, ведь мой проект внешне довольно сильно похож на игру «Жизнь», придуманную математиком Джоном Конвем в 1970 году. Еще больше меня вдохновили вот эта и эта статьи других хаброюзеров. Чтоб не гонять уважаемого читателя по ссылкам, коротко опишу о чем идет речь в данных статьях: первая — модификация игры Конвея с добавлением туда естественного отбора, а вторая — моделирование разумной жизни на базе нейронных сетей.

Определение требований


Писать еще один клон игры «Жизнь» я не хотел, да и идея естественного отбора мне настолько понравилась, что я решил сконцентрироваться именно на эволюции. Название пришло само собой — «The strongest survives».

Вот требования, которые я ставил себе при разработке:

  • разработать симулятор эволюции, в котором будет множество видов организмов – клеток;
  • добавить несколько объектов для взаимодействия клеток с ними (еда, яд, мертвы клетки);
  • сделать симуляцию производительной в первую очередь, чтоб можно было быстро увидеть результат за большой период;
  • симуляция должна как можно более соответствовать реальной жизни;
  • должна быть возможность изменения условий обитания организмов.

В TSS клетки имеют много показателей, от которых зависит их следующее состояние. Они могут анализировать окружающую обстановку и принимать на основе этих данных решения, которые помогут им выжить. На их приоритеты при осмотре окрестностей влияют модификаторы, записанные в геноме. Например, есть параметр агрессия. Когда клетка видит другую клетку с чужеродным геномом, то она прибавляет к стремлению двигаться в ту сторону этот модификатор (агрессию). То же самое с голодом, коллективностью, тягой к яду и тягой к мертвым клеткам. После расчетов выбирается сторона с наибольшей суммой модификаторов.

Основные правила игры:

  • вселенная игры имеет свою единицу времени – тик;
  • каждая клетка имеет определенный уровень энергии, убывающий во время каждого тика;
  • главная цель клетки – выжить и размножиться. Для этого ей нужно собрать как можно больше энергии;
  • все клетки имеют максимальный возраст;
  • клетки могут иметь разный геном, который влияет на их стратегию выживания, при этом их физические возможности равны;
  • во время каждого тика каждая клетка может сделать один шаг в одну из 4-х выбранных сторон. При выборе направления шага, в зависимости от находящегося на новом месте объекта, произойдет какое-то событие. Если там пустое место — она просто туда переместится, если там еда, яд, или мертвая клетка — она получит их уровень энергии и тоже переместиться туда. Если же там живая клетка — она останется на месте и, в зависимости от того, какая там клетка произойдет определенное событие. Если там клетка с таким же геномом, то обе получат энергию, если с другим геномом — данная клетка заберет у нее часть энергии.


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

Также клетка может размножиться. Размножение происходит делением, один раз за тик, если у клетки достаточно энергии. При этом, рядом должно быть пустое пространство, где создается новая клетка с таким же геномом, либо клетка с мутировавшим геномом. При этом ее уровень энергии делиться поровну между родительской и дочерней клетками. Требуемый для размножения уровень энергии определяет пользователь, но модификатором к нему выступает параметр «репродуктивность». Этот параметр храниться в геноме. Чем он выше — тем меньше энергии нужно клетке для размножения.


Как-то так представлял себе я эту игру, в то время. В целом, что-то подобное и получилось.

Суть геймплея


Геймплей заключается в том, что пользователю предоставляется редактор констант вселенной, в котором он может изменять условия обитания организмов и наблюдать за тем, как они приспосабливаются. Звучит не очень впечатляюще, но здесь довольно много значений, доступных игроку (около 30), меняя которые можно получить множество уникальных вариаций результата. Подробней о каждой константе вы можете почитать в мануале в папке с игрой или под спойлером.

Константы вселенной
MaxCountOfCellTypes — максимальное количество геномов клеток.

Mutation_Enable — позволяет включить или отключить мутацию клеток при размножении.

Mutation_AttackChildrenMutantsOfFirstGeneration — если это свойство отключено, то клетки не могут атаковать своих потомков первого поколения, когда те мутируют.

Mutation_AttackParentIfCellIsYouMutant — если это свойство отключено, то мутировавшие клетки не могут атаковать своих предков первого поколения.

Mutation_ChangedValuesAtOne — показывает сколько раз изменяются значения, отвечающие за поведение клеток, при каждой мутации. Например, если значение 10, то 10 раз будет выбрано и изменено на 1 случайное свойство клетки, отвечающее за поведение. Может быть от 1 до 200.

Mutation_ChancePercent — шанс мутации при размножении.

CellAge_Max — максимальный возраст клетки.

CellAge_AdultCell — возраст взрослой клетки. У клеток-детей уровень агрессии не выше CellGenome_Child_Aggression и они не могут размножаться.

EnergyLevel_CreatingCell — уровень энергии у клеток, при их генерации на старте.

EnergyLevel_NeededForReproduction — уровень энергии, при котором клетка может размножиться. Может варьироваться из-за CellGenome_ReproductionRange.

EnergyLevel_MaxForCell — максимальный уровень энергии, которую может накопить клетка.

EnergyLevel_DeadCell — уровень энергии, получаемый от мертвых клеток.

EnergyLevel_DefFood — уровень энергии, получаемый от еды.

EnergyLevel_PoisonedFood — уровень энергии, получаемый от яда.

EnergyLevel_MovesFriendly — уровень энергии, получаемый клеткой, когда к ней движется родственник или она движется к нему. Как правило он намного меньше чем уровень энергии обычной еды. Способствует созданию колоний.

EnergyLevel_MovesAggression — показывает сколько энергии отнимает клетка у чужеродных клеток при нападении.

CellsCount_MaxWithOneType — максимальное количество клеток на поле с одинаковым геномом.

CellGenome_Child_Aggression — уровень агрессии у клеток-детей.

CellsCount_MaxAtField — максимальное количество клеток на поле.

EnergyEntropyPerSecond — уровень энергии, который теряет клетка при каждом тике.

Special_FoodCountForTick — количество еды, генерируемое при каждом тике вселенной.

Special_PoisonCountForTick — количество яда, генерируемое при каждом тике вселенной.

CellGenome_HungerRange — диапазон значений голода при генерации клетки.

CellGenome_AggressionRange — диапазон значений агрессии при генерации клетки.

CellGenome_ReproductionRange — диапазон значений репродуктивности при генерации клетки. Чем выше значение, тем меньше энергии нужно клетке для размножения.

CellGenome_FriendlyRange — диапазон значений коллективности при генерации клетки.

CellGenome_PoisonRange — диапазон значений влечения к яду при генерации клетки. Обычно отрицателен.

CellGenome_CorpseRange — диапазон значений влечения к мертвым клеткам при генерации клетки.

Так выглядит этот редактор в веб версии.


А в версии под Windows можно еще и редактировать место, где генерируется еда или яд.

Разработка и результат


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

Версия на WindowsForms



Это первая рабочая версия моей программы. В данной версии еще не реализовано большей части игровой логики и она имеет много багов. Рендеринга тут нет, все выводится в текстовое поле в виде символов. Такой вот текстовый вывод работал очень медленно (1-2 тика в секунду), но, все же, есть в нем что-то ламповое.

Версия в текстовой консоли



Как ни странно – вторая версия программы. Ее я писал для лабораторных. В этой и последующих версиях игровая логика идентична.

Версия на WPF



На эту версию я потратил больше всего времени, но она и является наиболее проработанной. Рендеринг фреймов здесь занимает 20-30 мс и не зависит от размеров поля, да и сам тик обрабатывается довольно быстро. Пользователь может создавать огромное игровое поле (хоть 2000х2000 ячеек). Есть возможность просматривать информацию о каждой клетке, просто кликнув на нее.
Демонстрация работы программы.


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

1) Ручной рендеринг. Так как я решил писать велосипед — я делал отрисовку без движка, используя стандартные средства WPF для отрисовки примитивов. Но это позволило мне максимально оптимизировать отрисовку именно под свою программу.

Когда я только начал делать отрисовку — вся канва перерисовывалась с нуля при каждом фрейме, что очень било по производительности. Но потом я написал алгоритм, который хранил прошлый кадр в буфере и при отрисовке нового — закрашивал места, где были объекты, но теперь нет, а на месте, где они появились — рисовал объект. Не знаю насколько быстро это работало бы на движке, но здесь производительность выросла в разы и перестала зависеть от размеров поля.
Отсутствие движка также упростило портирование под браузер, там я использовал тот же алгоритм.

Но в этом способе есть и минусы — часто возникают помехи на изображении, особенно это заметно в WPF версии.

2) Динамический редактор классов. Чтоб не переделывать по сто раз форму, на которой отображались поля редактирования настроек вселенной, я использовал рефлексию и написал динамический редактор, который читал поля передаваемого объекта (в данном случае — объект с настройками) и строил форму для их редактирования.


Вот так выглядел этот редактор в версии WPF.

Веб версия


Через некоторое время после написания WPF версии мне захотелось портировать ее под браузер. Результат вы можете увидеть в шапке статьи, либо, лично перейдя на сайт и попробовав.

Я немного знал Javascript, но мне совсем не хотелось переписывать полторы тысячи строк кода игровой логики на нем. Но выход нашелся — я решил использовать Bridge.NET, открытый компилятор C# в Javascript. Да, дорогой читатель, ты все правильно понял — теперь мы пишем под браузер на C#. Благодаря этому компилятору я смог использовать язык, который хорошо знал. Оставалось только переписать рендеринг и остальной код, связанный с отображением. По причине Javascript лени, веб версия менее функциональна, чем версия под Windows, но служит неплохой демонстрацией работы симулятора.

Справедливо отметить, что мне, все же, очень пригодились знания Javascript и HTML из-за особенностей работы браузеров.

Итоги


При разработке данного проекта я сильно подтянул свои навыки в программировании в целом. На данный момент все версии игры доступны на этом сайте tss-game.cc.ua. Если вы сами хотите приступить к разработке или просто посмотреть код — вот мой гитхаб, тут вы можете найти все версии моей программы с обширными комментариями. Надеюсь, вам было интересно читать о моем опыте, возможно, эта статья вдохновит и вас на что-то подобное, как меня вдохновили другие статьи в свое время.
Поделиться с друзьями
-->

Комментарии (25)


  1. vlreshet
    02.05.2017 17:34
    +3

    Жаль что нельзя вставлять комментарии в комментарии. Сюда вот этот идеально подошёл бы :)


    1. Anarions
      02.05.2017 17:51

      Можно картинкой. А вообще — тут статья гораздо более качественная.


    1. Rastishka
      02.05.2017 17:56

      Ох если бы студенты, я почти 1 в 1 такое в школе писал на бейсике, только параметров меньше было.


    1. KogerCoder
      02.05.2017 18:08

      Ахах, так и есть.


    1. RaTyS
      03.05.2017 16:39

      У автора достаточно много проектов на github. Очень хорошее начало, сразу видно, что человеку действительно нравится программирование. Все мы когда-то были студентами =)


  1. mwambanatanga
    02.05.2017 17:41
    +4

    Согласно описанию, перемещение каждого объекта («клетки») зависит от текущего состояния «мира», без учёта перемещения других объектов. Из этого [неявно] следует, то объекты перемещаются по очереди. Если да, то как определяется очерёдность? Если нет, то как решается проблема конфликтов перемещения (когда два или больше объектов перемещаются на одно и то же место)?


    1. mwambanatanga
      02.05.2017 18:00

      И ещё: если они действительно перемещаются по очереди, то это не есть двухмерный клеточный автомат и сходство с игрой Конвея чисто внешнее.


    1. KogerCoder
      02.05.2017 18:08
      +2

      Абсолютно верно. Я и не позиционировал свою игру как клеточный автомат. У меня они действительно перемещаются по очереди. Но, ради более справедливой работы, эта очередь постоянно перемешивается в случайном порядке.


      1. quverty
        02.05.2017 21:34

        В своё время для решения подобной проблемы разбивал шаг на фазы: сначала смотрелись коллизии и т.д. Со случайным перемешиванием что-то у меня по опыту неважно получалось — и ещё это зависит от каких-то параметров генератора случайных чисел.


  1. izzholtik
    02.05.2017 18:23
    +3

    Как-то очень медленно. Не пробовали профайлером тыкать?


    1. KogerCoder
      02.05.2017 18:27

      Не пробовал. Вы про рендеринг?


      1. izzholtik
        02.05.2017 19:54
        +1

        В целом. Всё как-то очень печально бегает.
        Сравните с поделием конкурирующих студентов (10 мб) на Java, запущенном на унылом Core2 Quad

        (тут должен быть смайлик, но НЛО заставляет говорить, что мне они надоели:)


        1. KogerCoder
          02.05.2017 20:24

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


          1. izzholtik
            02.05.2017 20:28

            не, там тоже опрос ближайших клеток (8, правда), выбор действия, энергия, простенький геном.
            Я немного слукавил про студентов, конечно, хех. Один человек написал алгоритм, другой сделал интерфейс, третий два дня упарывался с оптимизацией.


          1. izzholtik
            02.05.2017 20:37
            +4

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


  1. DaneSoul
    02.05.2017 20:29

    Чтобы моделирование было ближе к реальности и интересней, поробуйте обратить внимание на следующие моменты.

    Основные мысли:

    1) Должен быть не просто набор параметров — должны быть продуманные противовесы: изменяя один параметр мутацией мы в одном улучшаем, в другом одновременно ухудшаем.

    2) Не обязательно делать уйму параметров — лучше меньше, но продуманней и сбалансированней.

    3) Добалвять дополнительные параметры уже после того, как на текущих система ведет себя логично и сбалансированно.

    По отдельным параметрам эмуляции:

    1) Агрессия должна уравновешиваться параметром «силы» клетки.
    Грубо говоря, у «агрессора» при нападении есть 3 варианта:
    — мы сильней, мы победили, мы получили энергию
    — мы равны, мы не победили, но потратили энергию на борьбу
    — мы проиграли и потеряли много энергии.
    Естетственно коэффициенты тут будут разные.
    Сюда логино было бы как-то привязать модификатор от колониальности — если нас много, нас сложно одолеть.
    И отдельный модификатор «защиты» — то есть если мы грубо говоря отрастили щипы и панцырь, то нас сожрать сложней, но мы на это тратим энергию — либо каждый тик, либо например требуем больше энергии на размножение.

    2) Концепция яда была бы интересней не как пища, а типа «зараженности среды» и соответственно параметр клеток «резистентность», то есть устойчивость к яду.
    На зараженных клетках энергия с каждым тиком теряется сильней.
    Тогда получается развивая параметр резистентности мы можем жить на среде где мало конкуренции и агрессоров.

    3) Спорная идея, но можно было бы попробовать:
    При случайных мутациях и отборе как в реальном мире нужно слишком большое количество особей и генераций. Чтобы это обойтиь, можно объеденить элементы отбора с мутациями — грубо говоря если яда вокруг нет, то мутации резистентности для нас не актуальны и мы сразу их исключаем.

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

    5) Колониальность должна давать преимущества:
    — Когда мы вместе, мы сильней, нас сложней победить агрессору
    — Когда мы вместе мы тратим меньше энергии на каждого, чем по-отдельности.
    Но в ней есть и минусы, так как меньше пищи вокруг остается.

    6) Пища появляется по отдельному алгоритму от развития клеток?
    Или она не появляется после сотворения мира и он в итоге обречен на умирание?


    1. KogerCoder
      02.05.2017 20:53

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


  1. hdfan2
    03.05.2017 06:28
    +4

    The strongest survives

    Выживает не сильнейший. Выживает самый приспособленный.


  1. vesper-bot
    03.05.2017 12:25

    По скриншотам — не хватает растений, т.е. генераторов энергии. В противном случае всякая жизнь в какой-то момент прекратится, так как отсутствует подпитка энергией извне.

    Life is merely an orderly decay of energy states, and survival requires the continual discovery of new energy to pump into the system.

    CEO Nwabudike Morgan


    1. saluev
      03.05.2017 14:17

      Тогда не растений, а солнца и геотермальных источников. Растения — лишь посредники.


    1. Anarions
      03.05.2017 14:18

      Есть генерация «еды» в рандомных точках, и даже константа регулирующая частоту появления еды.


  1. Seno125
    03.05.2017 16:05

    Я попробовал убрать с поля всю еду и весь яд. Запустив симуляцию с удивлением обнаружил что жизнь идет, а количество энергии только увеличивается. Как такое выходит?


    1. KogerCoder
      03.05.2017 16:08

      Наверно, клетки создают колонии. Когда клетки одного цвета рядом друг с другом, то они получают EnergyLevel_MovesFriendly энергии за тик из ниоткуда. Или же вы поставили EnergyEntropyPerSecond в минус, тогда все клетки получают энергию просто так, а не теряют.


  1. mad_god
    03.05.2017 16:51

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


    1. lexxpavlov
      09.05.2017 15:22

      Вы не читали книгу «Эндимион» Дэна Симмонса? рекомендую! В этой книге есть ваша идея…