Голодные игры - Ешь или умри

60

Голодные игры - Ешь или умри

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

обзор

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

Как играть

Создайте и отправьте программу командной строки, чтобы направить ваш пакет. Он будет получать информацию о состоянии от управляющей программы на STDIN и выводить команды на STDOUT. Формат подробно описан ниже. Каждая программа будет выполняться только один раз и должна продолжаться до тех пор, пока в ней не останется живых участников пакета. Вам нужно будет прочитать ввод, как он поступает, и быстро ответить. Для каждого ответа существует строгое время ожидания 200 мс. Если к тому времени вы не ответили, ваш пакет не получит новых инструкций для текущего хода.

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

Турнир будет проходить на 64-битной системе Linux. Имейте это в виду при указании любых необходимых указаний.

подробности

  • Положение и направление каждого существа имеют форму пары чисел с плавающей запятой двойной точности (например double), представляющих их xи yкоординаты соответственно.

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

  • Остров представляет собой квадрат, 500 единиц в сторону. Если вы попытаетесь выйти за эти рамки, вы будете зажаты на краю. Начало координат {0,0}вверху слева, с xувеличением вправо и yувеличением вниз. Опять же карта не переносится .

  • Игра начинается с 1500+ (packCount * 50) хищных животных. Они будут собраны в центре острова, но быстро решат начать движение.

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

  • Хищные животные могут видеть всех других животных в радиусе 30 единиц. Они могут двигаться максимум 6.0 единиц за ход.

  • Хищники могут видеть всех других животных в радиусе 50 единиц. Они могут двигаться со скоростью не более 6,1 единиц за ход. Это означает, что они могут видеть добычу до того, как ее увидят, и (едва) опередить ее.

  • Хищники живут и умирают в зависимости от уровня голода . Он начинается с 1000 и уменьшается на единицу каждый ход. Если после движения хищник находится в пределах 1 единицы добычи, он автоматически съест его. Это удаляет добычу и устанавливает голод хищника на 1000. Каждый хищник может съесть только одну добычу за ход. Если в пределах диапазона более одного, он будет съедать тот, к которому цикл попадет первым (не обязательно ближайший). Хищник умирает, если его голод достигает нуля.

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

  • Каждые 1000 ходов появляется больше жертв. Число новых жертв будет числом живых хищников минус один.

  • Хищники не могут атаковать других хищников. Они едят добычу, когда ловят ее. Вот и все.

  • Порядок за ход:

    • Все жертвы принимают решения
    • Все хищники принимают решения
    • Все добыча движется
    • Все хищники двигаются / едят
  • Порядок, в котором каждая колода принимает свои решения / поступает, будет рандомизирован на каждом ходу.

Протокол (Общий)

Все коммуникации осуществляются в строковом формате US-ASCII. Числа преобразуются в строки с использованием Java Double.toString()или Integer.toString(). Ваш вывод должен быть отформатирован так, чтобы его могли прочитать Java Double.valueOf(String)(вы не будете выводить целые числа). Подробнее о разбираемых форматах см. В документации поDouble . Все поля в строке разделены стандартным \tсимволом, а переводы строки \n. Вся строка будет завершена нулевым байтом \0.

В примерах ниже я использую, <>чтобы пометить поля для удобства чтения. Их нет в реальных строках.

Протокол (вход)

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

  • Строка 0: основная информация об игре. turnномер текущего хода, а количество - это общее количество добычи и хищников, оставшихся на поле. Это integerв виде строки.

    <turn>\t<preyCount>\t<predatorCount>\n
    
  • Строка 1: уникальные идентификаторы членов вашей группы и уровни голода. Они не даны в одинаковом порядке для каждого входа. Используйте уникальные идентификаторы для отслеживания отдельных членов, а не порядок, в котором они появляются на входе. Опять же, это integerкак строки. Для пакета из двух это будет:

    <id[0]>\t<hunger[0]>\t<id[1]>\t<hunger[1]>\n
    
  • Строка 2: Позиции участников вашей стаи, в том же порядке, который указан в строке 1 . Это doubleкак строка:

    <x[0]>\t<y[0]>\t<x[1]>\t<y[1]>\n
    

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

Первый для каждого состоит из мест для добычи, которую он может видеть. Второе - места для хищников, которых он может видеть. Эти места не являются уникальными в целом. Например, если два члена стаи могут видеть одно и то же животное, оно будет в строке обоих членов. Кроме того, ваши собственные участники пакета будут включены. Если вы хотите исключить их, вы можете сравнить местоположения с собственными участниками. Все местоположения в doubleформате строки.

Для каждого живущего члена:

<prey[0].x>\t<prey[0].y>\t<prey[1].x>\t<prey[1].y>\n
<predator[0].x>\t<predator[0].y>\t<predator[1].x>\t<predator[1].y>\n

Наконец, последний символ будет \0в начале следующей строки.

Исключение: если вы получили ввод dead\0, ваш пакет мертв. Пожалуйста, закончите свою программу изящно. Контроллер должен закрывать все живые процессы в закрытом состоянии, но я бы не хотел, чтобы процессы зомби были повсюду. В качестве любезности вы можете указать время ожидания ввода. Например, мой пример класса заканчивается, если он не получает ввод в течение 15 секунд.

Протокол (Выход)

Вывод прост. Вы дадите пару doubleзначений для каждого члена пакета live. Они представляют движение, которое вы хотели бы, чтобы они взяли на этом ходу. Например, если ваше существо находится в данный момент {100.0, 100.0}и вы даете им команду {-1.0, 1.0}, они перейдут в {99.0, 101.0}. Все числа будут в одной строке, разделенной табуляцией.

Например, если у вас было 3 активных участника пакета, это был бы правильный ответ:

1.0\t-1.0\t2.0\t-2.0\t3.0\t-3.0\0

Это будет двигаться ваши существа путем {1.0,-1.0}, {2.0,-2.0}и {3.0,-3.0}. Порядок такой же, как и полученный на входе. Не забудь конец \0!

Если вы введете неверные данные, последуют плохие результаты. Если какое-либо одно число не может быть проанализировано для a double, оно станет нулевым. Если строка в целом не может быть проанализирована, никаких новых инструкций не будет, и весь ваш пакет будет использовать указания из предыдущего хода.

Все направления будут зафиксированы на максимальном расстоянии 6,1 единиц. Вы можете двигаться медленнее, чем это, если хотите. Например, {1, 0}переместит вас на одну единицу. {6,8}(расстояние 10) только переместит вас на 6,1 единиц и уменьшит до примерно {3.66, 4.88}. Направление остается постоянным.

Важно: управляющая программа считывает ваш STDOUT и STDERR. Если вы сгенерируете исключение и напечатаете в STDERR, очень маловероятно, что сообщение будет иметь форму правильного ответа. Старайтесь избегать этого.

Программа управления / Тестирование

Источник для контроллера можно найти здесь на bitbucket.org . Вам нужно будет скомпилировать его перед запуском. Основной класс есть Game, и все классы находятся в пакете по умолчанию. Для запуска включите команду каждого пакета в качестве отдельного аргумента. Например, если вы хотите запустить Java ChaserPack и Python LazyPack.py, вы можете использовать:

java Game "java ChaserPack" "python LazyPack.py"

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

Игра будет продолжаться до тех пор, пока последний хищник не умрет от голода, и пишет в консоль, когда происходят события голодания или вымирания. Как только игра будет завершена, оценка будет выставляться за каждую колоду. Если вы не хотите видеть события голодания / вымирания, вы можете использовать -silentаргумент. Тогда он будет только выводить окончательный счет. Вы должны передать это в качестве первого аргумента :

java Game -silent "java ChaserCat" "./someOtherPack"

Включен скелет Java-пакета с именем GenericPack. Он включает в себя основные необходимые операции ввода / вывода. Это там, чтобы дать четкий пример того, как разобрать и ответить. Если вы хотите добавить шаблон на другом языке, дайте мне знать.

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

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

Скриншот

счет

Победитель будет определяться турниром, набирая очки в каждом раунде.

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

Первое место за каждый раунд получит 100 очков. За каждое место после этого вознаграждение будет уменьшено на 20% (округлено в меньшую сторону). Это будет продолжаться до тех пор, пока очки не достигнут нуля (после 17-го места). Места 18+ не получат очков за раунд. Пакеты, которые связывают, получат равные очки. Например:

1st : 100
2nd : 80
3rd : 64 (T)
3rd : 64 (T)
4th : 51
...
17th: 1
18th: 0
19th: 0

Максимально возможное количество очков в течение турнира составляет 1000, с первого места все десять раз.

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

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

Дополнительные правила (играй честно!)

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

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

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

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

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

Результаты - 29 апреля 2014

Вот результаты последнего турнира из десяти раундов:

Clairvoyant         : 1000
EcoCamels           : 752
Netcats             : 688
RubySpiders         : 436
RubyVultures        : 431
CivilizedBeasts     : 382
LazyPack            : 257

Пакеты, представленные до 09:00 EDT 2014/04/29, включены в этот прогон.

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

Обновления

2014/04/23: FGreg сообщил об ошибке, связанной с тайм- аутами (спасибо!). Исправление было реализовано, поэтому тестировщики захотят обновить код своей управляющей программы.

Geobits
источник
28
Мне нравятся эти вопросы о короле горы!
Cruncher
2
@Manu Я написал пример ботов на Windows 7 и протестировал на win и linux. Какие у вас проблемы с ними?
Geobits
2
Эти вопросы о короле горы весьма удивительны, и этот, безусловно, интересен. У меня сейчас два разных пакета в работе!
mackthehobbit
2
@githubphagocyte Я действительно не хочу убивать пачку в первый тайм-аут, просто потому, что я видел даже простые программы, тайм-аут которых один раз каждые 40k + ходов или подобное. Я зафиксировал изменение имен в контроллере. Повороты теперь известны как повороты по всей программе, если я где-то не пропустил.
Geobits
2
@ Geobits да, это нормально для меня. Вы знаете, это выглядит очень похоже на исследовательский проект, который делает один из моих профессоров физики, с которым я мог бы помочь в течение лета. Я объясню немного позже, если смогу.
krs013

Ответы:

10

ясновидящий

Код обновлен до AbleDogs

Woo Hoo! Наконец-то бьет этот Netcats! Я расширил существующий код (кредит Geobits!) С небольшой модификацией для создания этого будущего пакета прогнозирования. Ничто не сравнится с хищниками, которые знают, куда будет двигаться добыча!

Из двух тестов, которые я сделал, мой стэк всегда побеждал Netcats. Но это не будет работать так же хорошо, если других пакетов нет, так как предсказание все равно не будет выполнено, если поблизости будет слишком много других жертв.

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

Сделано за 5,21 минут
Ясновидящая (1): Поворот 9270: Счет 100
EcoCamel.pl (3): ход 8118: счет 80
Netcats (0): Ход 6111: Счет 64
RubyVultures.rb (5): ход 4249: счет 51
RubySpiders.rb (4): Ход 3495: Оценка 40
CivilizedBeasts (2): Ход 3176: Счет 32
ChaserPack (6): Ход 2492: Оценка 25

Из названия моего пакета вы должны знать, какую стратегию я использую = D

Редактировать :

  • Обновлена ​​система управления пакетами, чтобы не преследовать одну и ту же жертву (а также пытаться найти лучшее совпадение!)
  • Улучшите процесс странствий, когда количество жертв невелико (это важно для победы!).
  • Улучшение особых случаев, когда предыдущая версия просто застряла в углу.
  • Исправлена ​​ошибка в алгоритме обнаружения хищников (теперь он довольно точный!)
  • Включенный flock[ALIGN]фактор добычи
  • Держите одну добычу в качестве домашнего животного, если еды мало
  • Создайте логово, где стая будет загонять своих жертв
  • Соблазни ближайшего хищника в погоню за нашей добычей, которую они не выиграют

Я посчитал, сколько еды съедает каждая пачка, и вот результат:

Ясновидящий (1) поглотил 916 жертв в 9270 оборотах (0,099 жертв / оборот)
EcoCamel.pl (3) потреблял 73 жертвы за 8118 ходов (0,009 жертв / ход)
Netcats (0) потребляли 563 жертвы за 6111 ходов (0.092 жертв / ход)
RubyVultures.rb (5) потреблял 77 жертв за 4249 ходов (0.018 жертв / ход)
RubySpiders.rb (4) поглотил 293 жертвы за 3495 ходов (0,084 добычи / ход)
CivilizedBeasts (2) потребляли 10 жертв за 3176 ходов (0,003 жертв / ход)
ChaserPack (6) поглотил 43 жертвы за 2492 оборота (0,017 добычи / ход)

