Juego del hambre: comer o morir


60

Juego del hambre: comer o morir

Si no comes, mueres. Si comes, vives (hasta que mueras). Usted va a morir, así que trate de morir el pasado.

Visión de conjunto

Hay una isla poblada con una manada de presas. Tú controlas un paquete de cinco depredadores. Su objetivo es mantener viva su manada. Haz esto comiendo presas. Las presas tienden a huir de los depredadores y, de lo contrario, intentan permanecer en una bandada. Por supuesto, su paquete estará en el mismo campo que cualquier otro paquete , por lo que la competencia tratará de comerlos antes de que pueda. No dejes que esto te desanime, o morirás de hambre.

Cómo jugar

Cree y envíe un programa de línea de comando para dirigir su paquete. Recibirá información de estado del programa de control en STDIN y enviará comandos en STDOUT. El formato se detalla a continuación. Cada programa solo se ejecutará una vez y debe permanecer en ejecución hasta que no haya más miembros del paquete con vida. Deberá leer la entrada a medida que ingresa y responder rápidamente. Hay un tiempo de espera estricto de 200 ms para cada respuesta. Si no ha respondido para entonces, su paquete no recibirá nuevas instrucciones para el turno actual.

Si el controlador no puede ejecutar su programa, no se considerará válido. Incluya la cadena de línea de comando que necesitaré para ejecutar su envío. Si hay instrucciones especiales (para configurar compiladores, etc.), inclúyalas. Si no puedo hacerlo funcionar, le pediré ayuda en los comentarios. Si no responde, no podré aceptar su envío.

El torneo se llevará a cabo en un sistema Linux de 64 bits. Tenga esto en cuenta al dar las instrucciones necesarias.

Detalles

  • La posición y dirección de cada criatura tienen la forma de un par de números de coma flotante de doble precisión (por ejemplo double) que representan sus coordenadas xy y, respectivamente.

  • Cada criatura se considera un punto. Esto significa que pueden superponerse y ocupar el mismo espacio. No te dejarán de lado, y no existe un concepto de colisión con otras criaturas.

  • La isla es un cuadrado, 500 unidades a un lado. Si intentas aventurarte más allá de esos límites, estarás sujeto al borde. El origen {0,0}está en la esquina superior izquierda, con un xaumento hacia la derecha y un yaumento hacia abajo. Nuevamente, el mapa no se ajusta .

  • El juego comienza con más de 1500 presas (packCount * 50) . Se reunirán en el centro de la isla, pero rápidamente decidirán comenzar a moverse.

  • Los paquetes se organizarán en un círculo uniformemente espaciado alrededor del perímetro. El orden del paquete se baraja, así que no cuente con comenzar en una ubicación en particular.

  • Los animales de presa pueden ver a todos los demás animales dentro de un radio de 30 unidades. Se pueden mover a un máximo de 6.0 unidades por turno.

  • Los depredadores pueden ver a todos los demás animales dentro de un radio de 50 unidades. Pueden moverse a un máximo de 6.1 unidades por turno. Esto significa que pueden ver presas antes de ser vistas y (apenas) superarlas.

  • Los depredadores viven y mueren según su nivel de hambre . Comienza en 1000 y disminuye en uno cada turno. Si, después del movimiento, un depredador está dentro de 1 unidad de presa, se lo comerá automáticamente. Esto elimina la presa y establece el hambre del depredador en 1000. Cada depredador solo puede comer una presa por turno. Si hay más de uno dentro del rango, se comerá el que el bucle llegue primero (no necesariamente el más cercano). Un depredador muere si su hambre llega a cero.

  • Los paquetes comienzan con cinco miembros cada uno. Cada 5000 turnos, todos los paquetes que aún estén en el juego generarán un nuevo miembro. Se colocará dentro del rango visible de un miembro del paquete. Asegúrese de que sus entradas puedan manejar más de cinco miembros.

  • Cada 1000 turnos, se generarán más presas. El número de nuevas presas será el número de depredadores vivos menos uno.

  • Los depredadores no pueden atacar a otros depredadores. Comen presas cuando lo atrapan. Eso es.

  • El orden dentro de un turno es:

    • Todas las presas toman decisiones
    • Todos los depredadores toman decisiones.
    • Todas las presas se mueven
    • Todos los depredadores se mueven / comen
  • El orden en que cada paquete toma sus decisiones / movimientos será aleatorio en cada turno.

Protocolo (general)

Todas las comunicaciones se realizan en formato de cadena US-ASCII. Los números se convierten en cadenas usando Java Double.toString()o Integer.toString(). Su salida debe estar formateada para que Java la pueda leer Double.valueOf(String)(no generará números enteros). Para obtener detalles sobre los formatos analizables, consulte la documentación deDouble . Todos los campos de una línea están separados por el \tcarácter estándar , y las líneas nuevas lo están \n. La cadena completa terminará en un byte nulo \0.

