3D: Duelo discreto de peleas de perros (ahora abierto a envíos que no sean Java)

31

ACTUALIZACIÓN: se agregó isSuicidal () a la clase de avión, ¡esto le permite verificar si un avión está en curso de colisión irreversible con las paredes!

ACTUALIZACIÓN: updateCoolDown () separado de simulateMove ()

ACTUALIZACIÓN: contenedor de entrada no Java, escrito por Sparr , disponible para prueba, ver comentarios

ACTUALIZACIÓN Zove Games ha escrito un visualizador 3D impresionante para este KOTH, aquí hay un video de mierda de YouTube de PredictAndAVoid luchando PredictAndAVoid.

La función simulateMove () de la clase Plane se modificó ligeramente para que ya no actualice el enfriamiento, use la nueva función updateCoolDown () para eso, después de disparar. El nuevo isSuicidal () vuelve verdadero si un avión está destinado a terminar muerto, úselo para podar los movimientos enemigos y evitar golpear paredes. Para obtener el código actualizado, simplemente reemplace las clases Controller y Plane por las del repositorio github.

Descripción

El objetivo de este desafío es codificar dos aviones de peleas de perros que se enfrentarán contra dos aviones por otro competidor. Cada turno te mueves un espacio y tienes la oportunidad de disparar. Eso es todo, es tan simple como eso.

Bueno, casi...

Arena y posibles movimientos

La arena es un 14x14x14 amurallado en el espacio. los planos del concursante 1 comienzan en las ubicaciones (0,5,0) y (0,8,0) y los del concursante 2 en (13,5,13) y (13,8,13). Todos los aviones comienzan volando horizontalmente lejos de las paredes verticales más cercanas.

Ahora, ya que vuela aviones y no helicópteros, no puede simplemente cambiar de dirección a voluntad o incluso dejar de moverse, por lo que cada avión tiene una dirección y moverá una ficha en esa dirección cada turno.

Las direcciones posibles son: Norte (N), Sur (S), Este (E), Oeste (W), Arriba (U) y Abajo (D) y cualquier combinación lógica de esos seis. Donde el eje NS corresponde al eje x, WE a y y DU a z. NW, SU y NED vienen a la mente como posibles ejemplos de instrucciones; UD es un gran ejemplo de una combinación no válida.

Por supuesto, puede cambiar la dirección de sus aviones, pero hay una limitación, solo puede cambiar su dirección en un máximo de 45 grados. Para visualizar esto, toma tu cubo de rubik (sé que tienes uno) e imagina que los 26 cubitos exteriores son las direcciones posibles (las direcciones de una letra son caras, las direcciones de dos letras son bordes y las direcciones de tres letras son esquinas). Si te diriges en una dirección representada por un pequeño cubo, puedes cambiar la dirección de cada cubo que toca el tuyo (tocar diagonalmente cuenta, pero solo tocando visiblemente, eso no toca el cubo).

Después de que todos los planos hayan indicado en qué dirección les gustaría cambiar, lo hacen y mueven una ficha simultáneamente.

También puede optar por moverse en una dirección válida, pero siga volando en la dirección en la que iba, en lugar de cambiar su dirección en la dirección hacia la que se movió. Esto es análogo a la diferencia entre un automóvil que dobla una esquina y un automóvil que cambia de carril.

Disparando y muriendo

Puede disparar como máximo una vez por asalto y esto debe decidirse al mismo tiempo que decide en qué dirección volar y si desea mantener su avión (y, por extensión, su arma) apuntando en la misma dirección o no. La bala recibe un disparo justo después de que su avión se mueve. Hay un enfriamiento de un turno después de disparar, en el tercer turno, estás listo para volver. Solo puedes disparar en la dirección en la que estás volando. Una bala es instantánea y vuela en línea recta hasta que golpea una pared o un avión.

Teniendo en cuenta la forma en que puede cambiar de dirección, así como 'cambiar de carril', esto significa que puede amenazar una columna de hasta 3x3 líneas frente a usted, además de algunas líneas diagonales individuales.

Si golpea un avión, este avión muere y desaparece rápidamente del tablero (porque explota por completo o algo así). Las balas solo pueden golpear un avión como máximo. Las balas se disparan simultáneamente, por lo que dos aviones pueden dispararse entre sí. Sin embargo, dos balas no pueden chocar en el aire (triste, lo sé).

Sin embargo, dos planos pueden chocar (si terminan en el mismo cubo y NO si se cruzan entre sí sin terminar en el mismo plano), y esto hace que ambos planos mueran (y exploten por completo). También puede volar hacia la pared, lo que hará que el avión en cuestión muera y sea arrinconado para pensar en sus acciones. Las colisiones se manejan antes de disparar.

Comunicación con el controlador.

Aceptaré entradas en Java, así como en otros idiomas. Si su entrada está en Java, recibirá la entrada a través de STDIN y la salida a través de STDOUT.

Si su entrada está en Java, su entrada debe extender la siguiente clase:

package Planes;

//This is the base class players extend.
//It contains the arena size and 4 plane objects representing the planes in the arena.
public abstract class PlaneControl {

    // note that these planes are just for your information, modifying these doesn't affect the actual plane instances, 
    // which are kept by the controller
    protected Plane[] myPlanes = new Plane[2];
    protected Plane[] enemyPlanes = new Plane[2];
    protected int arenaSize;
    protected int roundsLeft;

    ...

    // Notifies you that a new fight is starting
    // FightsFought tells you how many fights will be fought.
    // the scores tell you how many fights each player has won.
    public void newFight(int fightsFought, int myScore, int enemyScore) {}

    // notifies you that you'll be fighting anew opponent.
    // Fights is the amount of fights that will be fought against this opponent
    public void newOpponent(int fights) {}

    // This will be called once every round, you must return an array of two moves.
    // The move at index 0 will be applied to your plane at index 0,
    // The move at index1 will be applied to your plane at index1.
    // Any further move will be ignored.
    // A missing or invalid move will be treated as flying forward without shooting.
    public abstract Move[] act();
}

La instancia creada de esa clase persistirá durante toda la competencia, por lo que puede almacenar cualquier dato que desee almacenar en variables. Lea los comentarios en el código para más información.

También te he proporcionado las siguientes clases de ayuda:

package Planes;

//Objects of this class contain all relevant information about a plane
//as well as some helper functions.
public class Plane {
    private Point3D position;
    private Direction direction;
    private int arenaSize;
    private boolean alive = true;
    private int coolDown = 0;

    public Plane(int arenaSize, Direction direction, int x, int y, int z) {}

    public Plane(int arenaSize, Direction direction, Point3D position) {}    

    // Returns the x coordinate of the plane
    public int getX() {}

    // Returns the y coordinate of the plane
    public int getY() {}

    // Returns the z coordinate of the plane
    public int getZ() {}

    // Returns the position as a Point3D.
    public Point3D getPosition() {}

    // Returns the distance between the plane and the specified wall,
    // 0 means right next to it, 19 means at the opposite side.
    // Returns -1 for invalid input.
    public int getDistanceFromWall(char wall) {}

    // Returns the direction of the plane.
    public Direction getDirection() {}

    // Returns all possible turning directions for the plane.
    public Direction[] getPossibleDirections() {}

    // Returns the cool down before the plane will be able to shoot, 
    // 0 means it is ready to shoot this turn.
    public int getCoolDown() {}

    public void setCoolDown(int coolDown) {}

    // Returns true if the plane is ready to shoot
    public boolean canShoot() {}

    // Returns all positions this plane can shoot at (without first making a move).
    public Point3D[] getShootRange() {}

    // Returns all positions this plane can move to within one turn.
    public Point3D[] getRange() {}

    // Returns a plane that represents this plane after making a certain move,
    // not taking into account other planes.
    // Doesn't update cool down, see updateCoolDown() for that.
    public Plane simulateMove(Move move) {}

    // modifies this plane's cool down
    public void updateCoolDown(boolean shot) {
        coolDown = (shot && canShoot())?Controller.COOLDOWN:Math.max(0, coolDown - 1);
    }


    // Returns true if the plane is alive.
    public boolean isAlive() {}

    // Sets alive to the specified value.
    public void setAlive(boolean alive) {}

    // returns a copy of itself.
    public Plane copy() {}

    // Returns a string representing its status.
    public String getAsString() {}

    // Returns a string suitable for passing to a wrapped plane process
    public String getDataString() {}

    // Returns true if a plane is on an irreversable colision course with the wall.
    // Use this along with simulateMove() to avoid hitting walls or prune possible emeny moves.
    public boolean isSuicidal() {}
}


// A helper class for working with directions. 
public class Direction {
    // The three main directions, -1 means the first letter is in the direction, 1 means the second is, 0 means neither is.
    private int NS, WE, DU;

    // Creates a direction from 3 integers.
    public Direction(int NSDir, int WEDir, int DUDir) {}

    // Creates a direction from a directionstring.
    public Direction(String direction) {}

    // Returns this direction as a String.
    public String getAsString() {}

    // Returns The direction projected onto the NS-axis.
    // -1 means heading north.
    public int getNSDir() {}

    // Returns The direction projected onto the WE-axis.
    // -1 means heading west.
    public int getWEDir() {}

    // Returns The direction projected onto the DU-axis.
    // -1 means heading down.
    public int getDUDir() {}

    // Returns a Point3D representing the direction.
    public Point3D getAsPoint3D() {}

    // Returns an array of chars representing the main directions.
    public char[] getMainDirections() {}

    // Returns all possible turning directions.
    public Direction[] getPossibleDirections() {}

    // Returns true if a direction is a valid direction to change to
    public boolean isValidDirection(Direction direction) {}
}

public class Point3D {
    public int x, y, z;

    public Point3D(int x, int y, int z) {}

    // Returns the sum of this Point3D and the one specified in the argument.
    public Point3D add(Point3D point3D) {}

    // Returns the product of this Point3D and a factor.
    public Point3D multiply(int factor) {}

    // Returns true if both Point3D are the same.
    public boolean equals(Point3D point3D) {}

    // Returns true if Point3D is within a 0-based arena of a specified size.
    public boolean isInArena(int size) {}
}


public class Move {
    public Direction direction;
    public boolean changeDirection;
    public boolean shoot;

    public Move(Direction direction, boolean changeDirection, boolean shoot) {}
}

Puede crear instancias de estas clases y utilizar cualquiera de sus funciones tanto como desee. Puede encontrar el código completo de estas clases auxiliares aquí .

Aquí hay un ejemplo de cómo se vería su entrada (aunque espero que lo haga mejor que yo, la mayoría de los partidos con estos aviones terminan volando contra una pared, a pesar de sus mejores esfuerzos para evitar la pared):

package Planes;

public class DumbPlanes extends PlaneControl {

    public DumbPlanes(int arenaSize, int rounds) {
        super(arenaSize, rounds);
    }

