3D: Duel Dogfighting discret (Maintenant ouvert aux soumissions non Java)

31

MISE À JOUR: isSuicidal () a été ajouté à la classe d'avion, cela vous permet de vérifier si un avion est sur une trajectoire de collision irréversible avec les murs !!

UPDATE: updateCoolDown () séparé de simulateMove ()

MISE À JOUR: wrapper d'entrée non Java, écrit par Sparr , disponible pour les tests, voir les commentaires

MISE À JOUR Zove Games a écrit un visualiseur 3D génial pour ce KOTH, voici une vidéo youtube de merde de PredictAndAVoid combattant PredictAndAVoid.

La fonction simulateMove () de la classe Plane a été légèrement modifiée afin de ne plus mettre à jour le refroidissement, utilisez pour cela la nouvelle fonction updateCoolDown () après le tournage. Le nouveau isSuicidal () retourne vrai si un avion est lié à la mort, utilisez-le pour tailler les mouvements ennemis et éviter de heurter les murs. Pour obtenir le code mis à jour, remplacez simplement les classes Controller et Plane par celles du dépôt github.

La description

Le but de ce défi est de coder deux avions de combat qui s'affronteront contre deux avions par un autre concurrent. À chaque tour, vous vous déplacez d'une case et avez la possibilité de tirer. C'est ça, c'est aussi simple que ça.

Enfin, presque ...

Arène et mouvements possibles

L'arène est un mur de 14x14x14 dans l'espace. les avions du concurrent 1 partent aux endroits (0,5,0) et (0,8,0) et ceux du concurrent 2 aux (13,5,13) et (13,8,13). Tous les avions commencent par voler horizontalement loin des murs verticaux dont ils sont les plus proches.

Maintenant que vous pilotez des avions et non des hélicoptères, vous ne pouvez pas simplement changer de direction à volonté ou même arrêter de bouger, donc chaque avion a une direction et déplace une tuile dans cette direction à chaque tour.

Les directions possibles sont: Nord (N), Sud (S), Est (E), Ouest (W), Haut (U) et Bas (D) et toute combinaison logique de ces six. Où l'axe NS correspond à l'axe x, WE à y et DU à z. NW, SU et NED viennent à l'esprit comme exemples possibles de directions; UD est un excellent exemple d'une combinaison non valide.

Vous pouvez bien sûr changer la direction de vos avions, mais il y a une limitation, vous ne pouvez changer votre direction que de 45 degrés au maximum. Pour visualiser cela, saisissez votre cube de rubik (je sais que vous en avez un) et imaginez que les 26 petits cubes externes sont les directions possibles (les directions à une lettre sont des faces, les directions à deux lettres sont des bords et les directions à trois lettres sont des coins). Si vous vous dirigez dans une direction représentée par un petit cube, vous pouvez changer la direction de chaque cube qui touche le vôtre (toucher en diagonale compte, mais seulement toucher visiblement, ce n'est pas toucher à travers le cube).

Une fois que tous les avions ont indiqué dans quelle direction ils aimeraient changer, ils le font et déplacent une tuile simultanément.

Vous pouvez également choisir de vous déplacer dans une direction valide mais de continuer à voler dans la direction que vous alliez, au lieu de changer votre direction dans la direction vers laquelle vous vous êtes déplacé. Ceci est analogue à la différence entre une voiture qui passe un virage et une voiture qui change de voie.

Tirer et mourir

Vous pouvez tirer au maximum une fois par tour et cela doit être décidé en même temps que vous décidez dans quelle direction voler et si vous voulez garder votre avion (et par extension, votre arme) pointé dans la même direction ou non. La balle est tirée juste après le déplacement de votre avion. Il y a un refroidissement d'un tour après le tir, au troisième tour, vous êtes prêt à recommencer. Vous ne pouvez tirer que dans la direction dans laquelle vous volez. Une balle est instantanée et vole en ligne droite jusqu'à ce qu'elle heurte un mur ou un avion.

Compte tenu de la façon dont vous pouvez changer de direction ainsi que des `` changements de voie '', cela signifie que vous pouvez menacer une colonne de 3x3 lignes devant vous en plus de quelques lignes diagonales simples.

S'il frappe un avion, cet avion meurt et disparaît rapidement du plateau (car il explose totalement ou quelque chose). Les balles ne peuvent frapper qu'un seul avion au maximum. Les balles sont tirées simultanément, donc deux avions peuvent se tirer dessus. Cependant, deux balles ne peuvent pas entrer en collision (triste, je sais).

Cependant, deux avions peuvent entrer en collision (s'ils se retrouvent dans le même cube et NON s'ils se croisent sans se retrouver dans le même plan), ce qui entraîne la mort des deux avions (et une explosion totale). Vous pouvez également voler dans le mur, ce qui entraînera l'avion en question à mourir et à être mis dans le coin pour réfléchir à ses actions. Les collisions sont traitées avant le tournage.

Communication avec le contrôleur

J'accepterai les entrées en java ainsi que dans d'autres langues. Si votre entrée est en java, vous obtiendrez une entrée via STDIN et une sortie via STDOUT.

Si votre entrée est en java, votre entrée doit étendre la classe suivante:

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

L'instance créée de cette classe persistera pendant toute la compétition, vous pouvez donc stocker toutes les données que vous souhaitez stocker dans des variables. Lisez les commentaires dans le code pour plus d'informations.

Je vous ai également fourni les classes d'assistance suivantes:

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) {}
}