En los ejemplos a continuación, estoy usando <>para marcar los campos por razones de legibilidad. Estos no están presentes en las cadenas reales.

Protocolo (entrada)

La cadena de entrada varía en longitud, dependiendo de cuántas criaturas son visibles para su paquete. Puede superar los 100k caracteres, así que prepárate para eso. La configuración básica es:

  • Línea 0: información básica sobre el juego. turnes el número de turno actual, y los recuentos son el número total de presas y depredadores que quedan en el campo. Estos están integeren forma de cadena.

    <turn>\t<preyCount>\t<predatorCount>\n
    
  • Línea 1: los ID únicos y los niveles de hambre de los miembros de tu manada. Estos no se dan en el mismo orden para cada entrada. Use los identificadores únicos para rastrear miembros individuales, no el orden en que aparecen en la entrada. De nuevo, estos son integercomo cadenas. Para un paquete de dos, esto sería:

    <id[0]>\t<hunger[0]>\t<id[1]>\t<hunger[1]>\n
    
  • Línea 2: las posiciones de los miembros de su manada, en el mismo orden que se indica en la línea 1 . Estos son doublecomo una cadena:

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

Las siguientes líneas son la visibilidad de cada miembro del paquete, en el mismo orden que se da en la línea 1 . Estos se darán como dos líneas por miembro.

El primero para cada uno consiste en ubicaciones para la presa que puede ver. El segundo es la ubicación de los depredadores que puede ver. Estas ubicaciones no son únicas en su conjunto. Por ejemplo, si dos miembros de la manada pueden ver el mismo animal, estará en la cadena de ambos miembros. Además, sus propios miembros de la manada se incluirán. Si desea excluirlos, puede comparar ubicaciones con sus propios miembros. Todas las ubicaciones están en doubleformato de cadena.

Para cada miembro vivo:

<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

Finalmente, el último personaje será \0, al comienzo de la siguiente línea.

Excepción: si recibe la entrada dead\0, su paquete está muerto. Termine su programa con gracia, por favor. El controlador debería cerrar todos los procesos vivos cuando está cerrado, pero prefiero no tener procesos zombies por todas partes. Como cortesía, puede incluir un tiempo de espera de entrada. Por ejemplo, mi clase de ejemplo finaliza si no recibe información durante 15 segundos.

Protocolo (salida)

La salida es simple. Dará un par de doublevalores para cada miembro del paquete en vivo. Estos representan el movimiento que le gustaría que tomaran en este turno. Por ejemplo, si tu criatura se encuentra actualmente {100.0, 100.0}y les das un comando {-1.0, 1.0}, se moverán a {99.0, 101.0}. Todos los números estarán en una sola línea, separados por tabulación.

Por ejemplo, si tuviera 3 miembros de la manada vivos, esta sería una respuesta válida:

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

Esto se movería sus criaturas por {1.0,-1.0}, {2.0,-2.0}y {3.0,-3.0}. El pedido es el mismo que el recibido en la entrada. ¡No olvides el final \0!

Si da una entrada no válida, seguirán malos resultados. Si un número único no se puede analizar a double, se convertirá en cero. Si la cadena en su conjunto no se puede analizar, no se darán nuevas instrucciones, y todo su paquete utilizará las instrucciones del turno anterior.

Todas las direcciones se sujetarán a una distancia máxima de 6.1 unidades. Puede moverse más lento que esto si lo desea. Por ejemplo, {1, 0}te moverá una unidad. {6,8}(distancia 10) solo te moverá 6.1 unidades, y se reducirá a alrededor {3.66, 4.88}. La dirección permanece constante.

Importante: El programa de control lee su STDOUT y STDERR. Si lanza una excepción e imprime en STDERR, es muy poco probable que el mensaje tenga la forma de una respuesta válida. Intenta evitar hacer esto.

Programa de Control / Pruebas

La fuente del controlador se puede encontrar aquí en bitbucket.org . Deberá compilarlo antes de ejecutarlo. La clase principal es Game, y todas las clases están en el paquete predeterminado. Para ejecutar, incluya el comando de cada paquete como un argumento separado. Por ejemplo, si desea ejecutar un Java ChaserPack y un Python LazyPack.py, puede usar:

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

En el mapa, las presas aparecen en verde y los depredadores en rojo. Sin embargo, el paquete que sea el primer paquete dado como argumento tendrá un color azul. Esto tiene la intención de distinguirlos más fácilmente para propósitos de prueba. Los depredadores también parpadearán en blanco durante cinco cuadros cuando coman.

El juego continuará hasta que el último depredador muera de hambre, escribiendo en la consola a medida que ocurran eventos de hambre o extinción. Una vez que se completa el juego, se otorgará la puntuación para cada paquete. Si quiere no quiere ver los eventos de inanición / extinción, puede usar el -silentargumento. Entonces solo generará el puntaje final. Debe pasar esto como primer argumento :

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

