Hunger Gaming - Eat or Die


60

Hunger Gaming - Eat or Die

Jeśli nie jesz, umierasz. Jeśli jesz, żyjesz (aż umrzesz). Państwo będzie umrzeć, więc starają się umiera ostatnia.

Przegląd

Istnieje wyspa zamieszkana przez stado drapieżnych zwierząt. Kontrolujesz paczkę pięciu drapieżników. Twoim celem jest utrzymanie paczki przy życiu. Zrób to, jedząc zdobycz. Ofiara ma tendencję do uciekania przed drapieżnikami i próbowania pozostania w stadzie. Oczywiście twoja paczka będzie na tym samym polu, co każda inna paczka , więc konkurencja spróbuje je zjeść, zanim będziesz mógł. Nie pozwólcie, aby to was zniechęciło, bo będziecie głodować.

Jak grać

Utwórz i prześlij program wiersza poleceń do kierowania paczką. Otrzyma informacje o stanie z programu sterującego na STDIN i wyda polecenia na STDOUT. Format jest szczegółowo opisany poniżej. Każdy program zostanie wykonany tylko raz i musi pozostać uruchomiony, dopóki nie będzie miał już żadnych członków pakietu. Będziesz musiał czytać wprowadzane dane i szybko reagować. Limit czasu reakcji wynosi 200 ms dla każdej odpowiedzi. Jeśli do tego czasu nie odpowiesz, twoja paczka nie otrzyma nowych instrukcji dotyczących bieżącej tury.

Jeśli program nie może być uruchomiony przez kontroler, nie zostanie uznany za prawidłowy. Podaj ciąg wiersza polecenia, którego będę musiał użyć, aby uruchomić przesyłanie. Jeśli są jakieś specjalne instrukcje (dotyczące konfiguracji kompilatorów itp.), Dołącz je. Jeśli nie uda mi się go uruchomić, poproszę cię o pomoc w komentarzach. Jeśli nie odpowiesz, nie będę w stanie zaakceptować Twojego zgłoszenia.

Turniej odbędzie się na 64-bitowym systemie Linux. Należy o tym pamiętać, podając wszelkie niezbędne wskazówki.

Detale

  • Pozycja i kierunek każdego stworzenia ma postać pary liczb zmiennoprzecinkowych podwójnej precyzji (np. double) Reprezentujących odpowiednio ich xi ywspółrzędne.

  • Każde stworzenie jest uważane za punkt. Oznacza to, że mogą się nakładać i zajmować to samo miejsce. Nie zostaniesz potrącony na bok i nie ma koncepcji kolizji z innymi stworzeniami.

  • Wyspa ma kwadrat, 500 jednostek w bok. Jeśli spróbujesz wyjść poza te granice, zostaniesz zaciśnięty na krawędzi. Początek {0,0}znajduje się w lewym górnym rogu, ze xwzrostem w prawo i ywzrostem w dół. Ponownie mapa się nie zawija .

  • Gra rozpoczyna się od 1500 + (packCount * 50) zwierząt drapieżnych. Zostaną zebrani w centrum wyspy, ale szybko zdecydują się ruszyć.

  • Paczki będą rozmieszczone w równomiernie rozmieszczonym okręgu na obwodzie. Kolejność paczek jest tasowana, więc nie licz na rozpoczęcie w określonej lokalizacji.

  • Zwierzęta drapieżne mogą zobaczyć wszystkie inne zwierzęta w promieniu 30 jednostek. Mogą się poruszać maksymalnie 6,0 jednostek na turę.

  • Drapieżniki mogą zobaczyć wszystkie inne zwierzęta w promieniu 50 jednostek. Mogą się one poruszać maksymalnie 6,1 jednostek na turę. Oznacza to, że mogą zobaczyć ofiarę, zanim zostaną zauważeni i (ledwo) ich wyprzedzić.

  • Drapieżniki żyją i umierają zgodnie z poziomem głodu . Zaczyna się od 1000 i zmniejsza o jedną w każdej turze. Jeśli po ruchu drapieżnik znajdzie się w odległości 1 jednostki ofiary, automatycznie go zje. To usuwa ofiarę i ustawia głód drapieżnika na 1000. Każdy drapieżnik może zjeść tylko jedną ofiarę na turę. Jeśli w zasięgu jest więcej niż jeden, zje to, do którego dojdzie pierwsza pętla (niekoniecznie najbliższa). Drapieżnik umiera, jeśli jego głód osiągnie zero.

  • Pakiety zaczynają się od pięciu członków . Co 5000 tur wszystkie paczki nadal w grze będą odradzać jednego nowego członka. Zostanie umieszczony w widocznym zasięgu innego członka paczki. Upewnij się, że twoje wpisy mogą obsłużyć więcej niż pięciu członków.

  • Co 1000 tur pojawi się więcej ofiar. Liczba nowych ofiar będzie liczbą żyjących drapieżników minus jeden.

  • Drapieżniki nie mogą atakować innych drapieżników. Jedzą zdobycz, kiedy ją łapią. Otóż ​​to.

  • Kolejność w turze to:

    • Wszystkie ofiary podejmują decyzje
    • Wszystkie drapieżniki podejmują decyzje
    • Wszystkie ofiary się ruszają
    • Wszystkie drapieżniki poruszają się / jedzą
  • Kolejność, w jakiej każda paczka podejmuje decyzje / ruchy, będzie losowa w każdej turze.

Protokół (ogólne)

Cała komunikacja odbywa się w formacie łańcuchowym US-ASCII. Liczby są konwertowane na ciągi znaków przy użyciu języka Java Double.toString()lub Integer.toString(). Twoje dane wyjściowe muszą być sformatowane, aby można je było odczytać w Javie Double.valueOf(String)(nie będziesz wypisywać liczb całkowitych). Aby uzyskać szczegółowe informacje na temat możliwych do przeanalizowania formatów, zobacz dokumentację dotyczącąDouble . Wszystkie pola w wierszu są oddzielone \tznakiem standardowym , a znaki nowego wiersza są \n. Cały ciąg zostanie zakończony bajtem zerowym \0.

