AKTUALIZACJA: do klasy samolotu dodano isSuicidal (), co pozwala sprawdzić, czy samolot znajduje się na nieodwracalnym kursie zderzenia ze ścianami !!
AKTUALIZACJA: updateCoolDown () oddzielone od simulateMove ()
AKTUALIZACJA: opakowanie wejściowe inne niż Java, napisane przez Sparr , dostępne do testowania, patrz komentarze
AKTUALIZACJA Zove Games napisała niesamowity wizualizator 3D dla tego KOTH, oto gówniany film na YouTube z PredictAndAVoid walczącymi PredictAndAVoid.
Funkcja simulateMove () klasy Plane została nieznacznie zmodyfikowana, aby nie aktualizowała już chłodzenia, użyj do tego nowej funkcji updateCoolDown () po zakończeniu strzelania. Nowa funkcja isSuicidal () zwraca wartość true, jeśli samolot musi zginąć, użyj go do przycinania ruchów wroga i unikania uderzania w ściany. Aby uzyskać zaktualizowany kod, po prostu zamień klasy Controller i Plane na klasy z repozytorium github.
Opis
Celem tego wyzwania jest zakodowanie dwóch samolotów bojowych , które zmierzą się z dwoma samolotami przez innego zawodnika. Za każdym razem poruszasz się o jedno pole i masz okazję strzelać. To wszystko, to takie proste.
Cóż prawie...
Arena i możliwe ruchy
Arena ma wymiary 14 x 14 x 14 w przestrzeni. samoloty zawodnika 1 rozpoczynają się w miejscach (0,5,0) i (0,8,0), a samoloty zawodnika 2 w (13,5,13) i (13,8,13). Wszystkie samoloty zaczynają się od lotu poziomo od pionowych ścian, do których są najbliżej.
Teraz, gdy lecisz samolotem, a nie helikopterem, nie możesz po prostu dowolnie zmieniać kierunku, a nawet przestać się poruszać, więc każdy samolot ma kierunek i przesuwa jedną płytkę w tym kierunku co turę.
Możliwe kierunki to: północ (N), południe (S), wschód (E), zachód (W), góra (U) i dół (D) oraz dowolna logiczna kombinacja tych sześciu. Gdzie oś NS odpowiada osi x, WE to y i DU do z. NW, SU i NED przychodzą na myśl jako możliwe przykłady kierunków; UD jest doskonałym przykładem nieprawidłowej kombinacji.
Możesz oczywiście zmienić kierunek swoich samolotów, ale jest ograniczenie, możesz zmienić kierunek tylko o maksymalnie 45 stopni. Aby to sobie wyobrazić, chwyć kostkę rubika (wiem, że ją masz) i wyobraź sobie, że wszystkie 26 zewnętrznych małych kostek to możliwe kierunki (jedna litera to twarze, dwie litery to krawędzie, a trzy litery to rogi). Jeśli zmierzasz w kierunku reprezentowanym przez małą kostkę, możesz zmienić kierunek na każdą kostkę, która dotyka twojego (liczy się dotykanie ukośne, ale tylko dotykanie widoczne, to znaczy nie dotykanie przez kostkę).
Po tym, jak wszystkie samoloty wskażą kierunek, w którym chcą się zmienić, robią to i przesuwają jeden kafelek jednocześnie.
Możesz także zdecydować się na ruch we właściwym kierunku, ale kontynuuj lot w kierunku, w którym jedziesz, zamiast zmieniać kierunek w kierunku, w którym się poruszasz. Jest to analogiczne do różnicy między samochodem jadącym za rogiem a samochodem zmieniającym pas.
Strzelanie i umieranie
Możesz strzelać najwyżej raz na rundę i to musi być ustalone w tym samym czasie, kiedy decydujesz, w którym kierunku lecieć i czy chcesz utrzymać swój samolot (a przez to broń) skierowany w tym samym kierunku, czy nie. Kula zostaje wystrzelona zaraz po tym, jak twój samolot się porusza. Po strzale następuje ochłodzenie w jednej turze, w trzeciej kolejce możesz zacząć od nowa. Możesz strzelać tylko w kierunku, w którym lecisz. Kula jest natychmiastowa i leci w linii prostej, aż uderzy w ścianę lub samolot.
Biorąc pod uwagę sposób, w jaki możesz zmieniać kierunek, a także „zmieniać pasy”, oznacza to, że możesz zagrozić kolumną do 3x3 linii przed tobą oprócz niektórych ukośnych, pojedynczych linii.
Jeśli uderzy w samolot, samolot ten umiera i natychmiast znika z planszy (ponieważ całkowicie eksploduje lub coś takiego). Pociski mogą trafić tylko jeden samolot. Pociski strzelają jednocześnie, więc dwa samoloty mogą strzelać do siebie. Dwie kule nie mogą jednak zderzyć się w powietrzu (smutne, wiem).
Jednak dwie płaszczyzny mogą się zderzyć (jeśli kończą się w tym samym sześcianie, a NIE, jeśli przecinają się bez lądowania w tej samej płaszczyźnie), co powoduje śmierć obu płaszczyzn (i całkowite wybuchnięcie). Możesz także wlecieć do ściany, co spowoduje śmierć samolotu i umieszczenie go w kącie, aby pomyśleć o jego działaniach. Kolizje są obsługiwane przed zrobieniem zdjęcia.
Komunikacja z kontrolerem
Przyjmę zgłoszenia w języku Java i innych językach. Jeśli twój wpis jest w Javie, otrzymasz dane wejściowe przez STDIN i dane wyjściowe przez STDOUT.
Jeśli twój wpis jest w Javie, Twój wpis musi rozszerzyć następującą klasę:
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();
}
Instancja utworzona dla tej klasy będzie utrzymywać się przez całą konkurencję, więc możesz przechowywać dowolne dane, które chcesz przechowywać w zmiennych. Przeczytaj komentarze w kodzie, aby uzyskać więcej informacji.
Udostępniłem Ci również następujące klasy pomocników:
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) {}
}
Możesz tworzyć instancje tych klas i korzystać z dowolnej ich funkcji tak, jak chcesz. Pełny kod dla tych klas pomocników można znaleźć tutaj .
Oto przykład tego, jak może wyglądać Twoje zgłoszenie (mam nadzieję, że poradzisz sobie lepiej niż ja, ale większość meczów z tymi samolotami kończy się lataniem w ścianę, pomimo dołożenia wszelkich starań, aby ominąć ścianę.):
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 dołączy do turnieju wraz z innymi wpisami, więc jeśli skończysz ostatni, to twoja wina, że przynajmniej nie radzisz sobie lepiej niż DumbPlanes.
Ograniczenia
Obowiązują ograniczenia wymienione w wiki KOTH :
- Wszelkie próby majsterkowania przy kontrolerze, środowisku wykonawczym lub innych zgłoszeniach zostaną zdyskwalifikowane. Wszystkie zgłoszenia powinny działać tylko z danymi wejściowymi i pamięcią.
- Boty nie powinny być pisane w celu pokonania lub wspierania określonych innych botów. (Może to być pożądane w rzadkich przypadkach, ale jeśli nie jest to podstawowa koncepcja wyzwania, lepiej je wykluczyć).
- Zastrzegam sobie prawo do zdyskwalifikowania zgłoszeń, które zużywają zbyt dużo czasu lub pamięci, aby przeprowadzić testy z rozsądną ilością zasobów.
- Bot nie może wdrożyć dokładnie tej samej strategii, co istniejąca, celowo lub przypadkowo.
Testowanie zgłoszenia
Pobierz kod kontrolera stąd . Dodaj swoje zgłoszenie jako Something.java. Zmodyfikuj Controller.java, aby uwzględnić wpisy dla swojego samolotu we wpisach [] i nazwach []. Skompiluj wszystko jako projekt Eclipse lub za pomocą javac -d . *.java
, a następnie uruchom Kontroler za pomocą java Planes/Controller
. Pojawi się dziennik zawodów test.txt
z tablicą wyników na końcu. Możesz także wywoływać matchUp()
bezpośrednio z dwoma wpisami jako argumentami, aby po prostu przetestować dwie płaszczyzny względem siebie.
Wygrać walkę
Zwycięzcą walki jest ten, który leci ostatnim samolotem, jeśli po 100 turach pozostanie więcej niż 1 drużyna, drużyna z największą ilością pozostałych samolotów wygrywa. Jeśli to jest równe, to remis.
Punktacja i konkurencja
Następny oficjalny turniej zostanie rozegrany, gdy skończy się aktualna nagroda.
Każdy wpis będzie walczył z każdym innym (przynajmniej) 100 razy, zwycięzcą każdego pojedynku jest ten, który wygra najwięcej ze 100 i otrzyma 2 punkty. W przypadku losowania oba zgłoszenia otrzymują 1 punkt.
Zwycięzcą konkursu jest ten, który zdobył najwięcej punktów. W przypadku remisu zwycięzcą jest ten, który wygrał w meczu pomiędzy losowanymi zakładami.
W zależności od liczby zgłoszeń, ilość walk między wpisami może zostać znacznie zwiększona, mogę również wybrać 2-4 najlepsze wpisy po pierwszym turnieju i ustawić elitarny turniej między tymi wejściami z większą liczbą walk (i ewentualnie więcej rund na walka)
(wstępna) tablica wyników
Mamy nowy wpis, który zdecydowanie zajmuje drugie miejsce w kolejnym ekscytującym turnieju , wydaje się, że Crossfire jest niezwykle trudny do strzelenia dla wszystkich oprócz PredictAndAvoid. Pamiętaj, że ten turniej był rozgrywany tylko z 10 walkami między każdym zestawem samolotów i dlatego nie jest do końca dokładnym przedstawieniem tego, jak się rzeczy mają.
----------------------------
¦ 1. PredictAndAvoid: 14 ¦
¦ 2. Crossfire: 11 ¦
¦ 3. Weeeeeeeeeeee: 9 ¦
¦ 4. Whirligig: 8 ¦
¦ 4. MoveAndShootPlane: 8 ¦
¦ 6. StarFox: 4 ¦
¦ 6. EmoFockeWulf: 2 ¦
¦ 7. DumbPlanes: 0 ¦
----------------------------
Oto przykład danych wyjściowych z opakowania innego niż Java:
NEW CONTEST 14 20
wskazuje, że rozpoczyna się nowy konkurs na arenie 14x14x14 i będzie on obejmować 20 tur na walkę.
NEW OPPONENT 10
oznacza, że stoisz w obliczu nowego przeciwnika i będziesz z nim walczył 10 razy
NEW FIGHT 5 3 2
wskazuje, że rozpoczyna się nowa walka z obecnym przeciwnikiem, że do tej pory walczyłeś z nim 5 razy, wygrywając 3 i przegrywając 2 walki
ROUNDS LEFT 19
wskazuje, że w bieżącej walce pozostało 19 rund
NEW TURN
wskazuje, że masz zamiar otrzymywać dane dla wszystkich czterech samolotów w tej rundzie walki
alive 13 8 13 N 0
alive 13 5 13 N 0
dead 0 0 0 N 0
alive 0 8 0 S 0
Te cztery linie wskazują, że oba wasze samoloty żyją, odpowiednio o współrzędnych [13,8,13] i [13,5,13], obie skierowane na północ, obie z zerowym czasem odnowienia. Pierwszy samolot wroga jest martwy, a drugi żyje, w [0,8,0] i skierowany na południe z zerowym czasem odnowienia.
W tym momencie twój program powinien wypisać dwa wiersze podobne do następujących:
NW 0 1
SU 1 0
Oznacza to, że twój pierwszy samolot będzie podróżował po północnym zachodzie, nie skręcając z bieżącego kierunku i strzelając, jeśli to możliwe. Drugi samolot będzie podróżował SouthUp, obracając się w stronę SouthUp, nie strzelając.
Teraz ROUNDS LEFT 18
następuje NEW TURN
itd. Trwa to do momentu wygrania przez kogoś lub przekroczenia limitu czasu, w którym to momencie otrzymujesz kolejną NEW FIGHT
linię ze zaktualizowaną liczbą walk i wynikami, być może poprzedzoną znakiem NEW OPPONENT
.