Se incluye un paquete esqueleto de Java llamado GenericPack. Incluye las operaciones básicas de entrada / salida necesarias. Está allí para dar un claro ejemplo de cómo analizar y responder. Si desea agregar una plantilla en otro idioma, avíseme.

También se incluye un depredador basado en la plantilla, ChaserPack. No se incluirá en el torneo, y solo se incluye con fines de prueba. Se desempeña bastante mal, debido a un defecto de objetivo intencional. Si no puedes vencerlo, sigue intentándolo.

A continuación se muestra un ejemplo de ejecución del programa de control (haga clic para ver el video). La calidad del video no es excelente (lo siento), pero puedes tener una idea de cómo se mueve la presa. ( precaución: audio )

captura de pantalla

Puntuación

El ganador será determinado por torneo, ganando puntos en cada ronda.

Cada ronda continúa hasta que todos los depredadores estén muertos. Cada paquete se puntuará según el momento en que su último miembro murió de hambre. Luego se les asignarán puntos según el orden. Los puntos se acumularán durante diez rondas, y el vencedor es el paquete con los puntos totales más altos.

El primer lugar para cada ronda recibirá 100 puntos. Para cada lugar después de eso, la recompensa se reducirá en un 20% (redondeado hacia abajo). Esto continuará hasta que los puntos lleguen a cero (después del puesto 17). Los lugares mayores de 18 años no recibirán puntos por la ronda. Los paquetes que empaten recibirán puntos iguales. Por ejemplo:

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

El máximo de puntos posibles en el transcurso del torneo es 1000, desde el primer lugar las diez veces.

Si varios programas finalizan el torneo empatado en el primer lugar, se llevará a cabo otro torneo de diez rondas con solo las entradas del primer lugar enviadas. Esto continuará hasta que emerja un vencedor.

Intentaré organizar un torneo aproximadamente semanalmente, o cuando lleguen nuevas presentaciones.

Reglas adicionales (¡juega limpio!)

  • No puede leer ni escribir en ningún recurso externo. Como no va a invocar su programa varias veces, cualquier información de estado puede almacenarse internamente.

  • No interfiera con otros procesos / presentaciones. Esto no significa que no intentes robar a sus presas, sobrepasarlas, etc. Significa que no interfieras con la ejecución del proceso. Esto queda a mi discreción.

  • Los concursantes están limitados a un máximo de tres entradas. Si envía más, solo calificaré los tres primeros. Si desea revocar uno, bórrelo.

  • Las entradas pueden no existir únicamente para apuntalar otras entradas. Cada uno debe jugar para ganar por su propio mérito.

  • Su programa puede generar un máximo de un proceso secundario a la vez ( descendientes totales , no directos). De cualquier manera, asegúrese de no pasar el tiempo de espera. No puede invocar la Gameclase en sí de ninguna manera.

Resultados - 29 de abril de 2014

Aquí están los resultados del último torneo de diez rondas:

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

Los paquetes enviados antes de las 09:00 EDT del 29/04/2014 se incluyen en esta ejecución.

También puede ver los detalles de cada ronda . Por alguna razón, decidí numerar las rondas hacia atrás, por lo que comienza con la "ronda 10".

Actualizaciones

23/04/2014: FGreg informó un error relacionado con los tiempos de espera (¡gracias!). Se ha implementado una solución, por lo que los evaluadores querrán actualizar su código de programa de control.


28
¡Me gustan estas preguntas sobre el rey de la colina!
Cruncher

2
@Manu Escribí los bots de ejemplo en Windows 7, y probé en win y linux. ¿Qué problemas tienes con ellos?
Geobits

2
Estas preguntas del rey de la colina son bastante impresionantes, y esta definitivamente es interesante. ¡Tengo dos paquetes diferentes en proceso ahora!
mackthehobbit

2
@githubphagocyte Realmente no quiero matar a un paquete en el primer tiempo de espera, simplemente porque he visto que incluso los programas simples caducan una vez cada 40k + turnos o similar. Realicé el cambio de nombre en el controlador. Los turnos ahora se conocen como turnos en todo el programa, a menos que me haya perdido uno en alguna parte.
Geobits

2
@Geobits eh, eso está bien para mí. Sabes, esto se parece mucho a un proyecto de investigación que está haciendo uno de mis profesores de física, en el que podría estar ayudando durante el verano. Explicaré un poco sobre eso más tarde si puedo.
krs013

Respuestas:


10

Clarividente

Código actualizado para enfrentar AbleDogs

Woo hoo! ¡Finalmente vence a los Netcats! Expandí el código existente (¡créditos a Geobits!) Con algunas pequeñas modificaciones para crear este futuro paquete de predicción. ¡Nada supera a los depredadores que saben dónde se moverá la presa!

De las dos pruebas que hice, mi manada siempre ganó contra Netcats. Pero esto no funcionará tan bien si no hay otros paquetes, ya que la predicción aún falla si hay muchas otras presas en las cercanías.