W poniższych przykładach używam <>do zaznaczenia pól ze względu na czytelność. Nie są one obecne w rzeczywistych ciągach.

Protokół (wejście)

Łańcuch wejściowy ma różną długość, w zależności od liczby stworzeń widocznych dla twojej paczki. Może przekraczać 100 000 znaków, więc bądź na to przygotowany. Podstawowa konfiguracja to:

  • Wiersz 0: podstawowe informacje o grze. turnjest bieżącym numerem tury, a liczby to łączna liczba zdobyczy i drapieżników pozostawionych na polu. Są integerw formie ciągów.

    <turn>\t<preyCount>\t<predatorCount>\n
    
  • Wiersz 1: Unikalne identyfikatory i poziomy głodu członków Twojej paczki. Nie są one podawane w tej samej kolejności dla każdego wejścia. Użyj unikalnych identyfikatorów do śledzenia poszczególnych członków, a nie w kolejności, w jakiej pojawiają się na wejściu. Ponownie są to integerciągi znaków. W przypadku paczki dwóch byłoby to:

    <id[0]>\t<hunger[0]>\t<id[1]>\t<hunger[1]>\n
    
  • Wiersz 2: pozycje członków twojej paczki, w tej samej kolejności, jak podano w wierszu 1 . Są to doubleciąg:

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

Poniższe wiersze pokazują widoczność każdego członka paczki, w tej samej kolejności, jak podano w wierszu 1 . Zostaną one podane jako dwie linie na członka.

Pierwszy dla każdego składa się z lokalizacji zdobyczy, którą widzi. Drugi to lokalizacje drapieżników, które widzi. Te lokalizacje nie są wyjątkowe jako całość. Na przykład, jeśli dwóch członków paczki może zobaczyć to samo zwierzę, będzie ono w łańcuchu obu członków. Uwzględnione zostaną również członkowie Twojej paczki . Jeśli chcesz je wykluczyć, możesz porównać lokalizacje z własnymi członkami. Wszystkie lokalizacje mają doubleformat ciągów.

Dla każdego żyjącego członka:

<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

Wreszcie ostatni znak będzie \0na początku następnego wiersza.

Wyjątek: jeśli otrzymasz dane wejściowe dead\0, twoja paczka jest martwa. Proszę zakończyć swój program z wdziękiem. Kontroler powinien zamknąć wszystkie żywe procesy, gdy są zamknięte, ale wolałbym, aby procesy zombie nie były wszędzie. W ramach grzeczności możesz wprowadzić limit czasu wprowadzania danych. Na przykład moja przykładowa klasa kończy się, jeśli nie otrzyma danych wejściowych przez 15 sekund.

Protokół (wynik)

Wyjście jest proste. Podasz parę doublewartości dla każdego członka pakietu na żywo. Reprezentują ruch, który chciałbyś, aby wykonali w tej turze. Na przykład, jeśli twoje stworzenie jest obecnie w pobliżu {100.0, 100.0}i wydasz mu rozkaz {-1.0, 1.0}, zostaną przeniesione do {99.0, 101.0}. Wszystkie liczby będą w jednym wierszu, oddzielone tabulatorem.

Na przykład, jeśli żyłeś 3 członków paczki, byłaby to prawidłowa odpowiedź:

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

To byłoby przenieść stworzeń przez {1.0,-1.0}, {2.0,-2.0}i {3.0,-3.0}. Kolejność jest taka sama jak otrzymana na wejściu. Nie zapomnij zakończenia \0!

Jeśli podasz nieprawidłowe dane wejściowe, pojawią się złe wyniki. Jeśli jakikolwiek pojedynczy numer nie może zostać przetworzony na a double, stanie się zero. Jeśli ciąg jako całość nie może zostać przeanalizowany, nie zostaną podane żadne nowe instrukcje, a cała paczka użyje wskazówek z poprzedniej tury.

Wszystkie kierunki zostaną zaciśnięte w maksymalnej odległości 6,1 jednostek. Jeśli chcesz, możesz poruszać się wolniej. Na przykład {1, 0}przeniesie cię o jedną jednostkę. {6,8}(odległość 10) przeniesie Cię tylko 6,1 jednostek i zmniejszy się do około {3.66, 4.88}. Kierunek pozostaje stały.

Ważne: Program sterujący odczytuje zarówno STDOUT, jak i STDERR. Jeśli zgłosisz wyjątek i wydrukujesz do STDERR, jest bardzo mało prawdopodobne, że wiadomość będzie miała poprawną odpowiedź. Staraj się tego unikać.

Program sterujący / testowanie

Źródło kontrolera można znaleźć tutaj na bitbucket.org . Musisz go skompilować przed uruchomieniem. Główną klasą jest Game, a wszystkie klasy znajdują się w domyślnym pakiecie. Aby uruchomić, dołącz polecenie każdej paczki jako osobny argument. Na przykład, jeśli chcesz uruchomić Java ChaserPack i Python LazyPack.py, możesz użyć:

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

Na mapie zdobycz pojawia się na zielono, a drapieżniki na czerwono. Jednak którakolwiek paczka jest pierwszą paczką podaną jako argument, zamiast tego będzie miała kolor niebieski. Ma to na celu ich łatwiejsze rozróżnienie do celów testowych. Drapieżniki będą również migać na biało przez pięć klatek podczas jedzenia.

