Давным-давно, когда я еще учился в университете, я услышал что на математическом факультете в нашем вузе программистам задают интересную задачу: смоделировать так называемый «волчий остров». Суть ее примерно в следующем.



Что на картинке
Stop/Start — Запустить мир
Turn — Остановить мир
Restart — Пересоздать мир
Зеленые клетки — Клетки с травой. Чем зеленее, тем больше травы.
Маленькие зайцы и волки — щенки
Большие зайцы и волки — взрослые особи
Красные и синие полоски на пиктограммой зверей — текущая сытость. Красные — самцы, синие — самки.
Число в левом нижнем углу каждой клетки — количество существ на данной клетке
Внизу общее количество зайцев и волков, а также время, занявшее обработку последнего ход

Волчий остров размером N * N заселен дикими зайцами и волками. Имеется по несколько представителей каждого вида. Зайцы в каждый момент времени с одинаковой вероятностью 1/9 передвигаются в один из восьми соседних квадратов (за исключением участков, ограниченных береговой линией) или сидят неподвижно. Каждый заяц с вероятностью p(br) превращается в двух зайцев. Каждый волк с вероятностью p(bw) превращается в двух волков. Каждый волк передвигается случайным образом (как и заяц), пока в одном из соседних восьми квадратов не окажется заяц, за которым он охотится. Если волк и заяц оказываются в одном квадрате, волк съедает зайца и получает h «очков». В противном случае он теряет d «очков». Волки с нулевым количеством очков умирают. В начальный момент времени все волки имеют H0 очков. Параметры задачи могли незначительно изменяться, часто имели место модификации: например, «честное» половое размножение хотя бы волков, но суть оставалась все той же.

Эта модель меня крайне заинтересовала, и хотелось попробовать ее реализовать хоть в каком-нибудь виде. Но так уж случилось, что руки дошли только недавно. Однако теперь я мог это сделать гораздо интереснее, в том числе и «пофантазировать» на тему поведения и волков, и зайцев. В этой статье я хотел бы рассказать о тернистом, но крайне интересном пути к балансу сложной экосистемы.

Выбор начальных параметров


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

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

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

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

В качестве языка реализации была выбрана Java 8 просто потому, что самая знакомая технология, да и объектно-ориентированный язык отлично подходил для подобного рода моделей. Для вывода я воспользовался графическими примитивами стандартных средств Java — все-таки сама модель интересовала меня куда больше, нежели ее представление.

Первый блин


Да, он был комом. Изначально казалось, что экосистема заработает с первого раза, но нет. Первой проблемой, с которой я столкнулся, была слабая выживаемость волков: волки не всегда успевали размножиться. Да, они охотились на зайцев, но к противоположному полу интерес в них заложен не был. Лишь изредка два волка сталкивались на соседней клетке, порождали третьего и продолжали бесцельно бродить по лесу. Поэтому я решил добавить несколько волков на карту. Первый же запуск большой стаи волков в лес показал, насколько экосистема неустойчива. Давайте порассуждаем как волк (для определенности — самец): бродим по лесу, если встречаем зайца, то съедаем. Если встречаем самку, то порождаем свою маленькую копию. А на следующем ходу еще одну маленькую копию, ведь самка, скорее всего, никуда убежать не успела. Более того, правилами не было введено никакого монопольного доступа на партнера, и по факту, один и тот же волк или волчиха могли быть использованы для генерации третьего волка не один раз за ход. Самое страшное, что новорожденные волки сразу же начинали действовать по такому же алгоритму. В таких условиях рост популяции волков происходил лавинообразно — им даже не надо было есть зайцев: новые волки рождались полностью сытыми, и за свою сытость успевали породить много больше волков, чем одного.

protected void breed(Visibility visibility) {
    // Критически важная проверка
    if (this.sex != Sex.FEMALE || !adult()) {
        return;
    }
    ...
}

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

  1. только волчиха генерировала маленького волка случайного пола при встрече с самцом (ранее появлялось как минимум два новых волка);
  2. введены понятия «возраста» и «взрослости»: волки могли размножаться только с определенного возраста. И волки, и зайцы стали смертными. Данное ограничение было введено, чтобы предотвратить слишком сильный взрыв популяции;
  3. введено понятие «видимости»: волк мог анализировать только объекты, находящиеся в определенном радиусе (пока на своей клетке), но ничего не знал об объектах за его пределами;
  4. зайцы стали давать больше «очков» сытости, чтобы скомпенсировать сниженную эффективность размножения как средства поддержания популяции.

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

