Weźmy wojnę czołgów!


18

Weźmy wojnę czołgów!

Częściowo zainspirowany Destroy Them With Lazers

Cel

Twoim zadaniem jest kontrolowanie czołgu. Poruszaj się i strzelaj do innych czołgów i przeszkód na polu bitwy 2D. Ostatni stojący czołg zostanie zwycięzcą!

Format mapy

Zbiornik będzie na polu 2D oparte na nprzez nsiatkę kwadratów jednostkowych. Zdecyduję, co nbędzie oparte na liczbie zgłoszeń. Każdy kwadrat może zawierać tylko jeden z:

  • Czołg
  • Drzewo
  • Skała
  • Ściana
  • Nic

Wszystkie przeszkody i czołgi całkowicie wypełniają swoje pola i blokują wszystkie strzały, które do nich trafiły, od zadawania obrażeń dalej.

Oto przykład pola z #= tank; T= drzewo; R= kamień; W= ściana; .= nic z n= 10

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

Współrzędne mają format, w x, yktórym xzwiększa się od lewej do prawej i yzwiększa się od dołu do góry. Lewa dolna spacja ma współrzędną 0, 0. Każdy czołg może przenieść się na dowolne puste miejsce i strzelać w dowolnym kierunku.

Dynamika mapy

Twój czołg nie musi tylko strzelać do innych czołgów! Jeśli strzela coś na mapie, może się zdarzyć.

  • Jeśli zostanie wystrzelona ściana, zostanie zniszczona po pewnej liczbie strzałów, od 1 do 4
  • Jeśli drzewo zostanie wystrzelone, zostanie natychmiast zniszczone
  • Jeśli zostanie wystrzelona skała, strzał przejdzie nad nią i uszkodzi następną rzecz, w którą trafi

Gdy coś zostanie zniszczone, nie będzie go już na mapie (zostanie zastąpione niczym). Jeśli strzał zniszczy przeszkodę, zostanie ona zablokowana i nie uszkodzi niczego dalej na swojej drodze.

Dynamika czołgu

Każdy czołg zaczyna się od life= 100. Każdy strzał w czołg zmniejsza się o 20-30 w lifezależności od odległości. Można to obliczyć za pomocą delta_life=-30+(shot_distance*10/diagonal_map_length)(gdzie diagonal_map_lengthjest (n-1)*sqrt(2)). Dodatkowo każdy zbiornik regeneruje 1 lifena turę.

Okazuje

Zostanie przeprowadzona pewna liczba rund (zdecyduję, kiedy otrzymam zgłoszenia). Na początku każdej rundy losowo generowana jest mapa, na której umieszczane będą czołgi w losowo pustych miejscach. Podczas każdej rundy każdy czołg otrzyma turę w dowolnej kolejności. Po tym, jak każdy czołg otrzyma turę, otrzymają kolejność w tej samej kolejności. Runda trwa, dopóki nie zostanie tylko jeden czołg. Ten czołg będzie zwycięzcą i otrzyma 1 punkt. Gra przejdzie do następnej rundy.

Po rozegraniu wszystkich rund opublikuję wyniki dotyczące tego pytania.

Podczas tury czołgu może wykonać jedną z następujących czynności

  • Poruszaj się maksymalnie o 3 spacje w jednym kierunku, poziomo lub pionowo. Jeśli zbiornik zostanie zablokowany przez przeszkodę lub inny czołg, zostanie przesunięty tak daleko, jak to możliwe, bez przechodzenia przez przeszkodę lub zbiornik.
  • Strzelaj w określonym kierunku, reprezentowanym przez kąt zmiennoprzecinkowy w stopniach. Oś X lokalnej przestrzeni twojego czołgu (poziomo od lewej do prawej, czyli wschód lub TurnAction.Direction.EAST) wynosi 0 stopni, a kąty rosną przeciwnie do ruchu wskazówek zegara. Strzały są niedokładne, a rzeczywisty kąt strzału może być o 5 stopni większy lub mniejszy niż wybrany kąt.
  • Nic nie robić.

Tury nie są ograniczone w czasie, ale nie oznacza to, że możesz celowo tracić czas na rozłączanie wszystkiego.

Zgłoszenia / protokół