Gra będzie trwać, dopóki ostatni drapieżnik głoduje, pisząc na konsolę, gdy zdarzają się zdarzenia głodu lub wyginięcia. Po zakończeniu gry wynik będzie przyznawany za każdą paczkę. Jeśli nie chcesz widzieć zdarzeń głodu / wyginięcia, możesz użyć -silentargumentu. Wtedy wyświetli tylko końcowy wynik. Musisz przekazać to jako pierwszy argument :

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

Dołączony jest szkieletowy pakiet Java o nazwie GenericPack. Obejmuje podstawowe niezbędne operacje wejścia / wyjścia. Jest tam, aby dać jasny przykład, jak parsować i odpowiadać. Jeśli chcesz dodać szablon w innym języku, daj mi znać.

Również jest drapieżnikiem na podstawie szablonu ChaserPack. Nie zostanie uwzględniony w turnieju i jest uwzględniony tylko w celach testowych. Działa dość słabo z powodu celowej wady celowania. Jeśli nie możesz tego pokonać, próbuj dalej.

Poniżej znajduje się przykładowe uruchomienie programu sterującego (kliknij, aby zobaczyć wideo). Jakość wideo nie jest świetna (przepraszam), ale możesz poczuć, jak porusza się ofiara. ( uwaga: audio )

zrzut ekranu

Punktacja

Zwycięzca zostanie wyłoniony na podstawie turnieju, zdobywając punkty w każdej rundzie.

Każda runda trwa do momentu śmierci wszystkich drapieżników. Każda paczka będzie punktowana na podstawie tego, kiedy jej ostatni członek zmarł z głodu. Następnie zostaną im przypisane punkty na podstawie zamówienia. Punkty będą gromadzone przez dziesięć rund, a zwycięzcą jest paczka z najwyższą sumą punktów.

Pierwsze miejsce w każdej rundzie otrzyma 100 punktów. Za każde kolejne miejsce nagroda zostanie zmniejszona o 20% (zaokrąglona w dół). Będzie to trwało, dopóki punkty nie osiągną zera (po 17 miejscu). Miejsca 18+ nie otrzymają punktów za rundę. Pakiety, które remisują, otrzymają równe punkty. Na przykład:

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

Maksymalna możliwa liczba punktów w trakcie turnieju to 1000, od pierwszego miejsca wszystkie dziesięć razy.

Jeśli wiele programów zakończy turniej remisowy na pierwszym miejscu, odbędzie się kolejny turniej rundy dziesięciu z tylko zgłoszonymi pierwszymi miejscami . Będzie to trwało, dopóki nie pojawi się jeden zwycięzca.

Postaram się organizować turniej mniej więcej co tydzień lub w miarę pojawiania się nowych zgłoszeń.

Dodatkowe zasady (graj fair!)

  • Nie możesz czytać ani pisać do żadnych zasobów zewnętrznych. Ponieważ nie będziesz wywoływał wielokrotnie programu, wszystkie informacje o stanie mogą być przechowywane wewnętrznie.

  • Nie ingeruj w inne procesy / zgłoszenia. To nie nie znaczy nie próbować ukraść zdobycz, prześcignąć je, itd. Oznacza to, że nie przeszkadzają w prowadzeniu procesu. To zależy ode mnie.

  • Uczestnicy są ograniczeni do maksymalnie trzech zgłoszeń. Jeśli prześlesz więcej, zdobędę tylko pierwsze trzy przesłane. Jeśli chcesz je odwołać, usuń je.

  • Wpisy mogą nie istnieć wyłącznie w celu wspierania innych wpisów. Każdy powinien grać, aby wygrać na własną rękę.

  • Twój program może odradzać maksymalnie jeden proces potomny na raz ( całkowita liczba potomków, a nie bezpośredni). Tak czy inaczej, upewnij się, że nie przekroczysz limitu czasu. Nie możesz Gamew żaden sposób wywoływać samej klasy.

Wyniki - 29 kwietnia 2014 r

Oto wyniki ostatniego dziesięcio-rundowego turnieju:

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

Pakiety przesłane przed 09:00 EDT 2014/04/29 są uwzględnione w tym biegu.

Możesz także wyświetlić szczegóły każdej rundy . Z jakiegoś powodu postanowiłem numerować rundy do tyłu, więc zaczyna się od „rundy 10”.

Aktualizacje

2014/04/23: FGreg zgłosił błąd związany z przekroczeniem limitu czasu (dzięki!). Zaimplementowano poprawkę, więc testerzy będą chcieli zaktualizować kod programu sterującego.


28
Lubię pytania króla wzgórza!
Cruncher

2
@Manu Napisałem przykładowe boty na Windows 7 i przetestowałem zarówno na Windowsie jak i Linuksie. Jakie masz z nimi problemy?
Geobity

2
Te pytania króla na wzgórzu są dość niesamowite, a to z pewnością interesujące. Mam teraz dwie różne paczki!
mackthehobbit

2
@ githubphagocyte Nie chcę naprawdę zabijać paczki za pierwszym razem, po prostu dlatego, że widziałem nawet proste programy, które przekraczają limit co 40k lub więcej. Zatwierdziłem zmianę nazewnictwa w kontrolerze. Zakręty są teraz znane jako zakręty w całym programie, chyba że gdzieś mi umknął.
Geobits

2
@Geobits eh, w porządku dla mnie. Wiesz, to wygląda bardzo podobnie do projektu badawczego, który realizuje jeden z moich profesorów fizyki, z którym mógłbym pomagać latem. Wyjaśnię to trochę później, jeśli będę mógł.
krs013,

Odpowiedzi:


10

Jasnowidz

Kod zaktualizowany, aby zmierzyć się z AbleDogs

Woo hoo! Nareszcie bije to Netcats! Rozszerzyłem istniejący kod (napisy do Geobitów!) Z niewielkimi modyfikacjami, aby stworzyć ten pakiet do przewidywania przyszłości. Nic nie przebije drapieżników, którzy wiedzą, dokąd przeniesie się zdobycz!