Мой пакет очень агрессивен, и я думаю, что большинство из 916 очков получают от кражи добычи из Netcats, как и RubySpiders.

CivilizedBeasts, к сожалению, проигрывает из-за центрального верблюда от EcoCamel.

И EcoCamel (с критическим голодом 500) довольно эффективен, он ест достаточно, чтобы выжить до конца.

Также с этим обновленным Ясновидением игра едва достигает 10000 ходов.

Код:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.TreeSet;

public class Clairvoyant extends GenericPack {
    private static final double MAX_SPEED = 6.1;

    private TreeSet<Animal> foods = new TreeSet<Animal>(new AnimalComparator());
    private TreeSet<Animal> predators = new TreeSet<Animal>(new AnimalComparator());

    private XY abattoirCorner;
    private double abattoirRadius = 100;

    private MyMember[] myMembers = new MyMember[100];

    public class AnimalComparator implements Comparator<Animal>{

        @Override
        public int compare(Animal arg0, Animal arg1) {
            if(arg0.x < arg1.x){
                return -1;
            } else if (arg0.x > arg1.x){
                return 1;
            } else {
                if(arg0.y < arg1.y){
                    return -1;
                } else if(arg0.y > arg1.y){
                    return 1;
                } else {
                    return 0;
                }
            }
        }
    }

    public class MyMember extends Member{
        public XY target;
        public XY herdPos;
        public double herdRadius; 
        public boolean mayEat;
        public XY pos;
        public ArrayList<MyAnimal> closestPreys;
        public boolean outdated;

        public MyMember(int id) {
            super(id);
            this.pos = new XY(x, y);
            closestPreys = new ArrayList<MyAnimal>();
            mayEat = true;
            outdated = true;
        }

        public MyMember(Member member){
            super(member.id);
            this.pos = new XY(x, y);
            closestPreys = new ArrayList<MyAnimal>();
            mayEat = true;
            outdated = true;
        }

        public MyMember(Member member, Animal target){
            super(member.id);
            this.target = new XY(target.x, target.y);
            this.pos = new XY(x, y);
            closestPreys = new ArrayList<MyAnimal>();
            mayEat = true;
            outdated = true;
        }

        public void reset(Member me){
            x = me.x;
            y = me.y;
            pos = new XY(x, y);
            closestPreys.clear();
            mayEat = true;
            outdated = true;
        }
    }

    public class MyAnimal extends Animal{
        public ArrayList<MyMember> chasers;
        public XY pos;
        public boolean resolved;

        public MyAnimal(double x, double y){
            super(x, y);
            pos = new XY(x, y);
            chasers = new ArrayList<MyMember>();
            resolved = false;
        }

        public MyAnimal(Animal ani){
            super(ani.x, ani.y);
            pos = new XY(x, y);
            chasers = new ArrayList<MyMember>();
            resolved = false;
        }
    }

    public static void main(String[] args){
        new Clairvoyant().run();
    }

    public Clairvoyant(){
        for(int i=0; i<100; i++){
            nextIdx[i] = 0;
        }
        int cornerIdx = (int)Math.floor(Math.random()*4);
        switch (cornerIdx){
        case 0: abattoirCorner = new XY(0,0); break;
        case 1: abattoirCorner = new XY(500,0); break;
        case 2: abattoirCorner = new XY(500,500); break;
        case 3: abattoirCorner = new XY(0,500); break;
        }
    }

    @Override
    public void respond(){
        updateData();
        goToTarget();
    }

    private void updateData(){
        for(int i=0; i<100; i++){
            if(myMembers[i]!=null){
                myMembers[i].pos = null;
            }
        }
        foods.clear();
        predators.clear();
        for(Member me: members){
            foods.addAll(me.foods);
            predators.addAll(me.others);
            predators.add(new Animal(me.x, me.y));
            if(myMembers[me.id] != null){
                myMembers[me.id].reset(me);
            } else {
                myMembers[me.id] = new MyMember(me);
            }
        }
        for(int i=0; i<100; i++){
            if(myMembers[i]!=null && myMembers[i].pos == null){
                myMembers[i] = null;
            }
        }

        TreeSet<MyAnimal> closestPreys = new TreeSet<MyAnimal>(new AnimalComparator());
        for(int i=0; i<100; i++){
            if (myMembers[i]==null) continue;
            MyMember me = myMembers[i];
            ArrayList<Animal> animals = findClosest(foods, me.pos, members.size());
            boolean first = true;
            for(Animal ani: animals){
                MyAnimal myAni = new MyAnimal(ani);
                if(closestPreys.contains(ani)){
                    myAni = closestPreys.ceiling(myAni);
                } else {
                    closestPreys.add(myAni);
                }
                if(first){
                    myAni.chasers.add(me);
                    first = false;
                }
                me.closestPreys.add(myAni);
            }
        }
        performMatching();
        for(int i=0; i<100; i++){
            if (myMembers[i] == null) continue;
            MyMember me = myMembers[i];
            if(!me.outdated) continue;
            if(me.closestPreys.size() == 0) continue;
            MyAnimal closestPrey = me.closestPreys.get(0);
            if(closestPrey.resolved) continue;
            if(closestPrey.chasers.size() > 1){
                MyMember hungriest = me;
                MyMember closest = me;
                for(MyMember otherMe: closestPrey.chasers){
                    if(sqDist(closestPrey.pos, otherMe) < sqDist(closestPrey.pos, closest)){
                        closest = otherMe;
                    }
                    if(otherMe.hunger < hungriest.hunger){
                        hungriest = otherMe;
                    }
                }
                if(hungriest.hunger > 200){ // Nobody's critically hungry, the closest takes the prey
                    closest.target = closestPrey.pos;
                    closest.mayEat = true;
                    closest.herdPos = abattoirCorner;
                    closest.herdRadius = abattoirRadius;
                    closest.outdated = false;
                } else {
                    if(hungriest == closest){
                        closest.target = closestPrey.pos;
                        closest.mayEat = true;
                        closest.herdPos = abattoirCorner;
                        closest.herdRadius = abattoirRadius;
                        closest.outdated = false;
                    } else {
                        closest.target = closestPrey.pos;
                        closest.mayEat = false;
                        closest.herdPos = hungriest.pos;
                        closest.herdRadius = 0;
                        closest.outdated = false;
                        hungriest.target = closestPrey.pos;
                        hungriest.mayEat = true;
                        hungriest.herdPos = abattoirCorner;
                        hungriest.herdRadius = 10;
                        hungriest.outdated = false;
                    }
                }
                closestPrey.resolved = true;
            } else {
                me.target = closestPrey.pos;
                me.herdPos = abattoirCorner;
                me.herdRadius = abattoirRadius;
                me.mayEat = true;
                me.outdated = false;
            }
        }
        for(int i=0; i<100; i++){
            if (myMembers[i] == null) continue;
            MyMember me = myMembers[i];
            if(me.outdated){
                me.target = null;
                me.outdated = false;
            }
        }
    }

    private void goToTarget(){
        for(Member me: members){
            MyMember mem = myMembers[me.id];
            if(mem.target == null){
                wander(me, 2*(me.id%2)-1);
                continue;
            } else {
                nextIdx[me.id] = 0;
                XY[] nearestHostile = new XY[100];
                for(Animal other: me.others){
                    XY otherPos = new XY(other.x, other.y);
                    boolean isMember = false;
                    for(Member otherMember: members){
                        if(other.x==otherMember.x && other.y==otherMember.y){
                            isMember = true;
                            break;
                        }
                    }
                    if(!isMember){
                        if(nearestHostile[me.id] == null || XY.sqDistance(mem.pos, otherPos) < XY.sqDistance(mem.pos,  nearestHostile[me.id])){
                            nearestHostile[me.id] = otherPos;
                        }
                    }
                }

                // Go towards the target by predicting its next position
                XY target = predictNextPos(mem.target, me);

                me.dx = (target.x - me.x);
                me.dy = (target.y - me.y); 

                // Try to herd the target to our abattoir if this member is not too hungry
                // and if there is no other hostile predator who is closer to the target than us
                // This will make the other hostile predator to keep targeting this target, while
                // it is certain that we will get the target.
                // This is a win situation for us, since it will make the other predator wasting his turn.
                if((me.hunger <= 200 && XY.sqDistance(mem.target, mem.pos) > 400) || me.hunger <= 50 ||
                        (nearestHostile[me.id] != null && Math.sqrt(XY.sqDistance(mem.target, nearestHostile[me.id])) < Math.sqrt(XY.sqDistance(mem.target, mem.pos)))){
                    continue;
                }

                // Don't eat if not threatened nor hungry
                if(me.hunger > 50 && (nearestHostile[me.id] == null ||
                        Math.sqrt(XY.sqDistance(mem.target, nearestHostile[me.id])) > Math.sqrt(XY.sqDistance(mem.target, mem.pos)) + 6)){
                    mem.mayEat = false;
                }

                // Herd to abattoir corner
                double distFromHerd = Math.sqrt(XY.sqDistance(target, mem.herdPos));
                XY oppositeAbattoirCorner = new XY(500-abattoirCorner.x, 500-abattoirCorner.y);
                double distFromOpposite = Math.sqrt(XY.sqDistance(target, oppositeAbattoirCorner));
                if((me.dx*me.dx+me.dy*me.dy > 64 && distFromHerd > mem.herdRadius && distFromOpposite > abattoirRadius)
                        || (preyCount < 5*predCount)){
                    double herdDistance = 4*(distFromHerd-mem.herdRadius)/(Island.SIZE-mem.herdRadius);
                    if(!mem.mayEat) herdDistance = 4;
                    XY gradient = target.minus(abattoirCorner);
                    me.dx += gradient.x*herdDistance/distFromHerd;
                    me.dy += gradient.y*herdDistance/distFromHerd;
                }
            }
        }
    }

    private void performMatching(){
        for(int i=0; i<100; i++){
            if (myMembers[i] == null) continue;
            MyMember me = myMembers[i];
            if(me.closestPreys.size()==0) continue;
            MyAnimal closestPrey = me.closestPreys.get(0);
            if(closestPrey.chasers.size() > 1){
                resolveConflict(closestPrey);
            }
        }
    }

    private void resolveConflict(MyAnimal prey){
        ArrayList<MyMember> chasers = prey.chasers;
        MyMember winner = null;
        double closestDist = Double.MAX_VALUE;
        for(MyMember me: chasers){
            if(sqDist(prey.pos, me) < closestDist){
                closestDist = sqDist(prey.pos, me);
                winner = me;
            }
        }
        for(int i=chasers.size()-1; i>=0; i--){
            MyMember me = chasers.get(i);
            if(me!=winner){
                me.closestPreys.get(0).chasers.remove(me);
                me.closestPreys.add(me.closestPreys.remove(0));
                me.closestPreys.get(0).chasers.add(me);
            }
        }
    }

    private Animal findClosest(Collection<Animal> preys, XY me){
        Animal target = null;
        double cDist = Double.MAX_VALUE;
        double x, y, sqDist;
        for (Animal food : preys) {
            x = food.x - me.x;
            y = food.y - me.y;
            sqDist = x * x + y * y + Double.MIN_NORMAL;
            if (sqDist < cDist) {
                cDist = sqDist;
                target = food;
            }
        }
        return target;
    }

    private ArrayList<Animal> findClosest(Collection<Animal> preys, XY me, int num){
        ArrayList<Animal> result = new ArrayList<Animal>();
        for(Animal food: preys){
            int addIdx = -1;
            for(int i=0; i<num && i<result.size(); i++){
                Animal regFood = result.get(i);
                if(sqDist(me, food) < sqDist(me, regFood)){
                    addIdx = i;
                    break;
                }
            }
            if(addIdx == -1){
                result.add(food);
            } else {
                result.add(addIdx, food);
            }
            if(result.size() > num){
                result.remove(num);
            }
        }
        return result;
    }

    private Member findClosestToTarget(Collection<Member> members, Animal target){
        Member member = null;
        double cDist = Double.MAX_VALUE;
        double x, y, sqDist;
        for (Member me : members) {
            x = me.x - target.x;
            y = me.y - target.y;
            sqDist = x * x + y * y + Double.MIN_NORMAL;
            if (sqDist < cDist) {
                cDist = sqDist;
                member = me;
            }
        }
        return member;
    }

    private static final XY[] CHECKPOINTS = new XY[]{
        new XY(49.5,49.5),
        new XY(450.5,49.5),
        new XY(450.5,100),
        new XY(49.5,100),
        new XY(49.5,150),
        new XY(450.5,150),
        new XY(450.5,200),
        new XY(49.5,200),
        new XY(49.5,250),
        new XY(450.5,250),
        new XY(450.5,300),
        new XY(49.5,300),
        new XY(49.5,350),
        new XY(450.5,350),
        new XY(450.5,400),
        new XY(49.5,400),
        new XY(49.5,450.5),
        new XY(450.5,450.5)};
    private int[] nextIdx = new int[100];