Probablemente pueda incluir el truco de CivilizedBeasts para reducir sustancialmente el número de presas durante los primeros miles de turnos.

Hecho en 5.21 minutos
Clarividente (1): Turno 9270: Puntuación 100
EcoCamel.pl (3): Turno 8118: Puntuación 80
Netcats (0): Turno 6111: Puntuación 64
RubyVultures.rb (5): Turn 4249: Puntuación 51
RubySpiders.rb (4): Turno 3495: Puntuación 40
Bestias Civilizadas (2): Turno 3176: Puntuación 32
ChaserPack (6): Turno 2492: Puntuación 25

Por el nombre de mi paquete, debes saber qué estrategia uso = D

Editar :

  • Sistema de administración de paquetes actualizado para no perseguir a la misma presa (¡y también tratar de encontrar la mejor coincidencia!)
  • Mejore el proceso de deambulación cuando el número de presas es pequeño (¡esto es crucial para ganar!).
  • Mejore los casos especiales cuando la versión anterior se atascó en la esquina.
  • Se corrigió un error en el algoritmo de detección de depredadores (¡ahora es bastante preciso!)
  • flock[ALIGN]Factor de presa incluido
  • Mantenga una presa como mascota si la comida es escasa
  • Crea una madriguera donde la manada reunirá a sus presas
  • Atrae al depredador cercano para que persiga a nuestra presa, que no ganará

Conté cuántas presas come cada paquete, y aquí está el resultado:

Clarividente (1) consumió 916 presas en 9270 turnos (0.099 presas / turno)
EcoCamel.pl (3) consumió 73 presas en 8118 vueltas (0.009 presas / vuelta)
Netcats (0) consumió 563 presas en 6111 turnos (0.092 presas / turno)
RubyVultures.rb (5) consumió 77 presas en 4249 turnos (0,018 presas / turno)
RubySpiders.rb (4) consumió 293 presas en 3495 turnos (0.084 presas / turno)
Bestias civilizadas (2) consumieron 10 presas en 3176 turnos (0,003 presas / turno)
ChaserPack (6) consumió 43 presas en 2492 turnos (0,017 presas / turno)

Mi manada es muy agresiva, y creo que la mayoría de los 916 recuentos se obtienen robando presas de Netcats, al igual que RubySpiders.

CivilizedBeasts desafortunadamente está perdiendo debido al camello central de EcoCamel.

Y EcoCamel (con 500 de hambre crítica) es bastante eficiente, come lo suficiente para sobrevivir hasta el final.

También con este clarividente actualizado, el juego apenas alcanza los 10,000 turnos.

El código:

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
Se ve muy bien, el tuyo realmente es mejor que los netcats en mi juego. Pero odio no poder ejecutar a los otros depredadores ya que mis bestias hacen un trabajo realmente malo en tus estadísticas (mientras que evilcamel es demasiado bueno). Tal vez tenga que intentar instalar un compilador perl más o menos.
Herjan

Sí, creo que su método no funciona si hay un depredador en el medio, como se explica en su respuesta. He intentado implementar otra versión que se comporta de manera similar a la tuya. Puede cambiar la formación dependiendo del número de depredadores disponibles, por lo que es bastante divertido de ver, aunque no mucho mejor que el tuyo.
justhalf

Sí, mi estrategia se puede actualizar de muchas maneras, como otras formaciones con otra cantidad de miembros porque mis bestias están condenados con <4 depredadores. O lugares aleatorios para reunirse (en lugar de solo el medio), por ejemplo. Pero soy demasiado vago para implementar eso (ahora). Y nunca será tan bueno como este, ya que si la presa baja, mi táctica simplemente no funciona. Ahí es cuando necesitas una bestia como la tuya (ya mencionaste comenzar con mi táctica y cuando la presa se baja para usar esta táctica). Así que supongo que ya lo has pensado.
Herjan

Estoy en otro desafío en este momento, y GeoBits parece haber perdido interés en este, por lo que lo dejaré reposar por un tiempo a menos que los resultados se actualicen. Tengo ideas para algunas otras presentaciones, así que espero que este desafío se mantenga vivo. Echaré un vistazo a tu actualización, por supuesto.

15

Netcats

Aquí hay un paquete para que ustedes comiencen. Extiende la GenericPackclase incluida con el programa de control. Se ha mejorado desde la publicación original, y ya no se mata de hambre con un rebaño escaso.

Los netcats usan una formación de red en forma de vee para atrapar a las presas en la esquina, donde pueden comerlas cuando lo deseen. La red está formada con un miembro de "cabeza" en el centro. Una vez que la cabeza come, intercambia lugares con el miembro más hambriento de la manada, ya que la cabeza es normalmente la primera en tener la oportunidad de comer.

La red comienza bastante pequeña, pero se ensancha cuando el rebaño se hace más pequeño para poder rastrear el campo de manera más eficiente.