Z dwóch testów, które przeprowadziłem, moja paczka zawsze wygrywała z Netcats. Ale to nie zadziała tak dobrze, jeśli nie będzie innych paczek, ponieważ przewidywanie wciąż nie powiedzie się, jeśli w pobliżu będzie zbyt wiele innych ofiar.

Prawdopodobnie mogę zastosować sztuczkę CivilizedBeasts, aby znacznie zmniejszyć liczbę ofiar w ciągu pierwszych kilku tysięcy tur.

Sporządzono w 5,21 minut
Jasnowidz (1): Turn 9270: Wynik 100
EcoCamel.pl (3): Turn 8118: Score 80
Netcats (0): Turn 6111: Score 64
RubyVultures.rb (5): Turn 4249: Score 51
RubySpiders.rb (4): Turn 3495: Score 40
CivilizedBeasts (2): Turn 3176: Score 32
ChaserPack (6): Turn 2492: Score 25

Od nazwy mojej paczki powinieneś wiedzieć, jakiej strategii używam = D

Edytuj :

  • Zaktualizowany system zarządzania paczkami, aby nie ścigał tej samej ofiary (a także stara się znaleźć najlepsze dopasowanie!)
  • Usprawnij proces wędrówki, gdy liczba ofiar jest niewielka (ma to kluczowe znaczenie dla wygranej!).
  • Popraw szczególne przypadki, gdy poprzednia wersja utknęła w rogu.
  • Naprawiono błąd w algorytmie wykrywania drapieżników (teraz jest dość dokładny!)
  • Zawarty flock[ALIGN]czynnik ofiary
  • Trzymaj jedną ofiarę jako zwierzę domowe, jeśli brakuje pożywienia
  • Utwórz jaskinię, w której stado będzie gromadzić swoje ofiary
  • Zwab w pobliżu drapieżnika, aby ścigał naszą zdobycz, której nie wygrają

Policzyłem, ile ofiar zjada każda paczka, a oto wynik:

Jasnowidz (1) zużył 916 ofiar w 9270 zwojach (0,099 ofiar / tura)
EcoCamel.pl (3) zjadł 73 ofiar w 8118 zwojach (0,009 ofiar na turę)
Netcats (0) zużył 563 ofiar w 6111 zwojach (0,092 ofiar w jednym zwoju)
RubyVultures.rb (5) zużył 77 ofiar w 4249 turach (0,018 ofiar / turę)
RubySpiders.rb (4) zjadł 293 ofiar w 3495 zwojach (0,084 ofiar / tura)
CivilizedBeasts (2) pochłonęły 10 ofiar w 3176 tur (0,003 ofiar / turę)
ChaserPack (6) zużył 43 zdobycze w 2492 zwojach (0,017 ofiar / tura)

Moja paczka jest bardzo agresywna i większość z 916 liczy się, jak sądzę, dzięki kradzieży zdobyczy Netcats, podobnie jak RubySpiders.

CivilizedBeasts przegrywa niestety z powodu środkowego wielbłąda z EcoCamel.

A EcoCamel (z krytycznym poziomem głodu 500) jest dość wydajny, zjada tylko tyle, aby przetrwać do końca.

Również dzięki temu zaktualizowanemu jasnowidzowi gra osiąga zaledwie 10 000 tur.

Kod:

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;
    }
}

1
Wygląda bardzo dobrze, twoja gra jest naprawdę lepsza niż netcats w mojej grze. Ale nienawidzę tego, że nie mogę ścigać innych drapieżników, ponieważ moje bestie wykonują naprawdę kiepską pracę w twoich statystykach (podczas gdy evilcamel robi to zbyt dobrze). Może muszę spróbować zainstalować kompilator Perla lub coś podobnego.
Herjan

Tak, myślę, że twoja metoda nie działa, jeśli na środku znajduje się drapieżnik, jak omówiono w twojej odpowiedzi. Próbowałem zaimplementować inną wersję, która zachowuje się podobnie do twojej. Może zmieniać formację w zależności od liczby dostępnych drapieżników, więc oglądanie jej jest przyjemne, choć niewiele lepsze niż twoje.
justhalf

Tak, moja strategia może zostać ulepszona na wiele sposobów, podobnie jak inne formacje z inną ilością członków, ponieważ moje bestie są skazane na <4 drapieżników. Lub na przykład losowe miejsca do zebrania (zamiast tylko środkowego). Ale jestem zbyt leniwy, aby to zaimplementować (teraz). I nigdy nie będzie tak dobra, jak ta, ponieważ jeśli zdobycz się wyczerpie, moja taktyka po prostu nie zadziała. Właśnie wtedy potrzebujesz bestii takiej jak Twoja (już wspomniałeś, żeby zacząć od mojej taktyki i kiedy ofiara jest niska, by użyć tej taktyki). Myślę, że już to przemyślałeś.
Herjan

W tej chwili mam inne wyzwanie i wydaje się, że GeoBits stracił zainteresowanie tym, więc pozwolę mu odczekać chwilę, chyba że wyniki zostaną zaktualizowane. Mam pomysły na kilka innych zgłoszeń, więc mam nadzieję, że to wyzwanie zostanie utrzymane. Oczywiście obejrzę twoją aktualizację.

15

Netcats

Oto paczka na początek. Rozszerza GenericPackklasę zawartą w programie sterującym. Zostało ulepszone od czasu pierwotnego opublikowania i nie głodzi już rzadkiego stada.

Netcaty używają formacji siatki w kształcie vee, aby uwięzić zdobycz w rogu, gdzie mogą ją zjeść w wolnym czasie. Siatka jest utworzona z jednym elementem „główkowym” pośrodku. Gdy głowa je, zamienia się miejscami z najbardziej głodnym członkiem paczki, ponieważ głowa jest zwykle pierwszą, która ma okazję zjeść.