    private int advanceIdx(int idx, int sign, int amount){
        return sign*(((Math.abs(idx)+CHECKPOINTS.length-1+sign*amount) % CHECKPOINTS.length) + 1);
    }

    private void wander(Member me, int sign) {
        if(preyCount > 20*predCount){
            if (me.dx == 0 && me.dy == 0) {
                me.dx = 250 - me.x;
                me.dy = 250 - me.y;
                return;
            }

            double lx, ly, px, py;
            lx = me.dx / 4;
            ly = me.dy / 4;
            boolean dir = Math.random() < 0.5 ? true : false;
            px = dir ? ly : -ly;
            py = dir ? -lx : lx;

            me.dx += px;
            me.dy += py;
        } else {
            if(nextIdx[me.id]==0){
                XY farthest = new XY(2000,2000);
                int farthestIdx = -1;
                for(int i=0; i<CHECKPOINTS.length; i++){
                    if(sign*sqDist(CHECKPOINTS[i], me) > sign*sqDist(farthest, me)){
                        farthest = CHECKPOINTS[i];
                        farthestIdx = i+1;
                    }
                }
                nextIdx[me.id] = farthestIdx*sign;
                for(Member mem: members){
                    if(mem.id == me.id) continue;
                    if(nextIdx[mem.id]==nextIdx[me.id]){
                        nextIdx[me.id] = advanceIdx(nextIdx[me.id], sign, 5); 
                    }
                }
            }
            if(sqDist(CHECKPOINTS[Math.abs(nextIdx[me.id])-1],me) < 1){
                nextIdx[me.id] = advanceIdx(nextIdx[me.id], sign, 1);
            }
            me.setDirection(CHECKPOINTS[Math.abs(nextIdx[me.id])-1].x-me.x,
                    CHECKPOINTS[Math.abs(nextIdx[me.id])-1].y-me.y);
        }
    }

    private double sqDist(XY me, Animal target){
        double dx = me.x-target.x;
        double dy = me.y-target.y;
        return dx*dx + dy*dy + Double.MIN_NORMAL;
    }

    private double sqDist(XY me, Member target){
        double dx = me.x-target.x;
        double dy = me.y-target.y;
        return dx*dx + dy*dy + Double.MIN_NORMAL;
    }

    private double sqDist(Animal target, Member me){
        double dx = me.x-target.x;
        double dy = me.y-target.y;
        return dx*dx + dy*dy + Double.MIN_NORMAL;
    }

    private List<Animal> getNeighbors(double radius, XY pos, Collection<Animal> candidates) {
        List<Animal> neighbors = new ArrayList<Animal>();
        for(Animal neighbor: candidates){
            if(sqDist(pos, neighbor) < radius * radius){
                neighbors.add(neighbor);
            }
        }
        return neighbors;
    }

    final double[] weights = { 1, 1, 0.96, 2, 4 };
    double weightSum;

    static final int ALIGN = 0;
    static final int SEPARATE = 1;
    static final int COHESION = 2;
    static final int FLEE = 3;
    static final int WALL = 4;
    static final int VISIBLE = 30;
    static final int VISIBLE_PRED = 50;

    private HashMap<Member, List<Animal>> prevPreys = new HashMap<Member, List<Animal>>();

    private XY matchPreys(List<Animal> prevs, List<Animal> curs, XY prey){
        XY result = new XY();
        double sqDist = 0;
        Animal candidate;
        XY otherPos;
        for(Animal otherPrey: curs){
            otherPos = new XY(otherPrey.x, otherPrey.y);
            sqDist = XY.sqDistance(prey, otherPos);
            if(sqDist > VISIBLE * VISIBLE)
                continue;
            candidate = findClosest(getNeighbors(6, otherPos, prevs), prey);
            if(candidate == null){
                return null;
            }
            result.add(otherPos.x-candidate.x, otherPos.y-candidate.y);
        }
        return result;
    }

    private XY predictNextPos(XY prey, Member me) {
        List<Animal> preys = getNeighbors(VISIBLE_PRED, prey, foods);
        List<Animal> preds = getNeighbors(VISIBLE, prey, predators);

        XY flock[] = new XY[weights.length];
        for (int i = 0; i < weights.length; i++)
            flock[i] = new XY();

        double dx, dy, dist, sqDist;
        for (Animal otherPrey : preys) {
            sqDist = XY.sqDistance(prey, new XY(otherPrey.x, otherPrey.y));
            if(sqDist > VISIBLE * VISIBLE)
                continue;
            dx = otherPrey.x - prey.x;
            dy = otherPrey.y - prey.y;
            flock[COHESION].add(dx*sqDist, dy*sqDist);
            flock[SEPARATE].add(-dx*(1d/sqDist), -dy*(1d/sqDist));
            flock[ALIGN].add(new XY(prey.x-me.x,prey.y-me.y));
        }

        if(sqDist(prey, me) < 400){
            if(prevPreys.get(me) == null){
                prevPreys.put(me, preys);
            } else {
                XY flockAlign = matchPreys(prevPreys.get(me), preys, prey);
                if(flockAlign == null){
                    prevPreys.put(me , null);
                } else {
                    flock[ALIGN] = flockAlign;
                    prevPreys.put(me, preys);
                }
            }
        }

        flock[ALIGN].unitize().multiply(5);
        flock[COHESION].unitize().multiply(5);
        flock[SEPARATE].unitize().multiply(5);

        for (Animal predator : preds){
            flock[FLEE].add(prey.x-predator.x, prey.y-predator.y);
        }

        dx = Island.CENTER.x - prey.x;
        dy = Island.CENTER.y - prey.y;
        dist = Math.max(Math.abs(dx), Math.abs(dy));
        if(dist > 240){
            flock[WALL].x = dx * dist;
            flock[WALL].y = dy * dist;
            flock[WALL].unitize().multiply(5);
        }

        XY vec = new XY();
        vec.x = 0;
        vec.y = 0;
        for (int i = 0; i < flock.length; i++) {
            flock[i].multiply(weights[i]);
            vec.add(flock[i]);
        }
        limitSpeed(vec);
        return vec.add(prey);
    }

    private XY limitSpeed(XY move) {
        if (move.x*move.x+move.y*move.y > MAX_SPEED*MAX_SPEED)
            move.unitize().multiply(MAX_SPEED);
        return move;
    }
}
justhalf
источник
1
Выглядит очень хорошо, ваша игра действительно лучше, чем у netcats в моей игре. Но я ненавижу то, что я не могу управлять другими хищниками, так как мои звери действительно плохо справляются с вашей статистикой (в то время как злая камера слишком хороша). Может быть, я должен попытаться установить компилятор Perl или около того.
Herjan
Да, я думаю, что ваш метод не работает, если в середине есть хищник, как обсуждалось в вашем ответе. Я попытался реализовать другую версию, которая ведет себя так же, как ваша. Это может изменить формирование в зависимости от количества доступных хищников, так что это довольно интересно смотреть, хотя и не намного лучше, чем у вас.
justhalf
Да, моя стратегия может быть улучшена во многих отношениях, как и другие соединения с другим количеством участников, потому что мои звери обречены <4 хищниками. Или случайные места для сбора (вместо только середины), например. Но мне лень это реализовывать (сейчас). И он никогда не будет так хорош, как этот, потому что, если жертва ослабнет, моя тактика просто не сработает. Вот когда вам нужен такой зверь, как ваш (вы уже упоминали, что начинаете с моей тактики, и когда жертва не может использовать эту тактику). Так что, я думаю, вы уже обдумали это.
Herjan
Я сейчас на другом вызове, и GeoBits, похоже, потерял к нему интерес, поэтому я оставлю его на некоторое время, пока результаты не будут обновлены. У меня есть идеи для нескольких других заявок, поэтому я надеюсь, что этот вызов останется в живых. Я посмотрю на ваше обновление, конечно.
15

Netcats

Вот пачка, чтобы вы, ребята, начали. Расширяет GenericPackкласс, включенный в управляющую программу. Он был улучшен с момента первоначальной публикации, и больше не голодает с редким стадом.

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

Сеть начинается довольно мало, но расширяется, когда стадо становится меньше, чтобы более эффективно тралить поле.

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

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

Эта версия выживает намного лучше, чем наивные Netcats, показанные в видео, связанном в вопросе.

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

public class Netcats extends GenericPack {

    boolean seeking;
    Member head = null;
    Set<Animal> foods;

    public static void main(String[] args) {
        new Netcats().run();
    }

    @Override
    public void respond() {
        if (foods == null)
            foods = new HashSet<Animal>();
        else
            foods.clear();
        for (Member member : members)
            foods.addAll(member.foods);

        if (members.size() < 3) {
            soloRun();
        } else {
            head = setHead();
            setHeadVec();
            for (int i = 1; i < members.size(); i++) {
                setMemberVec(i);
            }
        }
    }

    Member setHead() {
        if (!members.contains(head))
            return members.get(0);

        Member hungry = head;
        int idx = 0;
        for (int i = 0; i < members.size(); i++) {
            Member me = members.get(i);
            if (me.hunger < hungry.hunger) {
                hungry = me;
                idx = i;
            }
        }

        if (hungry != head) {
            members.remove(hungry);
            members.remove(head);
            members.add(0, hungry);
            members.add(idx, head);
            return hungry;
        }
        return head;
    }

    void setHeadVec() {
        double x = 0, y = 0;

        Collection<Animal> yummy = getFoods(head);

        seeking = false;
        if (yummy.size() == 0) {
            scoutHead();
            return;
        }

        if (members.size() == 1)
            if (findFood(head))
                return;

        for (Animal food : yummy) {
            x += food.x - head.x;
            y += food.y - head.y;
        }
        x *= 10000000;
        y *= 10000000;

        head.dx = x;
        head.dy = y;
        if (members.size() > 1)
            limitSpeed(head, MAX_SPEED * HEAD_MULT);
    }

    void scoutHead() {
        seeking = true;
        head.dy = 250 - head.y;
        head.dx = round % 80 < 40 ? -head.x : 500 - head.x;
    }

    void setMemberVec(int idx) {
        Member me = members.get(idx);
        Member leader;
        leader = idx < 3 ? members.get(0) : members.get(idx - 2);
        if (findFood(me))
            return;

        double lx, ly, px, py, tx, ty, dist;
        lx = -leader.dx;
        ly = -leader.dy;
        dist = Math.sqrt(lx * lx + ly * ly) + Double.MIN_NORMAL;
        lx /= dist;
        ly /= dist;
        px = idx % 2 == 0 ? ly : -ly;
        py = idx % 2 == 0 ? -lx : lx;

        tx = leader.x + leader.dx;
        ty = leader.y + leader.dy;
        int xtrack = seeking ? COMB : preyCount > 400 ? ASIDE : MID_SIDE;
        tx += lx * BEHIND + px * xtrack;
        ty += ly * BEHIND + py * xtrack;

        me.dx = tx - me.x;
        me.dy = ty - me.y;
        limitSpeed(me, MAX_SPEED * (idx < 3 ? MID_MULT : 1));
    }

    Collection<Animal> getFoods(Member me) {
        return me.foods.size() == 0 ? foods : me.foods;
    }

    boolean findFood(Member me) {
        if (me.hunger > 500)
            return false;

        Collection<Animal> yummy = getFoods(me);
        if (yummy.size() == 0)
            return false;

        double x, y, sqDist, cDist = 10 * 10;
        Animal target = null;
        for (Animal food : me.foods) {
            x = food.x - me.x;
            y = food.y - me.y;
            sqDist = x * x + y * y + Double.MIN_NORMAL;
            if (sqDist < cDist) {
                cDist = sqDist;
                target = food;
            }
        }

        if (target == null)
            return false;

        if (cDist < 5 * 5 || me.hunger < 200) {
            me.dx = (target.x - me.x) * 10000000d;
            me.dy = (target.y - me.y) * 10000000d;
            return true;
        }
        return false;
    }

    void soloRun() {
        double x, y, sqDist, cDist;
        for (Member me : members) {
            Collection<Animal> yummy = getFoods(me);
            if (yummy.size() == 0) {
                wander(me);
                continue;
            }

            Animal target = null;
            cDist = Double.MAX_VALUE;
            for (Animal food : yummy) {
                x = food.x - me.x;
                y = food.y - me.y;
                sqDist = x * x + y * y + Double.MIN_NORMAL;
                if (sqDist < cDist) {
                    cDist = sqDist;
                    target = food;
                }
            }

            me.dx = (target.x - me.x) * 100000d;
            me.dy = (target.y - me.y) * 100000d;
        }
    }