Si no se ven presas, la formación se amplía en un patrón de búsqueda ingenuo que cubre la mayor parte de la isla.

Una vez que el paquete se reduce a dos miembros, la red simplemente no funciona. En ese punto, cada uno sigue su propio camino, comiendo con avidez lo más cercano que puede encontrar y haciendo una caminata semi aleatoria de lo contrario.

Esta versión sobrevive mucho mejor que los ingenuos Netcats vistos en el video vinculado en la pregunta.

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

Arañas de rubí

Como a veces menos es más y muchas soluciones probablemente intentarían arrinconar a la presa de todos modos ...

Pensé que mi manada podría dividirse y esperar a que otros hicieran el trabajo.

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

Advertencia: en realidad no se mantiene en funcionamiento, ni lee la entrada a medida que entra ni responde rápidamente. Aún así, como funciona bien con el controlador, espero que califique sin más ajustes.


44
+1 primera solución de parásito. Creo que este tipo de respuesta aumentará la calidad de otras respuestas al eliminar gradualmente las lagunas ...
trichoplax

@githubphagocyte Tenía un parásito más inteligente en mente, pero esto es más eficiente en términos de tiempo en vivo / líneas de código. Espero encontrar tiempo para implementarlo.
Legat

Tal vez @Synthetica está codificando mi idea en este momento. O si su idea es otra más, pronto podríamos tener más parásitos que cazadores;)
Legat

1
@githubphagocyte se nos permite hacer tres entradas, así que publicaré otro paquete una vez que esté listo. Aún así, me parece interesante que este haya sido codificado mientras tanto y podría resultar más efectivo. Aprovecha Netcats realmente bien y en realidad sobrevive a mi primer paquete de cazadores.
Legat

3
Esto puede entrar tal cual, incluso si me tomó un segundo descubrir por qué. Parece hacerlo mejor cuantos más Netcats agregue (lo cual tiene sentido). +1 de mi parte, veamos qué tipo de cazadores salen para evitar esquinas :)
Geobits

11

Bestias Civilizadas

¡Finalmente, es hora de mostrar mis bestias!

Mi raza piensa que la caza es algo primitiva, por lo que trabajan juntos en un equipo de 4 y abandonan a su quinto aliado, porque: menos depredadores = más presas para ellos mismos. Lo que básicamente hacen es lo que hacen los humanos, atrapan presas y cuidan bien su ganado;)

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

Se vuelve bastante difícil para mis senos sobrevivir con menos de 200 presas en el turno + -12,000 con solo Netcats enemigos en el juego. Estarás contento con esta raza, ya que realmente devora cantidades masivas de presas con una velocidad como ninguna otra puede jamás (no es que las matanzas rápidas y grandes otorguen la victoria, pero influyen en el tiempo (largo) que toma una ronda completa considerablemente).


3
Si por " cuidarlos bien " quiere decir " llevarlos repetidamente al medio y matarlos / comerlos ", entonces sí, lo hacen bien. +1
Geobits

Es divertido, con la versión no mutada (original) de Evil Camels, las tácticas civilizadas son totalmente ineficientes debido al 'camello central'.
user2846289

1
@VadimR Crap, gracias por actualizar tu camello: PI no puede probarlo ya que no es Java, pero sé que mi estrategia es un poco inútil con los depredadores en el medio de mi territorio: P
Herjan

55
¡Es Herjan otra vez! Además, "se vuelve bastante difícil para mis senos sobrevivir con menos de 200 presas" (énfasis agregado). No me di cuenta de que la vitalidad de tus senos depende de la cantidad de presas en una simulación por computadora ...
Justin

5

Buitres de rubí

Aquí viene un paquete de parásitos más activos . Están tratando de rodear al depredador en movimiento más cercano , para poder robar su presa . Dependen un poco de la suerte, ya que no tienen una forma inteligente de elegir a quién seguir, pero generalmente están golpeando cazadores y, a veces, arañas .

No están del todo terminados, ya que publiqué esto para impulsar el tempo :)

Espero:

  • hacer que busquen depredadores fuera del campo de visión
  • tenga en cuenta a la presa : ¡a menudo uno de ellos está entre otro paquete y la presa!
  • comience a rotarlos para evitar morir de hambre cuando todos los demás estén bien alimentados

22 de abril de 2014: Se agregó aburrimiento , lo que los hace menos pegajosos y les permite cazar presas por su cuenta y buscar depredadores.

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

Definitivamente necesitas más "cazadores" en la mezcla. Tal como están las cosas, tienden a unirse a otros parásitos (ya que esa es la mayoría en el campo). Sin embargo, me gusta verlos y puedo ver cómo serían efectivos con una mezcla diferente de competidores.
Geobits

Oh sí, en mi entorno de prueba tengo otros dos paquetes de cazadores. Sin ellos, los buitres probablemente no tengan ni idea. Especialmente que los netcats pueden trabajar rápidamente en las esquinas sin ser vistos desde el medio.
Legat