Siatka zaczyna się raczej mała, ale rozszerza się, gdy stado staje się mniejsze, aby bardziej efektywnie trałować pole.

Jeśli nie widać żadnej ofiary, formacja staje się naiwnym wzorem poszukiwań obejmującym większość wyspy.

Gdy paczka spadnie do dwóch członków, sieć po prostu nie działa. W tym momencie każdy idzie swoją drogą, łapczywie jedząc najbliższą rzecz, jaką może znaleźć, i idąc w przeciwnym razie na wpół losowy spacer.

Ta wersja przetrwa znacznie lepiej niż naiwne Netcaty widoczne na filmie z linkiem do pytania.

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;
}

11

Rubinowe Pająki

Ponieważ czasami mniej znaczy więcej, a wiele rozwiązań prawdopodobnie i tak spróbuje zaatakować ofiarę ...

Myślałem, że moja paczka może po prostu się rozdzielić i czekać, aż inni wykonają pracę.

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

Uwaga: W rzeczywistości nie działa, nie odczytuje danych wejściowych ani nie reaguje szybko. Mimo to, ponieważ działa dobrze z kontrolerem, mam nadzieję, że kwalifikuje się bez dalszych dostosowań.


4
+1 pierwszy roztwór pasożyta. Myślę, że tego typu odpowiedź poprawi jakość innych odpowiedzi poprzez stopniowe eliminowanie luk ...
trichoplax

@ githubphagocyte Miałem na myśli mądrzejszego pasożyta, ale jest to bardziej wydajne pod względem czasu na żywo / linii kodu. Mam nadzieję, że znajdę czas na jego wdrożenie.
Legat

Może @Synthetica koduje teraz mój pomysł. A jeśli jego pomysł jest jeszcze inny, możemy wkrótce mieć więcej pasożytów niż myśliwych;)
Legat

1
@ githubphagocyte możemy wprowadzić trzy wpisy, więc opublikuję kolejną paczkę, gdy będzie gotowa. Mimo to uważam za interesujące, że ten został w międzyczasie zakodowany i może okazać się bardziej skuteczny. Wykorzystuje Netcats naprawdę dobrze i faktycznie przeżywa mój pierwszy pakiet łowców.
Legat

3
To może wejść w obecnym stanie, nawet jeśli zajęło mi sekundę, aby dowiedzieć się, dlaczego. Wydaje się, że im lepiej, tym więcej Netcatów dodajesz (co ma sens). +1 ode mnie, zobaczmy, którzy łowcy wychodzą, aby uniknąć zakrętów :)
Geobits

11

CivilizedBeasts

Wreszcie czas pokazać moje bestie!

Moja rasa uważa, że ​​polowanie jest nieco prymitywne, dlatego pracują razem w 4-osobowym zespole, dlatego porzucają swojego piątego sojusznika, ponieważ: mniej drapieżników = więcej ofiar dla siebie. Zasadniczo robią to, co robią ludzie, łapią zdobycz i dbają o swoje bydło;)

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();
    }
}

Moje piersi stają się dość trudne do przeżycia z mniej niż 200 ofiarami na turie + -12 000, mając w grze tylko wrogie Netcaty. Będziesz zadowolony z tej rasy, ponieważ naprawdę pochłania ogromne ilości zdobyczy z prędkością, jakiej żadna inna rasa nigdy nie może (nie tak, że szybkie i duże rzeźnie zapewniają zwycięstwo, ale ma to znaczący wpływ na (długi) czas trwania całej rundy).


3
Jeśli przez „ dbaj o nie ” masz na myśli „ powtarzaj je do połowy i zabijaj / jedz ”, to tak, robią to dobrze. +1
Geobity

To zabawne, z niezmutowaną (oryginalną) wersją Złych Wielbłądów, cywilizowana taktyka jest całkowicie nieefektywna z powodu „środkowego wielbłąda”.
user2846289

1
@VadimR Bzdury, dzięki za aktualizację twojego wielbłąda: PI nie może go przetestować, ponieważ nie jest to Java, ale wiem, że moja strategia jest trochę bezużyteczna w przypadku drapieżników na środku mojego terytorium: P
Herjan

5
To znowu Herjan! Również „Trudno jest mi przetrwać moje piersi z mniej niż 200 ofiarami” (podkreślenie dodane). Nie zdawałem sobie sprawy, że żywotność twoich piersi zależy od liczby ofiar w symulacji komputerowej ....
Justin

5

Rubinowe Sępy

Oto paczka bardziej aktywnych pasożytów . Próbują otoczyć najbliższego poruszającego się drapieżnika , aby ukraść jego ofiarę . Są trochę uzależnieni od szczęścia, ponieważ nie mają sprytnego sposobu na wybranie, za kim podążać, ale zwykle biją ścigających, a czasem pająków .

Nie są jeszcze całkiem skończone, ponieważ opublikowałem to, aby przyspieszyć tempo :)

Mam nadzieję, że:

  • każ im szukać drapieżników poza polem widzenia
  • weź pod uwagę zdobycz - często jedna z nich znajduje się między inną paczką a ofiarą!
  • zacznij je obracać , aby uniknąć jednego głodu, gdy wszystkie inne będą dobrze odżywione

22 kwietnia 2014: Dodano nudę , dzięki czemu są mniej lepkie i pozwalają im samodzielnie polować na zdobycz i szukać drapieżników

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

Na pewno potrzebujesz więcej „myśliwych” w mieszance. W tej chwili mają one tendencję do przyczepiania się do innych pasożytów (ponieważ jest to większość na polu). Lubię je oglądać i widzę, jak byłyby skuteczne w przypadku różnych kombinacji konkurentów.
Geobits

O tak, w moim środowisku testowym mam dwie inne paczki myśliwych. Bez nich sępy są prawdopodobnie dość nieświadome. Zwłaszcza, że ​​netcaty mogą szybko pracować w rogach, nie będąc widzianym od środka.
Legat