    void wander(Member me) {
        if (me.dx == 0 && me.dy == 0) {
            me.dx = 250 - me.x;
            me.dy = 250 - me.y;
            return;
        }

        double lx, ly, px, py;
        lx = me.dx / 4;
        ly = me.dy / 4;
        boolean dir = Math.random() < 0.5 ? true : false;
        px = dir ? ly : -ly;
        py = dir ? -lx : lx;

        me.dx += px;
        me.dy += py;
    }

    void limitSpeed(Member me, double max) {
        double x = me.dx, y = me.dy;
        double dist = Math.sqrt(x * x + y * y) + Double.MIN_NORMAL;
        if (dist > max) {
            x = (x / dist) * max;
            y = (y / dist) * max;
        }
        me.dx = x;
        me.dy = y;
    }

    final static double MAX_SPEED = 6.1;
    final static double HEAD_MULT = 0.85;
    final static double MID_MULT = 0.92;
    final static int BEHIND = -25;
    final static int ASIDE = 15;
    final static int MID_SIDE = 30;
    final static int COMB = 150;
}
Geobits
источник
11

Рубиновые Пауки

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

Я думал, что мой пакет может просто расстаться и ждать, пока другие сделают эту работу.

gets
print "3.0\t3.0\t3.0\t-3.0\t-3.0\t-3.0\t-3.0\t3.0\t0.0\t0.0\0"
STDOUT.flush

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

Легат
источник
4
+1 первое решение от паразитов. Я думаю, что этот тип ответа повысит качество других ответов, постепенно устраняя лазейки ...
trichoplax
@githubphagocyte Я имел в виду более умного паразита, но это более эффективно с точки зрения времени жизни / строк кода. Я надеюсь, что найду время для его реализации.
Легат
Может быть @Synthetica сейчас кодирует мою идею. Или, если его идея еще одна, у нас скоро может появиться больше паразитов, чем охотников;)
Legat
1
@githubphagocyte нам разрешено сделать три записи, поэтому я выложу еще один пакет, как только он будет готов. Тем не менее, я нахожу интересным, что этот кодировался тем временем, и он может оказаться более эффективным. Он очень хорошо использует преимущества Netcats и фактически переживает мой первый набор охотников.
Легат
3
Это может войти как есть, даже если мне потребовалась секунда, чтобы понять, почему. Кажется, чем больше вы добавляете Netcats (что имеет смысл), тем лучше. +1 от меня, посмотрим, какие охотники выходят, чтобы избежать поворотов :)
Geobits
11

CivilizedBeasts

Наконец-то пришло время похвастаться моими животными

Моя порода думает, что охота несколько примитивна, поэтому они работают вместе в команде из 4 человек, и поэтому они отказываются от своего 5-го союзника, потому что: меньше хищников = больше добычи для себя. То, что они в основном делают, это то, что люди делают, они ловят добычу и заботятся о своем скоте;)

public class CivilizedBeasts extends GenericPack{

    private static int TL = 0, TR = 0, BL = 0, BR = 0; // TopLeft/BotRight
    private static int teamSize = 0, turnsWaiting = 0, turnsToWait = 20;

    private boolean out = true;
    private double maxSpeed = 6.1, mapSize = 500;

    public CivilizedBeasts(){
    }

    @Override
    public void respond(){
        if(teamSize > members.size()){

            Member check = getMemberById(TL);
            totalLoop:
            if(check == null){
                for (Member member : members) {
                    if(member.id != TR && member.id != BL && member.id != BR){
                        TL = member.id;
                        break totalLoop;
                    }
                }

                TL = 0;
            }

            check = getMemberById(TR);
            totalLoop:
            if(check == null){
                for (Member member : members) {
                    if(member.id != TL && member.id != BL && member.id != BR){
                        TR = member.id;
                        break totalLoop;
                    }
                }

                TR = 0;
            }

            check = getMemberById(BL);
            totalLoop:
            if(check == null){
                for (Member member : members) {
                    if(member.id != TL && member.id != TR && member.id != BR){
                        BL = member.id;
                        break totalLoop;
                    }
                }

                BL = 0;
            }

            check = getMemberById(BR);
            totalLoop:
            if(check == null){
                for(Member member : members) {
                    if(member.id != TL && member.id != TR && member.id != BL){
                        BR = member.id;
                        break totalLoop;
                    }
                }

                BR = 0;
            }
        }else if(teamSize < members.size()){
            for(Member member : members) {
                if(member.id != TL && member.id != TR && member.id != BL && member.id != BR){
                    if(TL == 0)
                        TL = member.id;
                    else if(TR == 0)
                        TR = member.id;
                    else if(BL == 0)
                        BL = member.id;
                    else if(BR == 0)
                        BR = member.id;
                }
            }
        }

        teamSize = members.size();

        double border = 1;
        double x, y;
        boolean reached = true;

        double distance = 16.3;

        for (Member member : members) {
            boolean doesNotCount = false;
            x = 0; y = 0;
            if(member.id == TL){
                if(out){
                    x = -(member.x - border);
                    y = -(member.y - border);
                }else{
                    x = ((mapSize/2 - distance) - member.x);
                    y = ((mapSize/2 - distance) - member.y);
                }
            }else if(member.id == TR){
                if(out){
                    x = (mapSize - member.x - border);
                    y = -(member.y - border);
                }else{
                    x = ((mapSize/2 + distance) - member.x);
                    y = ((mapSize/2 - distance) - member.y);
                }
            }else if(member.id == BL){
                if(out){
                    x = -(member.x - border);
                    y = (mapSize - member.y - border);
                }else{
                    x = ((mapSize/2 - distance) - member.x);
                    y = ((mapSize/2 + distance) - member.y);
                }
            }else if(member.id == BR){
                if(out){
                    x = (mapSize - member.x - border);
                    y = (mapSize - member.y - border);
                }else{
                    x = ((mapSize/2 + distance) - member.x);
                    y = ((mapSize/2 + distance) - member.y);
                }
            }else{
                double dist = 50, temp = 0;
                int index = -1;
                for(int i = 0; i < member.foods.size(); i++){
                    temp = (Math.abs(member.foods.get(i).x - member.x)+Math.abs(member.foods.get(i).y - member.y));
                    if(temp < dist){
                        dist = temp;
                        index = i;
                    }
                }
                if(index != -1){
                    x = (member.foods.get(index).x - member.x);
                    y = (member.foods.get(index).y - member.y);
                }
                doesNotCount = true;
            }

            if(!doesNotCount && Math.abs(x)+Math.abs(y) > maxSpeed)
                reached = false;
            member.setDirection(x,y);
        }

        if(reached){
            if(!out){ // in the middle.
                if(teamSize < 4){
                    int temp = TL;
                    TL = BR;
                    BR = temp;
                    temp = TR;
                    TR = BL;
                    BL = temp;
                    out = true;
                }else{
                    turnsWaiting++;
                }
            }else // no need to wait in the corners
                out = false;

            if(turnsWaiting >= turnsToWait){
                turnsToWait = 15;
                out = true;
                turnsWaiting = 0;
            }

        }

    }

    public static void main(String[] args){
        new CivilizedBeasts().run();
    }
}

Моим грудям становится довольно трудно выжить с менее чем 200 жертвами на ходу + -12.000 только с вражескими Netcats в игре. Вы будете счастливы с этой породой, так как она действительно пожирает огромное количество добычи со скоростью, с которой не может справиться ни одна другая порода (не то, что быстрая и большая бойня дает победу, но это влияет на (длительное) время, которое занимает целый раунд).

Herjan
источник
3
Если под « заботиться о них » вы имеете в виду « многократно загонять их на середину и убивать / есть их », то да, они делают это хорошо. +1
Geobits
Забавно, но в немутантной (оригинальной) версии «Злых верблюдов» цивилизованная тактика совершенно неэффективна из-за «центрального верблюда».
user2846289
1
@VadimR Дерьмо, спасибо за обновление вашего верблюда: PI не может проверить это, так как это не Java, но я знаю, что моя стратегия отчасти бесполезна с хищниками в середине моей территории: P
Herjan
5
Это снова Херьян! Кроме того, «моей груди становится довольно трудно выжить с менее чем 200 добычей» (выделение добавлено). Я не осознавал, что жизнеспособность вашей груди зависит от количества жертв в компьютерном моделировании ...
Джастин
5

Рубиновые грифы

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

Они не совсем закончены, так как я опубликовал это, чтобы подтолкнуть темп :)

Я надеюсь:

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

22 апреля 2014: добавлена скука , которая делает их менее липкими и позволяет им охотиться за добычей самостоятельно и искать хищников

class Animal
  attr_accessor :x, :y
end

class Hunter < Animal
  attr_accessor :id, :bored

  def initialize diff
   @diff = diff
   @lastGoal = nil
   @bored = false
  end

  def move goal
    if not goal.nil? 
      if @bored or goal != @lastGoal
        @lastGoal = goal
        return [goal.first - x + @diff.first, goal.last - y + @diff.last]
      end
    end
    [250 - x + 3*@diff.first, 250.0 - y + 3*@diff.last]
  end
end

class Pack
  def initialize
    @file = File.open "pack_log", "w"
    @count = 0
    @pack = []
    @order = []
    @hunters = []
    @closest = nil
    @random_goal = [250.0, 250.0]
    @locations = []
    @timer = 0
    d = 25.0
    diffs = [[d, d], [d, -d], [-d, -d], [-d, d], [0.0, 0.0]]
    5.times do |i|
      @pack << (Hunter.new diffs[i])
    end
    line = 0
    s = gets
    loop do
      s = gets
      if not (s =~ /dead\0/).nil?
        break
      end
      if line == 0
        get_structure s
      elsif line == 1
        get_positions s
      end
      @pack.length.times do |i|
        if line == i*2 + 3
          look_for_hunters s
          if @count <= i+1
            @closest = closest_hunter
            move
          end
        end
      end
      if not (s =~ /\0/).nil?
        line = 0
        @hunters = []
      else
        line += 1
      end
    end
  end

  def member_by_id id
    member = nil
    @pack.each do |v|
      if v.id == id
        member = v
        break
      end
    end
    member
  end

  def member_by_order index
    member_by_id @order[index]
  end

  def distance a, b
    Math.sqrt((a.first - b.first)**2 + (a.last - b.last)**2)
  end

  def bored?
    bored = true
    l1 = @locations.first
    @locations.each do |l2|
      if distance(l1, l2) > 20
        bored = false
      end
    end 
    bored
  end

  def bored_move v
    if @timer <= 0
      @random_goal = [rand(1000).to_f - 250, rand(1000).to_f - 250]
      @pack.each do |m|
        m.bored = true
      end
      @timer = 250 
    else
      @timer -= 1
    end
    v.move @random_goal
  end

  def move
    first_one = true
    answer = ""
    @order.each do |id|
      v = member_by_id id
      x, y = 0, 0
      if bored?
        x, y = (bored_move v)
      elsif @timer > 0
        @location = []
        x, y = (bored_move v)
      else
        @pack.each do |m|
          m.bored = false
        end
        @timer = 0
        x, y = v.move @closest
      end
      if not first_one
        answer << "\t"
      end
      answer << "#{x.to_i}.0\t#{y.to_i}.0"
      first_one = false
    end
    answer << "\0"
    print answer
    STDOUT.flush
  end

  def get_structure line
    @order = []
    if @pack.first.id.nil? 
      @count = 0
      line.split.each_with_index do |v, i|
        if i % 2 == 0
          @order << v.to_i
          @pack[i/2].id = v.to_i
          @count += 1
        end
      end
    else
      @count = 0
      line.split.each_with_index do |v, i|
        if i % 2 == 0
          @order << v.to_i
          @count += 1
        end
      end
    end
  end

  def get_positions line
    if not @order.empty?
      line.split.each_with_index do |v, i|
        if i % 2 == 0
          member_by_order(i/2).x = v.to_f
        else
          member_by_order(i/2).y = v.to_f
        end
      end
    end
  end

  def look_for_hunters line
    line.split.each_with_index do |v, i|
      if i % 2 == 0
        @hunters << [v.to_f]
      else
        @hunters.last << v.to_f
      end
    end
  end

  def closest_hunter
    mass_center
    closest = nil
    bestDist = 500*500
    if not @hunters.nil? and not @hunters == []
      @hunters.each do |h|
        our = false
        @pack.each do |v|
          if h.first == v.x and h.last == v.y
            our = true
          end
        end
        if our
          next
        end
        sqDist = (@mass_center.first - h.first)**2 + (@mass_center.last - h.last)**2
        if sqDist < bestDist
          closest = []
          closest << h.first
          closest << h.last
        end
      end
    end
    closest
  end

  def mass_center
    center_x = 0
    center_y = 0
    @pack.each do |v|
      center_x += v.x
      center_y += v.y
    end
    @mass_center = [center_x.to_f / @count, center_y.to_f / @count]
    if @locations.length > 30
      @locations.shift
      @locations << @mass_center
    else
      @locations << @mass_center
    end
  end