    @Override
    public Move[] act() {
        Move[] moves = new Move[2];
        for (int i=0; i<2; i++) {
            if (!myPlanes[i].isAlive()) {
                moves[i] = new Move(new Direction("N"), false, false); // If we're dead we just return something, it doesn't matter anyway.
                continue;
            }
            Direction[] possibleDirections = myPlanes[i].getPossibleDirections(); // Let's see where we can go.

            for (int j=0; j<possibleDirections.length*3; j++) {

                int random = (int) Math.floor((Math.random()*possibleDirections.length)); // We don't want to be predictable, so we pick a random direction out of the possible ones.

                if (myPlanes[i].getPosition().add(possibleDirections[random].getAsPoint3D()).isInArena(arenaSize)) { // We'll try not to fly directly into a wall.
                    moves[i] = new Move(possibleDirections[random], Math.random()>0.5, myPlanes[i].canShoot() && Math.random()>0.2);
                    continue; // I'm happy with this move for this plane.
                }

                // Uh oh.
                random = (int) Math.floor((Math.random()*possibleDirections.length));
                moves[i] = new Move(possibleDirections[random], Math.random()>0.5, myPlanes[i].canShoot() && Math.random()>0.2);
            }
        }

        return moves;
    }

    @Override
    public void newFight(int fightsFought, int myScore, int enemyScore) {
        // Using information is for schmucks.
    }

    @Override
    public void newOpponent(int fights) {
        // What did I just say about information?
    }
}

DumbPlanes se unirá al torneo junto con las otras entradas, por lo que si terminas en último lugar, es tu culpa por no al menos hacerlo mejor que DumbPlanes.

Restricciones

Se aplican las restricciones mencionadas en la wiki KOTH :

  • Cualquier intento de jugar con el controlador, el tiempo de ejecución u otras presentaciones será descalificado. Todos los envíos solo deberían funcionar con las entradas y el almacenamiento que se les proporciona.
  • Los bots no deben escribirse para vencer o admitir otros bots específicos. (Esto puede ser deseable en casos raros, pero si este no es un concepto central del desafío, es mejor descartarlo).
  • Me reservo el derecho de descalificar las presentaciones que usan demasiado tiempo o memoria para ejecutar pruebas con una cantidad razonable de recursos.
  • Un bot no debe implementar exactamente la misma estrategia que una existente, intencional o accidentalmente.

Probar su envío

Descargue el código del controlador desde aquí . Agregue su presentación como Something.java. Modifique Controller.java para incluir entradas para su avión en las entradas [] y nombres []. Compile todo como un proyecto Eclipse o con javac -d . *.java, luego ejecute el controlador con java Planes/Controller. Un registro del concurso estará en test.txt, con un marcador al final. También puede llamar matchUp()directamente con dos entradas como argumentos para probar dos planos uno contra el otro.

Ganando la pelea

El ganador de la pelea es el que tiene el último avión volando, si después de 100 turnos, todavía queda más de 1 equipo, el equipo con la mayoría de los aviones restantes gana. Si esto es igual, es un empate.

Puntuación y la competencia

El próximo torneo oficial se ejecutará cuando se agote la recompensa actual.

Cada entrada peleará con cualquier otra entrada (al menos) 100 veces, el ganador de cada enfrentamiento es el que tenga más victorias de las 100 y se le otorgarán 2 puntos. En caso de empate, ambas entradas reciben 1 punto.

El ganador de la competencia es el que tiene más puntos. En caso de empate, el ganador es el que ganó en un enfrentamiento entre las entradas que empataron.

Dependiendo de la cantidad de entradas, la cantidad de peleas entre las entradas podría aumentar significativamente, también podría seleccionar las 2-4 mejores entradas después del primer torneo y establecer un torneo de élites entre esas entradas con más peleas (y posiblemente más rondas por lucha)

(preliminar) Cuadro de indicadores

Tenemos una nueva entrada que ocupa firmemente el segundo lugar en otro emocionante torneo , parece que Crossfire es increíblemente difícil de disparar para todos, excepto para PredictAndAvoid. Tenga en cuenta que este torneo se realizó con solo 10 peleas entre cada conjunto de aviones y, por lo tanto, no es una representación completamente precisa de cómo están las cosas.

----------------------------
¦ 1. PredictAndAvoid:   14 ¦
¦ 2. Crossfire:         11 ¦
¦ 3. Weeeeeeeeeeee:      9 ¦
¦ 4. Whirligig:          8 ¦
¦ 4. MoveAndShootPlane:  8 ¦
¦ 6. StarFox:            4 ¦
¦ 6. EmoFockeWulf:       2 ¦
¦ 7. DumbPlanes:         0 ¦
----------------------------

Aquí hay un ejemplo de salida del reiniciador no Java:

NEW CONTEST 14 20 indica que está comenzando un nuevo concurso, en una arena de 14x14x14, e implicará 20 turnos por pelea.

NEW OPPONENT 10 indica que te enfrentas a un nuevo oponente y que lucharás contra este oponente 10 veces

NEW FIGHT 5 3 2 indica que está comenzando una nueva pelea contra el oponente actual, que has luchado contra este oponente 5 veces hasta ahora, ganando 3 y perdiendo 2 peleas

ROUNDS LEFT 19 indica que quedan 19 rondas en la pelea actual

NEW TURN indica que estás a punto de recibir datos de los cuatro aviones para esta ronda de la pelea

alive 13 8 13 N 0
alive 13 5 13 N 0
dead 0 0 0 N 0
alive 0 8 0 S 0

Estas cuatro líneas indican que sus dos aviones están vivos, en las coordenadas [13,8,13] y [13,5,13] respectivamente, ambos orientados hacia el norte, ambos con cero enfriamiento. El primer avión enemigo está muerto, y el segundo está vivo, a [0,8,0] y mirando hacia el sur con cero enfriamiento.

En este punto, su programa debería generar dos líneas similares a las siguientes:

NW 0 1
SU 1 0

Esto indica que su primer avión viajará hacia el noroeste, sin desviarse de su rumbo actual, y disparando si puede. Su segundo avión viajará hacia SouthUp, girando hacia SouthUp, no disparando.

Ahora te ROUNDS LEFT 18siguenNEW TURN etc. Esto continúa hasta que alguien gana o la ronda se agota, en ese momento obtienes otra NEW FIGHTlínea con el conteo y puntajes de pelea actualizados, posiblemente precedidos por a NEW OPPONENT.

overactor
fuente
Si alguien necesita ayuda con este desafío, puede ingresar al chat que creé para este desafío.
overactor
¿Los aviones comienzan rumbo este / oeste o norte / sur? ¿o algo mas?
seudónimo117
2
@overactor hay un error en el código de enfriamiento. Está utilizando simulateMove en la sección "Calcular las nuevas posiciones", que disminuye el tiempo de reutilización además de encontrar nuevas posiciones. Esto significa que un avión puede disparar cada turno si ignora su propio contador de enfriamiento.
Sparr
2
Para aquellos que puedan encontrarlo útil, esta expresión regular buscará en el registro para encontrar dónde dispara su avión ^ Mover (. *?) Disparar: verdadero $ (reemplace "Mover" con su nombre y asegúrese de que no capture nuevos líneas)
usuario2813274
1
Aquí hay una confirmación para mi envoltura de avión, junto con un avión de pitón tonto. Me encantaría que alguien escribiera un avión más inteligente en perl / python / lua / bash / lo que sea y me diera algún comentario sobre si / cómo funciona el contenedor para usted. github.com/sparr/Dogfight-KOTH/commit/… si la gente puede / usará esto, podemos incluirlo en el repositorio de @ overactor y permitir envíos de lenguaje arbitrario.
Sparr

Respuestas:

5

Fuego cruzado

Mi idea inicial era disparar a un avión enemigo con mis dos aviones al mismo tiempo, pero no pude resolverlo ... Así que aquí hay un avión que intenta mantenerse alejado de las paredes y fuera del campo de tiro del enemigo. Los aviones nunca deben chocar ni disparar aviones amigos.

Editar: el método possibleHitssiempre devolvió 0, después de arreglarlo y agregar varias pequeñas mejoras, funciona mejor que antes.