Myślę, że wiem, co może ich szczególnie martwić. Taniec wojenny złych wielbłądów. @Geobits, co powiesz na umieszczenie walk na Youtube? 10 rund to nie za dużo, aby pozostać obserwowanym. Oczywiście potrzebna byłaby kwatera główna. Nie spodziewam się milionów widzów, ale zabawnie byłoby zobaczyć, jak radzą sobie twoje paczki i może trochę dla nich kibicować :)
Legat

1
Pełny turniej może być nieco długi (teraz ~ 8 minut na rundę), aby zwrócić na siebie uwagę, ale nagranie jednej rundy „obserwatora” może działać. Zastanowię się nad przyszłymi biegami.
Geobits

@Geobits Czy prędkość zmienia się znacznie podczas 8-minutowej rundy? Zastanawiam się, czy warto nagrywać klatki na turę, aby można je było odtwarzać ze stałą szybkością, zamiast spowalniać podczas intensywnych obliczeniowo partii. To znaczy do celów YouTube.
trichoplax

5

Złe Eco Wielbłądy

Edycja: Mutacja # 2. Och, nie, spóźniłem się z wprowadzeniem przewidywania ruchu zdobyczy, aby jako pierwszy pokonać Netcats. OK, niech tak będzie.

Ta mutacja ma $hunger_criticalzmienną (stałą). Zmiana jej na wartość powyżej 1000 powoduje, że Wielbłądy zawsze polują, tak jak jasnowidze. Następnie:

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

Jeśli $hunger_criticaljest ustawiony na np. 500 (jak poniżej), to moje Wielbłądy (po zobaczeniu okropności cywilizacji ) starają się zachowywać w sposób przyjazny dla środowiska (stąd zmieniły nazwę rasy), tzn. Zabijają tylko wtedy, gdy są głodne. Jeśli nie są głodni, patrolują krytyczne obszary wyspy - środek i rogi, aby zapobiec bezcelowemu rzeźnictwu przez innych łowców. Cóż, z centrum, mniej więcej działa. Ideą krążenia w rogach było odepchnięcie ofiary i utrudnienie życia Kotom i pasożytom. Cóż, to nie działa. Głupia zdobycz i tak idzie w kąty.

Interesujące jest również to, że flock[ALIGN]drapieżniki mogą odgadnąć ten komponent, a moja implementacja różni się od implementacji justhalfa. Obawiam się, że istnieje jakiś drobny błąd w moim zdzierstwo wykonania kodu Geobits', oglądanie / porównując polowania indywidualnego cameli vs jasnowidzów.

A program jest trochę długi, przepraszam.


Edycja: Mutacja # 1. Wyspa okazuje się dość radioaktywna (co tłumaczy brak roślinności i niewytłumaczalną naturę „drapieżnych” stworzeń), więc oto pierwsza mutacja moich Wielbłądów. Każdy z nich może zostać łowcą solo, jeśli jest głodny lub jeśli nie ma wolnego kąta dla wszystkich. Hunter próbuje aktywnie ścigać pobliską zdobycz. Jeśli nie ma, patroluje w szerokim kręgu wokół centrum wyspy, a następnie goni najbliższe stworzenie, gdy je znajdzie. Niestety, kierunek zdobyczy staje się nieprzewidywalny, gdy jest blisko roju (warto to zbadać ...), więc pościg solo nie jest zbyt skuteczny. Ale jeśli się powiedzie, wielbłąd idzie do trawienia do najbliższego wolnego rogu (jeśli istnieje). Kiedy poziom głodu spadnie poniżej pewnego poziomu, każdy wielbłąd opuszcza swój róg (prawdopodobnie przeklinając Netcats („gdzie jest jedzenie?”) )) i samodzielnie rozpoczyna bezpłatny roaming. I tak dalej.


Ten sam dowcip dwa razy nie jest śmieszny, ale (1) musiałem gdzieś zacząć i jestem nowy w tych sprawach, (2) szczerze mówiąc, myślałem o taktach narożnych (a kto nie?) Oglądając Netcats przed Ruby Pająki pojawiły się na wyspie.

Czy słyszałeś kiedyś o wielbłądach mięsożernych? Biedne zwierzęta obudziły się pewnego dnia na zapomnianej przez Boga wyspie, by nie znaleźć trawy ani drzew, ale mnóstwo dziwnych zielonych, choć jadalnych, szybko poruszających się (dość irytujących) drobiazgów. Nie mając przyzwyczajeń związanych z polowaniem (mam nadzieję, że wkrótce zmutują), moje Wielbłądy opracowały bardzo zły plan przetrwania: dzielą się i idą każdy z 1 na 4 rogi, a piąty idzie do centrum (aby tam umrzeć jako pierwszy, ponieważ okazuje się). W miejscu docelowym cierpliwie czekają, wykonując rodzaj wielbłądziego tańca wojennego, a może po prostu starają się nie deptać już tam innych zwierząt, pająków i wszystkich ...

#!/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__

5
To chyba najbardziej czytelny skrypt perla, jaki widziałem na tej stronie.
Geobits

Musisz udać się dokładnie do rogu, aby skutecznie ich odkurzyć, w przeciwnym razie po prostu weźmiesz udział w rzezi Netcats, haha
połowę

@ justhalf, to tak jak powiedziałem: plan nie zadziałał. Pasożyty siedzące w rogach również nie odciągnęły ofiary. Hm-m, może 2 lub więcej bestii patrolujących jeden róg pomoże.
user2846289

Twoje wielbłądy są całkiem dobre! Na szczęście (dla mnie) poprawiłem moich jasnowidzów, więc przez większość czasu (nie zawsze) moja paczka wygrywała z twoją podczas ostatniej bitwy. Ciekawy!
justhalf