end

Pack.new
Легат
источник
Вам определенно нужно больше "охотников" в смеси. Как это, они имеют тенденцию прикрепляться к другим паразитам (так как это большинство на поле). Мне нравится наблюдать за ними, и я вижу, как они будут эффективны с другим составом конкурентов.
Geobits
О да, в моей тестовой среде у меня есть два других пакета охотников. Без них стервятники, вероятно, совершенно невежественны. Тем более, что сетчатые кошки могут быстро обрабатывать углы, не будучи видимыми с середины.
Легат
Я думаю, я знаю, что может их особенно беспокоить. Война злых верблюдов. @Geobits как насчет размещения боев на Youtube? 10 раундов не так уж много, чтобы оставаться смотрибельным. Конечно, HQ будет необходим. Не ожидая миллионов зрителей, но было бы интересно посмотреть, как работают ваши команды, и, возможно, немного поболеть за них :)
Legat
1
Полный турнир может быть немного длинным (~ 8 минут на раунд), чтобы удержать внимание, но запись одного «зрительского» раунда может сработать. Я подумаю над этим для будущих пробегов.
Geobits
@ Geobits Скорость сильно варьируется в течение 8-минутного раунда? Мне интересно, стоит ли записывать кадры за ход, чтобы они могли воспроизводиться с постоянной скоростью, а не замедляться во время сложных вычислительных операций. Я имею в виду для целей YouTube.
трихоплакс
5

Злые Эко Верблюды

Изменить: мутация # 2. О, нет, я опоздал с моей реализацией прогноза движения добычи, чтобы быть первым, кто победил Netcats. ОК, пусть будет так.

Эта мутация имеет $hunger_criticalпеременную (постоянную). Изменение его значения выше 1000 заставляет Верблюдов всегда охотиться, как Ясновидящих. Затем:

Done in 11.93 minutes
camels1.pl(0)                   : Turn 23112    : Score 100
Netcats(1)                      : Turn 22508    : Score 80

Если $hunger_criticalустановлено, например, 500 (как показано ниже), то мои верблюды (увидев ужасы цивилизации ) пытаются вести себя экологически безвредно (следовательно, они изменили название своей породы), то есть они убивают только когда голодны. Если они не голодны, они патрулируют критические районы острова - центр и углы, чтобы предотвратить бессмысленную бойню других охотников. Ну, с центром, это более или менее работает. Идея кружить по углам состояла в том, чтобы отогнать добычу и усложнить жизнь кошкам и паразитам. Ну, это не работает. Глупая добыча все равно уходит в углы.

Интересно также, что flock[ALIGN]компонент может быть угадан только хищниками, и моя реализация отличается от реализации justhalf. Я боюсь , что какая - то незначительная ошибка в моем плагиате реализации кода Geobits', наблюдая / сравнивая отдельные охоты на Верблюды против ясновидящих.

И программа довольно длинная, извините.


Изменить: мутация # 1. Остров оказывается довольно радиоактивным (что объясняет отсутствие растительности и необъяснимый характер «добывающих» существ), поэтому вот первая мутация моих верблюдов. Любой из них может стать одиночным охотником, если голоден или нет свободного уголка для всех. Охотник пытается активно преследовать ближайшую добычу. Если его нет, он патрулирует широким кругом вокруг центра острова, а затем преследует ближайшее существо, когда находит его. К сожалению, направление добычи становится непредсказуемым, когда оно приближается к своему рое (стоит исследовать ...), поэтому одиночная погоня не очень эффективна. Но если это удастся, верблюд идет переварить в ближайший свободный угол (если есть). Когда уровень голода ниже определенного уровня, любой верблюд покидает свой угол (вероятно, проклинает Netcats ('где еда?') )) и выходит в свободный роуминг самостоятельно. И так далее.


Одна и та же шутка, рассказанная дважды, не смешна, но (1) я должен был начать где-то, и я новичок в этом, (2) Честно, я думал о угловой тактике (а кто нет?), Наблюдая за Netcats, перед Ruby Пауки появились на острове.

Итак, когда-нибудь слышали о плотоядных верблюдах? Бедные животные однажды проснулись на этом забытом богом острове, чтобы вообще не найти ни травы, ни деревьев, но множество странных зеленых, хотя съедобных, быстро движущихся (довольно раздражающих) мелочей. Не имея охотничьих привычек (но они скоро мутируют, я надеюсь), мои верблюды разработали очень злую схему выживания: они делятся и идут каждый в 1 из 4 углов, а пятый идет в центр (чтобы умереть там первым, так как оказывается). На своих направлениях они терпеливо ждут, выполняя своего рода верблюжий военный танец, или, может быть, они просто стараются не наступать на других животных, уже находящихся там, пауков и всех ...

#!/usr/bin/env perl
use strict;
use warnings;

binmode STDOUT;
binmode STDIN;
$| = 1;
$, = "\t";

my $hunger_critical = 500;
my %pack;
my ($turn, $prey_count, $predators_count);
my $patrol_radius_hunt = 150;
my $patrol_radius_corner = 16;
my $patrol_radius_center = 1;
my @roles = qw/C LL LR UL UR/; # or P (patrol if > 5), H (hunt)
my %places = (
    UL => {x =>   1 + $patrol_radius_corner, y =>   1 + $patrol_radius_corner},
    UR => {x => 499 - $patrol_radius_corner, y =>   1 + $patrol_radius_corner},
    LR => {x => 499 - $patrol_radius_corner, y => 499 - $patrol_radius_corner},
    LL => {x =>   1 + $patrol_radius_corner, y => 499 - $patrol_radius_corner},
    C  => {x => 250, y => 250},
);

sub sq_dist {
    my ($x1, $y1, $x2, $y2) = @_;
    return ($x1 - $x2)**2 + ($y1 - $y2)**2
}

sub distance {
    return sqrt(&sq_dist)
}

sub assign_role {
    my $camel = shift;
    if (@roles) {
        my %choice = (d => 1000, i => 0);
        for my $i (0..$#roles) {
            my $r = $roles[$i];
            if ($r eq 'C') {
                if ($prey_count > 700) {
                    $choice{i} = $i;
                    last
                }
                else {
                    next
                }
            }
            my $d = distance($camel->{x}, $camel->{y}, $places{$r}{x}, $places{$r}{y});
            if ($d < $choice{d}) {
                @choice{qw/d i/} = ($d, $i)
            }
        }
        return splice @roles, $choice{i}, 1
    }
    else {
        return 'P'
    }
}

sub xy_average {
    my $xy = shift;
    my $x = my $y = 0;
    if ($xy && @$xy) {
        for my $item (@$xy) {
            $x += $item ->{x};
            $y += $item->{y}
        }
        $x /= @$xy;
        $y /= @$xy
    }
    return $x, $y
}

sub patrol {
    my ($xc, $yc, $radius, $camel) = @_;
    my ($x, $y) = ($camel->{x} - $xc, $camel->{y} - $yc);
    my $d = distance(0, 0, $x, $y);
    my $a = atan2($y, $x);
    if (abs($d - $radius) < 3) {
        $a += 6 / $radius
    }
    return $radius * cos($a) - $x, $radius * sin($a) - $y
}

while (1) {

    # Get input

    my @in;
    # Line 0 - turn, counts
    $_ = <>;
    die if /dead/;
    ($turn, $prey_count, $predators_count) = /\0?(\S+)\t(\S+)\t(\S+)/;
    # Line 1 - pack's ids and hunger
    $_ = <>;
    while (/(\S+)\t(\S+)/g) {
        push @in, {id => $1, hunger => $2}
    };
    # Line 2 - positions
    $_ = <>;
    for my $animal (@in) {
        /(\S+)\t(\S+)/g;
        ($animal->{x}, $animal->{y}) = ($1, $2);
    }
    # 2 lines per member, visible prey and predators
    for my $animal (@in) {
        $_ = <>;
        my @prey;
        while (/(\S+)\t(\S+)/g) {
            push @prey, {x => $1, y => $2}
        };
        $animal->{prey} = \@prey;
        $_ = <>;
        my @beasts;
        while (/(\S+)\t(\S+)/g) {
            push @beasts, {x => $1, y => $2}
        };
        $animal->{beasts} = \@beasts
    }
    # trailing \0 zero will be prepended to next turn input

    # Update my pack

    for my $n (0..$#in) {
        my $animal = $in[$n];
        my $id = $animal->{id};
        # old average prey position
        my @opp = xy_average($pack{$id}{prey});
        # new average prey position
        my @npp = xy_average($animal->{prey});
        # average prey displacement
        my %apd = (x => $npp[0] - $opp[0], y => $npp[1] - $opp[1]);
        $pack{$id}{apd}    = \%apd;
        $pack{$id}{hunger} = $animal->{hunger};
        $pack{$id}{x}      = $animal->{x};
        $pack{$id}{y}      = $animal->{y};
        $pack{$id}{prey}   = $animal->{prey};
        $pack{$id}{beasts} = $animal->{beasts};
        $pack{$id}{num}    = $n;
        $pack{$id}{dead}   = 0
    }

    # Bury dead animals, retrieve their roles

    while (my ($id, $camel) = each %pack) {
        if ($camel->{dead}) {
            my $role = $camel->{role};
            push @roles, $role if $role ne 'P' and $role ne 'H';
            delete $pack{$id};
        }
        else {
            $camel->{dead} = 1
        }
    }

    # See that everyone has a role and lives accordingly

    my @out;
    for my $camel (values %pack) {
        my $role = $camel->{role} ||= assign_role($camel);
        if ($camel->{hunger} < $hunger_critical and $role ne 'H') {
            push @roles, $role if $role ne 'P';
            $role = $camel->{role} = 'H'
        }
        if ($camel->{hunger} > $hunger_critical and ($role eq 'H' or $role eq 'P') and $prey_count > 400) {
            $role = $camel->{role} = assign_role($camel)
        }
        my @vector = (0, 0);
        if ($role eq 'H') {
            my @prey = @{$camel->{prey}};
            if (@prey) {
                my %nearest = (p => undef, dd => 2500);
                for my $prey (@prey) {
                    my $dd = sq_dist($camel->{x}, $camel->{y}, $prey->{x}, $prey->{y});
                    if ($dd <= $nearest{dd}) {
                        @nearest{qw/p dd/} = ($prey, $dd)
                    }
                }
                my $target = $nearest{p};
                if ($nearest{dd} > 900) {
                    @vector = ($target->{x} - $camel->{x}, $target->{y} - $camel->{y})
                }
                else {
                    my @vect = map{{x => 0, y => 0}}1..5;
                    my $n = 0;
                    for my $prey (@prey) {
                        next if $prey eq $target;
                        my $dd = sq_dist($target->{x}, $target->{y}, $prey->{x}, $prey->{y}) + 1/(~0);
                        next if $dd > 900;
                        $n ++;
                        my $dx = $prey->{x} - $target->{x};
                        my $dy = $prey->{y} - $target->{y};
                        $vect[1]{x} -= $dx / $dd;
                        $vect[1]{y} -= $dy / $dd;
                        $vect[2]{x} += $dx * $dd;
                        $vect[2]{y} += $dy * $dd
                    }
                    $vect[0] = {x => $n * $camel->{apd}{x}, y => $n * $camel->{apd}{y}};
                    my $dx = abs(250 - $target->{x});
                    my $dy = abs(250 - $target->{y});
                    my $d = $dx > $dy ? $dx : $dy;
                    if ($d > 240) {
                        $vect[4]{x} = $dx * $d;
                        $vect[4]{y} = $dy * $d;
                    }
                    for my $v (@vect) {
                        my $d = sqrt($v->{x}**2 + $v->{y}**2) + 1/(~0);
                        $v->{x} /= $d;
                        $v->{y} /= $d;
                    }
                    for my $beast (@{$camel->{beasts}}, $camel) {
                        my $dd = sq_dist($target->{x}, $target->{y}, $beast->{x}, $beast->{y});
                        next if $dd > 900;
                        $vect[3]{x} += $target->{x} - $beast->{x};
                        $vect[3]{y} += $target->{y} - $beast->{y};
                    }
                    $vector[0] = 5 * 1   * $vect[0]{x}
                               + 5 * 1   * $vect[1]{x}
                               + 5 * .96 * $vect[2]{x}
                               + 1 * 2   * $vect[3]{x}
                               + 5 * 4   * $vect[4]{x};
                    $vector[1] = 5 * 1   * $vect[0]{y}
                               + 5 * 1   * $vect[1]{y}
                               + 5 * .96 * $vect[2]{y}
                               + 1 * 2   * $vect[3]{y}
                               + 5 * 4   * $vect[4]{y};
                    my $dd = $vector[0]**2 + $vector[1]**2;
                    if ($dd > 36) {
                        my $d = sqrt($dd);
                        @vector = map {$_ * 6.1 /$d} @vector
                    }
                }
            }
            else {
                @vector = patrol(250, 250, $patrol_radius_hunt, $camel)
            }
        }
        elsif ($role eq 'P') {
            @vector = patrol(250, 250, $patrol_radius_hunt, $camel)
        }
        else {
            my $r = $role eq 'C' 
                ? $patrol_radius_center 
                : $patrol_radius_corner;
            @vector = patrol($places{$role}{x}, $places{$role}{y}, $r, $camel)
        }
        my $id_x = $camel->{num} << 1;
        my $id_y = $id_x + 1;
        @out[$id_x, $id_y] = @vector
    }

    # And let the cruel world know about it

    print @out;
    print "\0"
}