Każdy zgłoszony program będzie kontrolować jeden czołg na polu. Program sterujący jest w Javie, więc twoje programy na razie muszą być w Javie (prawdopodobnie w pewnym momencie napiszę opakowanie dla innych języków lub możesz napisać własny).

Twoje programy zaimplementują Tankinterfejs, który ma następujące metody:

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

BattlefieldKlasa zawiera 2D tablicę obiektów ( Battlefield.FIELD_SIZEprzez Battlefield.FIELD_SIZE), która reprezentuje to, co na polu bitwy. Battlefield.getObjectTypeAt(...)da FieldObjectTypedla obiektu w określonych współrzędnych (jeden FieldObjectType.ROCK, FieldObjectType.TREE, FieldObjectType.TANK, FieldObjectType.WALL, i FieldObjectType.NOTHING). Jeśli spróbujesz wyciągnąć obiekt poza zasięg mapy (współrzędne <0 lub> = Battlefield.FIELD_SIZE), wówczas IllegalArgumentExceptionzostanie wyrzucony znak „ an” .

MapPointto klasa do określania punktów na mapie. Użyj MapPoint.getX()i, MapPoint.getY()aby uzyskać dostęp do współrzędnych.

EDIT: Niektóre metody użytkowe zostały dodane: MapPoint.distanceTo(MapPoint), MapPoint.angleBetween(MapPoint), Battlefield.find(FieldObjectType), i TurnAction.createShootActionRadians(double)jak sugeruje Wasmoo .

Więcej informacji można znaleźć w javadocs, patrz sekcja poniżej.

Wszystkie klasy (publiczne API) znajdują się w pakiecie zove.ppcg.tankwar.

Program kontroli

Pełne źródło i javadocs programu sterującego i interfejsu API czołgu można znaleźć na moim repozytorium GitHub: https://github.com/Hungary-Dude/TankWarControl

Możesz wysyłać żądania ściągnięcia i / lub komentować, jeśli zobaczysz błąd lub chcesz poprawić.

Napisałem dwa programy przykładowych zbiorników RandomMoveTanki RandomShootTank(nazwa mówi wszystko).

Aby uruchomić swój czołg, dodaj swoją w pełni kwalifikowaną (nazwa pakietu + nazwa klasy) klasę czołgu tanks.list(jedną klasę na linię), edytuj ustawienia w razie potrzeby w zove.ppcg.tankwar.Control(opóźnienie skrętu, czy wyświetlać graficzną reprezentację pola itp.), i biegnij zove.ppcg.tankwar.Control. Upewnij się, że na liście znajdują się co najmniej 2 czołgi, w przeciwnym razie wyniki są niezdefiniowane. (W razie potrzeby użyj zbiorników na próbki).

Twoje programy będą uruchamiane na moim komputerze w ramach tego programu sterującego. Po napisaniu dołączę link do źródła. Możesz sugerować zmiany w źródle.

Zasady

  • Twoje zgłoszenia muszą być zgodne z powyższymi wytycznymi
  • Twoje programy mogą nie uzyskiwać dostępu do systemu plików, sieci ani próbować atakować mojego komputera w jakikolwiek sposób
  • Twoje programy nie mogą próbować wykorzystywać mojego programu kontrolnego do oszukiwania
  • Bez trollingu (np. Celowe marnowanie programu na rozłączanie wszystkiego)
  • Możesz mieć więcej niż jedno zgłoszenie
  • Staraj się być kreatywny przy przesyłaniu zgłoszeń!
  • Zastrzegam sobie prawo do dowolnego zezwalania lub niedozwolania programów

Powodzenia!

AKTUALIZACJA: Po naprawieniu błędu teleportacji na ścianie i wdrożeniu regeneracji, uruchomiłem bieżące zgłoszenia przez 100 rund zBattlefield.FIELD_SIZE = 30

AKTUALIZACJA 2: Dodałem nowe zgłoszenie, RunTank, po wygłupieniu z Groovy na trochę ...

Zaktualizowane wyniki:

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

Obecnie czołgi regenerują 1 życie na turę. Czy należy to zwiększyć?


1
Dlaczego MapPoint„s xa y floats? Nie powinny ints?
IchBinKeinBaum

