Faisons une guerre de chars!


18

Faisons une guerre de chars!

Partiellement inspiré par Destroy Them With Lazers

Objectif

Votre tâche consiste à contrôler un réservoir. Déplacez-vous et tirez sur d'autres chars et obstacles sur le champ de bataille 2D. Le dernier tank debout sera le vainqueur!

Format de carte

Votre réservoir sera sur un champ 2D basé sur un npar ngrille de carrés unitaires. Je déciderai ce qui nest basé sur le nombre de soumissions. Chaque carré ne peut contenir qu'un seul des éléments suivants:

  • Un réservoir
  • Un arbre
  • Un rocher
  • Un mur
  • Rien

Tous les obstacles et les chars remplissent complètement leurs espaces, et ils bloquent tous les tirs qui les frappent d'endommager les choses plus bas.

Voici un exemple de champ avec #= tank; T= arbre; R= roche; W= mur; .= rien avec n= 10

.....#....
..T....R..
WWW...WWWW
W......T..
T...R...Ww
W...W.....
W....W...T
WWWWWW...R
W.........
WWWWWWRT..

Les coordonnées sont dans le format x, yxaugmente de gauche à droite et yaugmente de bas en haut. L'espace en bas à gauche a les coordonnées 0, 0. Chaque char peut se déplacer vers n'importe quel espace vide et tirer dans n'importe quelle direction.

Dynamique de la carte

Votre char n'a pas qu'à tirer sur d'autres chars! S'il tire quelque chose sur la carte, des choses peuvent arriver.

  • Si un mur est abattu, il sera détruit après un certain nombre de tirs, allant de 1 à 4
  • Si un arbre est abattu, il sera immédiatement détruit
  • Si un rocher est abattu, le tir passe dessus et endommage la prochaine chose qu'il frappe

Une fois que quelque chose est détruit, il n'est plus sur la carte (il sera remplacé par rien). Si un tir détruit un obstacle, il sera bloqué et n'endommagera plus rien sur sa trajectoire.

Dynamique du réservoir

Chaque réservoir commence avec life= 100. Chaque tir sur un char réduit de 20 à 30 en lifefonction de la distance. Cela peut être calculé avec delta_life=-30+(shot_distance*10/diagonal_map_length)(où diagonal_map_lengthest (n-1)*sqrt(2)). De plus, chaque tank régénère 1 lifepar tour.

Se tourne

Un certain nombre de tours seront organisés (je déciderai une fois que j'aurai des soumissions). Au début de chaque manche, une carte sera générée aléatoirement et des chars y seront placés dans des emplacements vides aléatoires. À chaque tour, chaque tank recevra un tour, dans n'importe quel ordre arbitraire. Après que chaque char ait reçu un tour, il recevra à nouveau des tours dans le même ordre. La manche se poursuit jusqu'à ce qu'il ne reste plus qu'un tank. Ce char sera le vainqueur et il recevra 1 point. Le jeu se poursuivra ensuite au tour suivant.

Une fois tous les tours terminés, je publierai les scores sur cette question.

Pendant le tour d'un tank, il peut effectuer l'une des actions suivantes

  • Déplacez jusqu'à 3 espaces dans une seule direction, horizontalement ou verticalement. Si le réservoir est bloqué par un obstacle ou un autre réservoir, il sera déplacé le plus loin possible sans passer par l'obstacle ou le réservoir.
  • Tirez dans une certaine direction, représentée par un angle à virgule flottante en degrés. L'axe des x de l'espace local de votre réservoir (horizontalement de gauche à droite, alias est ou TurnAction.Direction.EAST) est de 0 degré et les angles augmentent dans le sens antihoraire. Les prises de vue sont inexactes et l'angle réel de la prise de vue peut être supérieur ou inférieur de 5 degrés à l'angle que vous choisissez.
  • Ne fais rien.

Les virages ne sont pas limités dans le temps, mais cela ne signifie pas que vous pouvez intentionnellement perdre du temps pour tout raccrocher.

Soumissions / Protocole

Chaque programme soumis contrôlera un réservoir sur le terrain. Le programme de contrôle est en Java, donc vos programmes doivent être en Java pour l'instant (je vais probablement écrire un wrapper pour d'autres langages à un moment donné, ou vous pourriez écrire le vôtre).

Vos programmes implémenteront l' Tankinterface, qui a les méthodes suivantes:

public interface Tank {
    // Called when the tank is placed on the battlefield.
    public void onSpawn(Battlefield field, MapPoint position);
    // Called to get an action for the tank on each turn.
    public TurnAction onTurn(Battlefield field, MapPoint position, float health);
    // Called with feedback after a turn is executed.
    // newPosition and hit will be populated if applicable.
    public void turnFeedback(MapPoint newPosition, FieldObjectType hit);
    // Called when the tank is destroyed, either by another tank,
    // or because the tank won. The won parameter indicates this.
    public void onDestroyed(Battlefield field, boolean won);
    // Return a unique name for your tank here.
    public String getName();
}