__END__
user2846289
источник
5
Это должен быть самый разборчивый скрипт на Perl, который я когда-либо видел на этом сайте.
Geobits
Вам нужно идти точно в угол, чтобы эффективно стрелять в них, иначе вы просто будете фактически участвовать в бойне Netcats, ха-ха
justhalf
@justhalf, Как я уже сказал: план не сработал. Паразиты, сидящие в углах, тоже не отгоняли добычу. Хм-м, может быть, 2 или более зверей, патрулирующих один угол, помогут.
user2846289
Ваши верблюды довольно хороши на самом деле! К счастью (для меня), я улучшил свои Ясновидящие, так что большую часть времени (не всегда) моя команда выигрывает у вас во время последней битвы. Интересно!
justhalf
1
Если вы ближе, чем 8 (20-2 * 6) единиц от добычи, мы можем видеть любое движение всех других жертв, которые находятся в пределах 30 единиц нашей добычи в текущем ходу. И vecсвойство в основном просто смещение от предыдущего поворота к текущему. И, как я уже сказал, мы проводим сопоставление из предыдущего хода, чтобы выяснить, какая добыча идет в какую сторону, мы не можем полагаться на порядок добычи. Это возможно, потому что жертвы обычно (в типичном сценарии) держатся на достаточном расстоянии друг от друга (> 12 единиц), и поэтому большую часть времени мы можем сопоставить жертвы в предыдущем ходу с текущим ходом.
полугодие
4

AbleDogs - PHP

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

Поместите код в AbleDogsфайл и запустите его сphp AbleDogs

<?php
// simulation parameters

define ("ARENA_SIZE", 500);

define ("HUNGER_MAX", 1000);

define ("PREY_SPEED", 6);
define ("PRED_SPEED", 6.1);

define ("PREY_VISION", 30);
define ("PRED_VISION", 50);

define ("WALL_BOUNCE", 10); // distance from a wall from which a prey starts bouncing

// derived constants

define ("PRED_SPEED2" , PRED_SPEED  * PRED_SPEED );
define ("PRED_VISION2", PRED_VISION * PRED_VISION);
define ("PREY_VISION2", PREY_VISION * PREY_VISION);

// grid to speedup preys lookup

define ("GRID_SIZE", ceil (ARENA_SIZE/PRED_VISION));
define ("GRID_STEP", ARENA_SIZE/GRID_SIZE);

// search patterns

define ("SEARCH_OFFSET", WALL_BOUNCE+PRED_VISION/sqrt(2));
define ("SEARCH_WIDTH" , 2*sqrt(PRED_VISION2-PRED_SPEED2/4));
define ("SEARCH_HEIGHT", ARENA_SIZE-2*SEARCH_OFFSET);
define ("SEARCH_SIZE"  , ceil(SEARCH_HEIGHT/SEARCH_WIDTH));
define ("SEARCH_STEP"  , SEARCH_HEIGHT/SEARCH_SIZE);
define ("SEARCH_LEGS"  , 2*SEARCH_SIZE+1);

// tracking

define ("MAX_TRACK_ERROR", 10); // max abs distance for prey tracking correlation
define ("TRACKING_HUNGER_START", HUNGER_MAX*.9); // hunger limit to try and eat the tracked prey (start of game)
define ("TRACKING_HUNGER_END", 4);     // idem, for endgame
define ("TRACKING_DISTANCE", PREY_SPEED*2.5);
define ("TRACKING_DISTANCE2", TRACKING_DISTANCE * TRACKING_DISTANCE);

class Point {
    public $x = 0;
    public $y = 0;

    function __construct ($x=0, $y=0)
    {
        $this->x = (float)$x;
        $this->y = (float)$y;
    }

    function __toString() // for comparisons
    {
        return "$this->x,$this->y";
    }

    function multiply ($scalar)
    {
        return new Point ($this->x * $scalar, $this->y * $scalar);
    }

    function add ($v)
    {
        return new Point ($this->x + $v->x, $this->y + $v->y);
    }

    function dot($v)
    {
        return $this->x * $v->x + $this->y * $v->y;
    }

    function rotate90()
    {
        return new Point (-$this->y, $this->x);
    }

    function vector_to ($goal)
    {
        return new Point ($goal->x - $this->x, $goal->y - $this->y);
    }

    function norm2 ()
    {
        return $this->dot ($this);
    }

    function norm ()
    {
        return sqrt ($this->norm2());
    }

    function normalize ($norm = 1)
    {
        $n = $this->norm();
        if ($n != 0) $n = $norm/$n;
        return $this->multiply ($n);
    }

    function limit ($norm)
    {
        return $this->norm() > $norm
             ? $this->normalize($norm)
             : clone $this;
    }
}

class Search {

    function __construct ($direction)
    {
        switch ($direction % 4)
        {
            case 0: $this->pos = new Point (          0,           0); break;
            case 1: $this->pos = new Point (SEARCH_SIZE,           0); break;
            case 2: $this->pos = new Point (SEARCH_SIZE, SEARCH_SIZE); break;
            case 3: $this->pos = new Point (          0, SEARCH_SIZE); break;
        }
        $this->start();
    }

    private function start ()
    {
        $this->dir = $this->pos->x == $this->pos->y;
        $this->adj = $this->pos->x ? -1 : 1;
        $this->target = new Point ($this->pos->x * SEARCH_STEP + SEARCH_OFFSET,
                                   $this->pos->y * SEARCH_STEP + SEARCH_OFFSET);
        $this->leg = 0;
    }

    function point ($pos)
    {
        if ($pos == $this->target)
        {
            if ($this->leg % 2)
            {
                if ($this->dir) $this->pos->y+= $this->adj;
                else            $this->pos->x+= $this->adj;
            }
            else
            {
                if ($this->dir) $this->pos->x = $this->pos->x ? 0 : SEARCH_SIZE;
                else            $this->pos->y = $this->pos->y ? 0 : SEARCH_SIZE;
            }
            $this->leg++;
            if ($this->leg == SEARCH_LEGS) $this->start();
            $this->target = new Point ($this->pos->x * SEARCH_STEP + SEARCH_OFFSET,
                                       $this->pos->y * SEARCH_STEP + SEARCH_OFFSET);
        }
        return $this->target;
    }
}

class Pack {

    public static $turn;   // turn number
    public static $size;   // number of live members
    public static $member; // array of members

    public static $prev_preys;     // previous coordinates of all preys
    public static $prev_preds;     // previous coordinates of foreign predators

    public static $n_preys; // total number of preys     (including those not currently seen)
    public static $n_preds; // total number of predators (including those not currently seen)

    public static $preys;     // coordinates of all preys
    public static $preds;     // coordinates of all predators
    public static $own_preds; // coordinates of all predators in our pack
    public static $foe_preds; // coordinates of all foreign predators

    public static $arena_center; // arena center

    private static $output_order; // to send output according to input order

    function init ()
    {
        Pack::$member = array();
        Pack::$arena_center = new Point (ARENA_SIZE/2, ARENA_SIZE/2);
    }

    function read_line ($line)
    {
        $values = array();
        if ($line == "") return $values;
        $input = explode ("\t", $line);
        $num = count($input);
        if ($num % 2) panic ("read_line: invalid input $line num $num");
        $num /= 2;
        for ($i = 0 ; $i != $num ; $i++)
        {
            $values[] = new Point ($input[$i*2  ], $input[$i*2+1]);
        }
        return $values;
    }

    function read_input ()
    {
        // read controller input (blocking)
        $input = "";
        while (($in = fread(STDIN, 1)) !== false)
        {
            if ($in == "\0") break;
            $input .= $in;
        }

        // check extinction
        if ($input == "dead") return false;
        $lines = explode ("\n", $input);

        // save previous predators and preys positions
        Pack::$prev_preys = Pack::$preys;
        Pack::$prev_preds = Pack::$foe_preds;

        // line 0: turn, preys, predators
        list (self::$turn, Pack::$n_preys, Pack::$n_preds) = explode ("\t", $lines[0]);

        // line 1: list of ids and hunger levels
        $id = array();
        Pack::$size = 0;
        Pack::$output_order = array();
        foreach (Pack::read_line($lines[1]) as $i=>$v)
        {
            $id[$i] = $v->x;
            Pack::$output_order[] = $id[$i];

            if (!isset (Pack::$member[$id[$i]])) Pack::$member[$id[$i]] = static::new_member();
            Pack::$size++;
            Pack::$member[$id[$i]]->hunger = $v->y;
            Pack::$member[$id[$i]]->ttl = self::$turn;
        }

        // line 2: member positions
        Pack::$own_preds = array();
        foreach (Pack::read_line($lines[2]) as $i=>$pos)
        {
            Pack::$member[$id[$i]]->pos = $pos;
            Pack::$own_preds[] = $pos;
        }

        // lines 3 to 2*#members+3: coordinates of all visible preys and predators
        $preys = array();
        $preds = array();
        $y_seen = array();
        $d_seen = array();
        for ($i = 0 ; $i != Pack::$size ; $i++)
        {
            // visible preys
            foreach (Pack::read_line($lines[2*$i+3]) as $coords)
            {
                if (!in_array ($coords, $preys) || !isset($y_seen[(string)$coords]))
                {
                    $preys[] = $coords;
                }
            }
            foreach ($preys as $p) $y_seen[(string)$p] = true;

            // visible predators
            foreach (Pack::read_line($lines[2*$i+4]) as $coords)
            {
                if (!in_array ($coords, $preds) || !isset($d_seen[(string)$coords]))
                {
                    $preds[] = $coords;
                }
            }
            foreach ($preds as $p) $d_seen[(string)$p] = true;
        }

        // remove dead members
        foreach (Pack::$member as $k => $m)
        {
            if ($m->ttl != self::$turn)
            {
                unset (Pack::$member[$k]);
            }
        }

        // filter out own positions from predators list
        Pack::$foe_preds = array_diff ($preds, Pack::$own_preds);
        Pack::$preds = Pack::$foe_preds;
        foreach (Pack::$own_preds as $p) Pack::$preds[] = $p;
        Pack::$preys = $preys;

        // done
        return true;
    }

    function output_moves ()
    {
        $output = array();
        foreach (Pack::$output_order as $i)
        {
            $output[] = Pack::$member[$i]->move->x;
            $output[] = Pack::$member[$i]->move->y;
        }
        echo implode ("\t", $output) . "\0";
    }

    static function point_closest_to_walls ($pos)
    {
        $delta = $pos->vector_to (Pack::$arena_center);
        if (abs ($delta->x) > abs ($delta->y))
        {
            $y = $pos->y;
            $x = $delta->x > 0 ? -1 : ARENA_SIZE+1;
        }
        else
        {
            $x = $pos->x;
            $y = $delta->y > 0 ? -1 : ARENA_SIZE+1;
        }
        return new Point ($x, $y);
    }

    static function in_arena ($pos)
    {
        $delta = $pos->vector_to (Pack::$arena_center);
        return abs ($delta->x) <= ARENA_SIZE/2 && abs ($delta->y) <= ARENA_SIZE/2;
    }

    static function clamp_to_arena (&$pos)
    {
        // mimics the slightly strange behaviour of the Java engine setInZeroBounds function
        if ($pos->x >= ARENA_SIZE) $pos->x = ARENA_SIZE-1; // should rather be ARENA_SIZE
        if ($pos->x <           0) $pos->x = 0;
        if ($pos->y >= ARENA_SIZE) $pos->y = ARENA_SIZE-1;
        if ($pos->y <           0) $pos->y = 0;
    }