Słuszna uwaga. Nie jestem pewien, dlaczego zdecydowałem się na ich pływanie. Zamienię je na ints. Edycja : zaktualizowałem je do ints, sprawdź repo
DankMemes

Jeśli staniesz na punkcie 1,1 i będziesz strzelał pod kątem 0 stopni, pocisk skieruje się na wschód, prawda?
CommonGuy,

@Manu Tak. Przepraszam, jeśli to nie było jasne.
DankMemes

Znalazłem kilka błędów: Battlefield.java:88 Czasami obj ma wartość zerową (myślę, że gdy czołg umiera, gdy oczekuje się jego ruchu) Control.java:151 Gdy czołgi jednocześnie się zabijają, get (0) jest nieważne
Wasmoo

Odpowiedzi:


2

HunterKiller

Ten inteligentny myśliwy spróbuje znaleźć bezpieczną pozycję, w której będzie mógł strzelać do dokładnie jednego celu. (I dlatego tylko jeden cel może go zastrzelić)

Działa najlepiej, gdy jest dużo ochrony.

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

}

I to wszystko. Jestem wydana


2

Ten prosty czołg znajduje najbliższy czołg wroga i strzela do niego. Byłoby miło, gdyby find, distancei anglezostały wbudowane, a jeśli createShootActionzaakceptowano by podwójną liczbę radianów (tj. Wynik angle)

Edycja: klasa przepisana w celu włączenia nowych metod narzędziowych

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)- Świetny pomysł, dodam go.
DankMemes

1

Nie jestem w tym zbyt dobry, ale myślałem, że nadal dam mu szansę, wiesz, ćwiczyć i tak dalej.

Mój czołg losowo zdecyduje się na ruch lub strzelanie. Kiedy zdecyduje się strzelić, spróbuje strzelić do najbliższego dostępnego celu.

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

Pełny kod zawierający program sterujący można znaleźć tutaj .


SpróbujDirection.getRandom()
DankMemes,

@ZoveGames Edytowałem to, dziękuję za podpowiedź.
MisterBla

1

Dodge Tank

Ten czołg będzie strzelał do najbliższego czołgu. Co jakiś czas, w zależności od stanu zdrowia i ostatniego ruchu, będzie próbował poruszać się prostopadle do najbliższego zbiornika, próbując uniknąć laserów.

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

To było o wiele bardziej skomplikowane niż myślałem ...

To jest mój wpis w groovy, musisz go zainstalować i skompilować

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

Aby to nazwać, musisz dodać $ GROOVY_HOME / Groovy / Groovy-2.3.4 / lib / groovy-2.3.4.jar (lub dowolną wersję) do ścieżki klasy.

Mogę przesłać Ci skompilowany plik .class i bibliotekę, jeśli nie chcesz go instalować.

Wygląda na to, że czołgi nie widzą tego inaczej, nie wiem, czy to jest zamierzone. To spowodowało zakleszczenia podczas testów.

W każdym razie tutaj jest RunTank: Boldy RunTank przesuwa się w przeciwnym kierunku do najbliższego zbiornika, jeśli jest to najbliższy zbiornik do najbliższego zbiornika lub więcej niż jeden zbiornik znajduje się w FIELD_SIZE / 3. Mam nadzieję, że to ma sens, jestem pijany :)

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

Mam jedną sugestię: dodaj kolory do zbiornika i metodę jego wdrożenia. Także etykiety byłyby fajne w GUI :)


def RandomMoveTank() {}- czy to ma tam być? (Nie wiem groovy)
DankMemes,

Nie, skopiowałem RandomMoveTank i zapomniałem usunąć konstruktora, dzięki :)
Fels,

Skompilowałem twój kod i dodałem folder zawierający pliki .class i groovy jar do mojej ścieżki klas projektu. Odbicie zadziałało! Opublikowałem zaktualizowane wyniki. Twój czołg spisał się całkiem dobrze :)
DankMemes,

1
Ładny! I cholera, DodgeTank!
Fels

1

Ten wariant jest wariantem „Strzelaj najbliżej”, w którym co drugi zakręt porusza się w danym kierunku, aż nie będzie już w stanie. Wystrzeliwuje co drugą turę.

Ma przydatne narzędzie, za pomocą pathktórego można zidentyfikować wszystkie punkty (a tym samym obiekty) między dwoma punktami.

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