Балансировка


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

Но все же выживаемость волка (как и любого другого живого существа) сильно зависит от модели поведения. Поэтому была добавлена простейшая реализация поведения волков. Для этого очень кстати оказалась «видимость» — когда мы будем передавать часть объектов мира для анализа конкретному существу.

Волки начинают охранять траву от зайцев

Коричневые квадраты — бесполые зайцы
Синие круги — волчихи
Желтые круги — волки

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

  • update— обновление естественных счетчиков существа: голод (здоровье), беременность и возраст;
  • move — функция выбора направления движения из состояния существа и списка объектов, находящихся внутри радиуса видимости. Зайцы двигаются хаотично, а волки гоняются за зайцами в области видимости и особями противоположного пола;
  • feed — волк съедает зайца, находящегося на той же клетке, а зайцы же пока питаются солнечной энергией;
  • multiply — заяц с определенной вероятностью порождает свою копию, случай волков описан выше.

Поведение волка «разумного»


Поведение существа в данной системе — совокупность функций, отвечающих за все основные действия, в которых необходимо делать выбор. На тот момент выбор волка определяется только направлением перемещения (move).

Хорошей мыслью казалось отделение базовых функций животного (update), которые не влияют на его выбор и должны происходить автоматически (правила), от функций поведения, которые в идеале должны бы заменяться на более совершенные версии в процессе развития алгоритмов. Таким образом, появляется первый вариант волка «разумного». Для дальнейшего изложения введем понятие «объект интереса» — существо в области видимости, которое может быть потенциально съедено или с которым возможно продолжение рода. Волк «разумный» действовал по следующей схеме:

  1. вычиляются все возможные направления движения;
  2. опеределяется, хочет ли волк есть (если здоровье ниже половины);
  3. если волк хочет есть, то среди видимых объектов выбирается ближайший объект интереса, который можно съесть (если несколько, то первый попавшийся);
  4. если волк не хочет есть, то среди видимых объектов выбирается ближайший объект интереса, с которым можно размножиться;
  5. возвращается направление движения к выбранному объекту интереса;
  6. если объектов интереса нет, то возвращается случайное направление движения из возможных.

Такая схема позволяла волкам переловить всех ближайших зайцев довольно быстро для поддержания собственной сытости, а также сбиваться в стаи пока позволяло чувство голода для быстрого доступа к противоположному полу. Однако, и такой мир вырождался: волки оставались в стаях, изредка съедая пробегающих мимо зайцев, и медленно умирали, в то время как зайцы безнаказанно плодились на другом конце мира, который впоследствии и захватывали. Стало ясно, что необходимо ввести усложняющие правила и для зайцев, чтобы предотвратить их бесконтрольное размножение.

И результат такого размножения


Поведение зайца «разумного»


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

Для реализации «желания есть» добавляем простой алгоритм вычисления направления от ближайшего хищника. Однако при таких правилах волк никогда не догонит ни одного зайца, поэтому было введено понятие «скорость» —количество полных циклов жизнедеятельности «двигайся» и «ешь». Таким образом, достаточно голодный волк со скоростью 2 почти всегда нагоняет зайца со скоростью 1, если тот появится в области его видимости. Чтобы волки не мешали друг другу в их алгоритм вычисления цели было добавлено легкое нежелание двигаться в сторону волка того же пола, ибо он является конкурентом и может съесть зайца раньше.

И тут в процессе стабилизации текущей модели мне взбрело в голову добавить запах (чтобы еще сильнее расшатать и без того хрупкий экологический баланс). Запах оставляют зайцы на той клетке где находятся (изначальное значение S), каждый ход это значение понижается на определенное dS до нуля. Такой механизм позволял теоретически увеличить видимость волка, ведь если он находился на клетке с запахом N, то это означало, что где-то в пределах $(S - N) \over dS$ клеток находится заяц, который его оставил.

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

Вот только отрицательные веса не хотят работать как положено вместе с положительными. Значит, нужно распространять вес каждой клетки на ее соседей с убыванием. Теперь на область видимости существа помещаются центральные точки, распространяющие убывающее с расстоянием влияние (в текущей реализации 1 на каждый шаг). В результате получается карта весов, по которой существо «скатывается» в положительные ямы, чтобы достигнуть желаемого.