Creo que sé lo que podría preocuparles en particular. Danza de guerra de los camellos malvados. @Geobits, ¿qué hay de poner las peleas en Youtube? 10 rondas no es demasiado para permanecer observable. Por supuesto, se necesitaría HQ. No esperaba millones de espectadores, pero sería entretenido ver cómo funcionan sus paquetes y tal vez animarlos un poco :)
Legat

1
El torneo completo puede ser un poco largo (~ 8 minutos por ronda ahora) para mantener la atención, pero grabar una ronda de "espectador" podría funcionar. Lo pensaré para futuras carreras.
Geobits

@Geobits ¿La velocidad varía mucho durante una ronda de 8 minutos? Me pregunto si vale la pena grabar un cuadro por turno para que puedan reproducirse a una velocidad constante, en lugar de reducir la velocidad durante las partes computacionalmente intensivas. Para fines de YouTube, quiero decir.
trichoplax

5

Malos Camellos Eco

Editar: Mutación # 2. Oh, no, llegué tarde con mi implementación de la predicción del movimiento de presas, para ser el primero en vencer a los Netcats. OK, que así sea.

Esta mutación tiene $hunger_criticalvariable (constante). Cambiarlo a un valor superior a 1000 hace que los camellos cacen siempre, como los clarividentes. Entonces:

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

Si $hunger_criticalse establece, por ejemplo, en 500 (como se muestra a continuación), mis Camellos (después de ver los horrores de la civilización ) intentan comportarse de manera ecológica (por lo tanto, han cambiado el nombre de su raza), es decir, matan solo cuando tienen hambre. Si no tienen hambre, patrullan las áreas críticas de la isla: el centro y las esquinas, para evitar la carnicería inútil de otros cazadores. Bueno, con centro, funciona más o menos. La idea de dar vueltas en las esquinas era ahuyentar a la presa y hacer la vida más difícil para los gatos y los parásitos. Pues no funciona. La estúpida presa va a las esquinas de todos modos.

También es interesante que el flock[ALIGN]componente solo pueda ser adivinado por los depredadores, y mi implementación es diferente de la mitad. Me temo que hay algún error menor en mi estafa ejecución de código Geobits', ver / comparar la caza individual de camellos vs clarividentes.

Y el programa es bastante largo ahora, lo siento.


Editar: Mutación # 1. La isla resulta ser bastante radiactiva (eso explica la falta de vegetación y la naturaleza inexplicable de las criaturas 'presas'), así que aquí está la primera mutación de mis camellos. Cualquiera de ellos puede convertirse en cazador en solitario, si tiene hambre o si no hay un rincón libre para todos. Hunter intenta perseguir activamente a sus presas cercanas. Si no hay ninguno, patrulla en un amplio círculo alrededor del centro de la isla, luego persigue a la criatura más cercana cuando la encuentra. Desafortunadamente, la dirección de la presa se vuelve impredecible cuando está cerca de su enjambre (vale la pena investigar ...), por lo que la persecución en solitario no es muy eficiente. Pero si tiene éxito, el Camel va a digerir al rincón libre más cercano (si lo hay). Cuando el nivel de hambre está por debajo de cierto nivel, cualquier Camello abandona su esquina (probablemente maldiciendo Netcats ('¿dónde está la comida?') )) y va en itinerancia libre por sí solo. Y así.


La misma broma contada dos veces no es graciosa, pero (1) tuve que comenzar en alguna parte y soy nuevo en estas cosas, (2) Honestamente, pensé en las tácticas de esquina (¿y quién no?), Mirando Netcats, antes de Ruby Las arañas aparecieron en la isla.

Entonces, ¿has oído hablar de los camellos carnívoros? Los animales pobres se despertaron un día en esta isla olvidada de Dios para no encontrar pasto ni árboles en absoluto, sino muchas pequeñas cosas verdes, aunque comestibles, de movimiento rápido (bastante molesto). Al no tener hábitos de caza (pero pronto mutarán, espero), mis Camellos desarrollaron un esquema muy malvado para sobrevivir: se separaron y se fueron a cada uno de los 4 rincones, y el quinto se fue al centro (para morir allí primero, como resulta) En sus destinos esperan pacientemente, realizando una especie de baile de guerra de camellos, o tal vez simplemente tratan de no pisar a otros animales que ya están allí, arañas y todo ...

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

55
Este tiene que ser el script perl más legible que he visto en este sitio hasta la fecha.
Geobits

Necesitas ir exactamente a la esquina para espantarlos de manera efectiva, de lo contrario solo estarás participando en la carnicería de Netcats, jaja
justo el

@justhalf, es como dije: el plan no funcionó. Los parásitos sentados en las esquinas tampoco ahuyentaron a la presa. Hm-m, tal vez 2 o más bestias patrullando una esquina ayudarán.
user2846289