1
Jeśli jesteś bliżej niż 8 (20-2 * 6) jednostek od ofiary, możemy zobaczyć ruch wszystkich innych ofiar, które znajdują się w promieniu 30 jednostek od naszej ofiary w bieżącej turze. A vecnieruchomość jest w zasadzie tylko przemieszczenie z poprzedniego kolei do bieżącej turze. I jak powiedziałem, dopasowujemy wyniki z poprzedniej tury, aby dowiedzieć się, która ofiara idzie w którą stronę, nie możemy polegać na kolejności zdobyczy. Jest to możliwe, ponieważ ofiary zwykle (w typowym scenariuszu) zachowują wystarczającą odległość od siebie (> 12 jednostek), więc przez większość czasu możemy dopasować zdobycze w poprzedniej turze do obecnej tury.
justhalf

4

AbleDogs - PHP

Te miłe psy nauczyły się gryźć łydki ofiary, aby ocierać ją o ściany. Uwielbiają też wędrować po pastwisku w poszukiwaniu nowych ofiar. Wreszcie, nauczono ich, aby powstrzymywali się od jedzenia, chyba że naprawdę potrzebują kalorii.

Umieść kod w AbleDogspliku i uruchom gophp 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();
}
?>

Uwagi ogólne

  • Liczy się gra końcowa. Możesz mieć najmądrzejszy algorytm polowania, jeśli nie zauważysz i nie złapiesz kilku ostatnich ofiar szybciej niż przeciwnik, przegrasz.

  • Jeśli twoje drapieżniki nie są w stanie złapać zdobyczy w pojedynkę (lub przynajmniej parami), wznosisz toast, gdy tylko gęstość zdobyczy spadnie wystarczająco nisko, aby polegać na ślepym szczęściu lub blokowaniu zdobyczy w rogach.

  • Predyktor ruchu zdobyczy jest zasadniczo obowiązkowy. Nie wyobrażam sobie pokonania programu opartego na predyktorach bez własnego predyktora.

Pogoń za ogonem

Najbardziej nieefektywnym sposobem złapania ofiary jest ściganie jej przez ogon. Zakładając, że pojedynczy drapieżnik goni pojedynczą ofiarę i nie ma żadnych wpływów zewnętrznych (ściany, inne ofiary itp.), Pościg za ogonem może trwać wiecznie. Jak tylko wejdziesz w promień widzenia jednostki 30, zdobycz ucieka z prędkością 6 dla twojego 6.1, więc zyskujesz 1 odległość na turę: w linii prostej potrzebujesz około 300 obrotów, aby go zdobyć.

Biorąc pod uwagę rozmiar areny, ofiara przemierzy najwyżej przekątną 500 jednostek kwadratowych, zanim uderzy w ścianę lub róg, co zajmie maksymalnie 117 obrotów.

Zwycięską strategią jest oczywiście znalezienie sposobu na spowolnienie zdobyczy, mianowicie poprzez umieszczenie przed sobą innego drapieżnika lub ściany / rogu.

Urządzenie prognozujące

Z prędkością zdobyczy 6, zdobycz może przenieść się na obszar 36 * pi jednostek do kwadratu. Przy promieniu łapania wynoszącym 1 ślepe przypuszczenie, gdzie będzie następna ofiara, ma szansę 1/36 * pi (około 1%) na odniesienie sukcesu. Oczywiście należy coś z tym zrobić!

Patrząc na kod silnika symulacji, widać, że dane wejściowe to:

  • widoczne pozycje ofiar i drapieżników
  • poprzednie prędkości zdobyczy

Chociaż wszystkie pozycje są dostępne, poprzednie prędkości zdobyczy nie są. Jedynym sposobem obliczenia tych prędkości byłoby śledzenie każdej ofiary z jednego zakrętu do następnego, co jest prawie niemożliwe (chyba że zaimplementujesz bardzo inteligentny algorytm śledzenia ruchu). Dzięki temu predyktor może z łatwością odtworzyć wszystkie warunki obliczeń, z wyjątkiem wymaganego udziału prędkości.

W przypadku pojedynczej ofiary prędkość można śledzić bez większych problemów, co pozwala zbudować „idealny” predyktor do złapania ofiary odizolowanej od stada. To w zasadzie wszystko, czego potrzebujesz do gry końcowej, gdy ofiar jest zbyt mało, aby ze sobą współdziałać. Kiedy ofiary są obfite, a efekt stada jest wystarczająco silny, aby oszukać predyktora, sama gęstość ofiar zrekompensuje błędy (jeśli nie złapiesz tego, którego szukałeś, istnieje szansa, że ​​dostaniesz jednego z jego najbliższych kumpli ).

Łapanie ofiar

Dzięki dokładnej wiedzy na temat obliczania prędkości zdobyczy możliwe jest „ukierunkowanie” danej zdobyczy w pożądanym kierunku poprzez dostosowanie położenia drapieżnika.

Pozwala to przypiąć zdobycz do ściany lub skierować ją w stronę innego członka paczki. Wypróbowałem kilka wyrafinowanych strategii, takich jak szczypanie ofiary między dwoma członkami stada. Niestety okazało się to mniej wydajne niż obecna procedura „pin and scan”, ponieważ utrzymywanie dwóch drapieżników zajętych ściganiem jednej ofiary pozostawia przeciwnikom zbyt wiele drapieżników swobodnych do wypatrywania pastwiska.

Kradzież ofiar

Jedną z cech zachowania ofiar jest to, że wpływ drapieżnika wzrasta proporcjonalnie do jego odległości od ofiary (pod warunkiem, że pozostaje on w promieniu widzenia ofiary). Im najbliższy drapieżnik trafi na zdobycz, najmniej ofiara będzie się od niego oddalać.

