ACTUALIZACIÓN: se agregó isSuicidal () a la clase de avión, ¡esto le permite verificar si un avión está en curso de colisión irreversible con las paredes!
ACTUALIZACIÓN: updateCoolDown () separado de simulateMove ()
ACTUALIZACIÓN: contenedor de entrada no Java, escrito por Sparr , disponible para prueba, ver comentarios
ACTUALIZACIÓN Zove Games ha escrito un visualizador 3D impresionante para este KOTH, aquí hay un video de mierda de YouTube de PredictAndAVoid luchando PredictAndAVoid.
La función simulateMove () de la clase Plane se modificó ligeramente para que ya no actualice el enfriamiento, use la nueva función updateCoolDown () para eso, después de disparar. El nuevo isSuicidal () vuelve verdadero si un avión está destinado a terminar muerto, úselo para podar los movimientos enemigos y evitar golpear paredes. Para obtener el código actualizado, simplemente reemplace las clases Controller y Plane por las del repositorio github.
Descripción
El objetivo de este desafío es codificar dos aviones de peleas de perros que se enfrentarán contra dos aviones por otro competidor. Cada turno te mueves un espacio y tienes la oportunidad de disparar. Eso es todo, es tan simple como eso.
Bueno, casi...
Arena y posibles movimientos
La arena es un 14x14x14 amurallado en el espacio. los planos del concursante 1 comienzan en las ubicaciones (0,5,0) y (0,8,0) y los del concursante 2 en (13,5,13) y (13,8,13). Todos los aviones comienzan volando horizontalmente lejos de las paredes verticales más cercanas.
Ahora, ya que vuela aviones y no helicópteros, no puede simplemente cambiar de dirección a voluntad o incluso dejar de moverse, por lo que cada avión tiene una dirección y moverá una ficha en esa dirección cada turno.
Las direcciones posibles son: Norte (N), Sur (S), Este (E), Oeste (W), Arriba (U) y Abajo (D) y cualquier combinación lógica de esos seis. Donde el eje NS corresponde al eje x, WE a y y DU a z. NW, SU y NED vienen a la mente como posibles ejemplos de instrucciones; UD es un gran ejemplo de una combinación no válida.
Por supuesto, puede cambiar la dirección de sus aviones, pero hay una limitación, solo puede cambiar su dirección en un máximo de 45 grados. Para visualizar esto, toma tu cubo de rubik (sé que tienes uno) e imagina que los 26 cubitos exteriores son las direcciones posibles (las direcciones de una letra son caras, las direcciones de dos letras son bordes y las direcciones de tres letras son esquinas). Si te diriges en una dirección representada por un pequeño cubo, puedes cambiar la dirección de cada cubo que toca el tuyo (tocar diagonalmente cuenta, pero solo tocando visiblemente, eso no toca el cubo).
Después de que todos los planos hayan indicado en qué dirección les gustaría cambiar, lo hacen y mueven una ficha simultáneamente.
También puede optar por moverse en una dirección válida, pero siga volando en la dirección en la que iba, en lugar de cambiar su dirección en la dirección hacia la que se movió. Esto es análogo a la diferencia entre un automóvil que dobla una esquina y un automóvil que cambia de carril.
Disparando y muriendo
Puede disparar como máximo una vez por asalto y esto debe decidirse al mismo tiempo que decide en qué dirección volar y si desea mantener su avión (y, por extensión, su arma) apuntando en la misma dirección o no. La bala recibe un disparo justo después de que su avión se mueve. Hay un enfriamiento de un turno después de disparar, en el tercer turno, estás listo para volver. Solo puedes disparar en la dirección en la que estás volando. Una bala es instantánea y vuela en línea recta hasta que golpea una pared o un avión.
Teniendo en cuenta la forma en que puede cambiar de dirección, así como 'cambiar de carril', esto significa que puede amenazar una columna de hasta 3x3 líneas frente a usted, además de algunas líneas diagonales individuales.
Si golpea un avión, este avión muere y desaparece rápidamente del tablero (porque explota por completo o algo así). Las balas solo pueden golpear un avión como máximo. Las balas se disparan simultáneamente, por lo que dos aviones pueden dispararse entre sí. Sin embargo, dos balas no pueden chocar en el aire (triste, lo sé).
Sin embargo, dos planos pueden chocar (si terminan en el mismo cubo y NO si se cruzan entre sí sin terminar en el mismo plano), y esto hace que ambos planos mueran (y exploten por completo). También puede volar hacia la pared, lo que hará que el avión en cuestión muera y sea arrinconado para pensar en sus acciones. Las colisiones se manejan antes de disparar.
Comunicación con el controlador.
Aceptaré entradas en Java, así como en otros idiomas. Si su entrada está en Java, recibirá la entrada a través de STDIN y la salida a través de STDOUT.
Si su entrada está en Java, su entrada debe extender la siguiente clase:
package Planes;
//This is the base class players extend.
//It contains the arena size and 4 plane objects representing the planes in the arena.
public abstract class PlaneControl {
// note that these planes are just for your information, modifying these doesn't affect the actual plane instances,
// which are kept by the controller
protected Plane[] myPlanes = new Plane[2];
protected Plane[] enemyPlanes = new Plane[2];
protected int arenaSize;
protected int roundsLeft;
...
// Notifies you that a new fight is starting
// FightsFought tells you how many fights will be fought.
// the scores tell you how many fights each player has won.
public void newFight(int fightsFought, int myScore, int enemyScore) {}
// notifies you that you'll be fighting anew opponent.
// Fights is the amount of fights that will be fought against this opponent
public void newOpponent(int fights) {}
// This will be called once every round, you must return an array of two moves.
// The move at index 0 will be applied to your plane at index 0,
// The move at index1 will be applied to your plane at index1.
// Any further move will be ignored.
// A missing or invalid move will be treated as flying forward without shooting.
public abstract Move[] act();
}
La instancia creada de esa clase persistirá durante toda la competencia, por lo que puede almacenar cualquier dato que desee almacenar en variables. Lea los comentarios en el código para más información.
También te he proporcionado las siguientes clases de ayuda:
package Planes;
//Objects of this class contain all relevant information about a plane
//as well as some helper functions.
public class Plane {
private Point3D position;
private Direction direction;
private int arenaSize;
private boolean alive = true;
private int coolDown = 0;
public Plane(int arenaSize, Direction direction, int x, int y, int z) {}
public Plane(int arenaSize, Direction direction, Point3D position) {}
// Returns the x coordinate of the plane
public int getX() {}
// Returns the y coordinate of the plane
public int getY() {}
// Returns the z coordinate of the plane
public int getZ() {}
// Returns the position as a Point3D.
public Point3D getPosition() {}
// Returns the distance between the plane and the specified wall,
// 0 means right next to it, 19 means at the opposite side.
// Returns -1 for invalid input.
public int getDistanceFromWall(char wall) {}
// Returns the direction of the plane.
public Direction getDirection() {}
// Returns all possible turning directions for the plane.
public Direction[] getPossibleDirections() {}
// Returns the cool down before the plane will be able to shoot,
// 0 means it is ready to shoot this turn.
public int getCoolDown() {}
public void setCoolDown(int coolDown) {}
// Returns true if the plane is ready to shoot
public boolean canShoot() {}
// Returns all positions this plane can shoot at (without first making a move).
public Point3D[] getShootRange() {}
// Returns all positions this plane can move to within one turn.
public Point3D[] getRange() {}
// Returns a plane that represents this plane after making a certain move,
// not taking into account other planes.
// Doesn't update cool down, see updateCoolDown() for that.
public Plane simulateMove(Move move) {}
// modifies this plane's cool down
public void updateCoolDown(boolean shot) {
coolDown = (shot && canShoot())?Controller.COOLDOWN:Math.max(0, coolDown - 1);
}
// Returns true if the plane is alive.
public boolean isAlive() {}
// Sets alive to the specified value.
public void setAlive(boolean alive) {}
// returns a copy of itself.
public Plane copy() {}
// Returns a string representing its status.
public String getAsString() {}
// Returns a string suitable for passing to a wrapped plane process
public String getDataString() {}
// Returns true if a plane is on an irreversable colision course with the wall.
// Use this along with simulateMove() to avoid hitting walls or prune possible emeny moves.
public boolean isSuicidal() {}
}
// A helper class for working with directions.
public class Direction {
// The three main directions, -1 means the first letter is in the direction, 1 means the second is, 0 means neither is.
private int NS, WE, DU;
// Creates a direction from 3 integers.
public Direction(int NSDir, int WEDir, int DUDir) {}
// Creates a direction from a directionstring.
public Direction(String direction) {}
// Returns this direction as a String.
public String getAsString() {}
// Returns The direction projected onto the NS-axis.
// -1 means heading north.
public int getNSDir() {}
// Returns The direction projected onto the WE-axis.
// -1 means heading west.
public int getWEDir() {}
// Returns The direction projected onto the DU-axis.
// -1 means heading down.
public int getDUDir() {}
// Returns a Point3D representing the direction.
public Point3D getAsPoint3D() {}
// Returns an array of chars representing the main directions.
public char[] getMainDirections() {}
// Returns all possible turning directions.
public Direction[] getPossibleDirections() {}
// Returns true if a direction is a valid direction to change to
public boolean isValidDirection(Direction direction) {}
}
public class Point3D {
public int x, y, z;
public Point3D(int x, int y, int z) {}
// Returns the sum of this Point3D and the one specified in the argument.
public Point3D add(Point3D point3D) {}
// Returns the product of this Point3D and a factor.
public Point3D multiply(int factor) {}
// Returns true if both Point3D are the same.
public boolean equals(Point3D point3D) {}
// Returns true if Point3D is within a 0-based arena of a specified size.
public boolean isInArena(int size) {}
}
public class Move {
public Direction direction;
public boolean changeDirection;
public boolean shoot;
public Move(Direction direction, boolean changeDirection, boolean shoot) {}
}
Puede crear instancias de estas clases y utilizar cualquiera de sus funciones tanto como desee. Puede encontrar el código completo de estas clases auxiliares aquí .
Aquí hay un ejemplo de cómo se vería su entrada (aunque espero que lo haga mejor que yo, la mayoría de los partidos con estos aviones terminan volando contra una pared, a pesar de sus mejores esfuerzos para evitar la pared):
package Planes;
public class DumbPlanes extends PlaneControl {
public DumbPlanes(int arenaSize, int rounds) {
super(arenaSize, rounds);
}
@Override
public Move[] act() {
Move[] moves = new Move[2];
for (int i=0; i<2; i++) {
if (!myPlanes[i].isAlive()) {
moves[i] = new Move(new Direction("N"), false, false); // If we're dead we just return something, it doesn't matter anyway.
continue;
}
Direction[] possibleDirections = myPlanes[i].getPossibleDirections(); // Let's see where we can go.
for (int j=0; j<possibleDirections.length*3; j++) {
int random = (int) Math.floor((Math.random()*possibleDirections.length)); // We don't want to be predictable, so we pick a random direction out of the possible ones.
if (myPlanes[i].getPosition().add(possibleDirections[random].getAsPoint3D()).isInArena(arenaSize)) { // We'll try not to fly directly into a wall.
moves[i] = new Move(possibleDirections[random], Math.random()>0.5, myPlanes[i].canShoot() && Math.random()>0.2);
continue; // I'm happy with this move for this plane.
}
// Uh oh.
random = (int) Math.floor((Math.random()*possibleDirections.length));
moves[i] = new Move(possibleDirections[random], Math.random()>0.5, myPlanes[i].canShoot() && Math.random()>0.2);
}
}
return moves;
}
@Override
public void newFight(int fightsFought, int myScore, int enemyScore) {
// Using information is for schmucks.
}
@Override
public void newOpponent(int fights) {
// What did I just say about information?
}
}
DumbPlanes se unirá al torneo junto con las otras entradas, por lo que si terminas en último lugar, es tu culpa por no al menos hacerlo mejor que DumbPlanes.
Restricciones
Se aplican las restricciones mencionadas en la wiki KOTH :
- Cualquier intento de jugar con el controlador, el tiempo de ejecución u otras presentaciones será descalificado. Todos los envíos solo deberían funcionar con las entradas y el almacenamiento que se les proporciona.
- Los bots no deben escribirse para vencer o admitir otros bots específicos. (Esto puede ser deseable en casos raros, pero si este no es un concepto central del desafío, es mejor descartarlo).
- Me reservo el derecho de descalificar las presentaciones que usan demasiado tiempo o memoria para ejecutar pruebas con una cantidad razonable de recursos.
- Un bot no debe implementar exactamente la misma estrategia que una existente, intencional o accidentalmente.
Probar su envío
Descargue el código del controlador desde aquí . Agregue su presentación como Something.java. Modifique Controller.java para incluir entradas para su avión en las entradas [] y nombres []. Compile todo como un proyecto Eclipse o con javac -d . *.java
, luego ejecute el controlador con java Planes/Controller
. Un registro del concurso estará en test.txt
, con un marcador al final. También puede llamar matchUp()
directamente con dos entradas como argumentos para probar dos planos uno contra el otro.
Ganando la pelea
El ganador de la pelea es el que tiene el último avión volando, si después de 100 turnos, todavía queda más de 1 equipo, el equipo con la mayoría de los aviones restantes gana. Si esto es igual, es un empate.
Puntuación y la competencia
El próximo torneo oficial se ejecutará cuando se agote la recompensa actual.
Cada entrada peleará con cualquier otra entrada (al menos) 100 veces, el ganador de cada enfrentamiento es el que tenga más victorias de las 100 y se le otorgarán 2 puntos. En caso de empate, ambas entradas reciben 1 punto.
El ganador de la competencia es el que tiene más puntos. En caso de empate, el ganador es el que ganó en un enfrentamiento entre las entradas que empataron.
Dependiendo de la cantidad de entradas, la cantidad de peleas entre las entradas podría aumentar significativamente, también podría seleccionar las 2-4 mejores entradas después del primer torneo y establecer un torneo de élites entre esas entradas con más peleas (y posiblemente más rondas por lucha)
(preliminar) Cuadro de indicadores
Tenemos una nueva entrada que ocupa firmemente el segundo lugar en otro emocionante torneo , parece que Crossfire es increíblemente difícil de disparar para todos, excepto para PredictAndAvoid. Tenga en cuenta que este torneo se realizó con solo 10 peleas entre cada conjunto de aviones y, por lo tanto, no es una representación completamente precisa de cómo están las cosas.
----------------------------
¦ 1. PredictAndAvoid: 14 ¦
¦ 2. Crossfire: 11 ¦
¦ 3. Weeeeeeeeeeee: 9 ¦
¦ 4. Whirligig: 8 ¦
¦ 4. MoveAndShootPlane: 8 ¦
¦ 6. StarFox: 4 ¦
¦ 6. EmoFockeWulf: 2 ¦
¦ 7. DumbPlanes: 0 ¦
----------------------------
Aquí hay un ejemplo de salida del reiniciador no Java:
NEW CONTEST 14 20
indica que está comenzando un nuevo concurso, en una arena de 14x14x14, e implicará 20 turnos por pelea.
NEW OPPONENT 10
indica que te enfrentas a un nuevo oponente y que lucharás contra este oponente 10 veces
NEW FIGHT 5 3 2
indica que está comenzando una nueva pelea contra el oponente actual, que has luchado contra este oponente 5 veces hasta ahora, ganando 3 y perdiendo 2 peleas
ROUNDS LEFT 19
indica que quedan 19 rondas en la pelea actual
NEW TURN
indica que estás a punto de recibir datos de los cuatro aviones para esta ronda de la pelea
alive 13 8 13 N 0
alive 13 5 13 N 0
dead 0 0 0 N 0
alive 0 8 0 S 0
Estas cuatro líneas indican que sus dos aviones están vivos, en las coordenadas [13,8,13] y [13,5,13] respectivamente, ambos orientados hacia el norte, ambos con cero enfriamiento. El primer avión enemigo está muerto, y el segundo está vivo, a [0,8,0] y mirando hacia el sur con cero enfriamiento.
En este punto, su programa debería generar dos líneas similares a las siguientes:
NW 0 1
SU 1 0
Esto indica que su primer avión viajará hacia el noroeste, sin desviarse de su rumbo actual, y disparando si puede. Su segundo avión viajará hacia SouthUp, girando hacia SouthUp, no disparando.
Ahora te ROUNDS LEFT 18
siguenNEW TURN
etc. Esto continúa hasta que alguien gana o la ronda se agota, en ese momento obtienes otra NEW FIGHT
línea con el conteo y puntajes de pelea actualizados, posiblemente precedidos por a NEW OPPONENT
.