package Planes;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Crossfire extends PlaneControl {
    final List<Point3D> dangerList = new ArrayList<>(); //danger per point
    final List<Plane> targets = new ArrayList<>(); //targets being shot
    Plane[] futurePlanes = null; //future friendly planes

    public Crossfire(int arenaSize, int rounds) {
        super(arenaSize, rounds);
    }

    @Override
    public Move[] act() {
        dangerList.clear();     //initialize
        targets.clear();
        final int PLANE_COUNT = myPlanes.length;
        Move[] moves = new Move[PLANE_COUNT];
        futurePlanes = new Plane[PLANE_COUNT];

        // calculate danger per field/enemy
        for (int i = 0; i < PLANE_COUNT; i++) {
            updateDanger(enemyPlanes[i]);
        }   

        // get best moves for each plane
        for (int i = 0; i < PLANE_COUNT; i++) {         
            moves[i] = getBestMove(myPlanes[i]);
            futurePlanes[i] = myPlanes[i].simulateMove(moves[i]);
            updateTargets(futurePlanes[i]);
        }

        // try to shoot if no friendly plane is hit by this bullet
        for (int i = 0; i < myPlanes.length; i++) {
            if (myPlanes[i].canShoot() && canShootSafely(futurePlanes[i]) && possibleHits(futurePlanes[i]) > 0) {
                moves[i].shoot = true;
            }
        }

        return moves;
    }

    private void updateTargets(Plane plane) {
        if (!plane.canShoot() || !canShootSafely(plane)) {
            return;
        }
        Point3D[] range = plane.getShootRange();
        for (Plane enemyPlane : enemyPlanes) {
            for (Move move : getPossibleMoves(enemyPlane)) {
                Plane simPlane = enemyPlane.simulateMove(move);
                for (Point3D dest : range) {
                    if (dest.equals(simPlane.getPosition())) {
                        targets.add(enemyPlane);
                    }
                }
            }           
        }
    }

    private void updateDanger(Plane plane) {
        if (!plane.isAlive()) {
            return;
        }
        for (Move move : getPossibleMoves(plane)) {
            Plane futurePlane = plane.simulateMove(move);
            // add position (avoid collision)
            if (!isOutside(futurePlane)) {
                dangerList.add(futurePlane.getPosition());
                // avoid getting shot
                if (plane.canShoot()) {
                    for (Point3D dest : futurePlane.getShootRange()) {
                        dangerList.add(dest);
                    }
                }
            }
        }
    }

    private Move getBestMove(Plane plane) {
        if (!plane.isAlive()) {
            return new Move(new Direction("N"), false, false);
        }

        int leastDanger = Integer.MAX_VALUE;
        Move bestMove = new Move(new Direction("N"), false, false);
        for (Move move : getPossibleMoves(plane)) {
            Plane futurePlane = plane.simulateMove(move);
            int danger = getDanger(futurePlane) - (possibleHits(futurePlane) *2);
            if (danger < leastDanger) {
                leastDanger = danger;
                bestMove = move;
            }
        }
        return bestMove;
    }

    private int getDanger(Plane plane) {
        if (!plane.isAlive() || hugsWall(plane) || collidesWithFriend(plane) || isOutside(plane)) {
            return Integer.MAX_VALUE - 1;
        }
        int danger = 0;
        Point3D pos = plane.getPosition();
        for (Point3D dangerPoint : dangerList) {
            if (pos.equals(dangerPoint)) {
                danger++;
            }
        }
        // stay away from walls
        for (char direction : plane.getDirection().getMainDirections()) {
            if (plane.getDistanceFromWall(direction) <= 2) {
                danger++;
            }
        }
        return danger;
    }

    private boolean collidesWithFriend(Plane plane) {
        for (Plane friendlyPlane : futurePlanes) {
            if (friendlyPlane != null && plane.getPosition().equals(friendlyPlane.getPosition())) {
                return true;
            }
        }
        return false;
    }

    private boolean hugsWall(Plane plane) {
        if (!plane.isAlive() || isOutside(plane)) {
            return true;
        }
        char[] mainDirs = plane.getDirection().getMainDirections();
        if (mainDirs.length == 1) {
            return plane.getDistanceFromWall(mainDirs[0]) == 0;
        }
        if (mainDirs.length == 2) {
            return plane.getDistanceFromWall(mainDirs[0]) <= 1
                    && plane.getDistanceFromWall(mainDirs[1]) <= 1;
        }
        if (mainDirs.length == 3) {
            return plane.getDistanceFromWall(mainDirs[0]) <= 1
                    && plane.getDistanceFromWall(mainDirs[1]) <= 1
                    && plane.getDistanceFromWall(mainDirs[2]) <= 1;
        }
        return false;
    }

    private Set<Move> getPossibleMoves(Plane plane) {
        Set<Move> possibleMoves = new HashSet<>();
        for (Direction direction : plane.getPossibleDirections()) {
            possibleMoves.add(new Move(direction, false, false));
            possibleMoves.add(new Move(direction, true, false));
        }
        return possibleMoves;
    }

    private boolean canShootSafely(Plane plane) {
        if (!plane.canShoot() || isOutside(plane)) {
            return false;
        }
        for (Point3D destPoint : plane.getShootRange()) {
            for (Plane friendlyPlane : futurePlanes) {
                if (friendlyPlane == null) {
                    continue;
                }
                if (friendlyPlane.isAlive() && friendlyPlane.getPosition().equals(destPoint)) {
                    return false;
                }
            }
        }
        return true;
    }

    private int possibleHits(Plane plane) {
        if (!plane.canShoot() || !canShootSafely(plane)) {
            return 0;
        }
        int possibleHits = 0;
        Point3D[] range = plane.getShootRange();
        for (Plane enemyPlane : enemyPlanes) {
            for (Move move : getPossibleMoves(enemyPlane)) {
                Plane simPlane = enemyPlane.simulateMove(move);
                for (Point3D dest : range) {
                    if (dest.equals(simPlane.getPosition())) {
                        possibleHits++;
                    }
                }
            }           
        }
        return possibleHits;
    }

    private boolean isOutside(Plane plane) {
        return !plane.getPosition().isInArena(arenaSize);
    }
}
CommonGuy
fuente
1
Actualmente eres la segunda mejor entrada, después de PredictAndAvoid. Ganas contra cualquier otra entrada, pero empatas bastante. Contra PredictAndAvoid, Whirligig logra imponer más victorias y empates que tú. Buena entrada independientemente!
overactor
1
@overactor ¡Gracias por tu aporte! Eso significa que tengo que trabajar en el rodaje ...
CommonGuy
1
Acabo de hacer algunas pruebas más, parece que pierdes menos contra Whirligig que PredictAndAvoid, PredictAndAvoid logra muchas más victorias, sin embargo, aquí están los datos para 2000 peleas: PredictAndAvoid: 1560 Whirligig: 138 | PredictAndAvoid: 1564 Crossfire: 125 | Perinola: 25 Crossfire: 600
overactor
@overactor Encontré el tiempo para mejorar mi presentación. Ahora, a veces gana, empata y pierde contra PredictAndAvoid.
CommonGuy
1
Bien hecho, después de 10,000 peleas: PUNTUACIÓN: PredictAndAvoid: 1240 Crossfire: 6567
overactor
20
/*
    PREDICT AND AVOID

    Rules of behavior:
    - Avoid hitting walls
    - Move, safely, to shoot at spaces our enemy might fly to
    - (contingent) Move to a safe space that aims closer to the enemy
    - Move to a safe space
    - Move, unsafely, to shoot at spaces our enemy might fly to
    - Move to any space (remember to avoid walls)

    Chooses randomly between equally prioritized moves

    contingent strategy is evaluated during early fights
*/

package Planes;

import java.util.Random;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;


public class PredictAndAvoid extends PlaneControl {

    public PredictAndAvoid(int arenaSize, int rounds) {
        super(arenaSize, rounds);
    }


    private int fightsPerMatch = 0;
    private int fightNum = 0;
    private int roundNum = 0;
    private boolean useHoming = true;
    private int homingScore = 0;
    private int[][][] enemyHistory = new int[arenaSize][arenaSize][arenaSize];

    // don't need to take roots here, waste of cpu cycles
    int distanceCubed(Point3D a, Point3D b) {
        return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + (a.z-b.z)*(a.z-b.z);
    }

    // is this plane guaranteed to hit a wall, now or soon?
    boolean dangerZone(Plane icarus) {
        // outside the arena?
        // already dead
        // this should never happen for my planes
        if (!icarus.getPosition().isInArena(arenaSize)) {
            return true;
        }
        // adjacent to a wall?
        // directly facing the wall?
        // death next turn
        if (
            icarus.getDirection().getMainDirections().length==1 &&
            icarus.getDistanceFromWall(icarus.getDirection().getMainDirections()[0]) == 0
        ) {
                return true;
        }
        // on an edge?
        // 2d diagonal facing into that edge?
        // death next turn
        if (
            icarus.getDirection().getMainDirections().length==2 &&
            icarus.getDistanceFromWall(icarus.getDirection().getMainDirections()[0]) == 0 &&
            icarus.getDistanceFromWall(icarus.getDirection().getMainDirections()[1]) == 0
        ) {
                return true;
        }
        // near a corner?
        // 3d diagonal facing into that corner?
        // death in 1-2 turns
        if (
            icarus.getDirection().getMainDirections().length==3 &&
            icarus.getDistanceFromWall(icarus.getDirection().getMainDirections()[0]) < 2 &&
            icarus.getDistanceFromWall(icarus.getDirection().getMainDirections()[1]) < 2 &&
            icarus.getDistanceFromWall(icarus.getDirection().getMainDirections()[2]) < 2
        ) {
                return true;
        }
        // there's at least one way out of this position
        return false;
    }

    @Override
    public Move[] act() {
        Move[] moves = new Move[2];

        for (int i=0; i<2; i++) {
            Plane p = myPlanes[i];
            if (!p.isAlive()) {
                moves[i] = new Move(new Direction("N"), false, false); // If we're dead we just return something, it doesn't matter anyway.
                continue;
            }

            // a list of every move that doesn't commit us to running into a wall
            // or a collision with the previously moved friendly plane
            ArrayList<Move> potentialMoves = new ArrayList<Move>();
            for (Direction candidateDirection : p.getPossibleDirections()) {
                if (i==1 && myPlanes[0].simulateMove(moves[0]).getPosition().equals(myPlanes[1].simulateMove(new Move(candidateDirection,false,false)).getPosition())) {

                } else {                
                    Plane future = new Plane(arenaSize, 0, p.getDirection(), p.getPosition().add(candidateDirection.getAsPoint3D())); 
                    if (!dangerZone(future)) {
                                potentialMoves.add(new Move(candidateDirection, false, false));
                    }
                    future = new Plane(arenaSize, 0, candidateDirection, p.getPosition().add(candidateDirection.getAsPoint3D())); 
                    if (!dangerZone(future)) {
                            potentialMoves.add(new Move(candidateDirection, true, false));
                    }
                }
            }

            // everywhere our enemies might end up
            // including both directions they could be facing for each location
            ArrayList<Plane> futureEnemies = new ArrayList<Plane>();
            for (Plane e : enemyPlanes) {
                if (e.isAlive()) {
                    for (Direction candidateDirection : e.getPossibleDirections()) {
                        futureEnemies.add(new Plane(
                            arenaSize, 
                            e.getCoolDown(), 
                            candidateDirection, 
                            e.getPosition().add(candidateDirection.getAsPoint3D())
                            ));
                        // don't make a duplicate entry for forward moves
                        if (!candidateDirection.getAsPoint3D().equals(e.getDirection().getAsPoint3D())) {
                            futureEnemies.add(new Plane(
                                arenaSize, 
                                e.getCoolDown(), 
                                e.getDirection(), 
                                e.getPosition().add(candidateDirection.getAsPoint3D())
                                ));
                        }
                    }
                }
            }

            // a list of moves that are out of enemies' potential line of fire
            // also skipping potential collisions unless we are ahead on planes
            ArrayList<Move> safeMoves = new ArrayList<Move>();
            for (Move candidateMove : potentialMoves) {
                boolean safe = true;
                Point3D future = p.simulateMove(candidateMove).getPosition();
                for (Plane ec : futureEnemies) {
                    if (ec.getPosition().equals(future)) {
                        if (
                            (myPlanes[0].isAlive()?1:0) + (myPlanes[1].isAlive()?1:0)
                            <= 
                            (enemyPlanes[0].isAlive()?1:0) + (enemyPlanes[1].isAlive()?1:0)
                        ) {
                            safe = false;
                            break;
                        }
                    }
                    if (ec.isAlive() && ec.canShoot()) {
                        Point3D[] range = ec.getShootRange();
                        for (Point3D t : range) {
                            if (future.equals(t)) {
                                safe = false;
                                break;
                            }
                        }
                        if (safe == false) {
                            break;
                        }
                    }
                }
                if (safe == true) {
                    safeMoves.add(candidateMove);
                }
            }

            // a list of moves that let us attack a space an enemy might be in
            // ignore enemies committed to suicide vs a wall
            // TODO: don't shoot at friendly planes
            ArrayList<Move> attackMoves = new ArrayList<Move>();
            for (Move candidateMove : potentialMoves) {
                int attackCount = 0;
                Plane future = p.simulateMove(candidateMove);
                Point3D[] range = future.getShootRange();
                for (Plane ec : futureEnemies) {
                    for (Point3D t : range) {
                        if (ec.getPosition().equals(t)) {
                            if (!dangerZone(ec)) {
                                    attackMoves.add(new Move(candidateMove.direction, candidateMove.changeDirection, true));
                                    attackCount++;
                            }
                        }
                    }
                }
                if (attackCount > 0) {

                }
            }

            // find all attack moves that are also safe moves
            ArrayList<Move> safeAttackMoves = new ArrayList<Move>();
            for (Move safeCandidate : safeMoves) {
                for (Move attackCandidate : attackMoves) {
                    if (safeCandidate.direction == attackCandidate.direction) {
                        safeAttackMoves.add(attackCandidate);
                    }
                }
            }

            // choose the safe move that aims closest potential enemy positions
            int maxDistanceCubed = arenaSize*arenaSize*arenaSize*8;
            Move homingMove = null;
            int bestHomingMoveTotalDistancesCubed = maxDistanceCubed*1000;
            for (Move candidateMove : safeMoves) {
                int totalCandidateDistancesCubed = 0;
                for (Plane ec : futureEnemies) {
                    if (ec.isAlive()) {
                        int distThisEnemyCubed = maxDistanceCubed;
                        Point3D[] range = p.simulateMove(candidateMove).getShootRange();
                        for (Point3D t : range) {
                            int d1 = distanceCubed(t, ec.getPosition());
                            if (d1 < distThisEnemyCubed) {
                                distThisEnemyCubed = d1;
                            }
                        }
                        totalCandidateDistancesCubed += distThisEnemyCubed;
                    }
                }
                if (totalCandidateDistancesCubed < bestHomingMoveTotalDistancesCubed) {
                    bestHomingMoveTotalDistancesCubed = totalCandidateDistancesCubed;
                    homingMove = candidateMove;
                }
            }

            Random rng = new Random();
            // move to attack safely if possible
            // even if we can't shoot, this is good for chasing enemies
            if (safeAttackMoves.size() > 0) {
                moves[i] = safeAttackMoves.get(rng.nextInt(safeAttackMoves.size()));
                }
            // turn towards enemies if it's possible and safe
            // tests indicate value of this strategy varies significantly by opponent
            // useHoming changes based on outcome of early fights with[out] it
            // TODO: track enemy movement, aim for neighborhood
            else if (useHoming == true && homingMove != null) {
                moves[i] = homingMove;
                }
            // make random move, safe from attack
            else if (safeMoves.size() > 0) {
                moves[i] = safeMoves.get(rng.nextInt(safeMoves.size()));
                }
            // move to attack unsafely only if there are no safe moves
            else if (attackMoves.size() > 0 && p.canShoot()) {
                moves[i] = attackMoves.get(rng.nextInt(attackMoves.size()));
                }
            // make random move, safe from walls
            else if (potentialMoves.size() > 0) {
                moves[i] = potentialMoves.get(rng.nextInt(potentialMoves.size()));
                }
            // keep moving forward
            // this should never happen
            else {
                moves[i] = new Move(p.getDirection(), false, true);
                }
        }
        roundNum++;
        return moves;
    }