La Battlefieldclasse contient un tableau 2D d'objets ( Battlefield.FIELD_SIZEpar Battlefield.FIELD_SIZE) qui représente des choses sur le champ de bataille. Battlefield.getObjectTypeAt(...)donnera une FieldObjectTypede l'objet aux coordonnées spécifiées ( l' un des FieldObjectType.ROCK, FieldObjectType.TREE, FieldObjectType.TANK, FieldObjectType.WALL, ou FieldObjectType.NOTHING). Si vous essayez de sortir un objet hors de portée de la carte (coordonnées <0 ou> = Battlefield.FIELD_SIZE), un IllegalArgumentExceptionsera lancé.

MapPointest une classe permettant de spécifier des points sur la carte. Utilisez MapPoint.getX()et MapPoint.getY()pour accéder aux coordonnées.

EDIT: Certaines méthodes utilitaires ont été ajoutées: MapPoint.distanceTo(MapPoint), MapPoint.angleBetween(MapPoint), Battlefield.find(FieldObjectType)et TurnAction.createShootActionRadians(double)comme suggéré par Wasmoo .

Plus d'informations peuvent être trouvées dans les javadocs, voir la section ci-dessous.

Toutes les classes (API publiques) sont sous le package zove.ppcg.tankwar.

Programme de contrôle

La source complète et les javadocs du programme de contrôle et de l'API du réservoir peuvent être trouvés sur mon dépôt GitHub: https://github.com/Hungary-Dude/TankWarControl

N'hésitez pas à envoyer des demandes de tirage et / ou des commentaires si vous voyez un bug ou souhaitez une amélioration.

J'ai écrit deux exemples de programmes de réservoir, RandomMoveTanket RandomShootTank(le nom dit tout).

Pour exécuter votre réservoir, ajoutez votre classe de réservoir entièrement qualifiée (nom du package + nom de classe) à tanks.list(une classe par ligne), modifiez les paramètres si nécessaire dans zove.ppcg.tankwar.Control(délai de rotation, pour afficher ou non une représentation GUI du champ, etc.), et courir zove.ppcg.tankwar.Control. Assurez-vous qu'il y a au moins 2 réservoirs sur la liste, sinon les résultats ne sont pas définis. (Utilisez les réservoirs d'échantillonnage si nécessaire).

Vos programmes seront exécutés sur ma machine sous ce programme de contrôle. Je vais inclure un lien vers la source une fois que je l'ai écrit. N'hésitez pas à suggérer des modifications à la source.

Règles

  • Vos soumissions doivent suivre les directives ci-dessus
  • Vos programmes ne peuvent pas accéder au système de fichiers, au réseau ou tenter d'attaquer ma machine de quelque manière que ce soit
  • Vos programmes ne peuvent pas tenter d'exploiter mon programme de contrôle pour tricher
  • Pas de pêche à la traîne (comme faire intentionnellement perdre du temps à votre programme pour tout raccrocher)
  • Vous pouvez avoir plus d'une soumission
  • Essayez d'être créatif avec les soumissions!
  • Je me réserve le droit d'autoriser ou non les programmes arbitrairement

Bonne chance!

MISE À JOUR: Après avoir corrigé le bug de téléportation du mur et implémenté la régénération, j'ai exécuté les soumissions actuelles pendant 100 tours avecBattlefield.FIELD_SIZE = 30

MISE À JOUR 2: J'ai ajouté la nouvelle soumission, RunTank, après avoir un peu trompé Groovy ...

Résultats mis à jour:

+-----------------+----+
| RandomMoveTank  | 0  |
| RandomShootTank | 0  |
| Bouncing Tank   | 4  |
| Richard-A Tank  | 9  |
| Shoot Closest   | 19 |
| HunterKiller 2  | 22 |
| RunTank         | 23 |
| Dodge Tank      | 24 |
+-----------------+----+

Actuellement, les tanks régénèrent 1 point de vie par tour. Cela devrait-il être augmenté?


1
Pourquoi sont MapPoint« s xet y floats? Ne devraient-ils pas l'être ints?
IchBinKeinBaum

Bon point. Je ne sais pas pourquoi j'ai décidé de les faire flotter. Je vais les changer en pouces. Edit : les a mis à jour en pouces, vérifiez le repo
DankMemes

Si vous vous tenez au point 1,1 et tirez avec un angle de 0 degré, le projectile va dans la direction EST, non?
CommonGuy