// Алгоритм перемещения зайца
static {
    VALUE_MAP.put(RegularRabbitTargetAttitude.PREDATOR, -20);
    VALUE_MAP.put(RegularRabbitTargetAttitude.COMPETITOR, -5);
    VALUE_MAP.put(RegularRabbitTargetAttitude.MATE, 10);
    VALUE_MAP.put(RegularRabbitTargetAttitude.FOOD, 10);
}

public Optional<Position> move(Visibility visibility) {
    // Карта весов для юнитов
    Map<Position, Set<RegularRabbitTargetAttitude>> positionValues
        = updateWithUnits(visibility.units());
    // Карта весов для клеток
    HashMap<Position, Integer> positionValueMap
        = positionValues.entrySet().stream()
        .collect(Collectors.toMap(
            Map.Entry::getKey,
            e -> e.getValue().stream()
                .mapToInt(VALUE_MAP::get).sum(),
            Integer::sum,
            HashMap::new));
    // Карта весов для хищника
    Map<Position, Integer> predatorValueMap
        = updateProliferatingValues(visibility, PREDATOR, positionValues);
    // Карта весов для сородича того же пола
    Map<Position, Integer> competitorValueMap
        = updateProliferatingValues(visibility, COMPETITOR, positionValues);
    // Карта весов для еды (травы)
    Integer foodCellValue = VALUE_MAP.get(FOOD);
    Map<Position, Integer> cellValues
        = visibility.cells()
            .collect(Collectors.toMap(
                    Cell::getPosition,
                    c -> 
                        c.getGrass().getFoodCurrent() >= c.getGrass().getFoodValue()
                        ? foodCellValue
                        : 0));

    Map<Position, Integer> totalValueMap
        = CollectionUtils.mergeMaps(
            Integer::sum,
            predatorValueMap,
            competitorValueMap,
            positionValueMap,
            cellValues);
    return totalValueMap.entrySet().stream()
            .max(Comparator.comparingInt(Map.Entry::getValue))
            .map(max -> {
                List<Direction> availableDirections
                    = Direction.shuffledValues()
                        .filter(dir ->
                            !getPosition().by(dir)
                                .adjustableIn(
                                    0,
                                    0,
                                    visibility.getWidth(),
                                    visibility.getHeight()))
                        .collect(Collectors.toList());
                return getPosition()
                    .inDirectionTo(max.getKey(), availableDirections);
            });
}

private Map<Position, Integer> updateProliferatingValues(
    Visibility visibility,
    RegularRabbitTargetAttitude key,
    Map<Position,
    Set<RegularRabbitTargetAttitude>> positionValues
) {
    List<Position> negativePositions
        = positionValues.entrySet().stream()
            .filter(e -> e.getValue().contains(key))
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());

    Integer epicenterValue = VALUE_MAP.get(key);

    return negativePositions.stream()
            .map(position ->
                    (Map<Position, Integer>) visibility.cells()
                            .map(Cell::getPosition)
                            .collect(Collectors.toMap(
                                    Function.identity(),
                                    pos -> epicenterValue + position.distance(pos),
                                    Integer::sum, HashMap::new)))
            .reduce((map1, map2) ->
                CollectionUtils.mergeMaps(Integer::sum, map1, map2))
        .orElse(Collections.emptyMap());
}

private Map<Position, Set<RegularRabbitTargetAttitude>> updateWithUnits(
    Stream<LivingUnit> units
) {
    Map<Position, Set<RegularRabbitTargetAttitude>> positionValues
        = new HashMap<>();
    Map<Position, List<LivingUnit>> positionUnits
        = units.collect(Collectors.groupingBy(LivingUnit::getPosition));

    positionUnits.forEach((key, value) -> value.stream()
        .map(InterestUnit::new)
        .forEach(iu -> {
            if (iu.asPredator != null) {
                positionValues.computeIfAbsent(key,
                    (pos) -> new HashSet<>()).add(PREDATOR);
            }
            if (iu.asMate != null) {
                RegularRabbitTargetAttitude attitude
                    = goodPartner(iu.asMate) ? MATE : COMPETITOR;
                positionValues.computeIfAbsent(key,
                    (pos) -> new HashSet<>()).add(attitude);
            }
        }));

    return positionValues;
}

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

Посмотреть историю «в разрезе», а также запустить и поиграть можно на github'е.