    function get_closest ($pos, $set, $max_dist)
    {
        // check for empty set
        if (count ($set) == 0) return null;

        // construct an array of distances with the same indexes as the points
        $dist = array();
        $max_dist *= $max_dist;
        foreach ($set as $k=>$pt)
        {
            $d = $pos->vector_to($pt)->norm2();
            if ($d <= $max_dist) $dist[$k] = $d;
        }
        if (count($dist) == 0) return false;

        // get the key of the smallest distance and use it to retrieve the closest point
        $keys = array_keys ($dist, min($dist));
        return $set[$keys[0]];
    }

    function get_visible ($pos, $set)
    {
        $res = array();
        $skipped = false;
        $pts = 0;
        foreach ($set as $point)
        {
            $d = $pos->vector_to($point)->norm2();
            if ($d == 0 && !$skipped)
            {
                $skipped = true;
                continue; // skip ourself
            }
            if ($d > PREY_VISION2) continue; // skip far points
            $res[] = $point;
            if ($pts++ > 10) break; // too many points are useless since prediction will go haywire anyway
        }
        return $res;
    }
}
Pack::init();

class PackMember {
    public $pos; // current position
    public $ttl; // last turn reported alive

    function move_to ($goal)
    {
        $this->move = $this->pos->vector_to ($goal);
    }

    function intercept ($target_pos, $target_speed)
    {
        // change reference to position difference
        $delta = $this->pos->vector_to($target_pos);
        $i = $delta->normalize();
        $j = $i->rotate90();

        // match tangential speeds
        $vj = $target_speed->dot ($j);

        // deduce axial speed
        $vi = PRED_SPEED2 - $vj*$vj; // this should always be positive since predators are faster than preys
        $vi = sqrt ($vi);

        // return intercept speed in original reference coordinates
        return $i->multiply($vi)->add($j->multiply($vj));
    }
}

class Target {
    public $pos;      // current position
    public $pos_next; // predicted position
    public $speed;    // estimated speed

    function __construct ($pos)
    {
        $this->pos    = $pos;
        $this->speed  = new Point(0,0);
        $this->predict();
    }

    private function predict()
    {
        // predators contribution
        $preds = Pack::get_visible ($this->pos, Pack::$preds);
        $this->preds = count ($preds);
        $res = new Point();
        foreach ($preds as $predator)
        {
            $res = $res->add ($predator->vector_to ($this->pos));
        }
        $res = $res->multiply (2);

        // preys contribution
        $preys = Pack::get_visible ($this->pos, Pack::$preys);
        $this->preys = count ($preys);

        $f_cohesion  = new Point;
        $f_separate  = new Point();
        foreach ($preys as $prey)
        {
            $delta = $this->pos->vector_to ($prey);
            $d2 = $delta->norm2();
            if ($d2 != 0)
            {
                $f_cohesion  = $f_cohesion ->add ($delta->multiply ($d2));
                $f_separate  = $f_separate ->add ($delta->multiply (-1/$d2));
            }
        }

        $res = $res
        ->add ($this->speed->normalize(5*.96)) // assume all preys have same speed as target
        ->add ($f_cohesion ->normalize(5*1))
        ->add ($f_separate ->normalize(5*1));
        $delta = $this->pos->vector_to(Pack::$arena_center);
        $dist = max (abs($delta->x), abs($delta->y));
        if ($dist > (ARENA_SIZE/2-WALL_BOUNCE))
        {
            $res = $res->add ($delta->normalize(5*4));
        }

        $this->raw_speed = $res;
        $this->speed = $res->limit(PREY_SPEED);
        $this->pos_next = $this->pos->add ($this->speed);
        Pack::clamp_to_arena ($this->pos_next);
    }

    function track ()
    {
        // see if we can find our prey at the start of a new turn
        $min = 1e10;
        foreach (Raptors::$free_preys as $k=>$prey)
        {
            $dist = abs ($this->pos_next->x - $prey->x) + abs ($this->pos_next->y - $prey->y);
            if ($dist < $min)
            {
                $min = $dist;
                $new_pos = $prey;
                $new_k = $k;
                if ($min < .001) break;
            }
        }
        if ($min > MAX_TRACK_ERROR) return false;

        // remove this prey from free preys
        unset(Raptors::$free_preys[$new_k]);

        $delta = $new_pos->vector_to($this->pos_next);

        // update postion and speed
        if ($this->speed->norm2() == 0)
        {
            // this can be either an endgame prey not yet moving
            // OR initial speed for a new target
            $this->speed = $this->pos->vector_to ($new_pos);
        }
        $this->pos = $new_pos;

        // predict speed and position
        $this->predict();
        return true;
    }
}

class Raptor extends PackMember {

    // possible states
    const IDLE     = 1;
    const TRACKING = 2;
    const HUNTING  = 3;
    const RUSHING  = 4;
    public $state;

    public  $target;  // current prey
    public  $patrol;  // patrol governor

    private static $id_gen;

    function __construct ()
    {
        $this->patrol = new Search (++self::$id_gen);
        $this->target  = null;
        $this->state = Raptor::IDLE;
        $this->pos = null;
        $this->hunger = HUNGER_MAX;
    }

    function __destruct ()
    {
        $this->tracking_lost();
    }

    function tracking_lost()
    {
        $this->target  = null;
        $this->state = Raptor::IDLE;
    }

    function track_prey()
    {
        // stop tracking if hunger went back to max
        if ($this->hunger == HUNGER_MAX)
        {
            $this->tracking_lost();
        }

        // try to acquire a new target
        if (!$this->target)
        {
            $victim = Pack::get_closest ($this->pos, Raptors::$free_preys, PRED_VISION);
            if (!$victim) return;
            $this->target = new Target ($victim);
            $this->state = Raptor::TRACKING;
        }

        // track prey
        if (!$this->target->track (Pack::$preys))
        {
            // prey was eaten or move prediction failed
            $this->tracking_lost();
        }
    }

    function beat_competition ()
    {
        if ($this->target === null) return;
        $pm = $this->target->pos_next->vector_to ($this->pos);
        $dm = $pm->norm2();
        foreach (Pack::$foe_preds as $f)
        {
            $pf = $this->target->pos_next->vector_to($f);
            $df = $pf->norm2();
            if ($df > PRED_VISION2) continue;
//          if ($df < ($dm*2))
            {
                $this->state = Raptor::RUSHING;
                return;
            }
        }
        if ($this->state == Raptor::RUSHING) $this->state = Raptor::TRACKING;
        return;
    }
}

class Raptors extends Pack {
    public static $free_preys; // coordinates of all preys that are not targeted

    // allows generic Pack to create a proper pack member instance
    static function new_member()
    {
        return new Raptor();
    }

    // main AI loop
    static function think ()
    {
        $hunger_limit = Pack::$n_preys > 2 * Pack::$n_preds ? TRACKING_HUNGER_START : TRACKING_HUNGER_END;
        self::$free_preys = static::$preys;

        // update targets and members states
        foreach (Pack::$member as $m)
        {
            // track current targets
            $m->track_prey();

            // rush to target if a competitor draws near
            $m->beat_competition();

            // hunt if hungry enough
            if ($m->state == Raptor::TRACKING && $m->hunger < $hunger_limit)
            {
                $m->state = Raptor::HUNTING;
            }
        }

        // move members
        foreach (Pack::$member as $m)
        {
            switch ($m->state)
            {
            case Raptor::IDLE:
                $destination = $m->patrol->point($m->pos);
                break;
            case Raptor::TRACKING:
                $wall_point = Pack::point_closest_to_walls ($m->target->pos_next);
                $destination = $wall_point->vector_to ($m->target->pos_next)->normalize (TRACKING_DISTANCE)->add($m->target->pos_next);
                break;
            case Raptor::HUNTING:
                $wall_point = Pack::point_closest_to_walls ($m->target->pos_next);
                $to_hunter = $m->target->pos_next->vector_to ($m->pos);
                $dist_to_target = $to_hunter->norm();

                if ($dist_to_target > (PREY_VISION-PREY_SPEED)) // intercept the prey
                {
                    // use actual speed (i.e. true position delta, including wall stops)
                    $target_true_speed = $m->target->pos->vector_to ($m->target->pos_next);
                    $intercept_speed = $m->intercept ($m->target->pos, $target_true_speed);
                    $destination = $m->pos->add ($intercept_speed);
                }
                else if ($dist_to_target < PRED_SPEED) // pounce on the prey!
                {
                    $destination = $m->target->pos_next;
                }
                else if ($to_hunter->dot($m->target->speed) > 0)
                {
                    $destination = $m->target->pos_next;
                }
                else // goad the prey
                {
                    $to_wall = $m->target->pos->vector_to ($wall_point);
                    $wall_point = $wall_point;
                    $raw_speed = $m->target->raw_speed->add($m->target->pos->vector_to($m->pos)->multiply(2))->multiply (-0.5);
                    $wpd_t = $m->target->pos->vector_to ($wall_point)->normalize()->rotate90(); // wpd = Wanted Prey Direction
                    $delta = $wpd_t->multiply ($raw_speed->dot ($wpd_t));
                    $destination = $delta->vector_to ($m->target->pos_next);
                    if (!Pack::in_arena ($destination)) $destination = $m->target->pos_next;
                }
                break;
            case Raptor::RUSHING:
                $destination = $m->target->pos_next;
                break;
            }
            $m->move_to ($destination);
        }
    }
}

while (Raptors::read_input())
{
    Raptors::think();
    Raptors::output_moves();
}
?>

Общие Соображения

  • Это финал, который имеет значение. У вас может быть самый умный алгоритм охоты, если вы не заметите и поймаете последние несколько одиночных жертв быстрее, чем противник, вы проиграете.

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

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

Хвостовая погоня

Самый неэффективный способ поймать добычу - преследовать ее. Если предположить, что один хищник преследует одну добычу и не имеет внешних воздействий (стены, другие жертвы и т. Д.), Погоня за хвостом может длиться вечно. Как только вы вводите радиус видимости добычи в 30 единиц, жертва бежит со скоростью 6 для ваших 6,1, так что вы получаете 0,1 расстояние за ход: по прямой линии вам потребуется около 300 поворотов, чтобы ее получить.

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

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

предсказатель

Со скоростью добычи 6, жертва может перемещаться на площадь 36 * пи единиц в квадрате. С радиусом ловли 1, слепое предположение о том, где будет добыча, имеет шанс 1/36 * пи (около 1%) на успех. Очевидно, что-то должно быть сделано, чтобы улучшить это!

Глядя на код механизма моделирования, вы можете видеть, что входные данные:

  • видимые позиции добычи и хищника
  • предыдущие скорости добычи

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

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

Голодные жертвы

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

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

Кража добычи

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

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

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

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

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

Предлагаемые изменения

Чтобы учесть более сложные стратегии, перемещение хищника выше максимальной скорости добычи может привести к голоду, пропорциональному превышению скорости. Скажем, например, движение до скорости 6 бесплатно, и каждая точка скорости выше 6 стоит 100 пунктов голода (переход к 6.3 стоит 30 пунктов голода за ход, сжигание 1000 пунктов голода позволит достичь скорости 16 за один ход - и умереть, если вы не наденете поймай добычу при этом!).

Вместо того, чтобы отдать убийство случайному хищнику, когда несколько человек достаточно близко, чтобы съесть добычу, я предлагаю разделить выгоду (например, 3 хищника будут получать по 333,33 очка голода каждый). Это позволило бы использовать более интересные стратегии эндшпиля (например, замаскирование врага-хищника может оказаться полезным, если вы считаете, что у вас больше голодающих очков).

Особый цвет для первой упаковки довольно сложно увидеть. Я предлагаю голубой или оранжевый вместо синего.


источник
Наконец еще один конкурент! Я нарочно реализовал все, что вы упомянули, за исключением кражи добычи, за что меня удовлетворил текущий побочный эффект. Из ваших комментариев кажется, что вы выигрываете у моего Ясновидца? Это интересно, я проверю завтра. = D. Кроме того, вы можете попробовать мое обновление GUI, чтобы увидеть лучшую (по крайней мере, по мне) графику.
полугодие
Я не выигрываю все время. Это зависит от того, кто ближе к последним жертвам, когда они появляются. Мои способные собаки могут иметь статистическое преимущество. Я также настроил движок симуляции для отображения каждой команды в разных цветах, но в итоге она оказалась слишком яркой на мой вкус, поэтому я остановился на оранжевом цвете вместо синего для 1-й команды и всех остальных красных.
Вау, я только что проверил твое представление. Это безумие, я не знал, что ты можешь заставить молитву стоять на месте. И я чувствовал себя немного обманутым, потому что, когда жертва находится точно на грани, мои хищники не смогут их обнаружить, это определенно большой недостаток для меня.
полугодие
Это векторная арифметика для вас :). Именно это я и пытался объяснить в своем посте: для одной жертвы вы знаете все параметры движения (включая предыдущую скорость), поэтому вы можете вычислить положение хищника, которое даст соответствующую скорость добычи, чтобы она стояла у стены.
1
Хорошо решение уравнения жестко закодировано (никакие итерации не нужны). По сути, вы вычисляете вектор, который жертва должна использовать для скорости следующего поворота, если вашего хищника там не было. Учитывая направление, в котором вы хотите, чтобы добыча двигалась, вы определяете разность векторов, необходимую для определения скорости добычи в этом направлении. Эта векторная разница дает вам положение хищника относительно добычи. Это дает вам определенную степень свободы, которая позволяет вам (в определенных пределах) выбирать расстояние от добычи.
3