@Manu Oui. Je suis désolé si ce n'était pas clair.
DankMemes

J'ai trouvé quelques erreurs: Battlefield.java:88 Parfois obj est nul (je pense quand un tank meurt alors que son action de déplacement est en attente) Control.java:151 Lorsque les tanks se tuent simultanément, get (0) n'est pas valide
Wasmoo

Réponses:


2

Chasseur tueur

Ce chasseur intelligent tentera de trouver une position sûre où il pourra proprement tirer sur exactement une cible. (Et donc, une seule cible peut le tirer)

Fonctionne mieux lorsqu'il y a beaucoup de couverture.

import zove.ppcg.tankwar.*;
import java.util.*;
public final class HunterKiller implements Tank {

    private final int MAX_DEPTH = 2;
    private Battlefield field;
    private List<MapPoint> enemies;
    private MapPoint me;
    private HashMap<MapPoint, MoveNode> nodeMap = new HashMap();

    //A description of how safe the position is from each tank in enemies
    private class Safety extends java.util.ArrayList<Double> {
        public int threats;
        public Safety(MapPoint position) {
            for (MapPoint p : enemies) {
                int obstacles = countObstacles(position, p, false);
                if (obstacles > 0) {
                    add((double) obstacles);
                } else {
                    add(missChance(position.distanceTo(p)));
                    threats++;
                }
            }
        }
    };

    //A description of a move
    private class Move {

        public TurnAction.Direction direction;
        public int distance;
        public MapPoint point;

        public Move(TurnAction.Direction direction, int distance, MapPoint point) {
            this.direction = direction;
            this.distance = distance;
            this.point = point;
        }

        public TurnAction action() {
            return TurnAction.createMoveAction(direction, distance);
        }

        @Override
        public String toString() {
            return direction + " " + distance;
        }
    }

    /**
     * A MoveNode holds a point and all the moves available from that point.
     * The relative safety of the node can be calculated as a function
     * of its depth; ie. this position is safe because we can can move to safety
     */
    private class MoveNode {

        MapPoint point;
        ArrayList<Move> moves;
        ArrayList<Safety> safetyArray = new ArrayList();

        public MoveNode(MapPoint point) {
            this.point = point;
            this.moves = getMoves(point);
        }

        public Safety getSafety(int depth) {
            if (safetyArray.size() <= depth) {
                Safety value;
                if (depth == 0 || this.moves.isEmpty()) {
                    value = new Safety(point);
                } else {
                    ArrayList<Safety> values = new ArrayList();
                    for (Move m : moves) {
                        MoveNode n = nodeMap.get(m.point);
                        if (n == null) {
                            n = new MoveNode(m.point);
                            nodeMap.put(n.point, n);
                        }
                        values.add(n.getSafety(depth - 1));
                    }
                    Collections.sort(values, cmp);
                    value = values.get(0);
                }
                safetyArray.add(depth, value);
            }
            return safetyArray.get(depth);
        }
    }

    /**
     * Find all the points between here and there, excluding those points
     */
    private java.util.ArrayList<MapPoint> path(final MapPoint p1, MapPoint p2) {
        java.util.ArrayList<MapPoint> ret = new ArrayList();
        float tankX = p1.getX();
        float tankY = p1.getY();
        double angle = p1.angleBetweenRadians(p2);
        double maxDistance = p1.distanceTo(p2);
        for (int x = 0; x < Battlefield.FIELD_SIZE; x++) {
            for (int y = 0; y < Battlefield.FIELD_SIZE; y++) {
                float x2 = (float) (((x - tankX) * Math.cos(-angle)) - ((y - tankY) * Math.sin(-angle)));
                float y2 = (float) (((x - tankX) * Math.sin(-angle)) + ((y - tankY) * Math.cos(-angle)));
                if (x2 > 0 && y2 >= -0.5 && y2 <= 0.5) {
                    MapPoint p = new MapPoint(x, y);
                    if (maxDistance > p1.distanceTo(p)) {
                        ret.add(p);
                    }
                }
            }
        }
        Collections.sort(ret, new Comparator<MapPoint>() {
            @Override
            public int compare(MapPoint o1, MapPoint o2) {
                return (int) Math.signum(p1.distanceTo(o2) - p1.distanceTo(o1));
            }
        });
        return ret;
    }

    /**
     * Find the number of obstacles between here and there, excluding those
     * points
     */
    private int countObstacles(MapPoint p1, MapPoint p2, boolean countRocks) {
        java.util.ArrayList<MapPoint> points = path(p1, p2);
        int count = 0;
        for (MapPoint p : points) {
            Object obj = field.getObjectTypeAt(p);
            if (FieldObjectType.NOTHING.equals(obj)
                    || (!countRocks && FieldObjectType.ROCK.equals(obj))
                    || (!countRocks && FieldObjectType.TANK.equals(obj))
                    || p.equals(me)) {
                count += 0;
            } else {
                count += 1;
            }
        }
        return count;
    }