Vous pouvez créer des instances de ces classes et utiliser autant de fonctions que vous le souhaitez. Vous pouvez trouver le code complet de ces classes d'assistance ici .

Voici un exemple de ce à quoi votre entrée pourrait ressembler (j'espère que vous ferez mieux que moi, cependant, la plupart des matchs avec ces avions se terminent avec eux volant dans un mur, malgré leurs meilleurs efforts pour éviter le mur.):

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 rejoindra le tournoi avec les autres entrées, donc si vous finissez dernier, c'est de votre faute pour au moins faire mieux que DumbPlanes.

Les restrictions

Les restrictions mentionnées dans le wiki KOTH s'appliquent:

  • Toute tentative de bricolage avec le contrôleur, le temps d'exécution ou d'autres soumissions sera disqualifiée. Toutes les soumissions ne devraient fonctionner qu'avec les entrées et le stockage qui leur sont fournis.
  • Les robots ne doivent pas être écrits pour battre ou soutenir d'autres robots. (Cela peut être souhaitable dans de rares cas, mais s'il ne s'agit pas d'un concept central du défi, il est préférable de l'exclure.)
  • Je me réserve le droit de disqualifier les soumissions qui utilisent trop de temps ou de mémoire pour exécuter des essais avec une quantité raisonnable de ressources.
  • Un bot ne doit pas implémenter la même stratégie exacte qu'une stratégie existante, intentionnellement ou accidentellement.

Tester votre soumission

Téléchargez le code du contrôleur ici . Ajoutez votre soumission en tant que Something.java. Modifiez Controller.java pour inclure les entrées de votre avion dans les entrées [] et les noms []. Compilez tout en tant que projet Eclipse ou avec javac -d . *.java, puis exécutez le contrôleur avec java Planes/Controller. Un journal du concours sera disponible test.txt, avec un tableau de bord à la fin. Vous pouvez également appeler matchUp()directement avec deux entrées comme arguments pour simplement tester deux plans l'un contre l'autre.

Gagner le combat

Le vainqueur du combat est celui qui a le dernier avion volant, si après 100 tours, il reste encore plus d'une équipe, l'équipe avec le plus d'avions restants gagne. Si c'est égal, c'est un match nul.

Scoring et compétition

Le prochain tournoi officiel aura lieu lorsque la prime actuelle sera épuisée.

Chaque entrée combattra chaque autre entrée (au moins) 100 fois, le vainqueur de chaque match sera celui qui aura le plus de victoires sur 100 et recevra 2 points. En cas d'égalité, les deux participations obtiennent 1 point.

Le vainqueur de la compétition est celui qui a le plus de points. En cas d'égalité, le vainqueur est celui qui a gagné dans un match up entre les entrées qui ont fait match nul.

Selon le nombre d'entrées, le nombre de combats entre les entrées pourrait être considérablement augmenté, je pourrais également sélectionner les 2-4 meilleures entrées après le premier tournoi et mettre en place un tournoi d'élite entre ces entrées avec plus de combats (et éventuellement plus de tours par bats toi)

(préliminaire) Tableau de bord

Nous avons une nouvelle entrée qui prend fermement la deuxième place dans un autre tournoi passionnant , il semble que Crossfire soit incroyablement difficile à tirer pour tout le monde, sauf pour PredictAndAvoid. Notez que ce tournoi a été organisé avec seulement 10 combats entre chaque ensemble d'avions et n'est donc pas une représentation entièrement exacte de la situation.

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

Voici un exemple de sortie du wrapper non Java:

NEW CONTEST 14 20 indique qu'un nouveau concours commence, dans une arène 14x14x14, et qu'il impliquera 20 tours par combat.

NEW OPPONENT 10 indique que vous faites face à un nouvel adversaire et que vous combattrez cet adversaire 10 fois

NEW FIGHT 5 3 2 indique qu'un nouveau combat contre l'adversaire actuel commence, que vous avez combattu cet adversaire 5 fois jusqu'à présent, en gagnant 3 et en perdant 2 combats

ROUNDS LEFT 19 indique qu'il reste 19 rounds dans le combat en cours

NEW TURN indique que vous êtes sur le point de recevoir des données pour les quatre avions pour ce tour de combat

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

Ces quatre lignes indiquent que vos deux avions sont vivants, aux coordonnées [13,8,13] et [13,5,13] respectivement, tous deux orientés vers le nord, tous les deux avec un temps de recharge nul. Le premier avion ennemi est mort, et le second est vivant, à [0,8,0] et face au sud avec zéro temps de recharge.

À ce stade, votre programme doit produire deux lignes similaires à la suivante:

NW 0 1
SU 1 0

Cela indique que votre premier avion voyagera vers le nord-ouest, sans se détourner de son cap actuel, et tirera si possible. Votre deuxième avion voyagera vers SouthUp, se tournant pour faire face à SouthUp, sans tirer.

Maintenant, vous êtes ROUNDS LEFT 18suivi par NEW TURNetc. Cela continue jusqu'à ce que quelqu'un gagne ou que le round expire, auquel cas vous obtenez une autre NEW FIGHTligne avec le compte de combat et les scores mis à jour, éventuellement précédé d'un NEW OPPONENT.

suracteur
la source
Si quelqu'un a besoin d'aide pour relever ce défi, vous pouvez accéder au chat que j'ai créé pour ce défi.
suracteur
Les avions commencent-ils en direction Est / Ouest ou Nord / Sud? ou autre chose?
pseudonym117
2
@overactor il y a un bogue dans le code de récupération. Vous utilisez simulateMove dans la section "Calculer les nouvelles positions", qui diminue le temps de recharge en plus de trouver de nouvelles positions. Cela signifie qu'un avion peut tirer à chaque tour s'il ignore son propre compteur de temps de recharge.
Sparr
2
Pour ceux qui pourraient le trouver utile, cette expression régulière recherchera dans le journal pour trouver où votre avion tire ^ Déplacer (. *?) Tirer: vrai $ (remplacez "Move" par votre nom, et assurez-vous que. lignes)
user2813274
1
voici un commit pour mon wrapper d'avion, avec un plan python stupide. J'adorerais que quelqu'un écrive un avion plus intelligent en perl / python / lua / bash / peu importe et me donne des commentaires sur si / comment le wrapper fonctionne pour vous. github.com/sparr/Dogfight-KOTH/commit/… si les gens peuvent / vont utiliser cela, nous pouvons l'intégrer dans le dépôt de @ overactor et autoriser les soumissions de langue arbitraires.
Sparr

Réponses:

5

Feux croisés

Mon idée initiale était de tirer sur un avion ennemi avec mes deux avions en même temps, mais je n'ai pas pu le résoudre ... Voici donc un avion qui essaie de rester loin des murs et hors du champ de tir du ennemi. Les avions ne doivent jamais entrer en collision ni tirer sur des avions amis.

Edit: la méthode possibleHitsretourne toujours 0, après l'avoir corrigé et ajouté plusieurs petites améliorations, elle fonctionne mieux qu'avant.

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
la source
1
Vous êtes actuellement la deuxième meilleure entrée, après PredictAndAvoid. Vous gagnez contre toutes les autres participations, mais vous tirez beaucoup. Contre PredictAndAvoid, Whirligig parvient à imposer plus de victoires et de nuls que vous. Bonne entrée malgré tout!
suracteur
1
@overactor Merci pour votre contribution! Cela signifie que je dois travailler au tournage ...
CommonGuy
1
Je viens de faire plus de tests, il semble que vous perdiez moins contre Whirligig que PredictAndAvoid, PredictAndAvoid gère bien plus de victoires cependant, voici les données pour 2000 combats: PredictAndAvoid: 1560 Whirligig: 138 | PredictAndAvoid: 1564 Crossfire: 125 | Whirligig: 25 Crossfire: 600
overactor
@overactor J'ai trouvé le temps d'améliorer ma soumission. Maintenant, il gagne, dessine et perd parfois contre PredictAndAvoid.
CommonGuy
1
Bravo, après 10000 combats: SCORE: PredictAndAvoid: 1240 Crossfire: 6567
suracteur
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
la source
1
bat actuellement Whirligig presque à chaque fois. Besoin de rechercher un bug dans le code d'évitement des tirs ennemis.
Sparr
1
correction du bug. 0 défaite contre les adversaires actuels.
Sparr
2
travaille sur la diminution des tirages, des progrès importants. J'ai besoin d'avions ennemis plus intelligents avant de pouvoir progresser beaucoup plus.
Sparr
1
Je priorité à passer à un endroit sûr, au- dessus tir unsafely
Cruncher
1
@Cruncher le fait déjà dans ma copie locale, et il améliore les performances de quelques% contre les adversaires actuels. Évite également les collisions lorsque je ne suis pas en avance sur les avions survivants. mise à jour à venir!
Sparr
18

Visualiseur 3D Dogfight

J'ai écrit un petit visualiseur rapide pour ce défi. Les fichiers de code et de jar sont sur mon dépôt github: https://github.com/Hungary-Dude/DogfightVisualizer
Il est fait en utilisant libGDX ( http://libgdx.com ). En ce moment, l'interface utilisateur est assez mauvaise, j'ai mis cela en place rapidement.

J'apprends juste à utiliser Git et Gradle, veuillez donc commenter si j'ai fait quelque chose de mal

Courez dist/dogfight.batou dist/dogfight.shpour voir DumbPlanes en action!

Pour construire à partir des sources, vous aurez besoin de l' intégration Gradle ( http://gradle.org ) et Gradle pour votre IDE, si vous en avez un. Ensuite, clonez le dépôt et exécutez gradlew desktop:run. Espérons que Gradle importera toutes les bibliothèques nécessaires. La classe principale est zove.koth.dogfight.desktop.DesktopLauncher.

Exécuter sans importer

Copiez tous les fichiers de classe d'avion dans dist/. Ensuite, exécutez dist/desktop-1.0.jaravec cette commande:

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

Je mettrai à jour à mesure que la source du contrôleur Planes sera mise à jour, mais pour vous mettre à jour, vous devrez ajouter du code à Planes.Controller. Voir le readme de github pour plus d'informations à ce sujet.

Voici une capture d'écran: Capture d'écran

Si vous avez des questions ou des suggestions, laissez un commentaire ci-dessous!

DankMemes
la source
C'est génial, j'ai mis en place un projet où j'ai ajouté les classes d'avion, comment puis-je maintenant exécuter le visualiseur avec ces avions? Peut-être que cela serait mieux expliqué dans le chat . À titre de suggestion, ce serait formidable si vous pouviez coller un journal minimal d'une allumette et ensuite parcourir cette allumette, aussi, je pense que vous avez peut-être réduit les cooridats, les avions devraient commencer respectivement au sol et au plafond. Un travail incroyable cependant !!
suracteur
J'ai pris les Point3D représentant la position du plan et j'ai soustrait 6,5 de chaque coordonnée pour les déplacer dans la vue. Quelque chose comme plane.transform.setToTranslation(new Vector3(point3d.x-6.5f,point3d.y-6.5f,point3d.z-6.5f))Aucun avion semble sortir des limites donc je doute que quelque chose ne va pas
DankMemes
Ah attendez, utilisez-vous l'axe des y comme hauteur? (comme dans la plupart des jeux, je suppose) Dans mon système, le z représente la hauteur, pas que cela compte beaucoup, car il est symétrique
suracteur
Ohhhhhhh je comprends. Désolé, je n'ai pas vraiment regardé votre code. Je viens de traduire Point3Ds directement en libgdx Vector3s. Soit dit en passant, je serai absent pendant environ une semaine à partir de demain. Désolé si je ne suis pas ici si vous avez besoin de quelque chose. J'essaierai de m'enregistrer pendant mon absence.
DankMemes
12

EmoFockeWulf

Il est de retour. Il s'est affamé à 224 octets. Il ne sait pas comment il a fini comme ça.

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
la source
13
Cela devient sérieusement incontrôlable maintenant. Que diriez-vous de le bannir pour toujours au poste standard des échappatoires?
user80551
13
@ user80551 Je pense que c'est un style de jeu valide, peu importe. Aucune raison de le bannir.
Seiyria
3
Il est 47 octets plus gros qu'il ne l'était!
johnchen902
2
Il pourrait se suicider plus rapidement que ça. Emo pas très efficace.
Sparr
2
@Sparr oui, mais alors il y aurait un nombre d'octets plus élevé et cela perdrait l'ironie de ne pas toujours perdre. : P
cjfaure
10

Weeeeeeeeeeee - 344 octets après suppression de l'espace blanc

Est-ce que des boucles et des trucs impressionnants. Vous ne pouvez pas perdre si vous faites des boucles.

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

EDIT: apparemment, lorsque mon avion a commencé en équipe 2, ils se sont juste écrasés immédiatement contre le mur. Je pense que j'ai corrigé cela maintenant. J'espère.

pseudonyme117
la source
Votre déclaration de retour n'est pas légale. En Java, pour créer des tableaux d'objets spécifiant tout le contenu sur une seule ligne que vous devez utiliser new Type[]{item1, item2, ...}, dans ce cas, vous auriezreturn new Move[]{new Move(d,z,a),new Move(d,z,a^=z)};
DankMemes
Essayez également browxy.com si vous ne disposez pas d'un IDE téléchargé. (Ce n'est pas puissant du tout mais ça marche)
DankMemes
merci, j'ai oublié si cela fonctionnait ou non. Je ne voulais tout simplement pas télécharger ses cours pour que tout l'héritage et le package fonctionnent.
pseudonym117
Après avoir exécuté vos avions avec le nouveau code, il ne fait que retourner S et SU et mourir à chaque fois au 15e tour. Une idée pourquoi?
suracteur
hmm ... non. apparemment, j'ai foiré mon changement. espérait vraiment que ça marcherait ... va juste annuler la modification.
pseudonym117
6

Avancez et tirez

Évite les murs en trouvant quand il est près d'un mur et en tournant, tire quand il le peut.

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

Avertissement: je ne suis pas du tout un programmeur Java, donc si j'ai foiré quelque chose, veuillez le réparer pour moi!

Kyle Kanos
la source
Je ne l'ai pas encore testé, mais cela ne fonctionnera pas, vous essayez de tourner à 180 degrés à la fois. À titre indicatif, essayez N-> NU-> U-> SU-> S au lieu de N-> S ou remplacez U par D si le toit est plus proche que le sol.
suracteur
@overactor: J'ai raté le you may only change your angle by 45 degreesmorceau.
Kyle Kanos
Aucun problème, cela ne devrait pas être trop difficile à résoudre.
suracteur
Vous devriez probablement utiliser HashMap <String> au lieu de Hashtable. Sinon, new Direction(wayToGo.get(currentDirection))ne fonctionnera pas car il oublie de caster en chaîne. wayToGo.put après le champ n'est pas non plus valide, placez-le dans un bloc {wayToGo.put (blah); blah;} ou dans le constructeur.
Luna
7
Pour l'instant, il gagne tout en ne volant pas dans les murs.
suracteur
6

Toupie

Les deux avions se dirigent vers le centre (ish), puis bouclent en tirant aussi souvent que possible. Un des trois axes est choisi par combat, et la paire tourne toujours autour du même axe dans des directions opposées.

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

}
Géobits
la source
4