Update. Уже собранная версия с простым конфигом на github'е

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


  1. LbISS
    24.08.2017 19:58
    +1

    Было бы интересно увидеть ссылку на бинарник, для тех, у кого есть jre, но нет jdk. :)


    1. Evlikat Автор
      25.08.2017 07:05

      Добавил ссылку на бинарник


  1. rrust
    24.08.2017 20:26
    +3

    интересно что будет если добавить такое ограничение:
    породить нового зайца можно будет только после 20 съеденных трав, а волка после 5 съеденных зайцев. Тогда они не будут самопорождаться без еды. А с учетом старения будут участками вымирать, давая траве (и зайцам) возможность вырасти и накормить тех, кто не сидит на месте.


    1. IgeNiaI
      24.08.2017 20:39
      +1

      А я бы сделал так, что зайцы и волки могли бы порождать потомство только если сытость выше определённого уровня (например, 50 из 100), а после размножения сытость снижается (например, на 30).


      1. Evlikat Автор
        25.08.2017 06:27

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


        1. IgeNiaI
          25.08.2017 06:36

          Тут суть в том, что после размножения сытость уменьшается и существу нужно идти есть.


        1. Zenitchik
          25.08.2017 10:20
          +2

          "закон сохранения" должен быть. При рождении потомства сумма сытости матери и потомков не меняется.


    1. Evlikat Автор
      25.08.2017 06:31

      Мысль интересная, спасибо.
      На данный момент с этой задачей хорошо справляется «взрослость». Она показатель того, что что-то было съедено, в противном случае существо бы просто не дожило до взрослой стадии. Более того, поведение существ нужно будет менять под новые правила, иначе действовать они будут неэффективно.


  1. cepro
    24.08.2017 23:13
    -5

    Ну да, а в природе все как-то само-собой организовалось. Бывают же случайности!


    1. Zenitchik
      25.08.2017 01:34
      +6

      В природе всё отлаживалось миллиард лет. И случайность ту ни при чём.


      1. Free_Hand
        25.08.2017 09:22
        +1

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


        1. Zenitchik
          25.08.2017 11:11
          -2

          Случайность — не часть отладки. Она нужна для зашумления кода, чтобы было что отлаживать.


          1. Free_Hand
            25.08.2017 13:28
            -1

            может быть, но она явно дала свой эффект и даёт до сих пор.


      1. Steed
        25.08.2017 11:25
        -1

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


        1. Zenitchik
          25.08.2017 11:59
          +2

          Логично. В природе вымерла далеко не одна популяция.


      1. domix32
        25.08.2017 12:11
        -1

        Это сложно назвать отладкой


      1. alsii
        25.08.2017 14:25

        Кстати, можно все эти коэффициенты сделать индивидуальными для каждой особи и ввести наследование. В первом приближении брать средние значения от родителей + случайные мутации. А потом посмотреть, какие характеристики будут преобладать через несколько миллионов поколений.
        И еще. Если уж есть запах зайцев, должен быть и запах волков. Зайцы будут его избегать, волки использовать для моделирования сексуального поведения.


    1. alff31
      25.08.2017 15:26

      Случайностей нет, если помнить, что 99% всех живущих видов вымерло


      1. AllexIn
        25.08.2017 21:42

        99%… Эк вы загнули…
        99.999999999999999999999999999999999999%


    1. dmitrykazakov
      25.08.2017 17:24
      +1

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


  1. sotnikdv
    25.08.2017 00:55

    > если волк не хочет есть, то среди видимых объектов выбирается ближайший объект интереса, с которым можно размножиться

    Зайчика жалко.

    > Ну да, а в природе все как-то само-собой организовалось. Бывают же случайности!

    Отбор и эволюция, слышали? Модель Лотки — Вольтерры и прочая антихристианская ересь.


  1. sutasu
    25.08.2017 01:46
    -1

    Ваш основательный подход напомнил мне игру «Dwarf Fortress». :))


    1. Evlikat Автор
      25.08.2017 06:32
      +1

      Спасибо, но, боюсь, это сильно упрощенная модель по сравнению с «Dwarf Fortress».


  1. AndSoft
    25.08.2017 03:33

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


    1. Evlikat Автор
      25.08.2017 06:34
      +1

      Звучит как Feature request :)


      1. Labunsky
        25.08.2017 12:43
        +3

        Еще нужно, чтобы можно было грабить корованы


    1. Rsa97
      25.08.2017 06:40
      +3

      Остаётся добавить наследуемую генетическую информацию, например:
      — дальность зрения, за счёт постоянного небольшого увеличения энергозатрат;
      — скорость бега, за счёт постоянного небольшого увеличения энергозатрат и увеличения энергозатрат на передвижение;
      — масса тела, больше накапливаемый запас энергии, за счёт увеличения энергозатрат на передвижение…


      1. Areso
        25.08.2017 07:39
        +2

        Не забыть сделать рецессивные и доминантные признаки и их наследование по простейшим правилам.


  1. mwambanatanga
    25.08.2017 07:29
    +1

    Нужна целевая функция. У волка две цели — есть и размножаться. При пустом брюхе и большой плотности, волк ищет еду. При полном брюхе и недостатке общения волк ищет волчицу.


  1. kreo_OL
    25.08.2017 08:17

    А если добавить что при размножение забирается сытость?


  1. Vjatcheslav3345
    25.08.2017 08:34
    +2

    Осталось испробовать генетический алгоритм эволюции с отбором, изменчивостью и наследственностью...


  1. NeoCode
    25.08.2017 09:30
    +1

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


  1. mmMike
    25.08.2017 10:05

    Забавно. Как знакомо…
    Сам такие задачи делал в рамках лабораторных (даже не курсовая..)
    Правда не просто так, а в рамках изучения классов и наследования C++.
    Базовый класс животное… наследуемые классы зверей со своими свойствами и поведением.
    И общий цикл тайминга, передающий в цикле управления объектам классов.
    (вариант с многонитевой средой — отдельная лаба..)


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


    Хотя… может быть и нет. по описанию и кусочку кода судить сложно.


    1. Evlikat Автор
      25.08.2017 10:28

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

      Я бы поспорил. Взгляните на остальной код — ссылка на репозиторий указана.


      1. mmMike
        25.08.2017 11:00

        Ага… слишком бегло глянул.
        теперь вижу.


           newRabbits.forEach(newRabbit -> drawableRabbits.add(drawableZooFactory.wrap(newRabbit)));

        Спасибо за статью.
        Интересно было посмотреть как и чему сейчас учат...


  1. Goodkat
    25.08.2017 10:54
    +9

    Если волк и заяц оказываются в одном квадрате, волк съедает кролика
    Кролик у волка с собой? :)


    1. Evlikat Автор
      25.08.2017 10:57

      Спасибо! Поправил


  1. Igor_Sib
    25.08.2017 11:06
    +1

    Обожаю подобные статьи.

    Помню после публикации вот этой в 10-м году: geektimes.ru/post/90571 тоже стал делать подобное…

    У меня не было клеток, было свободное поле. Были деревья (ну или трава, не знаю как назвать верно), которые росли со временем, и раскидывали семена в заданном радиусе. И были существа (по аналогии с вдохновившей меня статьей назывались флексы), которые жрали травку, ну и сделал так же вероятность каннибализма в случае голода (опять же под впечатлением статьи). Размножение, смерть от старости — все это тоже было. ДНК как таковой не было, но в потомстве учитывалась скорость родителей, радиус зрения, уровень хищничества и тп параметры.

    ИИ был примерно такой — были потребность есть, потребность двигаться (чтобы шарились по карте), потребность размножется. Все это складывалось и по вероятности выбиралось действие. (Например если только поел — то потребность есть = 0, со временем повышается, если давно не размножался — то потребность размножаться =10, потребность двигаться всегда была = 5, генерится число от 0 до 15, если 0-10 — то выбираем размножаться, если 10-15 — то шаримся по карте). В случае если перс хищник надо было еще догнать жертву. Был стек действий, чтобы можно было запланировать несколько действий и выполнять их, если еще актуальны. Хотя сейчас я бы делал это на основе GOAP (очень похоже на то что я реализовал тогда, только GOAP круче).

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

    Баланс — да, сложно было добиться устойчивой системы. И быстродействие, у меня было все в риалтайме и без клеток (еще и в 3Д, хотя и плоскими спрайтами :)), через некоторое время начинало жутко тормозить. Ну и времени все это отнимало кучу.

    Сча прямо ностальгия, после прочтения статьи.


  1. opencloser
    25.08.2017 11:34

    Классическая задача хишник и жертва, просто модифицированная. Баланс находится решением диф.уров., она также показывает ограниченность десятичной дроби, тем самым теоретически бесконечный баланс на практике не достигается, но внушительное количество итераций можно пройти. Данную задачу делал еще в Школе, немного в другой формулировке, но впечатления от нее остались до сих пор.


  1. Kobalt_x
    25.08.2017 13:22

    получаются весёлые картинки, если волк с зайцем в одной клетке


    1. Kobalt_x
      25.08.2017 13:25

      (del)


  1. amarao
    25.08.2017 13:51

    Основное — почему роды бесплатны? Роды должны: понижать сытость самок, и при этом существенно (как минимум на объём прибавки у потомства). Новорожденные/молодые не должны иметь полной сытости после рождения.


  1. ScratchBoom
    25.08.2017 14:09
    +1

    Канал инди разработчика, который делает целую игру-экосистему со множеством видов www.youtube.com/watch?v=B41HV4QpS88


  1. dmrt
    25.08.2017 14:24

    Супер! Я точно такое же начинал делать, только в браузере, с WebSocket.
    И я хотел сделать с рыбками: хищными и не хищными. Так же хотел добавить объекты вроде камней, островов, потом хотел растянуть карту динамически, потом хотел чтобы они двигались попиксельно, а не большими квадратами, потом, чтобы объединялись в группы, потом, чтобы группы двигались определенными маршрутами, потом меняли маршрут, чтобы можно было управлять группами или даже каждой рыбкой в отдельности, посылать команды ей, сохранять текущее состояние игры, чтобы продолжить потом с сохраненного места, потом чтобы это были не только рыбки, а все что угодно.
    Потом испытал слишком сильную эйфорию от своей идеи и понял, что это потянет на огромное количество кода и времени и к тому же не был уверен, что моя идея правильная и осуществима, и что я верно продумал все, так что оставил не на долго, чтобы подумать, в итоге уже год не могу вернуться и начать заново. Не могу придумать какие технологии лучше задействовать.
    Но вы меня вдохновили.


  1. Bomkass
    25.08.2017 14:24

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


  1. saboteur_kiev
    25.08.2017 15:46
    +1

    В дографическую эпоху было нечто подобное, игра «жизнь».
    https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life

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


  1. devpony
    25.08.2017 15:49

    Есть хорошая книжка по теме:
    Математическое моделирование и исследование устойчивости биологических сообществ


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


  1. Algoritmist
    25.08.2017 17:21
    +1

    В детстве я читал такую статью в журнале «В мире науки» за февраль 1985 года.
    Статья «Акулы и рыбы ведут экологическую войну на тороидальной планете Аква-Тор»
    В мире науки, 1985 №2. По этой статье несколько раз делал программки для освоения разных языков программирования. Пару раз сам проводил занятия, используя этот материал для обучения. Кравивая модель.


    1. Algoritmist
      25.08.2017 17:33

      Кравивая => красивая


  1. RazorRunner
    25.08.2017 17:21
    +1

    Напомнило «Какое страшное самоубийство!» ithappens.me/story/365


  1. avi2011class
    25.08.2017 17:21
    -1

    github.com/alartum/cells как-то так


  1. l_e_v
    25.08.2017 17:34

    Следующий ход: перевести траву с питания солнечной энергией на трупы волков и зайцев, и/или заставить их удобрять травку через n ходов после еды.


  1. gotch
    25.08.2017 18:06

    Надеюсь, на седьмой-то день отдохнете?


  1. Timmma
    25.08.2017 18:10

    Что можно было бы добавить:

    • возможность задавать различное поведение для разных участников
    • возможность передавать свой код потомству


  1. Peter1010
    26.08.2017 06:56

    На 3м курсе писал подобное. Лаба называлась зайцы и лисы :)
    И точно так же зацепило.
    В итоге появились, камни, трава, лисы, оградки, лисы имели область видимости зайцы то же. Лисы искали зайцев или лис, зайцы искали траву или лис. Так же умели искать путь в лабиринтах оградок. Но система так и не стала стабильной. были параметры при которой она становилась стабильной, но… Сделать беременность лисам и зайцам я тогда не додумался :)


  1. Delphin92
    26.08.2017 06:56

    Решал такую задачу в школе, потом уже в более сознательном возрасте. Оба раза в условии был заложен интерес волков к волчицам: если поблизости нет зайцев, волк начинал охотиться за волчицей. Так же, как у вас, это привело к экспоненциальному размножению волков на одной клетке.

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

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