    /**
     * Returns a value between 1.0 and 0.0, where 1.0 is far and 0.0 is close
     */
    private double missChance(double distance) {
        return distance / Battlefield.DIAGONAL_FIELD_SIZE;
    }

    //Returns a list of valid moves from the given position
    private ArrayList<Move> getMoves(MapPoint position) {
        ArrayList<Move> ret = new ArrayList();
        for (TurnAction.Direction d : TurnAction.Direction.values()) {
            for (int i = 1; i <= 3; i++) {
                MapPoint p = d.translate(position, i);
                try {
                    FieldObjectType t = field.getObjectTypeAt(p);
                    if (t != FieldObjectType.NOTHING && !p.equals(me)) {
                        break;
                    }
                    ret.add(new Move(d, i, p));
                } catch (IllegalArgumentException ex) {
                    //Can't move off the map...
                    break;
                }
            }
        }
        return ret;
    }

    //Compares two safeties with a preference for exactly 1 threat
    private Comparator<Safety> cmp = new Comparator<Safety>() {
        @Override
        public int compare(Safety o1, Safety o2) {
            int tc1 = o1.threats;
            int tc2 = o2.threats;
            //Prefer 1 threat
            if (tc2 == 1 && tc1 != 1) {
                return 1;
            }
            if (tc2 != 1 && tc1 == 1) {
                return -1;
            }

            //Prefer fewer threats
            if (tc2 != tc1) {
                return tc1 - tc2;
            }

            //We're splitting hairs here
            //Determine the least safe option
            int ret = -1;
            double s = Double.MAX_VALUE;
            for (Double d : o1) {
                if (d < s) {
                    s = d;
                }
            }
            for (Double d : o2) {
                if (d < s) {
                    ret = 1;
                    break;
                }
                if (d == s) {
                    ret = 0;
                }
            }
            if (tc1 > 1) {
                //Prefer the safest
                return -ret;
            } else {
                //Prefer the least safest
                return ret;
            }
        }
    };

    @Override
    public TurnAction onTurn(Battlefield field, MapPoint position, float health) {
        //Find all the tanks
        List<MapPoint> enemies = field.find(FieldObjectType.TANK);
        enemies.remove(position);
        if (enemies.isEmpty()) {
            return TurnAction.createNothingAction();
        }

        //Set the constants needed for this turn
        this.enemies = enemies;
        this.field = field;
        this.me = position;

        //Create a new NodeMap
        MoveNode n = new MoveNode(position);
        this.nodeMap.clear();
        this.nodeMap.put(position, n);

        //Find the "best" safety within MAX_DEPTH moves
        int depth = 0;
        Safety safety = n.getSafety(0);
        for (depth = 0; depth < MAX_DEPTH; depth++) {
            int lastThreat = safety.threats;
            safety = n.getSafety(depth);
            int newThreat = safety.threats;
            if (newThreat == 1) {
                //Always prefer 1 threat
                break;
            }
            if (depth != 0 && lastThreat - newThreat >= depth) {
                //Prefer fewer threats only if we are much safer;
                //  Specifically, don't move twice for only 1 less threat
                break;
            }
        }

        //Depth == 0         : Only 1 threat; best position
        //Depth == MAX_DEPTH : Many or no threats, but no good moves available
        if (depth > 0 && depth < MAX_DEPTH) {
            //Move towards the better spot
            for (Move m : n.moves) {
                if (nodeMap.get(m.point).getSafety(depth - 1) == safety) {
                    return m.action();
                }
            }
        }

        //We're in a good position, shoot now
        //Calculate tank with most threat
        MapPoint threat = null;
        double biggestThreat = Double.MAX_VALUE;
        for (MapPoint p : enemies) {
            double t = missChance(position.distanceTo(p)) + countObstacles(position, p, false);
            if (t < biggestThreat) {
                biggestThreat = t;
                threat = p;
            }
        }

        return TurnAction.createShootActionRadians(position.angleBetweenRadians(threat));
    }
    public void onDestroyed(Battlefield field, boolean won) {}
    public void onSpawn(Battlefield field, MapPoint position) {}
    public void turnFeedback(MapPoint newPosition, FieldObjectType hit) {}
    public String getName() {
        return "HunterKiller " + MAX_DEPTH;
    }

}

Et c'est tout. Je suis épuisé.


2

Ce char simple trouve le char ennemi le plus proche et lui tire dessus. Ce serait bien si find, distanceet angleont été construits dans et si createShootActionaccepté un double en radians (le résultat de angle)