    @Override
    public void newFight(int fightsFought, int myScore, int enemyScore) {
        // try the homing strategy for 1/8 of the match
        // skip it for 1/8, then choose the winning option
        if (fightsFought == fightsPerMatch/8) {
            homingScore = myScore-enemyScore;
            useHoming = false;
        } else if (fightsFought == (fightsPerMatch/8)*2) {
            if (homingScore*2 > myScore-enemyScore) {
                useHoming = true;
            }
        }
        fightNum = fightsFought;
        roundNum = 0;
    }

    @Override
    public void newOpponent(int fights) {
        fightsPerMatch = fights;
    }
}
Sparr
fuente
1
Actualmente supera a Whirligig casi todas las veces. Necesito localizar un error en el código para evitar el disparo del enemigo.
Sparr
1
Arreglado el error. 0 pérdidas para los oponentes actuales ahora.
Sparr
2
estado trabajando en la reducción de los sorteos, progreso significativo. Necesito aviones enemigos más inteligentes antes de que pueda progresar mucho más.
Sparr
1
Priorizaría mudarme a un espacio seguro, por encima de disparar de manera insegura
Cruncher
1
@Cruncher ya lo está haciendo en mi copia local, y mejora el rendimiento un poco en comparación con los oponentes actuales. También ahora evitando colisiones cuando no estoy adelante en aviones supervivientes actualización para venir!
Sparr
18

Dogfight 3D Visualizer