¡Tus camellos son bastante buenos en realidad! Afortunadamente (para mí) he mejorado mis Clarividentes, así que la mayoría de las veces (no siempre), mi manada gana contra la tuya durante la última batalla. ¡Interesante!
justhalf

1
Si estás a menos de 8 (20-2 * 6) unidades de la presa, podemos ver cualquier movimiento de todas las demás presas que están dentro de las 30 unidades de nuestra presa en el turno actual. Y la vecpropiedad es básicamente solo el desplazamiento del turno anterior al turno actual. Y como dije, hacemos un emparejamiento del turno anterior para descubrir qué presa va en esa dirección, no podemos confiar en el orden de la presa. Esto es posible porque las presas generalmente (en el escenario típico) mantienen una distancia suficiente entre sí (> 12 unidades), por lo que la mayoría de las veces podemos hacer coincidir las presas en el turno anterior con el turno actual.
justhalf

4

AbleDogs - PHP

Estos bonitos perros han aprendido a morder las pantorrillas de una presa para empujarla a lo largo de las paredes. También les encanta deambular por el pasto en busca de nuevas presas. Por último, se les ha enseñado a abstenerse de comer a menos que realmente necesiten las calorías.

Pon el código en un AbleDogsarchivo y ejecútalo conphp 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();
}
?>

Consideraciones Generales

  • Es el final del juego lo que cuenta. Puede tener el algoritmo de caza más inteligente que haya existido, si no detecta y captura las últimas presas individuales más rápido que la oposición, pierde.

  • Si tus depredadores no pueden atrapar una presa solo (o al menos en parejas), estás tostado tan pronto como la densidad de la presa baje lo suficiente como para confiar en la suerte ciega o bloquear las presas en las esquinas.

  • Un predictor de movimiento de presas es básicamente obligatorio. No puedo imaginar superar un programa basado en predictores sin tener tu propio predictor.

Persecución de la cola

La forma más ineficiente de atrapar una presa es perseguirla. Suponiendo que un solo depredador persiga a una sola presa y no tenga influencias externas (paredes, otras presas, etc.), una persecución de cola podría durar para siempre. Tan pronto como ingrese al radio de visión de la presa de 30 unidades, la presa huye a la velocidad 6 para su 6.1, por lo que gana .1 distancia por turno: en línea recta, necesitará aproximadamente 300 turnos para obtenerlo.

Teniendo en cuenta el tamaño de la arena, una presa viajará como máximo la diagonal de un cuadrado de 500 unidades antes de golpear una pared o una esquina, lo que tomará como máximo 117 turnos.

Obviamente, la estrategia ganadora es encontrar una manera de reducir la velocidad de las presas, es decir, tener otro depredador o una pared / esquina frente a él.

Vaticinador

Con una velocidad de presa de 6, una presa puede moverse a un área de 36 * pi unidades al cuadrado. Con un radio de captura de 1, una suposición ciega de dónde será la próxima presa tiene una probabilidad de 1/36 * pi (aproximadamente 1%) de tener éxito. ¡Claramente hay que hacer algo para mejorar eso!

Mirando el código del motor de simulación, puede ver que las entradas son:

  • Presas visibles y posiciones de depredador
  • velocidades de presa anteriores

Si bien todas las posiciones están disponibles, las velocidades de presa anteriores no lo están. La única forma de calcular estas velocidades sería rastrear cada presa de un turno al siguiente, lo que es casi imposible de hacer (a menos que implemente un algoritmo de seguimiento de movimiento muy inteligente). Por lo tanto, un predictor puede reproducir fácilmente todos los términos del cálculo, excepto la contribución de velocidad que debe adivinarse.

En el caso de una sola presa, la velocidad se puede rastrear sin demasiados problemas, lo que permite construir un predictor "perfecto" para atrapar a una presa aislada del rebaño. Que es básicamente todo lo que necesitas para el final del juego, cuando las presas son muy pocas para interactuar entre ellas. Cuando abundan las presas y el efecto de bandada es lo suficientemente fuerte como para engañar al predictor, la gran densidad de las presas compensará los errores (si no atrapa al que estaba apuntando, es probable que obtenga uno de sus amigos más cercanos )

Presas incómodas

Con el conocimiento exacto del cálculo de la velocidad de la presa, es posible "dirigir" una presa dada hacia una dirección deseada, ajustando la posición de un depredador.

Esto permite fijar una presa contra una pared o dirigirla hacia otro miembro del paquete. Intenté algunas estrategias refinadas, como pellizcar una presa entre dos miembros del paquete. Desafortunadamente, esto resultó menos eficiente que la rutina actual de "pin and scan", ya que mantener a dos depredadores ocupados para perseguir a una sola presa deja a la oposición con demasiados depredadores libres para explorar el pasto.

Robar presas

Una característica del comportamiento de las presas es que la influencia de un depredador aumenta proporcionalmente a su distancia de la presa (siempre que permanezca dentro del radio de visión de la presa). Cuanto más cerca esté un depredador de una presa, menos se alejará de ella.

