3D: Discrete Dogfighting Duel (Now open to non Java submissions)
/*
PREDICT AND AVOID
Rules of behavior:
- Avoid hitting walls
- Move, safely, to shoot at spaces our enemy might fly to
- (contingent) Move to a safe space that aims closer to the enemy
- Move to a safe space
- Move, unsafely, to shoot at spaces our enemy might fly to
- Move to any space (remember to avoid walls)
Chooses randomly between equally prioritized moves
contingent strategy is evaluated during early fights
*/
package Planes;
import java.util.Random;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
public class PredictAndAvoid extends PlaneControl {
public PredictAndAvoid(int arenaSize, int rounds) {
super(arenaSize, rounds);
}
private int fightsPerMatch = 0;
private int fightNum = 0;
private int roundNum = 0;
private boolean useHoming = true;
private int homingScore = 0;
private int[][][] enemyHistory = new int[arenaSize][arenaSize][arenaSize];
// don't need to take roots here, waste of cpu cycles
int distanceCubed(Point3D a, Point3D b) {
return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + (a.z-b.z)*(a.z-b.z);
}
// is this plane guaranteed to hit a wall, now or soon?
boolean dangerZone(Plane icarus) {
// outside the arena?
// already dead
// this should never happen for my planes
if (!icarus.getPosition().isInArena(arenaSize)) {
return true;
}
// adjacent to a wall?
// directly facing the wall?
// death next turn
if (
icarus.getDirection().getMainDirections().length==1 &&
icarus.getDistanceFromWall(icarus.getDirection().getMainDirections()[0]) == 0
) {
return true;
}
// on an edge?
// 2d diagonal facing into that edge?
// death next turn
if (
icarus.getDirection().getMainDirections().length==2 &&
icarus.getDistanceFromWall(icarus.getDirection().getMainDirections()[0]) == 0 &&
icarus.getDistanceFromWall(icarus.getDirection().getMainDirections()[1]) == 0
) {
return true;
}
// near a corner?
// 3d diagonal facing into that corner?
// death in 1-2 turns
if (
icarus.getDirection().getMainDirections().length==3 &&
icarus.getDistanceFromWall(icarus.getDirection().getMainDirections()[0]) < 2 &&
icarus.getDistanceFromWall(icarus.getDirection().getMainDirections()[1]) < 2 &&
icarus.getDistanceFromWall(icarus.getDirection().getMainDirections()[2]) < 2
) {
return true;
}
// there's at least one way out of this position
return false;
}
@Override
public Move[] act() {
Move[] moves = new Move[2];
for (int i=0; i<2; i++) {
Plane p = myPlanes[i];
if (!p.isAlive()) {
moves[i] = new Move(new Direction("N"), false, false); // If we're dead we just return something, it doesn't matter anyway.
continue;
}
// a list of every move that doesn't commit us to running into a wall
// or a collision with the previously moved friendly plane
ArrayList<Move> potentialMoves = new ArrayList<Move>();
for (Direction candidateDirection : p.getPossibleDirections()) {
if (i==1 && myPlanes[0].simulateMove(moves[0]).getPosition().equals(myPlanes[1].simulateMove(new Move(candidateDirection,false,false)).getPosition())) {
} else {
Plane future = new Plane(arenaSize, 0, p.getDirection(), p.getPosition().add(candidateDirection.getAsPoint3D()));
if (!dangerZone(future)) {
potentialMoves.add(new Move(candidateDirection, false, false));
}
future = new Plane(arenaSize, 0, candidateDirection, p.getPosition().add(candidateDirection.getAsPoint3D()));
if (!dangerZone(future)) {
potentialMoves.add(new Move(candidateDirection, true, false));
}
}
}
// everywhere our enemies might end up
// including both directions they could be facing for each location
ArrayList<Plane> futureEnemies = new ArrayList<Plane>();
for (Plane e : enemyPlanes) {
if (e.isAlive()) {
for (Direction candidateDirection : e.getPossibleDirections()) {
futureEnemies.add(new Plane(
arenaSize,
e.getCoolDown(),
candidateDirection,
e.getPosition().add(candidateDirection.getAsPoint3D())
));
// don't make a duplicate entry for forward moves
if (!candidateDirection.getAsPoint3D().equals(e.getDirection().getAsPoint3D())) {
futureEnemies.add(new Plane(
arenaSize,
e.getCoolDown(),
e.getDirection(),
e.getPosition().add(candidateDirection.getAsPoint3D())
));
}
}
}
}
// a list of moves that are out of enemies' potential line of fire
// also skipping potential collisions unless we are ahead on planes
ArrayList<Move> safeMoves = new ArrayList<Move>();
for (Move candidateMove : potentialMoves) {
boolean safe = true;
Point3D future = p.simulateMove(candidateMove).getPosition();
for (Plane ec : futureEnemies) {
if (ec.getPosition().equals(future)) {
if (
(myPlanes[0].isAlive()?1:0) + (myPlanes[1].isAlive()?1:0)
<=
(enemyPlanes[0].isAlive()?1:0) + (enemyPlanes[1].isAlive()?1:0)
) {
safe = false;
break;
}
}
if (ec.isAlive() && ec.canShoot()) {
Point3D[] range = ec.getShootRange();
for (Point3D t : range) {
if (future.equals(t)) {
safe = false;
break;
}
}
if (safe == false) {
break;
}
}
}
if (safe == true) {
safeMoves.add(candidateMove);
}
}
// a list of moves that let us attack a space an enemy might be in
// ignore enemies committed to suicide vs a wall
// TODO: don't shoot at friendly planes
ArrayList<Move> attackMoves = new ArrayList<Move>();
for (Move candidateMove : potentialMoves) {
int attackCount = 0;
Plane future = p.simulateMove(candidateMove);
Point3D[] range = future.getShootRange();
for (Plane ec : futureEnemies) {
for (Point3D t : range) {
if (ec.getPosition().equals(t)) {
if (!dangerZone(ec)) {
attackMoves.add(new Move(candidateMove.direction, candidateMove.changeDirection, true));
attackCount++;
}
}
}
}
if (attackCount > 0) {
}
}
// find all attack moves that are also safe moves
ArrayList<Move> safeAttackMoves = new ArrayList<Move>();
for (Move safeCandidate : safeMoves) {
for (Move attackCandidate : attackMoves) {
if (safeCandidate.direction == attackCandidate.direction) {
safeAttackMoves.add(attackCandidate);
}
}
}
// choose the safe move that aims closest potential enemy positions
int maxDistanceCubed = arenaSize*arenaSize*arenaSize*8;
Move homingMove = null;
int bestHomingMoveTotalDistancesCubed = maxDistanceCubed*1000;
for (Move candidateMove : safeMoves) {
int totalCandidateDistancesCubed = 0;
for (Plane ec : futureEnemies) {
if (ec.isAlive()) {
int distThisEnemyCubed = maxDistanceCubed;
Point3D[] range = p.simulateMove(candidateMove).getShootRange();
for (Point3D t : range) {
int d1 = distanceCubed(t, ec.getPosition());
if (d1 < distThisEnemyCubed) {
distThisEnemyCubed = d1;
}
}
totalCandidateDistancesCubed += distThisEnemyCubed;
}
}
if (totalCandidateDistancesCubed < bestHomingMoveTotalDistancesCubed) {
bestHomingMoveTotalDistancesCubed = totalCandidateDistancesCubed;
homingMove = candidateMove;
}
}
Random rng = new Random();
// move to attack safely if possible
// even if we can't shoot, this is good for chasing enemies
if (safeAttackMoves.size() > 0) {
moves[i] = safeAttackMoves.get(rng.nextInt(safeAttackMoves.size()));
}
// turn towards enemies if it's possible and safe
// tests indicate value of this strategy varies significantly by opponent
// useHoming changes based on outcome of early fights with[out] it
// TODO: track enemy movement, aim for neighborhood
else if (useHoming == true && homingMove != null) {
moves[i] = homingMove;
}
// make random move, safe from attack
else if (safeMoves.size() > 0) {
moves[i] = safeMoves.get(rng.nextInt(safeMoves.size()));
}
// move to attack unsafely only if there are no safe moves
else if (attackMoves.size() > 0 && p.canShoot()) {
moves[i] = attackMoves.get(rng.nextInt(attackMoves.size()));
}
// make random move, safe from walls
else if (potentialMoves.size() > 0) {
moves[i] = potentialMoves.get(rng.nextInt(potentialMoves.size()));
}
// keep moving forward
// this should never happen
else {
moves[i] = new Move(p.getDirection(), false, true);
}
}
roundNum++;
return moves;
}
@Override
public void newFight(int fightsFought, int myScore, int enemyScore) {
// try the homing strategy for 1/8 of the match
// skip it for 1/8, then choose the winning option
if (fightsFought == fightsPerMatch/8) {
homingScore = myScore-enemyScore;
useHoming = false;
} else if (fightsFought == (fightsPerMatch/8)*2) {
if (homingScore*2 > myScore-enemyScore) {
useHoming = true;
}
}
fightNum = fightsFought;
roundNum = 0;
}
@Override
public void newOpponent(int fights) {
fightsPerMatch = fights;
}
}
Dogfight 3D Visualizer
I wrote a small, quick visualizer for this challenge. Code and jar files are on my github repo: https://github.com/Hungary-Dude/DogfightVisualizer
It's made using libGDX (http://libgdx.com). Right now the UI is pretty bad, I did put this together kind of fast.
I'm just learning how to use Git and Gradle so please comment if I did something wrong
Run dist/dogfight.bat
or dist/dogfight.sh
to see DumbPlanes in action!
To build from source, you'll need Gradle (http://gradle.org) and Gradle integration for your IDE, if you have one. Then clone the repo and run gradlew desktop:run
. Hopefully Gradle will import all the libraries required. The main class is zove.koth.dogfight.desktop.DesktopLauncher
.
Running without importing
Copy any plane class files into dist/
. Then, run dist/desktop-1.0.jar
with this command:
java -cp your-class-folder/;desktop-1.0.jar;Planes.jar zove.koth.dogfight.desktop.DesktopLauncher package.YourPlaneController1 package.YourPlaneController2 ...
I will update as the source for the Planes controller is updated, but to update yourself, you'll need to add in some code to Planes.Controller
. See the github readme for info on this.
Here's a screenshot:
If you have any questions or suggestions, leave a comment below!
EmoFockeWulf
He's back. He's starved himself to 224 bytes. He doesn't know how he ended up like this.
package Planes;public class EmoFockeWulf extends PlaneControl{public EmoFockeWulf(int s, int r){super(s,r);}public Move[] act(){Move[] m=new Move[2];m[0]=new Move(myPlanes[0].getDirection(),false,false);m[1]=m[0];return m;}}