31
14
UPDATE: isSuicidal() was added to the plane class, this allows you to check if a plane is on an irreversible collision course with the walls!!
UPDATE: updateCoolDown() separated from simulateMove()
UPDATE: non-Java entry wrapper, written by Sparr, available for testing, see comments
UPDATE Zove Games Has written an awesome 3D visualizer for this KOTH, here's a shitty youtube video of PredictAndAVoid fighting PredictAndAVoid.
The simulateMove() function of the Plane class was slightly modified so it doesn't update the cool down anymore, use the new updateCoolDown() function for that, after shooting. The new isSuicidal() returns true if a plane is bound to end up dead, use it to prune enemy moves and avoid hitting walls. To get the updated code, simply replace the Controller and Plane classes by the ones in the github repo.
Description
The goal of this challenge is to code two dogfighting planes that will face off against two planes by another contestant. Every turn you move one space and have the opportunity to shoot. That's it, it's as simple as that.
Well, almost...
Arena and possible moves
The arena is a 14x14x14 walled in space. the planes of contestant 1 start at the locations (0,5,0) and (0,8,0) and those of contestant 2 at (13,5,13) and (13,8,13). All planes start out by flying horizontally away from the vertical walls they are closest to.
Now since you are flying planes and not helicopters, you can't just change direction at will or even stop moving, so each plane has a direction and will move one tile in that direction every turn.
The possible directions are: North(N), South(S), East(E), West(W), Up(U) and Down(D) and any logical combination of those six. Where the NS axis corresponds to the x axis, WE to y and DU to z. NW, SU and NED come to mind as possible examples of directions; UD is a great example of an invalid combination.
You can of course change the direction of your planes, but there is a limitation, you can only change your direction by at most 45 degrees. To visualize this, grab your rubik's cube (I know you have one) and imagine all of the 26 outer little cubes are the possible directions (one letter directions are faces, two letter direction are edges and three letter directions are corners). If you're heading in a direction represented by a little cube, you can change direction to each cube that touches yours (diagonally touching counts, but only touching visibly, that is not touching through the cube).
After all planes have indicated to which direction they would like to change, they do so and move one tile simultaneously.
You can also opt to move in a valid direction but keep flying in the direction you were going, in stead of changing your direction into the direction you moved to. This is analogous to the difference between a car going around a corner and a car changing lanes.
Shooting and dying
You can shoot at most once per round and this has to be decided at the same time you decide which direction to fly in and whether you want to keep your plane (and by extension, your gun) pointed in the same direction or not. The bullet gets shot right after your plane moves. There is a cool down of one turn after shooting, on the third turn, you're good to go again. You can only shoot in the direction you're flying in. A bullet is instant and flies in a straight line until it hits a wall or a plane.
Taking into account the way you can change direction as well as 'change lanes', this means that you can threaten up a column of up to 3x3 lines in front of you additionally to some diagonal, single lines.
If it hits a plane, this plane dies and promptly disappears from the board (because it totally explodes or something). Bullets can only hit one plane at most. Bullets get shot simultaneously, so two planes can shoot each other. Two bullets can not collide in the air though (sad, I know).
Two planes can collide however (if they end up in the same cube and NOT if they cross each other without ending up in the same plane), and this results in both planes dying (and totally exploding). You can also fly into the wall which will result in the plane in question dying and being put in the corner to think about its actions. Collisions get handled before shooting does.
Communication with the controller
I will accept entries in java as well as other languages. If your entry is in java, you will get input through STDIN and will output through STDOUT.
If your entry is in java, .your entry must extend the following class:
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();
}
The instance created of that class will persist throughout the entire competition, so you can store any data you'd like to store in variables. Read the comments in the code for more information.
I've also provided you with the following helper classes:
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) {}
}
You can create instances of these classes and use any of their functions as much as you like. You can find the full code for these helper classes here.
Here's an example of what your entry could look like (Hopefully you'll do better than I did though, most of the matches with these planes end with them flying into a wall, despite their best efforts to avoid the wall.):
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 will join the tournament along with the other entries, so if you end last, it's your own fault for not at least doing better than DumbPlanes.
Restrictions
The restrictions mentioned in the KOTH wiki apply:
- Any attempt to tinker with the controller, runtime or other submissions will be disqualified. All submissions should only work with the inputs and storage they are given.
- Bots should not be written to beat or support specific other bots. (This might be desirable in rare cases, but if this is not a core concept of the challenge, it's better ruled out.)
- I Reserve the right to disqualify submissions that use too much time or memory to run trials with a reasonable amount of resources.
- A bot must not implement the exact same strategy as an existing one, intentionally or accidentally.
Testing your submission
Download the Controller code from here. Add your submission as Something.java. Modify Controller.java to include entries for your plane in entries[] and names[]. Compile everything as an Eclipse project or with javac -d . *.java
, then run the Controller with java Planes/Controller
. A log of the contest will be in test.txt
, with a scoreboard at the end.
You can also call matchUp()
directly with two entries as arguments to just test two planes against each other.
Winning the fight
The winner of the fight is the one who has the last plane flying, if after 100 turns, there is still more than 1 team left, the team with the most planes left wins. If this is equal, it's a draw.
Scoring and the competition
The next official tournament will be run when the current bounty runs out.
Each entry will fight every other entry (at least) 100 times, the winner of each match up is the ones with the most wins out of the 100 and will be awarded 2 points. In case of a draw, both entries are awarded 1 point.
The winner of the competition is the one with most points. In case of a draw, the winner is the one who won in a the match up between the entries that drew.
Depending on the amount of entries, The amount of fights between entries could be increased significantly, I might also select the 2-4 best entries after the first tournament and set up an elites tournament between those entries with more fights (and possibly more rounds per fight)
(preliminary) Scoreboard
We've got a new entry who firmly take sthe second place in yet another exciting tournament, it seems like Crossfire is incredibly hard to shoot for everyone except for PredictAndAvoid. Note that this tournament was run with only 10 fights between each set of planes and is therefor not an entirely accurate representation of how things stand.
----------------------------
¦ 1. PredictAndAvoid: 14 ¦
¦ 2. Crossfire: 11 ¦
¦ 3. Weeeeeeeeeeee: 9 ¦
¦ 4. Whirligig: 8 ¦
¦ 4. MoveAndShootPlane: 8 ¦
¦ 6. StarFox: 4 ¦
¦ 6. EmoFockeWulf: 2 ¦
¦ 7. DumbPlanes: 0 ¦
----------------------------
Here is an example of output from the non-Java wrapper:
NEW CONTEST 14 20
indicates that a new contest is starting, in a 14x14x14 arena, and it will involve 20 turns per fight.
NEW OPPONENT 10
indicates that you're facing a new opponent, and that you will fight this opponent 10 times
NEW FIGHT 5 3 2
indicates that a new fight against the current opponent is starting, that you've fought this opponent 5 times so far, winning 3 and losing 2 fights
ROUNDS LEFT 19
indicates there are 19 rounds left in the current fight
NEW TURN
indicates that you're about to receive data for all four planes for this round of the fight
alive 13 8 13 N 0
alive 13 5 13 N 0
dead 0 0 0 N 0
alive 0 8 0 S 0
These four lines indicate that both of your planes are alive, at coordinates [13,8,13] and [13,5,13] respectively, both facing North, both with zero cooldown. The first enemy plane is dead, and the second is alive, at [0,8,0] and facing south with zero cooldown.
At this point your program should output two lines similar to the following:
NW 0 1
SU 1 0
This indicates that your first plane will travel NorthWest, without turning from its current heading, and shooting if able. Your second plane will travel SouthUp, turning to face SouthUp, not shooting.
Now you get ROUNDS LEFT 18
followed by NEW TURN
etc. This continues until someone wins or the round times out, at which point you get another NEW FIGHT
line with the updated fight count and scores, possibly preceded by a NEW OPPONENT
.
If anyone needs help with this challenge, you can get into the chat I created for this challenge .
– overactor – 2014-07-29T14:30:52.913Do planes start headed East/West or North/South? or something else? – pseudonym117 – 2014-07-29T18:15:01.537
They start of flying horizontally away from the vertical walls they are closest to, so S for player 1 and N for player 2. – overactor – 2014-07-29T18:25:16.273
Can anything be done to account for randomness? In my tests, I get inconsistent results, changing the leaderboard a bit every time. Maybe a number of tournaments, taking either the mean or median scores? – Geobits – 2014-07-29T20:09:02.970
Depending on the number of entries, I'll increase the number of fights between individual players and maybe run several tournaments and take the mean score. These preliminary tournaments are just to give people an idea of where they stand. – overactor – 2014-07-29T21:44:50.447
I think performance within a match should matter. Winning 99% of the fights against an enemy is significantly better than winning 51%.
@Geobits I'm running my tests with 10000 fights instead of 100 and that eliminates a lot of randomness, as well as illustrating tiny edge cases (dumbplanes beat me 1 out of 1000 and illustrated a bug in my code). – Sparr – 2014-07-30T03:12:16.340
2@overactor there's a bug in the cooldown code. You're using simulateMove in the "Calculate the new positions" section, which decrements cooldown in addition to finding new positions. This means a plane can fire every turn if they ignore their own cooldown counter. – Sparr – 2014-07-30T05:19:24.870
@Sparr I fixed it, there's a newly added updateCooldown() function now and the simulateMove() function leaves the cool down as it is. So the way to work with it now is: simulate, check shootingrange, updateCoolDown, rinse and repeat. – overactor – 2014-07-30T06:13:07.987
2For those who may find it useful, this regex will search through the log to find where your plane shoots ^Move(.*?)shoot: true$ (replace "Move" with your name, and make sure that . does not capture new lines) – user2813274 – 2014-07-30T14:34:11.837
You may want to randomize which plane starts in position 1 and which starts in position 2. looking through the matches, Weeeeeeee was always player 2. Shouldn't affect my score much, but someone elses may change. – pseudonym117 – 2014-07-30T18:25:05.850
1
here's a commit for my plane wrapper, along with a dumb python plane. I'd love it if someone would write a smarter plane in perl/python/lua/bash/whatever and give me some feedback on if/how the wrapper works for you. https://github.com/sparr/Dogfight-KOTH/commit/6371d743b5c6130217877c5b16159e706c0a2ee1 if people can/will use this, we can get it into @overactor's repo and allow arbitrary language submissions.
– Sparr – 2014-07-30T18:56:27.437@pseudonym117 I considered that, but I figured it was symmetrical anyway. Maybe for the final tournament, I'll alternate between fights. – overactor – 2014-07-30T20:02:37.857
Would groovy be okay? It compiles to a java class, but you need to download the compiler and add a library to the classpath
– Fels – 2014-07-30T20:14:20.613made some fixes to the wrapper. two commits, diffed here: https://github.com/sparr/Dogfight-KOTH/compare/da7fa883c3302a7e7b74afed798826682a55da44...125518493454bbf38a35cc62effe1fb9ff40c8ab
– Sparr – 2014-07-31T02:58:29.520@Fels, I think I can probably handle that, test if it works on your machine before entering though. – overactor – 2014-07-31T05:20:06.727