Edit: classe réécrite pour inclure de nouvelles méthodes utilitaires

public final class ShootClosestTank implements Tank {

    @Override
    public TurnAction onTurn(Battlefield field, MapPoint position, float health) {
        //Find all the tanks
        List<MapPoint> tanks = field.find(FieldObjectType.TANK);
        tanks.remove(position);

        if (tanks.isEmpty()) return TurnAction.createNothingAction();

        //Calculate closest tank
        MapPoint cPoint = null;
        double cDist = Double.POSITIVE_INFINITY;
        for (MapPoint p : tanks) {
            double dist = position.distanceTo(p);
            if (dist < cDist) {
                cDist = dist;
                cPoint = p;
            }
        }

        //Shoot at closest tank
        return TurnAction.createShootActionRadians(position.angleBetweenRadians(cPoint));

    }

    @Override
    public void onDestroyed(Battlefield field, boolean won) {
        //Sucks to be me
    }

    @Override
    public void onSpawn(Battlefield field, MapPoint position) {
        //No setup
    }

    @Override
    public void turnFeedback(MapPoint newPosition, FieldObjectType hit) {
        //Nothing to update
    }

    @Override
    public String getName() {
        return "Shoot Closest";
    }
}

Would be nice if find, distance, and angle were built in, and if createShootAction accepted a double in radians (i.e. the result of angle)- Excellente idée, je vais l'ajouter.
DankMemes

1

Je ne suis pas très bon dans ce domaine, mais je pensais que je pourrais encore essayer, vous savez, vous entraîner et tout ça.

Mon char décidera au hasard de se déplacer ou de tirer. Lorsqu'il décide de tirer, il essaie de tirer sur la cible disponible la plus proche.

package com.richarda.tankwar;

import zove.ppcg.tankwar.*;

import java.util.Random;
import java.util.ArrayList;


public class RichardATank implements Tank
{
    private String name;

    public RichardATank()
    {
        this.name = "Richard-A Tank";
    }

    /**
     *
     * @param field
     *            The current battlefield, complete with all obstacle and tank
     *            positions
     * @param position
     */
    @Override
    public void onSpawn(Battlefield field, MapPoint position)
    {

    }

    /**
     * The tank will randomly move around and occasionally shoot at the nearest available target.
     *
     * @param field
     *            The current battlefield, complete with all obstacle and tank
     *            positions
     * @param position
     *            The tank's current position
     * @param health
     *            The tank's current health
     * @return
     */
    @Override
    public TurnAction onTurn(Battlefield field, MapPoint position, float health)
    {
        Random r = new Random();
        int n = r.nextInt(2);

        if(n == 1)
        {
            return this.tryShootAtNearestTank(field, position);
        }

        return TurnAction.createMoveAction(TurnAction.Direction.getRandom(), r.nextInt(2) + 1);
    }

    /**
     *
     * @param newPosition
     *            The tank's new position (may not be changed)
     * @param hit
     *            What the tank hit, if it decided to shoot
     */
    @Override
    public void turnFeedback(MapPoint newPosition, FieldObjectType hit)
    {

    }

    /**
     *
     * @param field
     *            The battlefield
     * @param won
     */
    @Override
    public void onDestroyed(Battlefield field, boolean won)
    {

    }

    @Override
    public String getName()
    {
        return this.name;
    }

    /**
     * Try and shoot at the nearest tank
     *
     * @param bf The battlefield
     * @param curTankLocation The current tank's location
     * @return TurnAction the shoot action to the nearest tank
     */
    private TurnAction tryShootAtNearestTank(Battlefield bf, MapPoint curTankLocation)
    {
        MapPoint nearestTankLoc = this.getNearestTankLocation(bf, curTankLocation);

        double firingAngle = curTankLocation.angleBetween(nearestTankLoc);

        return TurnAction.createShootAction((float) firingAngle);
    }

    /**
     * Try to find the nearest tank's location
     *
     * @param bf The battlefield
     * @param curTankLocation The current tank's location
     * @return MapPoint The location of the nearest tank
     */
    private MapPoint getNearestTankLocation(Battlefield bf, MapPoint curTankLocation)
    {
        ArrayList<MapPoint> enemyTankLocations = this.getEnemyTanksOnField(bf, curTankLocation);

        MapPoint nearestTankLoc = null;

        for(MapPoint enemyTankLoc : enemyTankLocations)
        {
            if(nearestTankLoc == null || curTankLocation.distanceTo(enemyTankLoc) < curTankLocation.distanceTo(nearestTankLoc))
            {
                nearestTankLoc = enemyTankLoc;
            }
        }

        return nearestTankLoc;
    }