Lazy Pack Haskell

import Control.Monad
import Control.Concurrent

main :: IO ()
main=do
    t<-forkIO $ forever $ (putStrLn "Pack is paralyzed with indecision.\0")
    loop
    killThread t
        where
            loop=do
                line <- getLine
                case line of
                    "dead\0" -> return ()
                    _        -> loop

Вам понадобится платформа haskell для запуска этого. Затем вы используете runhaskellкоманду, чтобы запустить его. Моя стая ждет, пока жертва придет к ним.

PyRulez
источник
+1 за скелетное решение на новом языке. Предположительно, вы рады, что люди строят новые стратегии на вершине этого?
trichoplax
Конечно, почему бы и нет. (Хотя он ничего не делает, кроме того, что производит постоянный вывод и завершает работу в «dead \ 0», поэтому я не уверен, будет ли это очень полезно.)
PyRulez
-silent
Тем
3

Не запись, я всегда заинтересован в добавлении индивидуального цвета для каждой участвующей записи в ;)

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

Game.java

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.JFrame;

public class Game {

    static int preyStartCount = 0; // 0 means 1500 + (packs * 50)
    static int turn;
    static boolean silent = false;
    long startTime;

    JFrame frame;
    BufferedImage img;
    Color[] colors;

    Island map;
    List<Prey> preys;
    List<Predator> predators;
    List<Pack> packs;
    List<Pack> initPacks;

    public static void main(String[] args) throws InterruptedException {

        Game game = new Game();
        game.init(args);
        if (game.packs.size() > 0){
            game.run();
            game.score();
        }
        game.end();
    }

    void end() {
        frame.setVisible(false);
        frame.dispose();
        for (Pack pack : packs)
            pack.handler.end();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        } finally {
            for (Pack pack : packs)
                pack.handler.shutdown();
        }

        System.exit(0);
    }

    void score() {
        Collections.sort(initPacks);
        int score = 100;
        initPacks.get(0).score = score;
        for (int i = 1; i < initPacks.size(); i++) {
            Pack pack = initPacks.get(i);
            if (pack.extinctionTurn < initPacks.get(i - 1).extinctionTurn)
                score = score < 1 ? score : score * 80 / 100;
            pack.score = score;
        }
        print("", true);
        print("Done in " + getElapsedTime(), true);
        for (Pack pack : initPacks)
            print(pack.toString() + "\t: Turn " + pack.extinctionTurn + "\t: Score " + pack.score, true);
    }

    String getElapsedTime(){
        double elapsed = (System.currentTimeMillis() - startTime) / 1000d;
        if(elapsed < 60)
            return String.format("%.2f", elapsed) + " seconds";
        elapsed /= 60;
        if(elapsed < 60)
            return String.format("%.2f", elapsed) + " minutes";
        elapsed /= 60;
        return String.format("%.2f", elapsed) + " hours";       
    }


    public Game() {
        initPacks = new ArrayList<Pack>();
        packs = new ArrayList<Pack>();
        preys = new ArrayList<Prey>();
        predators = new ArrayList<Predator>();
    }

    void run() throws InterruptedException {
        frame.setVisible(true);
        turn = 0;
        Graphics2D g = img.createGraphics();

        printStatus();
        while (true) {
            turn++;

            getAllMoves();
            moveAll();
            spawn();
            removeDead();
            shuffle();

            if (turn % 500 == 0)
                printStatus();
            paint(frame, g);
            Thread.sleep(5);
            if (packs.size() < 1)
                break;
        }
    }

    void getAllMoves(){
        for (Prey prey : preys)
            prey.setNextMove();
        for (Pack pack : packs){    
            pack.talk(preys.size(), predators.size(), turn);
        }
        while(true){
            int doneCount = 0;
            for(Pack pack : packs)
                if(pack.doneTalking)
                    doneCount++;
            if(doneCount >= packs.size())
                break;
            try {Thread.sleep(1);}catch(InterruptedException e){}
        }
    }

    void moveAll(){
        for (Creature prey : preys) 
            prey.move();
        for(Pack pack : packs){
            for (Predator predator : pack.members) {
                predator.move();
                predator.eatOrStarve();
            }
        }
    }

    void paint(JFrame frame, Graphics2D g){
        g.setPaint(Color.BLACK);
        g.fillRect(0, 0, img.getWidth(), img.getHeight());

        for(Prey prey : preys)
            prey.paint(g);
        for(Pack pack : packs)
            for(Predator predator : pack.members)
                predator.paint(g);

        frame.repaint();
    }

    List<Prey> deadPreys;
    List<Predator> deadPredators;
    List<Pack> deadPacks;

    void removeDead(){
        deadPreys.clear();
        for (Prey prey : preys)
            if (!prey.alive)
                deadPreys.add(prey);
        preys.removeAll(deadPreys);

        deadPredators.clear();
        for (Predator predator : predators)
            if (!predator.alive)
                deadPredators.add(predator);
        predators.removeAll(deadPredators);

        deadPacks.clear();
        for (Pack pack : packs)
            if (!pack.alive)
                deadPacks.add(pack);
        packs.removeAll(deadPacks);

        for (Pack pack : packs) {
            pack.members.removeAll(deadPredators);
        }

        map.rebuildLists(preys, predators);
    }

    void shuffle(){
        Collections.shuffle(packs);
        for(Pack pack : packs)
            Collections.shuffle(pack.members);
    }

    void spawn(){
        if(turn % 5000 == 0)
            addPredators(1);
        if(turn % 1000 == 0)
            populatePrey(predators.size()-1, false);
    }

    void addPredators(int count){
        for(Pack pack : packs){
            if(!pack.alive)
                continue;
            if(pack.aliveCount == 0)
                continue;
            Predator parent = null;
            for(Predator predator : pack.members)
                if(predator.alive)
                    parent = predator;
            if(parent != null){
                for(int i=0;i<count;i++){
                    XY pos = new XY(Math.random() * 30, Math.random()   * 30);
                    pos.add(parent.pos);
                    pos.setInZeroBounds(Island.SIZE, Island.SIZE);
                    Predator child = new Predator(pack);
                    child.color = colors[pack.id];
                    child.moveTo(pos, Island.getCellByPosition(pos));
                    predators.add(child);
                    pack.members.add(child);
                    pack.aliveCount++;
                }
            }
        }
    }

    Color[] generateColors(int n){
        Color[] result = new Color[n];
        double maxR = -1000;
        double minR = 1000;
        double maxG = -1000;
        double minG = 1000;
        double maxB = -1000;
        double minB = 1000;
        double[][] colors = new double[n][3];
        for(int i=0; i<n; i++){
            double cos = Math.cos(i * 2 * Math.PI / n);
            double sin = Math.sin(i * 2 * Math.PI / n);
            double bright = 1;
            colors[i][0] = bright + sin/0.88;
            colors[i][1] = bright - 0.38*cos - 0.58*sin;
            colors[i][2] = bright + cos/0.49;
            maxR = Math.max(maxR, colors[i][0]);
            minR = Math.min(minR, colors[i][0]);
            maxG = Math.max(maxG, colors[i][1]);
            minG = Math.min(minG, colors[i][1]);
            maxB = Math.max(maxB, colors[i][2]);
            minB = Math.min(minB, colors[i][2]);
        }
        double scaleR = 255/(maxR-minR);
        double scaleG = 255/(maxG-minG);
        double scaleB = 255/(maxB-minB);
        for(int i=0; i<n; i++){
            int R = (int)Math.round(scaleR*(colors[i][0]-minR));
            int G = (int)Math.round(scaleG*(colors[i][1]-minG));
            int B = (int)Math.round(scaleB*(colors[i][2]-minB));
            result[i] = new Color(R,G,B);
        }
        return result;
    }

    void populatePredators(String[] args) {
        int start = 0;
        if(args[0].equals("-silent")){
            silent = true;
            start = 1;
        }

        colors = generateColors(args.length-start);
        if(colors.length==1){
            colors[0] = Color.BLUE;
        }

        for (int i = start; i < args.length; i++) {
            Pack pack = new Pack(args[i]);
            if (pack.handler.init()) {
                packs.add(pack);
                initPacks.add(pack);
            }
        }
        Collections.shuffle(packs);
        XY[] positions = map.getPackStartLocations(packs.size());
        XY offset = new XY(-15, -15);
        for(int i=0;i<packs.size();i++){
            Pack pack = packs.get(i);
            for (Predator predator : pack.members) {
                predator.color = colors[pack.id];
                XY pos = new XY(Math.random() * 30, Math.random()   * 30);
                pos.add(positions[i]);
                pos.add(offset);
                pos.setInZeroBounds(Island.SIZE, Island.SIZE);
                predator.moveTo(pos, Island.getCellByPosition(pos));
                predators.add(predator);
            }
        }
        deadPredators = new ArrayList<Predator>(predators.size());
        deadPacks = new ArrayList<Pack>(packs.size());
    }

    void populatePrey(int count, boolean center) {
        XY pos = new XY();
        for (int i = 0; i < count; i++) {
            Prey prey = new Prey();
            if(center){
                pos.x = Math.random() * 100 + 200;
                pos.y = Math.random() * 100 + 200;
            } else {
                pos.x = Math.random() * 500;
                pos.y = Math.random() * 500;
            }

            prey.moveTo(pos, Island.getCellByPosition(pos));
            preys.add(prey);
        }
        deadPreys = new ArrayList<Prey>(preys.size());
    }

    static void print(String txt){
        print(txt, false);
    }

    static void print(String txt, boolean override){
        if(!silent || override)
            System.out.println(txt);
    }

    void printStatus(){
        print("Turn " + turn + " : Prey " + preys.size()
                + " : Predators " + predators.size() + " (" + getElapsedTime() + " elapsed)");
    }

    @SuppressWarnings("serial")
    void init(String[] args) {
        startTime = System.currentTimeMillis();
        map = new Island();
        populatePredators(args);
        if (preyStartCount == 0)
            preyStartCount = 1500 + (packs.size() * 50);

        populatePrey(preyStartCount, true);
        map.rebuildLists(preys, predators);
        img = new BufferedImage(Island.SIZE, Island.SIZE, 1);
        frame = new JFrame() {
            @Override
            public void paint(Graphics g) {
                g.drawImage(img, 32, 32, null);
            }
        };
        frame.setSize(Island.SIZE+64, Island.SIZE+64);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                for (Pack pack : packs)
                    pack.handler.end();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                } finally {
                    for (Pack pack : packs)
                        pack.handler.shutdown();
                }
            }
        });
    }
}

Predator.java

import java.awt.Graphics2D;


public class Predator extends Creature {

    static int count = 0;

    int id;
    int hunger;
    Pack pack;

    public Prey eatOrStarve() {
        for (Prey prey : preys) {
            if (prey.alive && pos.isCloserThan(prey.pos, eatDist)) {
                prey.die();
                hunger = MAX_HUNGER;
                return prey;
            }
        }
        if (hunger-- < 1)
            die();
        return null;
    }

    @Override
    public void die() {
        super.die();
        pack.aliveCount--;
        Game.print(pack.toString() + " starved! " + pack.aliveCount + " members remaining.");
    }

    @Override
    void paint(Graphics2D g){
        g.setPaint(color);
        int size = ((hunger + 10) > MAX_HUNGER && Game.turn > 10) ? 3+(int)Math.pow((hunger+10-MAX_HUNGER)/4,3) : 3;
        g.drawOval((int)pos.x - 1, (int)pos.y - 1, size, size);
        g.fillOval((int)pos.x - 1, (int)pos.y - 1, size, size);
    }

    Predator(Pack pack) {
        super();
        id = count++;
        this.pack = pack;
        MAX_SPEED = 6.1;
        VISIBLE = 50;
        hunger = MAX_HUNGER;
    }

    final double eatDist = 1;
    final static int MAX_HUNGER = 1000;
}
justhalf
источник