DumbPlanes

Les DumbPlanes essaient si fort de ne pas voler dans les murs, mais ils ne sont pas très intelligents à ce sujet et finissent généralement par heurter les murs de toute façon. Ils tirent aussi occasionnellement, si seulement ils savaient sur quoi ils tiraient.

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?
    }
}
suracteur
la source
4

Starfox (WIP - ne fonctionne pas encore):

Il n'utilise pas réellement tous les mouvements disponibles. Mais il essaie d'abattre les ennemis et de ne pas s'écraser contre les murs.

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?
    }
}
utilisateur2813274
la source
7
Mais peut-il faire un tonneau?
Erty Seidohl
1
Je reçois une exception, voici la trace de la pile: exception dans le thread "main" java.lang.NullPointerException chez Planes.Starfox $ 2.test (Starfox.java:99) chez Planes.Starfox $ 2.test (Starfox.java:1 ) sur java.util.ArrayList.removeIf (Source inconnue) sur Planes.Starfox.act (Starfox.java:90) sur Planes.Controller.fight (Controller.java:141) sur Planes.Controller.matchUp (Controller.java: 85) à Planes.Controller.main (Controller.java:35) J'ai dû ajouter des plans de paquets, sinon cela ne compilerait pas, peut-être que cela avait quelque chose à voir avec cela.
suracteur
Je l'ai fait fonctionner mais il ne fonctionne pas aussi bien que prévu, je pense que le problème pourrait être que le meilleur est nul trop souvent.
suracteur
Il semble que Starfox se déplace dans le feu ennemi plutôt que hors de lui, vous pouvez voir ce qui se passe ici.
suracteur
3

DangerZoner

  • Écrit en Python et s'interface avec le wrapper de code non Java écrit par Sparr.

  • Fait tout son calcul en Python pur et n'est pas optimisé. Un peu lent.

  • Hautement configurable et extensible.

  • Fait très bien contre les soumissions antérieures. Gagne 2: 1 combats pour chacun perdu contre Crossfireou PredictAndAvoid, et gagne 98 +% de tous les combats contre d'autres prétendants.

Comprend son propre outil de visualisation en option:

Combattre Crossfire/ PredictAndAvoid, avec les notes de zone de danger éponymes visualisées dans le volume environnant:

Vidéo de quatre avions combattant les chiens en deux tours avec une grille de voxels colorés se transformant autour d'eux.

  • Visualisé en utilisant nipy_spectralcolourmap de matplotlib. Des coordonnées plus dangereuses sont rendues en utilisant des couleurs plus proches du rouge / blanc dans le spectre électromagnétique et sont dessinées avec des points plus gros.

  • Danger: bleu <vert <jaune <rouge <gris clair

Performance:

1000 rounds avec les huit meilleurs algorithmes du classement:

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.

Code:

#!/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
la source