    /**
     * Get all enemy tanks on the field
     *
     * @param bf The battlefield
     * @param curTankLocation The current tank's location
     * @return ArrayList<MapPoint> A list with all enemy tanks in it
     */
    private ArrayList<MapPoint> getEnemyTanksOnField(Battlefield bf, MapPoint curTankLocation)
    {
        int maxSize = Battlefield.FIELD_SIZE;
        ArrayList<MapPoint> tanks = new ArrayList<MapPoint>();

        for(int i = 0; i < maxSize; i++)
        {
            for(int j = 0; j < maxSize; j++)
            {
                FieldObjectType objType = bf.getObjectTypeAt(i, j);

                if(objType == FieldObjectType.TANK)
                {
                    MapPoint tankLocation = new MapPoint(i, j);

                    if(!tankLocation.equals(curTankLocation))
                    {
                        tanks.add(tankLocation);
                    }
                }
            }
        }

        return tanks;
    }
}

Le code complet comprenant le programme de contrôle peut être trouvé ici .


EssayezDirection.getRandom()
DankMemes

@ZoveGames Modifié, merci pour l'astuce.
MisterBla

1

Dodge Tank

Ce char tirera sur le char le plus proche. De temps en temps, en fonction de sa santé et de la dernière fois qu'il a bougé, il essaiera de se déplacer perpendiculairement au char le plus proche afin d'esquiver ses lasers.

public class DodgeTank implements Tank {

private int lastMove;
@Override
public void onSpawn(Battlefield field, MapPoint position){
    //nada
}
@Override
public TurnAction onTurn(Battlefield field, MapPoint position, float health){
    List<MapPoint> tanks = field.find(FieldObjectType.TANK);
    tanks.remove(position);
    MapPoint nearest= new MapPoint(0,0);
    double dis=Double.POSITIVE_INFINITY;
        for (MapPoint tank: tanks){
            double distance=tank.distanceTo(position);
            if (distance<dis){
                nearest=tank;
                dis=distance;
            }
        }
    if (lastMove*Math.random()+(4-health/25)<5){//Attack
        lastMove++;
        return TurnAction.createShootActionRadians(position.angleBetweenRadians(nearest));
    }
    else {
        lastMove=0;
        double cumulativeAngle=position.angleBetweenRadians(nearest);
        for (MapPoint tank : tanks){
            cumulativeAngle+=position.angleBetweenRadians(tank);
        }
        cumulativeAngle/=tanks.size();
        cumulativeAngle/=(Math.PI/2);
        int dir=(int)Math.floor(cumulativeAngle);
        TurnAction move;
        switch (dir){
            case 0:
                if (position.getX()>2&&field.getObjectTypeAt(position.cloneAndTranslate(-3, 0)).equals(FieldObjectType.NOTHING)){
                    return TurnAction.createMoveAction(TurnAction.Direction.WEST, 3);
                }
                return TurnAction.createMoveAction(TurnAction.Direction.EAST, 3);
            case 1: 
                if (position.getY()>2&&field.getObjectTypeAt(position.cloneAndTranslate(0, -3)).equals(FieldObjectType.NOTHING)){
                    return TurnAction.createMoveAction(TurnAction.Direction.SOUTH, 3);
                }
                return TurnAction.createMoveAction(TurnAction.Direction.NORTH, 3);
            case -1:
                if ((position.getY()<(Battlefield.FIELD_SIZE-4))&&field.getObjectTypeAt(position.cloneAndTranslate(0,3)).equals(FieldObjectType.NOTHING)){
                    return TurnAction.createMoveAction(TurnAction.Direction.NORTH, 3);
                }
                return TurnAction.createMoveAction(TurnAction.Direction.SOUTH, 3);
            default:
                if ((position.getX()<(Battlefield.FIELD_SIZE-4))&&field.getObjectTypeAt(position.cloneAndTranslate(3,0)).equals(FieldObjectType.NOTHING)){
                    return TurnAction.createMoveAction(TurnAction.Direction.EAST, 3);
                }
                return TurnAction.createMoveAction(TurnAction.Direction.WEST, 3);
        }
    }
}
@Override
public void turnFeedback(MapPoint newPosition, FieldObjectType hit){
    //Nada
}
@Override
public void onDestroyed(Battlefield field, boolean won){
  //if (won) this.taunt();
  //else this.selfDestruct();
}
@Override
public String getName(){
    return "Dodge Tank";
}
}

1

C'était bien plus compliqué que je ne le pensais ..

Ceci est mon entrée dans groovy, vous devez installer groovy et le compiler avec

groovyc zove\ppcg\tankwar\felsspat\RunTank.groovy

Pour l'appeler, vous devez ajouter $ GROOVY_HOME / Groovy / Groovy-2.3.4 / lib / groovy-2.3.4.jar (ou n'importe quelle version) au chemin de classe.