Oznacza to, że gdy dwóch drapieżników rywalizuje o zdobycie ofiary, najbliższy jest zobowiązany ją zdobyć jako pierwszy. Nawet super sprytny pretendent, który zdołałby ustawić się dokładnie przed osią chasera / ofiary, zasadniczo przestraszyłby ofiarę w szczęki pretendenta.

Aby ukraść ofiarę, potrzebna jest przynajmniej para drapieżników. Jeden pójdzie na zabójstwo, a pozostali pozostaną w promieniu widzenia ofiary, tak daleko od ofiary, jak to możliwe, aby zmaksymalizować wpływ i przyciągnąć zdobycz do łowcy.

Poza tym każda zmiana kierunku pozwoli rywali skrócić róg w kierunku ofiary, a trzymanie się za rywalem jest możliwe tylko wtedy, gdy „goader” był wystarczająco blisko ofiary na początku akcji.

Tak więc kradzież zdobyczy ma szansę powodzenia tylko wtedy, gdy początkowe pozycje „złodziei” są sprzyjające i można oszczędzić przynajmniej drugiego drapieżnika. Z mojego doświadczenia nie jest to warte złożoności.

Sugerowane zmiany

Aby pozwolić na bardziej złożone strategie, przeniesienie drapieżnika powyżej maksymalnej prędkości ofiary może kosztować punkty głodu proporcjonalne do przekroczenia prędkości. Załóżmy na przykład, że przejście do prędkości 6 jest bezpłatne, a każdy punkt prędkości powyżej 6 kosztuje 100 punktów głodu (przejście do 6,3 kosztuje 30 punktów głodu na turę, spalenie 1000 punktów głodu pozwoliłoby osiągnąć prędkość 16 na jedną turę - i umarłbyś, jeśli nie łapie zdobycz robiąc to!).

Zamiast zabijać przypadkowego drapieżnika, gdy więcej niż jeden jest wystarczająco blisko, aby zjeść ofiarę, sugeruję podzielenie zysku (na przykład 3 drapieżniki dostałyby po 333,33 punktów głodu). Pozwoliłoby to na bardziej interesujące strategie gry końcowej (cieniowanie wrogiego drapieżnika przydałoby się, jeśli na przykład uważasz, że masz więcej punktów głodu).

Specjalny kolor pierwszego opakowania jest raczej trudny do zauważenia. Sugeruję cyjan lub pomarańcz zamiast niebieskiego.


Wreszcie kolejny konkurent! Celowo wdrożyłem każdy punkt, o którym wspomniałeś, z wyjątkiem kradzieży zdobyczy, dla których byłem zadowolony z obecnego efektu ubocznego. Z twoich komentarzy wynika, że ​​wygrywasz z moim jasnowidzem? To ciekawe, sprawdzę jutro. = D. Możesz także wypróbować moją aktualizację GUI, aby zobaczyć lepszą (przynajmniej według mnie) grafikę.
justhalf

Nie wygrywam cały czas. To zależy od tego, kto jest najbliżej ostatnich ofiar podczas odrodzenia. Jednak moje zdolne psy mogą mieć przewagę statystyczną. Ulepszyłem też silnik symulacji, aby wyświetlać każdą drużynę innym kolorem, ale ostatecznie okazało się, że jest zbyt kolorowy jak na mój gust, więc zdecydowałem się na pomarańczowy zamiast niebieskiego dla 1. drużyny i na wszystkie inne czerwone.

Wow, właśnie sprawdziłem twoje przesłanie. To szalone, nie wiedziałem, że możesz zmusić modlitwę do zatrzymania się w ten sposób. I poczułem się trochę oszukany, że gdy ofiara jest dokładnie na krawędzi, moi drapieżnicy nie będą w stanie ich wykryć, co jest dla mnie zdecydowanie dużą wadą.
justhalf

To arytmetyka wektorowa dla ciebie :). Właśnie to próbowałem wyjaśnić w moim poście: dla jednego ofiary znasz wszystkie parametry ruchu (w tym poprzednia prędkość), więc możesz obliczyć pozycję drapieżnika, która wytworzy odpowiednią prędkość ofiary, aby stała nieruchomo blisko ściany.

1
Cóż, rozwiązanie równania jest zakodowane na stałe (nie są potrzebne iteracje). Zasadniczo obliczasz wektor, którego ofiara powinna użyć dla prędkości następnego zwrotu, jeśli twojego drapieżnika nie było. Biorąc pod uwagę kierunek, w którym ma podążać ofiara, wnioskujesz o różnicy wektorowej potrzebnej do ustalenia punktu prędkości ofiary w tym kierunku. Ta różnica wektorów daje pozycję drapieżnika w stosunku do ofiary. To daje ci swobodę, która pozwala (w pewnych granicach) wybrać odległość od ofiary.

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

Do uruchomienia tego będziesz potrzebować platformy haskell . Następnie użyj runhaskellpolecenia, aby go uruchomić. Moja paczka czeka na ofiarę, która do nich przyjedzie.


+1 za szkieletowe rozwiązanie w nowym języku. Przypuszczalnie cieszysz się, że ludzie budują nowe strategie?
trichoplax

Jasne, czemu nie. (Chociaż nie robi nic poza wytwarzaniem stałej mocy wyjściowej i wychodzeniem z „martwego \ 0”, więc nie jestem pewien, czy byłoby to bardzo przydatne.)
PyRulez

Nadal doradzam każdemu, kto to uruchomi, aby skorzystał z tej -silentopcji ...
Geobits

3

Nie jest to wpis, zawsze jestem zainteresowany dodaniem niestandardowego koloru dla każdego uczestniczącego wpisu w ;)

Również proces jedzenia nie jest wizualizowany poprzez zmianę koloru, ale zmianę rozmiaru, dzięki czemu możemy zobaczyć wiele zdarzeń jedzenia w krótkim czasie.

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;
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.