Significa que cuando dos depredadores compiten para atrapar una presa, el más cercano está obligado a obtenerla primero. Incluso un contendiente súper inteligente que lograría posicionarse justo en frente del eje cazador / presa básicamente asustaría a la presa en las fauces del contendiente.

Para lograr robar una presa, se necesitan al menos un par de depredadores. Uno irá a matar, y los otros permanecerán justo dentro del radio de visión de la presa, lo más lejos posible de la presa para maximizar la influencia y empujar a la presa hacia el cazador.

Además, cada cambio de dirección permitirá que la competencia corte la esquina hacia la presa, y mantenerse detrás del contendiente solo es posible si el "goader" estaba lo suficientemente cerca de la presa al comienzo de la acción.

Por lo tanto, el robo de presas solo tiene una oportunidad de tener éxito si las posiciones iniciales de los "ladrones" son favorables y puedes ahorrar al menos un segundo depredador. En mi experiencia, esto no vale la complejidad.

Cambios sugeridos

Para permitir estrategias más complejas, mover el depredador por encima de la velocidad máxima de la presa podría tener un costo en puntos de hambre, proporcional al exceso de velocidad. Digamos, por ejemplo, subir a la velocidad 6 es gratis y cada punto de velocidad superior a 6 cuesta 100 puntos de hambre (ir a 6.3 cuesta 30 puntos de hambre por turno, quemar 1000 puntos de hambre permitiría alcanzar la velocidad 16 durante un turno, y morir si no lo hace ¡No atrapes a una presa haciéndolo!).

En lugar de matar a un depredador aleatorio cuando más de uno está lo suficientemente cerca como para comer una presa, sugiero dividir la ganancia (por ejemplo, 3 depredadores obtendrían 333.33 puntos de hambre cada uno). Esto permitiría estrategias de final de juego más interesantes (sombrear a un depredador enemigo sería útil si crees que tienes más puntos de hambre, por ejemplo).

El color especial para el primer paquete es bastante difícil de ver. Sugiero cian o naranja en lugar de azul.


¡Finalmente otro competidor! Implementé a propósito todos los puntos que mencionaste, excepto el robo de presas, por lo que estaba satisfecho con el efecto secundario actual. ¿De tus comentarios parece que estás ganando contra mi Clarividente? Eso es interesante, lo comprobaré mañana. = D. Además, puede probar mi actualización de la GUI para ver mejores gráficos (al menos según mí).
justhalf

No estoy ganando todo el tiempo. Depende de quién esté más cerca de las últimas presas cuando desovan. Sin embargo, mis perros capaces podrían tener una ventaja estadística. También ajusté el motor de simulación para mostrar a cada equipo en un color diferente, pero al final resultó demasiado colorido para mi gusto, así que me decidí por el naranja en lugar del azul para el primer equipo, y todos los demás rojos.

Wow, acabo de presentar tu presentación. Eso es una locura, no sabía que puedes hacer que la oración se detenga así. Y me sentí un poco engañado porque cuando la presa está precisamente en el borde, mis depredadores no podrán detectarlos, eso definitivamente es una gran desventaja para mí.
justhalf

Eso es aritmética vectorial para ti :). Eso es lo que intenté explicar en mi publicación: para una sola presa, conoces todos los parámetros de movimiento (incluida la velocidad anterior), por lo que puedes calcular una posición de depredador que producirá la velocidad de presa adecuada para que se quede quieto cerca de una pared.

1
Bueno, la solución de la ecuación está codificada (no se necesitan iteraciones). Básicamente, calcula el vector que la presa debe usar para la siguiente velocidad de giro si su depredador no estaba allí. Dada la dirección en la que desea que vaya la presa, infiere la diferencia vectorial necesaria para que la velocidad de la presa apunte en esa dirección. Esta diferencia vectorial te da la posición del depredador en relación con la presa. Esto te deja con un grado de libertad que te permite (dentro de ciertos límites) seleccionar una distancia de la presa.

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

Necesitará la plataforma haskell para ejecutar esto. Luego usas el runhaskellcomando para ejecutarlo. Mi manada espera a que la presa venga a ellos.


+1 para una solución de esqueleto en un nuevo idioma. Presumiblemente, ¿estás contento de que las personas desarrollen nuevas estrategias además de esto?
trichoplax

Seguro Por qué no. (Aunque no hace nada más que producir resultados constantes y salir en "dead \ 0", por lo que no estoy seguro de si sería muy útil).
PyRulez

Sin embargo, todavía aconsejaría a cualquiera que ejecute esto que use la -silentopción ...
Geobits 18/04/14

3

No es una entrada, siempre estoy interesado en agregar color personalizado para cada entrada participante en ;)

Y también el proceso de alimentación no se visualiza cambiando el color, sino cambiando el tamaño, para que podamos ver múltiples eventos de alimentación en poco tiempo.

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;
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.