Je pourrais vous envoyer un fichier .class compilé et la bibliothèque si vous ne voulez pas l'installer.

Il semble y avoir une situation où les chars ne peuvent pas le voir autrement, je ne sais pas si c'est prévu. Cela a provoqué des blocages lors des tests.

Quoi qu'il en soit, voici RunTank: RunTank avance hardiment dans la direction opposée du réservoir le plus proche s'il s'agit du réservoir le plus proche du réservoir le plus proche ou si plusieurs réservoirs se trouvent dans FIELD_SIZE / 3. J'espère que cela a du sens, je suis ivre :)

package zove.ppcg.tankwar.felsspat

import zove.ppcg.tankwar.Battlefield
import zove.ppcg.tankwar.FieldObjectType
import zove.ppcg.tankwar.MapPoint
import zove.ppcg.tankwar.Tank
import zove.ppcg.tankwar.TurnAction
import zove.ppcg.tankwar.TurnAction.Direction

public class RunTank implements Tank {  

    @Override
    public TurnAction onTurn(Battlefield field, MapPoint position, float health) {
        def targets = (field.find(FieldObjectType.TANK) - position).sort{ position.distanceTo(it) }

        if (targets) {

            def runDistance = (Battlefield.FIELD_SIZE / 3).toInteger()

            def closeTargets = targets.grep {
                position.distanceTo(it) < runDistance
            }

            if (position.distanceTo(closestEnemy(position, field)) < targets.first().distanceTo(closestEnemy(targets.first(), field))) {
                return run(field, position, targets)
            }           

            if (closeTargets.size() > 1) {
                return run(field, position, targets)
            } else  {
                return shootEnemy(position, targets)
            }
        } else {
            println "WTF! Targets: ${field.find(FieldObjectType.TANK)} + Position ${position}"
            return TurnAction.createMoveAction(Direction.getRandom(), 1);
        }

    }

    def shootEnemy(position, targets) {
        return TurnAction.createShootActionRadians(position.angleBetweenRadians(targets.first()))   
    }

    def run(field, position, targets) {
        def freePositions = (field.find(FieldObjectType.NOTHING) - position).grep { position.distanceTo(it) <= 3.0 && [0d, 90d, 180d, 270d].contains(position.angleBetween(it))}.sort{position.distanceTo(it)}      
        def availablePositions = []
        freePositions.each { targetPosition ->          
            def positions = getPositionsBetween(position,targetPosition)
            if (! positions || positions.every { it.equals(FieldObjectType.NOTHING) }) {
                availablePositions.add(targetPosition)  
            }                   
        }
        availablePositions = availablePositions.sort{closestEnemy(it, field)}.reverse()

        if (availablePositions) {
            def targetPosition = availablePositions.first()
            if (targetPosition.distanceTo(closestEnemy(targetPosition, field)) > position.distanceTo(closestEnemy(position, field))) { // Don't move closer to an enemy
                return moveTo(position, targetPosition)
            } else {
                return shootEnemy(position, targets)
            }       
        } else {
            return shootEnemy(position, targets)
        }
    }

    def moveTo(position, targetPosition) {
        def distance = position.distanceTo(targetPosition).toInteger()
        switch (position.angleBetween(targetPosition)) {
            case 0d:
                return TurnAction.createMoveAction(TurnAction.Direction.NORTH, distance)
                break

            case 90d:
                return TurnAction.createMoveAction(TurnAction.Direction.EAST, distance)
                break

            case 180d:
                return TurnAction.createMoveAction(TurnAction.Direction.SOUTH, distance)
                break

            case 270d:
                return TurnAction.createMoveAction(TurnAction.Direction.West, distance)
                break           

            default:
            println "I'm stupid :("

        }
    }

    def closestEnemy(position, field) {
        return field.find(FieldObjectType.TANK).sort { position.distanceTo(it) }.first()
    }

    def getPositionsBetween(self, target) {
        def positions = []
        if(self.x == target.x) {
            for (y in self.y..target.y) {
                positions.add(new MapPoint(self.x, y))
            }
        } else {
            for (x in self.x..target.x) {
                positions.add(new MapPoint(x, self.y))
            }
        }
        return positions - self - target
    }

    @Override
    public void turnFeedback(MapPoint newPosition, FieldObjectType hit) {
    }

    @Override
    public void onDestroyed(Battlefield field, boolean won) {
        println ":("
    }

    @Override
    public void onSpawn(Battlefield field, MapPoint position) {
        println "Go!"
    }

    @Override
    public String getName() {
        return "RunTank";
    }   
}