Escribí un visualizador pequeño y rápido para este desafío. El código y los archivos jar están en mi repositorio github: https://github.com/Hungary-Dude/DogfightVisualizer
Está hecho usando libGDX ( http://libgdx.com ). En este momento la interfaz de usuario es bastante mala, lo hice todo rápido.

Solo estoy aprendiendo a usar Git y Gradle, así que por favor comente si hice algo mal

¡Corre dist/dogfight.bato dist/dogfight.shve a DumbPlanes en acción!

Para compilar desde la fuente, necesitará Gradle ( http://gradle.org ) y la integración de Gradle para su IDE, si tiene una. Luego clona el repositorio y corre gradlew desktop:run. Con suerte, Gradle importará todas las bibliotecas necesarias. La clase principal es zove.koth.dogfight.desktop.DesktopLauncher.

Corriendo sin importar

Copie cualquier archivo de clase de avión en dist/. Luego, ejecute dist/desktop-1.0.jarcon este comando:

java -cp your-class-folder/;desktop-1.0.jar;Planes.jar zove.koth.dogfight.desktop.DesktopLauncher package.YourPlaneController1 package.YourPlaneController2 ...

Actualizaré a medida que se actualice la fuente del controlador de Aviones, pero para actualizarse, deberá agregar algún código Planes.Controller. Vea el archivo léame de github para obtener información sobre esto.

Aquí hay una captura de pantalla: Captura de pantalla

Si tiene alguna pregunta o sugerencia, ¡deje un comentario a continuación!

DankMemes
fuente
Esto es increíble, tengo un proyecto configurado donde agregué las clases de avión, ¿cómo ejecuto ahora el visualizador con estos aviones? Quizás esto se explicaría mejor en el chat . Como sugerencia, sería genial si pudieras pegar en un registro mínimo de una coincidencia y luego pasar por esa coincidencia, también, creo que podrías haber aumentado las cooridaciones, los aviones deberían comenzar en el piso y el techo respectivamente. ¡Increíble trabajo!
overactor
Tomé los Point3D que representan la posición del plano y resté 6.5 de cada coordenada para moverlos a la vista. Algo así como plane.transform.setToTranslation(new Vector3(point3d.x-6.5f,point3d.y-6.5f,point3d.z-6.5f))no los aviones parecen ir fuera de límites por lo que duda algo está mal
DankMemes
Ah, espera, ¿estás usando el eje y como altura? (como en la mayoría de los juegos, supongo) en mi sistema, el z representa la altura, no es que importe mucho, ya que es simétrica
overactor
Ohhhhhhh lo entiendo. Lo siento, en realidad no miré mucho tu código. Acabo de traducir Point3Ds directamente a libgdx Vector3s. Por cierto, estaré fuera por una semana más o menos a partir de mañana. Lo siento si no estoy aquí si necesitas algo. Intentaré registrarme mientras esté fuera.
DankMemes
12

EmoFockeWulf

Él está de vuelta. Se ha muerto de hambre a 224 bytes. No sabe cómo terminó así.

package Planes;public class EmoFockeWulf extends PlaneControl{public EmoFockeWulf(int s, int r){super(s,r);}public Move[] act(){Move[] m=new Move[2];m[0]=new Move(myPlanes[0].getDirection(),false,false);m[1]=m[0];return m;}}
cjfaure
fuente
13
Esto se está saliendo de las manos ahora. ¿Qué tal si lo desterramos a la publicación de vacíos legales para siempre?
user80551
13
@ user80551 Creo que es un estilo de juego válido, independientemente. No hay razón para desterrarlo.
Seiyria
3
¡Tiene 47 bytes más gordo que antes!
johnchen902
2
Podría suicidarse más rápido que esto. No muy efectivamente emo.
Sparr
2
@Sparr sí, pero tendría un mayor número de bytes y perdería la ironía de no perder siempre. : P
cjfaure
10

Weeeeeeeeeeee - 344 bytes después de eliminar espacios en blanco

Hace loops y cosas increíbles. No se puede perder si estás haciendo bucles.

package Planes;
public class W extends PlaneControl{
    int i,c;
    int[] s={1,1,1,0,-1,-1,-1,0};
    public W(int a,int r){
        super(a,r);
    }
    public void newFight(int a,int b,int c){
        i=4;
    }
    public Move[] act(){
        Plane p=myPlanes[0];
        if(++i<6)
            c=p.getX()==0?1:-1;
        Move n=new Move(i<8?p.getDirection():new Direction(c*s[(i+2)%8],0,c*s[i%8]),0<1,i%2<1);
        Move[] m={n,n};
        return m;
    }
}

EDITAR: aparentemente cuando mi avión comenzó como equipo 2, se estrellaron inmediatamente contra la pared. Creo que lo arreglé ahora. Ojalá.

seudónimo117
fuente
Su declaración de devolución no es legal. En Java, para crear matrices de objetos que especifiquen todo el contenido en una línea, debe usarlo, new Type[]{item1, item2, ...}por lo que en este caso tendríareturn new Move[]{new Move(d,z,a),new Move(d,z,a^=z)};
DankMemes
También pruebe browxy.com si no tiene un IDE descargado. (No es para nada poderoso pero funciona)
DankMemes
gracias, olvidé si eso funcionó o no. Simplemente no quería descargar sus clases para que toda la herencia y el paquete funcionaran.
seudónimo117
Después de ejecutar sus aviones con el nuevo código, solo regresa S y SU y muere en la ronda 15 cada vez. ¿Alguna idea de por qué?
overactor
hmm ... no Aparentemente me equivoqué con mi cambio. Realmente esperaba que simplemente funcionara ... solo deshacería la edición.
seudónimo117
6

Avión Move-and-Shoot

Evita las paredes al encontrar cuando está cerca de una pared y gira, dispara cuando puede.

    package Planes;

public class MoveAndShootPlane extends PlaneControl {

    public MoveAndShootPlane(int arenaSize, int rounds) {
        super(arenaSize, rounds);
    }

    @Override
    public Move[] act() {
        Move[] moves = new Move[2];

        for (int i=0; i<2; i++) {
            if (!myPlanes[i].isAlive()) {
                moves[i] = new Move(new Direction("N"), false, false); // If we're dead we just return something, it doesn't matter anyway.
                continue;
            }
            // What direction am I going again?
            Direction currentDirection = myPlanes[i].getDirection();

            // Is my plane able to shoot?
            boolean canIShoot = myPlanes[i].canShoot();

            // if a wall is near me, turn around, otherwise continue along
            if (myPlanes[i].getDirection().getAsString().equals("N") && myPlanes[i].getDistanceFromWall('N') <= 2) {
                if (myPlanes[i].getDistanceFromWall('U') > myPlanes[i].getDistanceFromWall('D')) {
                    moves[i] = new Move(new Direction("NU"), true, canIShoot);
                } else {
                    moves[i] = new Move(new Direction("ND"), true, canIShoot);
                } 
            } else if (myPlanes[i].getDirection().getAsString().equals("S") && myPlanes[i].getDistanceFromWall('S') <= 2) {
                if (myPlanes[i].getDistanceFromWall('U') > myPlanes[i].getDistanceFromWall('D')) {
                    moves[i] = new Move(new Direction("SU"), true, canIShoot);
                } else {
                    moves[i] = new Move(new Direction("SD"), true, canIShoot);
                } 
            } else {
                if (myPlanes[i].getDirection().getAsString().equals("N") || myPlanes[i].getDirection().getAsString().equals("S")) {             
                    moves[i] = new Move(currentDirection, false, canIShoot);
                } else if (myPlanes[i].getDistanceFromWall('N') < myPlanes[i].getDistanceFromWall('S')) {
                    if (myPlanes[i].getDirection().getAsString().equals("NU")) {
                        moves[i] = new Move(new Direction("U"), true, canIShoot);
                    } else if (myPlanes[i].getDirection().getAsString().equals("U")) {
                        moves[i] = new Move(new Direction("SU"), true, canIShoot);
                    } else if (myPlanes[i].getDirection().getAsString().equals("SU")) {
                        moves[i] = new Move(new Direction("S"), true, canIShoot);
                    } else if (myPlanes[i].getDirection().getAsString().equals("ND")) {
                        moves[i] = new Move(new Direction("D"), true, canIShoot);
                    } else if (myPlanes[i].getDirection().getAsString().equals("D")) {
                        moves[i] = new Move(new Direction("SD"), true, canIShoot);
                    } else if (myPlanes[i].getDirection().getAsString().equals("SD")) {
                        moves[i] = new Move(new Direction("S"), true, canIShoot);
                    }
                } else {
                    if (myPlanes[i].getDirection().getAsString().equals("SU")) {
                        moves[i] = new Move(new Direction("U"), true, canIShoot);
                    } else if (myPlanes[i].getDirection().getAsString().equals("U")) {
                        moves[i] = new Move(new Direction("NU"), true, canIShoot);
                    } else if (myPlanes[i].getDirection().getAsString().equals("NU")) {
                        moves[i] = new Move(new Direction("N"), true, canIShoot);
                    } else if (myPlanes[i].getDirection().getAsString().equals("SD")) {
                        moves[i] = new Move(new Direction("D"), true, canIShoot);
                    } else if (myPlanes[i].getDirection().getAsString().equals("D")) {
                        moves[i] = new Move(new Direction("ND"), true, canIShoot);
                    } else if (myPlanes[i].getDirection().getAsString().equals("ND")) {
                        moves[i] = new Move(new Direction("N"), true, canIShoot);
                    }
                }
            }
        }
        return moves;
    }

    @Override
    public void newFight(int fightsFought, int myScore, int enemyScore) {
        // Using information is for schmucks.
    }

    @Override
    public void newOpponent(int fights) {
        // What did I just say about information?
    }
}     

Descargo de responsabilidad: no soy en absoluto un programador de Java, así que si arruiné algo, ¡arréglenmelo!

Kyle Kanos
fuente
Todavía no lo he probado, pero esto no funcionará, estás intentando girar 180 grados a la vez. Como pista, intente N-> NU-> U-> SU-> S en lugar de N-> S o reemplace U por D si el techo está más cerca que el piso.
overactor
@overactor: me perdí el you may only change your angle by 45 degreesbit.
Kyle Kanos
No hay problema, no debería ser demasiado difícil de solucionar.
overactor
Probablemente debería usar HashMap <String> en lugar de Hashtable. De lo contrario, new Direction(wayToGo.get(currentDirection))no funcionará, ya que olvida lanzar a String. wayToGo.put después del campo tampoco es válido, póngalo en un bloque {wayToGo.put (blah); blah;} o en el constructor.
Luna
77
Por ahora está ganando todo en virtud de no volar contra las paredes.
overactor
6

Molinete

Ambos aviones se dirigen hacia el centro (ish), luego giran mientras disparan con la mayor frecuencia posible. Se elige uno de los tres ejes por pelea, y el par siempre gira alrededor del mismo eje en direcciones opuestas.

package Planes;

public class Whirligig extends PlaneControl{

    public Whirligig(int arenaSize, int rounds) {
        super(arenaSize, rounds);
        cycle = -1;
    }

    int cycle;
    String[][] cycles = {
            {"E","EU","U","WU","W","WD","D","ED"},
            {"N","NU","U","SU","S","SD","D","ND"},
            {"S","SW","W","NW","N","NE","E","SE"},
            {"ED","D","WD","W","WU","U","EU","E"},
            {"ND","D","SD","S","SU","U","NU","N"},
            {"SE","E","NE","N","NW","W","SW","S"},
    };

    private Move act(int idx){
        Plane plane = myPlanes[idx];
        Move move = new Move(plane.getDirection(), true, plane.canShoot());
        if(!plane.isAlive())
            return new Move(new Direction("N"), false, false);

        if(cycle < 0){
            if(idx == 0 && (myPlanes[1].getZ() == 0 || myPlanes[1].getZ() == 13)){
                return move;
            }
            if(distanceToCenter(plane.getPosition()) > 2){
                move.direction = initialMove(plane);
            } else {
                cycle = (int)(Math.random()*3);
            }
        } else {
            move.direction = continueCycle(plane, cycle + (idx*3));
        }
        return move;
    }

    private Direction initialMove(Plane plane){
        if(plane.getDirection().getNSDir() > 0)
            return new Direction("SU");
        else
            return new Direction("ND");
    }

    private Direction continueCycle(Plane plane, int pathIndex){
        Direction current = plane.getDirection();
        String[] path = cycles[pathIndex];
        for(int i=0;i<path.length;i++)
            if(path[i].equals(current.getAsString()))
                return new Direction(path[(i+1)%path.length]);

        Direction[] possible = plane.getPossibleDirections();
        int step = (int)(Math.random()*path.length);
        for(int i=0;i<path.length;i++){
            for(int j=0;j<possible.length;j++){
                if(path[(i+step)%path.length].equals(possible[j].getAsString()))
                    return new Direction(path[(i+step)%path.length]);
            }
        }       
        return plane.getDirection();
    }

    private int distanceToCenter(Point3D pos){
        int x = (int)Math.abs(pos.x - 6.5); 
        int y = (int)Math.abs(pos.y - 6.5); 
        int z = (int)Math.abs(pos.z - 6.5);
        return Math.max(x, Math.max(y,z));
    }

    @Override
    public Move[] act() {
        Move[] moves = new Move[2];
        for(int i=0;i<2;i++){
            moves[i] = act(i);
        }
        return moves;
    }

    @Override
    public void newFight(int fought, int wins, int losses){
        cycle = -1;
    }

    @Override
    public void newOpponent(int fights){
        cycle = -1;
    }

}
Geobits
fuente
4

DumbPlanes

Los DumbPlanes se esfuerzan tanto por no volar contra las paredes, pero no son muy inteligentes al respecto y generalmente terminan golpeando las paredes de todos modos. También disparan ocasionalmente, si supieran a qué están disparando.

package Planes;

public class DumbPlanes extends PlaneControl {

    public DumbPlanes(int arenaSize, int rounds) {
        super(arenaSize, rounds);
    }

    @Override
    public Move[] act() {
        Move[] moves = new Move[2];
        for (int i=0; i<2; i++) {
            if (!myPlanes[i].isAlive()) {
                moves[i] = new Move(new Direction("N"), false, false); // If we're dead we just return something, it doesn't matter anyway.
                continue;
            }
            Direction[] possibleDirections = myPlanes[i].getPossibleDirections(); // Let's see where we can go.

            for (int j=0; j<possibleDirections.length*3; j++) {

                int random = (int) Math.floor((Math.random()*possibleDirections.length)); // We don't want to be predictable, so we pick a random direction out of the possible ones.

                if (myPlanes[i].getPosition().add(possibleDirections[random].getAsPoint3D()).isInArena(arenaSize)) { // We'll try not to fly directly into a wall.
                    moves[i] = new Move(possibleDirections[random], Math.random()>0.5, myPlanes[i].canShoot() && Math.random()>0.2);
                    continue; // I'm happy with this move for this plane.
                }

                // Uh oh.
                random = (int) Math.floor((Math.random()*possibleDirections.length));
                moves[i] = new Move(possibleDirections[random], Math.random()>0.5, myPlanes[i].canShoot() && Math.random()>0.2);
            }
        }

        return moves;
    }

    @Override
    public void newFight(int fightsFought, int myScore, int enemyScore) {
        // Using information is for schmucks.
    }

    @Override
    public void newOpponent(int fights) {
        // What did I just say about information?
    }
}
overactor
fuente
4

Starfox (WIP - aún no funciona):

En realidad, no utiliza todos los movimientos disponibles. Pero él trata de derribar enemigos y no chocar contra las paredes.

package Planes;

import java.util.ArrayList;
import java.util.function.Predicate;

public class Starfox extends PlaneControl
{

    public Starfox(int arenaSize, int rounds)
    {
        super(arenaSize, rounds);
    }

    private ArrayList<Point3D> dangerousPositions;
    private ArrayList<Point3D> riskyPositions;

    @Override
    public Move[] act()
    {
        dangerousPositions = new ArrayList<>();
        riskyPositions = new ArrayList<>();

        // add corners as places to be avoided
        dangerousPositions.add(new Point3D(0,0,0));
        dangerousPositions.add(new Point3D(0,0,arenaSize-1));
        dangerousPositions.add(new Point3D(0,arenaSize-1,0));
        dangerousPositions.add(new Point3D(0,arenaSize-1,arenaSize-1));
        dangerousPositions.add(new Point3D(arenaSize-1,0,0));
        dangerousPositions.add(new Point3D(arenaSize-1,0,arenaSize-1));
        dangerousPositions.add(new Point3D(arenaSize-1,arenaSize-1,0));
        dangerousPositions.add(new Point3D(arenaSize-1,arenaSize-1,arenaSize-1));


        for (Plane p : super.enemyPlanes)
        {
            for (Direction d : p.getPossibleDirections())
            {
                Point3D potentialPosition = new Point3D(p.getX(), p.getY(), p.getZ()).add(d.getAsPoint3D());
                if (potentialPosition.isInArena(arenaSize))
                {
                    riskyPositions.add(potentialPosition);
                    if (p.canShoot())
                    {
                        for (Point3D range : p.getShootRange())
                        {
                            riskyPositions.add(range.add(potentialPosition));
                        }
                    }
                }
            }
        }

        ArrayList<Move> moves = new ArrayList<>();

        for (Plane p : myPlanes)
        {
            if (p.isAlive())
            {
                ArrayList<Direction> potentialDirections = new ArrayList<>();

                for (Direction d : p.getPossibleDirections())
                {
                    Point3D potentialPosition = new Point3D(p.getX(), p.getY(), p.getZ()).add(d.getAsPoint3D());
                    if (potentialPosition.isInArena(arenaSize))
                    {
                        potentialDirections.add(d);
                    }
                }

                // remove dangerous positions from flight plan
                potentialDirections.removeIf(new Predicate<Direction>()
                {
                    @Override
                    public boolean test(Direction test)
                    {
                        boolean result = false;
                        for (Point3D compare : dangerousPositions)
                        {
                            if (p.getPosition().add(test.getAsPoint3D()).equals(compare))
                            {
                                result = true;
                            }
                        }
                        return result && potentialDirections.size() > 0;
                    }
                });

                // remove positions with no future from flight plan

                potentialDirections.removeIf(new Predicate<Direction>()
                {
                    @Override
                    public boolean test(Direction test)
                    {
                        boolean hasFuture = false;
                        for (Direction compare : p.getPossibleDirections())
                        {
                            Plane future = new Plane(arenaSize, 0, compare, p.getPosition().add(compare.getAsPoint3D()));
                            if (future!=null && future.getDirection()!=null) {
                                for (Direction d : future.getPossibleDirections())
                                {
                                    if (future.getPosition().add(d.getAsPoint3D()).isInArena(arenaSize))
                                    {
                                        hasFuture = true;
                                        break;
                                    }
                                }
                            }
                        }
                        return !hasFuture;
                    }
                });

                // remove risky positions from flight plan
                potentialDirections.removeIf(new Predicate<Direction>()
                {
                    @Override
                    public boolean test(Direction test)
                    {
                        boolean result = false;
                        for (Point3D compare : riskyPositions)
                        {
                            if (p.getPosition().add(test.getAsPoint3D()).equals(compare))
                            {
                                result = true;
                            }
                        }
                        return result && potentialDirections.size() > 0;
                    }
                });

                // check for targets
                Direction best = null;
                if (p.canShoot())
                {
                    int potentialHits = 0;
                    for (Direction d : potentialDirections)
                    {
                        Plane future = new Plane(arenaSize, 0, d, p.getPosition().add(d.getAsPoint3D()));
                        for (Point3D t : future.getShootRange())
                        {
                            int targets = 0;
                            for (Plane e : super.enemyPlanes)
                            {
                                for (Direction s : e.getPossibleDirections())
                                {
                                    Plane target = new Plane(arenaSize, 0, s, e.getPosition().add(s.getAsPoint3D()));
                                    if (target.getPosition().equals(t))
                                    {
                                        targets++;
                                    }

                                }
                            }
                            if (targets > potentialHits)
                            {
                                best = d;
                                potentialHits = targets;
                            }
                        }
                    }
                }

                if (best == null)
                {
                    if (potentialDirections.size() > 0) {
                        best = potentialDirections.get((int) Math.floor(Math.random() * potentialDirections.size()));
                    } else {
                        best = new Direction("N");
                    }
                }

                moves.add(new Move(best, true, false));
                dangerousPositions.add(p.getPosition().add(best.getAsPoint3D()));

            }
            else
            {
                // this plane is dead, not much to do but go hide in corner
                moves.add(new Move(new Direction("N"), false, false));

            }
        }

        Move[] movesArr = {moves.get(0), moves.get(1)};
        return movesArr;
    }

    @Override
    public void newFight(int fightsFought, int myScore, int enemyScore)
    {
        // Using information is for schmucks.
    }

    @Override
    public void newOpponent(int fights)
    {
        // What did I just say about information?
    }
}
usuario2813274
fuente
77
¿Pero puede hacer un barril?
Erty Seidohl
1
Recibo una excepción, aquí está el seguimiento de la pila: Excepción en el hilo "main" java.lang.NullPointerException en Planes.Starfox $ 2.test (Starfox.java:99) en Planes.Starfox $ 2.test (Starfox.java:1 ) en java.util.ArrayList.removeIf (Fuente desconocida) en Planes.Starfox.act (Starfox.java:90) en Planes.Controller.fight (Controller.java:141) en Planes.Controller.matchUp (Controller.java: 85) en Planes.Controller.main (Controller.java:35) Tuve que agregar Package Planes, de lo contrario no se compilaría, tal vez eso tenía algo que ver con eso.
overactor
Lo ejecuté pero no funciona tan bien como se esperaba, creo que el problema podría ser que lo mejor es nulo con demasiada frecuencia.
overactor
Parece que Starfox se mueve al fuego enemigo en lugar de salir de él, puedes ver lo que está sucediendo aquí.
overactor
3

DangerZoner

  • Escrito en Python e interfaces con el contenedor de código no Java escrito por Sparr.

  • Hace todas sus matemáticas en Python puro y es completamente optimizado. Un poco lento.

  • Altamente configurable y extensible.

  • Lo hace muy bien contra presentaciones pasadas. Gana 2: 1 peleas por cada perdido contra Crossfireo PredictAndAvoid, y gana 98 +% de todas las peleas contra otros contendientes.

Incluye su propia herramienta de visualización opcional:

Fighting Crossfire/ PredictAndAvoid, con las clasificaciones de zonas de peligro del mismo nombre visualizadas en el volumen circundante:

Video de cuatro aviones peleando en dos rondas con una cuadrícula de vóxel coloreada transformándose a su alrededor.

  • Visualizado usando el nipy_spectralmapa de colores de matplotlib. Las coordenadas más peligrosas se representan usando colores más cercanos al rojo / blanco en el espectro electromagnético, y se dibujan con puntos más grandes.

  • Peligro: Azul <Verde <Amarillo <Rojo <Gris claro

Actuación:

1000 rondas con los otros ocho algoritmos principales en la tabla de clasificación:

SCORE: DumbPlanes: 0 Dangerzoner: 1000
SCORE: Crossfire: 132 Dangerzoner: 367
SCORE: PredictAndAvoid: 165 Dangerzoner: 465
SCORE: Wee: 0 Dangerzoner: 1000
SCORE: Whirligig: 0 Dangerzoner: 989
SCORE: MoveAndShootPlane: 0 Dangerzoner: 1000
SCORE: Starfox: 4 Dangerzoner: 984
SCORE: DumbPy: 0 Dangerzoner: 1000
SCORES:

DumbPlanes: 2 points.
Crossfire: 12 points.
PredictAndAvoid: 14 points.
Wee: 10 points.
Whirligig: 8 points.
MoveAndShootPlane: 6 points.
Starfox: 4 points.
DumbPy: 0 points.
Dangerzoner: 16 points.


THE OVERALL WINNER(S): Dangerzoner
With 16 points.

Código:

#!/usr/bin/env python3
"""
DangerZoner

Each turn:
    1) Make a list of all possible locations to move to, explicitly excluding suicidal positions that will collide with the walls, an ally, or an ally's bullet.
    2) Rate each possible location using heuristics that estimate the approximate danger in that zone, accounting for the following factors:
        -Proximity to walls. (Manoeuvring constrictions and risk of collision.)
        -Proximity to fronts of planes. (Risk of mid-air collisions.)
        -High distance from enemy planes. (Risk of enemies easily turning to shoot.)
        -Intersection with all enemy attack vectors. (Explicit safety on the next round.)
        -Proximity to enemy forward vectors. (Approximate probability of being targeted in upcoming rounds.)
    3) If certain respective thresholds are met in the possible moves' danger ratings, then do the following if possible:
        -Take a potshot at a random position that an enemy might move to next turn (but never shoot an ally).
        -Take a potshot at an extrapolated position that an enemy will likely move to next turn if they keep up their current rate of turn (but never shoot an ally).
        -Turn to pursue the closest enemy.
        -Move randomly to confound enemy predictive mechanisms. (Disabled since implementing explicit enemy attack vectors in danger zone calculation.)
    4) If none of those thresholds are met, then choose the move rated as least dangerous.
"""

import math, random, functools, sys

#import NGrids
NGrids = lambda: None
class NSpace(object):
    """Object for representing an n-dimensional space parameterized by a list of extents in each dimension."""
    def __init__(self, dimensions):
        self.dimensions = tuple(dimensions)
    def check_coordshape(self, coord):
        return len(coord) == len(self.dimensions)
    def enforce_coordshape(self, coord):
        if not self.check_coordshape(coord):
            raise ValueError(f"Attempted to access {len(coord)}-coordinate point from {len(self.dimensions)}-coordinate space: {coord}")
    def check_coordrange(self, coord):
        return all((0 <= c <= b) for c, b in zip(coord, self.dimensions))
    def enforce_coordrange(self, coord):
        if not self.check_coordrange(coord):
            raise ValueError(f"Attempted to access coordinate point out of range of {'x'.join(str(d) for d in self.dimensions)} space: {coord}")
    def check_coordtype(self, coord):
        return True
    def enforce_coordtype(self, coord):
        if not self.check_coordtype(coord):
            raise TypeError(f"Attempted to access grid point with invalid coordinates for {type(self).__name__}(): {coord}")
    def enforce_coord(self, coord):
        for f in (self.enforce_coordshape, self.enforce_coordrange, self.enforce_coordtype):
            f(coord)
    def coords_grid(self, step=None):
        if step is None:
            step = tuple(1 for i in self.dimensions)
        self.enforce_coord(step)
        counts = [math.ceil(d/s) for d, s in zip(self.dimensions, step)]
        intervals = [1]
        for c in counts:
            intervals.append(intervals[-1]*c)
        for i in range(intervals[-1]):
            yield tuple((i//l)*s % (c*s) for s, l, c in zip(step, intervals, counts))
NGrids.NSpace = NSpace

def Pythagorean(*coords):
    return math.sqrt(sum(c**2 for c in coords))

class Plane(object):
    """Object for representing a single dogfighting plane."""
    def __init__(self, alive, coord, vec, cooldown=None, name=None):
        self.alive = alive
        self.set_alive(alive)
        self.coord = coord
        self.set_coord(coord)
        self.vec = vec
        self.set_vec(vec)
        self.cooldown = cooldown
        self.set_cooldown(cooldown)
        self.name = name
    def set_alive(self, alive):
        self.lastalive = self.alive
        self.alive = alive
    def set_coord(self, coord):
        self.lastcoord = self.coord
        self.coord = coord
    def set_vec(self, vec):
        self.lastvec = self.vec
        self.vec = vec
    def set_cooldown(self, cooldown):
        self.lastcooldown = self.cooldown
        self.cooldown = cooldown
    def update(self, alive=None, coord=None, vec=None, cooldown=None):
        if alive is not None:
            self.set_alive(alive)
        if coord is not None:
            self.set_coord(coord)
        if vec is not None:
            self.set_vec(vec)
        if cooldown is not None:
            self.set_cooldown(cooldown)
    def get_legalvecs(self):
        return getNeighbouringVecs(self.vec)
    def get_legalcoords(self):
        return {tuple(self.coord[i]+v for i, v in enumerate(vec)) for vec in self.get_legalvecs()}
    def get_legalfutures(self):
        return (lambda r: r.union((c, self.vec) for c, v in r))({(vecAdd(self.coord, vec),vec) for vec in self.get_legalvecs()})

class DangerZones(NGrids.NSpace):
    """Arena object for representing an n-dimensional volume with both enemy and allied planes in it and estimating the approximate safety/danger of positions within it. """
    def __init__(self, dimensions=(13,13,13), walldanger=18.0, walldistance=3.5, wallexpo=2.0, walluniformity=5.0, planedanger=8.5, planeexpo=8.0, planeoffset=1.5, planedistance=15.0, planedistancedanger=2.0, planedistanceexpo=1.5, firedanger=9.0, collisiondanger=10.0, collisiondirectionality=0.6, collisiondistance=2.5, collisionexpo=0.2):
        NGrids.NSpace.__init__(self, dimensions)
        self.walldanger = walldanger
        self.walldistance = walldistance
        self.wallexpo = wallexpo
        self.walluniformity = walluniformity
        self.planedanger = planedanger
        self.planeexpo = planeexpo
        self.planeoffset = planeoffset
        self.planedistance = planedistance
        self.planedistancedanger = planedistancedanger
        self.planedistanceexpo = planedistanceexpo
        self.firedanger = firedanger
        self.collisiondanger = collisiondanger
        self.collisiondirectionality = collisiondirectionality
        self.collisiondistance = collisiondistance
        self.collisionexpo = collisionexpo
        self.set_planes()
        self.set_allies()
        self.clear_expectedallies()
    def filteractiveplanes(self, planes=None):
        if planes is None:
            planes = self.planes
        return (p for p in planes if all((p.alive, p.coord, p.vec)))
    def rate_walldanger(self, coord):
        self.enforce_coordshape(coord)
        return (lambda d: (max(d)*self.walluniformity+sum(d))/(self.walluniformity+1))((1-min(1, (self.dimensions[i]/2-abs(v-self.dimensions[i]/2))/self.walldistance)) ** self.wallexpo * self.walldanger for i, v in enumerate(coord))
    def rate_planedanger(self, coord, planecoord, planevec):
        for v in (planecoord, planevec, coord):
            self.enforce_coordshape(v)
        return max(0, (1 - vecAngle(planevec, vecSub(coord, vecSub(planecoord, vecMult(planevec, (self.planeoffset,)*len(self.dimensions)))) ) / math.pi)) ** self.planeexpo * self.planedanger
        offsetvec = convertVecTrinary(planevec, length=self.planeoffset)
        relcoord = [v-(planecoord[i]-offsetvec[i]) for i, v in enumerate(coord)]
        nrelcoord = (lambda m: [(v/m if m else 0) for v in relcoord])(Pythagorean(*relcoord))
        planevec = (lambda m: [(v/m if m else 0) for v in planevec])(Pythagorean(*planevec))
        return max(0, sum(d*p for d, p in zip(planevec, nrelcoord))+2)/2 ** self.planeexpo * self.planedanger + min(1, Pythagorean(*relcoord)/self.planedistance) ** self.planedistanceexpo * self.planedistancedanger
    def rate_planedistancedanger(self, coord, planecoord, planevec):
        return Pythagorean(*vecSub(planecoord, coord))/self.planedistance ** self.planedistanceexpo * self.planedistancedanger
    def rate_firedanger(self, coord, plane):
        return (min(vecAngle(vecSub(coord, c), v) for c, v in plane.get_legalfutures()) < 0.05) * self.firedanger
    def rate_collisiondanger(self, coord, planecoord, planevec):
        if coord == planecoord:
            return self.collisiondanger
        offsetvec = tuple(p-c for p,c in zip(planecoord, coord))
        return max(0, vecAngle(planevec, offsetvec)/math.pi)**self.collisiondirectionality * max(0, 1-Pythagorean(*offsetvec)/self.collisiondistance)**self.collisionexpo*self.collisiondanger
    def set_planes(self, *planes):
        self.planes = planes
    def set_allies(self, *allies):
        self.allies = allies
    def rate_planesdanger(self, coord, planes=None):
        if planes is None:
            planes = {*self.planes}
        return max((0, *(self.rate_planedanger(coord, planecoord=p.coord, planevec=p.vec) for p in self.filteractiveplanes(planes))))
    def rate_planedistancesdanger(self, coord, planes=None):
        if planes is None:
            planes = {*self.planes}
        return max((0, *(self.rate_planedistancedanger(coord, planecoord=p.coord, planevec=p.vec) for p in self.filteractiveplanes(planes))))
    def rate_firesdanger(self, coord, planes=None):
        if planes is None:
            planes = {*self.planes}
        return sum(self.rate_firedanger(coord, p) for p in self.filteractiveplanes(planes))
    def rate_collisionsdanger(self, coord, pself=None, planes=None):
        if planes is None:
            planes = {*self.planes, *self.allies}
        return max((0, *(self.rate_collisiondanger(coord , planecoord=p.coord, planevec=p.vec) for p in self.filteractiveplanes(planes) if p is not pself)))
    def rate_sumdanger(self, coord, pself=None, planes=None):
        return max((self.rate_walldanger(coord), self.rate_planesdanger(coord, planes=planes), self.rate_planedistancesdanger(coord, planes=planes), self.rate_firesdanger(coord, planes=planes), self.rate_collisionsdanger(coord, pself=pself, planes=planes)))
    def get_expectedallies(self):
        return {*self.expectedallies}
    def clear_expectedallies(self):
        self.expectedallies = set()
    def add_expectedallies(self, *coords):
        self.expectedallies.update(coords)
    def get_expectedshots(self):
        return {*self.expectedshots}
    def clear_expectedshots(self):
        self.expectedshots = set()
    def add_expectedshots(self, *rays):
        self.expectedshots.update(rays)
    def tickturn(self):
        self.clear_expectedallies()
        self.clear_expectedshots()

def stringException(exception):
    import traceback
    return ''.join(traceback.format_exception(type(exception), exception, exception.__traceback__))

try:
    import matplotlib.pyplot, matplotlib.cm, mpl_toolkits.mplot3d, time
    class PlottingDangerZones(DangerZones):
        """Arena object for calculating danger ratings and rendering 3D visualizations of the arena state and contents to both files and an interactive display on each turn."""
        plotparams = {'dangersize': 80, 'dangersizebase': 0.2, 'dangersizeexpo': 2.0, 'dangeralpha': 0.2, 'dangerres': 1, 'dangervrange': (0, 10), 'dangercmap': matplotlib.cm.nipy_spectral, 'dangermarker': 'o', 'allymarker': 's', 'enemymarker': 'D', 'vectormarker': 'x', 'planesize': 60, 'vectorsize': 50, 'planecolour': 'black', 'deathmarker': '*', 'deathsize': 700, 'deathcolours': ('darkorange', 'red'), 'deathalpha': 0.65, 'shotlength': 4, 'shotcolour': 'darkviolet', 'shotstyle': 'dashed'}
        enabledplots = ('enemies', 'allies', 'vectors', 'danger', 'deaths', 'shots', 'names')
        def __init__(self, dimensions=(13,13,13), plotparams=None, plotautoturn=0, plotsavedir=None, enabledplots=None, disabledplots=None, tickwait=0.0, plotcycle=0.001, **kwargs):
            DangerZones.__init__(self, dimensions, **kwargs)
            self.figure = None
            self.axes = None
            self.frame = None
            self.plotobjs = {}
            self.plotshown = False
            if plotparams:
                self.set_plotparams(plotparams)
            self.plotautoturn = plotautoturn
            self.plotsavedir = plotsavedir
            if enabledplots:
                self.enabledplots = tuple(enabledplots)
            if disabledplots:
                self.enabledplots = tuple(m for m in self.enabledplots if m not in disabledplots)
            self.tickwait = tickwait
            self.plotcycle = plotcycle
            self.lasttick = time.time()
        def set_plotparams(self, plotparams):
            self.plotparams = {**self.plotparams, **plotparams}
        def prepare_plotaxes(self, figure=None, clear=True):
            if self.figure is None and figure is None:
                self.figure = matplotlib.pyplot.figure()
                self.frame = 0
            if self.axes is None:
                self.axes = self.figure.add_subplot(projection='3d')
            elif clear:
                self.axes.clear()
            for d, h in zip((self.axes.set_xlim, self.axes.set_ylim, self.axes.set_zlim), self.dimensions):
                d(0, h)
            return (self.figure, self.axes)
        def plotter(kind):
            def plotterd(funct):
                def plott(self):
                    kws = dict(getattr(self, funct.__name__.replace('plot_', 'plotparams_'))())
                    if '*args' in kws:
                        args = tuple(kws.pop('*args'))
                    else:
                        args = tuple()
                    if False and funct.__name__ in self.plotobjs:
                        self.plotobjs[funct.__name__].set(**kws)
                    else:
                        self.plotobjs[funct.__name__] = getattr(self.axes, kind)(*args, **kws)
                return plott
            return plotterd
        def plotparams_enemies(self):
            r = {'xs': tuple(), 'ys': tuple(), 'zs': tuple(), 'marker': self.plotparams['enemymarker'], 's': self.plotparams['planesize'], 'c': self.plotparams['planecolour']}
            planes = tuple(self.filteractiveplanes(self.planes))
            if planes:
                r['xs'], r['ys'], r['zs'] = zip(*(p.coord for p in planes))
            return r
        def plotparams_allies(self):
            r = {'xs': tuple(), 'ys': tuple(), 'zs': tuple(), 'marker': self.plotparams['allymarker'], 's': self.plotparams['planesize'], 'c': self.plotparams['planecolour']}
            planes = tuple(self.filteractiveplanes(self.allies))
            if planes:
                r['xs'], r['ys'], r['zs'] = zip(*(p.coord for p in planes))
            return r
        def plotparams_vectors(self):
            r = {'xs': tuple(), 'ys': tuple(), 'zs': tuple(), 'marker': self.plotparams['vectormarker'], 's': self.plotparams['vectorsize'], 'c': self.plotparams['planecolour']}
            planes = tuple(self.filteractiveplanes(self.allies+self.planes))
            if planes:
                r['xs'], r['ys'], r['zs'] = zip(*(vecAdd(p.coord, p.vec) for p in planes))
            return r
        def plotparams_danger(self):
            r = {'xs': tuple(), 'ys': tuple(), 'zs': tuple(), 'marker': self.plotparams['dangermarker'], 'cmap': self.plotparams['dangercmap'], 'alpha': self.plotparams['dangeralpha']}
            coords = tuple(self.coords_grid((self.plotparams['dangerres'],)*len(self.dimensions)))
            r['xs'], r['ys'], r['zs'] = zip(*coords)
            r['c'] = tuple(self.rate_sumdanger(c) for c in coords)
            m = max(r['c'])
            r['s'] = tuple((d/m)**self.plotparams['dangersizeexpo']*self.plotparams['dangersize']+self.plotparams['dangersizebase'] for d in r['c'])
            if self.plotparams['dangervrange']:
                r['vmin'], r['vmax'] = self.plotparams['dangervrange']
            return r
        def plotparams_deaths(self):
            r = {'xs': tuple(), 'ys': tuple(), 'zs': tuple(), 'marker': self.plotparams['deathmarker'], 's': self.plotparams['deathsize'], 'c': self.plotparams['deathcolours'][0], 'linewidths': self.plotparams['deathsize']/180, 'edgecolors': self.plotparams['deathcolours'][1], 'alpha': self.plotparams['deathalpha']}
            deaths = tuple(p.lastcoord for p in self.planes+self.allies if p.lastalive and not p.alive)
            if deaths:
                r['xs'], r['ys'], r['zs'] = zip(*deaths)
            return r
        def plotparams_shots(self):
            r = {'length': self.plotparams['shotlength'], 'linestyles': self.plotparams['shotstyle'], 'color': self.plotparams['shotcolour'], 'arrow_length_ratio': 0.0, '*args': []}
            planes = tuple(p for p in self.filteractiveplanes(self.allies+self.planes) if not (p.lastcooldown is None or p.cooldown is None) and (p.cooldown > p.lastcooldown))
            if planes:
                for s in zip(*(p.coord for p in planes)):
                    r['*args'].append(s)
                for s in zip(*(p.vec for p in planes)):
                    r['*args'].append(s)
            else:
                for i in range(6):
                    r['*args'].append(tuple())
            return r
        @plotter('scatter')
        def plot_enemies(self):
            pass
        @plotter('scatter')
        def plot_allies(self):
            pass
        @plotter('scatter')
        def plot_vectors(self):
            pass
        @plotter('scatter')
        def plot_danger(self):
            pass
        @plotter('scatter')
        def plot_deaths(self):
            pass
        @plotter('quiver')
        def plot_shots(self):
            pass
        def plot_names(self):
            if 'plot_names' in self.plotobjs:
                pass
            self.plotobjs['plot_names'] = [self.axes.text(*p.coord, s=f"{p.name}") for i, p in enumerate(self.filteractiveplanes(self.allies+self.planes))]
        def plotall(self):
            for m in self.enabledplots:
                getattr(self, f'plot_{m}')()
        def updateallplots(self):
            self.prepare_plotaxes()
            self.plotall()
            if self.plotautoturn:
                self.axes.view_init(30, -60+self.frame*self.plotautoturn)
            matplotlib.pyplot.draw()
            if self.plotsavedir:
                import os
                os.makedirs(self.plotsavedir, exist_ok=True)
                self.figure.savefig(os.path.join(self.plotsavedir, f'{self.frame}.png'))
            self.frame += 1
            if not self.plotshown:
                matplotlib.pyplot.ion()
                matplotlib.pyplot.show()#block=False)
                self.plotshown = True
        def tickturn(self):
            DangerZones.tickturn(self)
            self.updateallplots()
            matplotlib.pyplot.pause(max(self.plotcycle, self.lasttick+self.tickwait-time.time()))
            self.lasttick = time.time()
except Exception as e:
    print(f"Could not define matplotlib rendering dangerzone handler:\n{stringException(e)}", file=sys.stderr)


def vecEquals(vec1, vec2):
    return tuple(vec1) == tuple(vec2)

def vecAdd(*vecs):
    return tuple(sum(p) for p in zip(*vecs))

def vecSub(vec1, vec2):
    return tuple(a-b for a, b in zip(vec1, vec2))

def vecMult(*vecs):
    return tuple(functools.reduce(lambda a, b: a*b, p) for p in zip(*vecs))

def vecDiv(vec1, vec2):
    return tuple(a-b for a, b in zip(vec1, vec2))

def vecDotProduct(*vecs):
    return sum(vecMult(*vecs))
    #return sum(d*p for d, p in zip(vec1, vec2))

def vecAngle(vec1, vec2):
    try:
        if all(c == 0 for c in vec1) or all(c == 0 for c in vec2):
            return math.nan
        return math.acos(max(-1, min(1, vecDotProduct(vec1, vec2)/Pythagorean(*vec1)/Pythagorean(*vec2))))
    except Exception as e:
        raise ValueError(f"{e!s}: {vec1} {vec2}")

def convertVecTrinary(vec, length=1):
    return tuple((max(-length, min(length, v*math.inf)) if v else v) for v in vec)

def getNeighbouringVecs(vec):
    vec = convertVecTrinary(vec, length=1)
    return {ve for ve in (tuple(v+(i//3**n%3-1) for n, v in enumerate(vec)) for i in range(3**len(vec))) if all(v in (-1,0,1) for v in ve) and any(v and v==vec[i] for i, v in enumerate(ve))}

def getVecRotation(vec1, vec2):
    #Just do a cross product/perpendicular to tangential plane/normal?
    pass

def applyVecRotation(vec, rotation):
    pass

class DangerZoner(Plane):
    """Dogfighting plane control object."""
    def __init__(self, arena, snipechance=0.60, snipechoices=3, firesafety=7.5, chasesafety=5.0, jinkdanger=math.inf, jink=0, name=None):
        Plane.__init__(self, True, None, None)
        self.arena = arena
        self.lookahead = 1
        self.snipechance = snipechance
        self.snipechoices = snipechoices
        self.firesafety = firesafety
        self.chasesafety = chasesafety
        self.jinkdanger = jinkdanger
        self.jink = jink
        self.vec = None
        self.name = name
    def get_enemies(self):
        return (p for p in self.arena.filteractiveplanes(self.arena.planes))
    def get_vecsuicidal(self, vec, coord=None, steps=5):
        if coord is None:
            coord = self.coord
        if all(3 < c < self.arena.dimensions[i]-3 for i, c in enumerate(coord)):
            return False
        if not all(0 < c < self.arena.dimensions[i] for i, c in enumerate(coord)):
            return True
        elif steps >= 0:
            return all(self.get_vecsuicidal(v, coord=vecAdd(coord, vec), steps=steps-1) for v in getNeighbouringVecs(vec))
        return False
    def get_sanevecs(self):
        legalvecs = self.get_legalvecs()
        s = {vec for vec in legalvecs if vecAdd(self.coord, vec) not in self.arena.get_expectedallies() and not any(vecAngle(vecSub(vecAdd(self.coord, vec), sc), sv) < 0.05 for sc, sv in self.arena.get_expectedshots()) and not self.get_vecsuicidal(vec, coord=vecAdd(self.coord, vec))}
        if not s:
            return legalvecs
            raise Exception()
        return s
    def rate_vec(self, vec, lookahead=None):
        if lookahead is None:
            lookahead = self.lookahead
        return self.arena.rate_sumdanger(tuple(c+v*lookahead for v, c in zip(vec, self.coord)), pself=self)
    def get_validshots(self, snipe=True):
        if snipe and random.random() < self.snipechance:
            enemypossibilities = set.union(*({vecAdd(p.coord, p.vec)} if not p.lastvec or vecEquals(p.vec, p.lastvec) else {vecAdd(p.coord, ve) for ve in sorted(p.get_legalvecs(), key=lambda v: -vecAngle(v, p.lastvec))[:self.snipechoices]} for p in self.get_enemies()))
        else:
            enemypossibilities = set().union(*(p.get_legalcoords() for p in self.get_enemies()))
        validshots = []
        if self.cooldown:
            return validshots
        for vec in self.get_sanevecs():
            coord = tuple(c + v for c, v in zip(self.coord, vec))
            if any(vecAngle(tuple(n-v for n, v in zip(t, self.coord)), self.vec) < 0.1 for t in enemypossibilities if t != self.coord) and not any(vecAngle(vecSub(a, coord), self.vec) < 0.05 for a in self.arena.get_expectedallies()):
                validshots.append({'vec': vec, 'turn': False, 'fire': True})
            if any(vecAngle(tuple(n-v for n, v in zip(t, self.coord)), vec) < 0.1 for t in enemypossibilities if t != self.coord) and not any(vecAngle(vecSub(a, coord), vec) < 0.05 for a in self.arena.get_expectedallies()):
                validshots.append({'vec': vec, 'turn': True, 'fire': True})
        if snipe and not validshots:
            validshots = self.get_validshots(snipe=False)
        return validshots
    def get_chase(self):
        enemydirs = {vecSub(vecAdd(p.coord, p.vec), self.coord) for p in self.get_enemies()}
        paths = sorted(self.get_sanevecs(), key=lambda vec: min([vecAngle(vec, e) for e in enemydirs if not all(v == 0 for v in e)]+[math.inf]))
        if paths:
            return paths[0]
    def get_move(self):
        if not self.alive:
            return {'vec': (1,1,1), 'turn': False, 'fire': False}
        fires = self.get_validshots()
        if fires:
            fires = sorted(fires, key=lambda d: self.rate_vec(d['vec']))
            if self.rate_vec(fires[0]['vec']) <= self.firesafety:
                return fires[0]
        vec = self.get_chase()
        if vec is None or self.rate_vec(vec) > self.chasesafety:
            vec = sorted(self.get_sanevecs(), key=self.rate_vec)
            vec = vec[min(len(vec)-1, random.randint(0,self.jink)) if self.rate_vec(vec[0]) > self.jinkdanger else 0]
        return {'vec': vec, 'turn': True, 'fire': False}
    def move(self):
        move = self.get_move()
        coord = vecAdd(self.coord, move['vec'])
        self.arena.add_expectedallies(coord)
        if move['fire']:
            self.arena.add_expectedshots((coord, move['vec'] if move['turn'] else self.vec))
        return move

VecsCarts = {(0,-1):'N', (0,1):'S', (1,1):'E', (1,-1):'W', (2,1):'U', (2,-1):'D'}

def translateCartVec(cartesian):
    vec = [0]*3
    for v,l in VecsCarts.items():
        if l in cartesian:
            vec[v[0]] = v[1]
    return tuple(vec)

def translateVecCart(vec):
    vec = convertVecTrinary(vec)
    return ''.join(VecsCarts[(i,v)] for i, v in enumerate(vec) if v != 0)

def parsePlaneState(text):
    return (lambda d: {'alive':{'alive': True, 'dead': False}[d[0]], 'coord':tuple(int(c) for c in d[1:4]), 'vec':translateCartVec(d[4]), 'cooldown': int(d[5])})(text.split(' '))

def encodePlaneInstruction(vec, turn, fire):
    return f"{translateVecCart(vec)} {int(bool(turn))!s} {int(bool(fire))!s}"

class CtrlReceiver:
    """Object for interacting through STDIN and STDOUT in a dogfight with an arena, controlled planes, and enemy planes."""
    def __init__(self, logname='danger_log.txt', arenatype=DangerZones, arenaconf=None, planetype=DangerZoner, planeconf=None, enemyname='Enemy', stdin=sys.stdin, stdout=sys.stdout):
        self.logname = logname
        self.arenatype = arenatype
        self.arenaconf = dict(arenaconf) if arenaconf else dict()
        self.planetype = planetype
        self.planeconf = dict(planeconf) if planeconf else dict()
        self.enemyname = enemyname
        self.stdin = stdin
        self.stdout = stdout
        self.log = open('danger_log.txt', 'w')
    def __enter__(self):
        return self
    def __exit__(self, *exc):
        self.log.__exit__()
    def getin(self):
        l = self.stdin.readline()
        self.log.write(f"IN: {l}")
        return l
    def putout(self, content):
        self.log.write(f"OUT: {content}\n")
        print(content, file=self.stdout, flush=True)
    def logout(self, content):
        self.log.write(f"MSG: {content}\n")
    def logerr(self, content):
        self.log.write(f"ERR: {content}\n")
    def run_setup(self, arenasize, rounds):
        self.arena = self.arenatype(dimensions=(arenasize,)*3, **self.arenaconf)
        self.planes = [self.planetype(arena=self.arena, name=f"{self.planetype.__name__} #{i}", **self.planeconf) for i in range(2)]
        self.arena.set_planes(*(Plane(True, None, None, name=f"{self.enemyname} #{i}") for i in range(2)))
        self.arena.set_allies(*self.planes)
    def run_move(self):
        self.arena.tickturn()
        for p in self.planes:
            p.update(**parsePlaneState(self.getin()))
        for p in self.arena.planes:
            p.update(**parsePlaneState(self.getin()))
        for p in self.planes:
            self.putout(encodePlaneInstruction(**p.move()))
    def run(self):
        line = ''
        while not line.startswith('NEW CONTEST '):
            line = self.getin()
        self.run_setup(arenasize=int(line.split(' ')[2])-1, rounds=None)
        while True:
            line = self.getin()
            if line.startswith('NEW TURN'):
                self.run_move()

if True and __name__ == '__main__' and not sys.flags.interactive:
    import time
    DoPlot = False
    #Use the arena object that visualizes progress every turn.
    DangerPlot = True
    #Compute and render a voxel cloud of danger ratings within the arena each turn if visualizing it.
    SparseDangerPlot = False
    #Use a lower resolution for the voxel cloud if visualizing danger ratings.
    TurntablePlot = True
    #Apply a fixed animation to the interactive visualization's rotation if visualizing the arena.
    with CtrlReceiver(logname='danger_log.txt', arenatype=PlottingDangerZones if DoPlot else DangerZones, arenaconf=dict(disabledplots=None if DangerPlot else ('danger'), plotparams=dict(dangerres=2) if SparseDangerPlot else dict(dangeralpha=0.1), plotautoturn=1 if TurntablePlot else 0, plotsavedir=f'PngFrames') if DoPlot else None, planetype=DangerZoner) as run:
        try:
            run.run()
        except Exception as e:
            run.logerr(stringException(e))
```
Will Chen
fuente