J'ai une suggestion: ajouter des couleurs au réservoir et une méthode pour le mettre en œuvre. Les étiquettes seraient également bien dans l'interface graphique :)


def RandomMoveTank() {}- est-ce censé être là? (Je ne sais pas groovy)
DankMemes

Non, j'ai copié le RandomMoveTank et j'ai oublié de supprimer le constructeur, merci :)
Fels

J'ai compilé votre code et ajouté le dossier contenant les fichiers .class et le pot groovy à mon chemin de classe de projet. La réflexion a fonctionné! J'ai publié les scores mis à jour. Votre tank a plutôt bien fonctionné :)
DankMemes

1
Agréable! Et bon sang DodgeTank!
Fels

1

Celui-ci est une variante du Shoot-Closest en ce que, tous les deux tours, il se déplace dans une direction jusqu'à ce qu'il ne puisse plus. Il tire tous les deux tours.

Il a un utilitaire pratique path, qui peut être utilisé pour identifier tous les points (et donc les objets) entre deux points.

public final class BouncingTank implements Tank {

    /**
     * Find all the points between here and there, excluding those points
     * @param p1 Here
     * @param p2 There
     * @return 
     */
    private java.util.ArrayList<MapPoint> path(MapPoint p1, MapPoint p2) {
        double dist = p1.distanceTo(p2);
        double dx = (p2.getX() - p1.getX()) / dist;
        double dy = (p2.getY() - p1.getY()) / dist;

        java.util.ArrayList<MapPoint> ret = new java.util.ArrayList();
        MapPoint lastP = null;
        for (int i = 0; i < dist; i++) {
            MapPoint p = p1.cloneAndTranslate((int)(i*dx), (int)(i*dy));
            if (p.equals(p1) || p.equals(lastP)) continue;
            if (p.equals(p2)) break;
            ret.add(p);
            lastP = p;
        }
        return ret;
    }

    /**
     * Find the number of legal moves in the given direction
     * @param field
     * @param position
     * @param dir
     * @return 
     */
    private int findMoves(Battlefield field, MapPoint position, TurnAction.Direction dir) {
        if (dir == null) return -1;
        int count = 0;
        MapPoint dest = dir.translate(position, Battlefield.FIELD_SIZE);
        java.util.ArrayList<MapPoint> obs = path(position, dest);
        for (MapPoint oMP : obs) {
            try {
                if (FieldObjectType.NOTHING.equals(field.getObjectTypeAt(oMP))) {
                    count++;
                } else {
                    break;
                }
            } catch (IllegalArgumentException ex) {
                break;
            }
        }
        return count;
    }

    /**
     * Finds the direction towards which there are the fewest obstacles
     * @param field
     * @param position
     * @return 
     */
    private TurnAction.Direction findDirection(Battlefield field, MapPoint position) {
        TurnAction.Direction ret = null;
        int mostMoves = 0;
        for (TurnAction.Direction d : TurnAction.Direction.values()) {
            int count = findMoves(field, position, d);
            if (count > mostMoves) {
                ret = d;
                mostMoves = count;
            }
        }
        return ret; //Maybe null
    }

    private TurnAction shootClosest(Battlefield field, MapPoint position) {
        //Find all the tanks
        List<MapPoint> tanks = field.find(FieldObjectType.TANK);
        tanks.remove(position);

        if (tanks.isEmpty()) return TurnAction.createNothingAction();

        //Calculate closest tank
        MapPoint cPoint = null;
        double cDist = Double.POSITIVE_INFINITY;
        for (MapPoint p : tanks) {
            double dist = position.distanceTo(p);
            if (dist < cDist) {
                cDist = dist;
                cPoint = p;
            }
        }

        //Shoot at closest tank
        return TurnAction.createShootActionRadians(position.angleBetweenRadians(cPoint));
    }

    private int turnsUntilShoot = 1;
    private TurnAction.Direction moveToward = null;

    @Override
    public TurnAction onTurn(Battlefield field, MapPoint position, float health) {
        //Determine if current direction is valid
        int moves = findMoves(field, position, moveToward);
        if (moves <= 0) {
            moveToward = findDirection(field, position);
            //Determine if we're stuck
            if (moveToward == null) {
                return shootClosest(field, position);
            }
        }


        //Shoot if it's time
        if (turnsUntilShoot == 0) {
            turnsUntilShoot = 1;
            return shootClosest(field, position);
        } else {
            turnsUntilShoot--;
            return TurnAction.createMoveAction(moveToward, Math.min(3, moves));
        }
    }

    public void onDestroyed(Battlefield field, boolean won) {}
    public void onSpawn(Battlefield field, MapPoint position) {}
    public void turnFeedback(MapPoint newPosition, FieldObjectType hit) {}
    public String getName() {
        return "Bouncing Tank";
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.