Skip to content

Instantly share code, notes, and snippets.

@sebastientromp
Created January 28, 2017 22:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sebastientromp/115b4de811a0aab64019f157b36586cf to your computer and use it in GitHub Desktop.
Save sebastientromp/115b4de811a0aab64019f157b36586cf to your computer and use it in GitHub Desktop.
import static java.lang.Math.abs;
import static java.lang.Math.round;
import static java.lang.Math.signum;
import static java.lang.Math.sqrt;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;
import java.util.Scanner;
/**
* Grab Snaffles and try to throw them through the opponent's goal! Move towards
* a Snaffle and use your team id to determine where you need to throw it.
**/
class Player {
static GameState expectedGameState = new GameState();
static GameState gameState = new GameState();
static long turnStart;
// static AI ai = new BasicAI();
// static AI ai = new MoveOnlyAI();
// static AI ai = new GeneticAI();
static AI ai = new MCAI();
static boolean firstTurnDone = false;
public static void main(String args[]) {
Scanner in = new Scanner(System.in);
// if 0 you need to score on the right of the map, if 1 you need to
// score on the left
int myTeamId = captureScannerInput(in);
gameState.myTeamId = myTeamId;
Cache.initEntities();
// System.err.println("Game netities init done");
// game loop
while (true) {
Cache.newTurn();
long start = System.currentTimeMillis();
turnStart = start;
gameState.clear();
initGameState(gameState, in);
// System.err.println("GameState init done after " +
// (System.currentTimeMillis() - start));
// System.err.println("Game state init " + gameState);
// System.err.println("Expected game state " + expectedGameState);
// System.err.println("Expected game state " +
// expectedGameState.myWizards);
// Validate scanner
// String scannerInputCopy = scannerInput;
// Scanner scannerTest = new Scanner(new ByteArrayInputStream(
// scannerInputCopy.getBytes(StandardCharsets.UTF_8)));
// initGameState(gameState, scannerTest);
// System.err.println(scannerInput);
// Don't compare the first turn
// start = System.currentTimeMillis();
if (expectedGameState.myWizards.length != 0 && expectedGameState.myWizards[0] != null) {
// System.err.println("Asserting");
// GameState.assertEquals(gameState, expectedGameState);
// System.err.println("Asserted everything went well");
gameState.copyTransientValuesFrom(expectedGameState);
}
expectedGameState.clear();
gameState.updateCounters();
// System.err.println("Carry over from last after " +
// (System.currentTimeMillis() - start));
// System.err.println("Game state after init: " + gameState);
// System.err.println("Computing actions " + gameState);
// start = System.currentTimeMillis();
Action[] actions = ai.computeActions(gameState);
// System.err.println("Output gamestate after AI actions: " +
// gameState);
// System.err.println("Computing actions after " +
// (System.currentTimeMillis() - start));
Action[] opponentActions = null;
// System.err.println(
// "actions computed " + Arrays.toString(actions) + " after " +
// (System.currentTimeMillis() - start));
// for (Action action : actions) {
// if (action.x < 0 || action.y < 0) {
// System.err.println("Negative action before simulating next state
// " + action);
// throw new RuntimeException();
// }
// }
expectedGameState.cloneFrom(gameState);
GameEngine.simulateNextTurn(gameState, expectedGameState, actions, opponentActions);
// System.err.println(expectedGameState.exportState());
// System.err.println(expectedGameState);
// System.err.println("Simulation done after " +
// (System.currentTimeMillis() - start));
// System.err.println(expectedGameState);
System.err.println(Arrays.toString(expectedGameState.myWizards));
for (Action action : actions) {
// if (action.x < 0 || action.y < 0) {
// System.err.println("Negative action before sending it " +
// action);
// throw new RuntimeException();
// }
// System.err.println(action.execute());
System.out.println(action.execute());
}
firstTurnDone = true;
Cache.endTurn();
}
}
static void initGameState(GameState gameState, Scanner in) {
initScanner();
int entities = captureScannerInput(in); // number of entities still in
// game
gameState.setEntities(entities);
for (int i = 0; i < entities; i++) {
int entityId = captureScannerInput(in); // entity identifier
Entity entity = null;
String entityType = captureScannerStringInput(in);
switch (entityType) {
case "WIZARD":
entity = gameState.getWizard(entityId);
((Wizard) entity).friendly = true;
break;
case "OPPONENT_WIZARD":
entity = gameState.getOpponentWizard(entityId);
break;
case "SNAFFLE":
entity = gameState.getSnaffle(entityId);
break;
case "BLUDGER":
entity = gameState.getBludger(entityId);
break;
default:
break;
}
entity.entityId = entityId;
entity.x = captureScannerInput(in); // position
entity.y = captureScannerInput(in); // position
entity.vx = captureScannerInput(in); // velocity
entity.vy = captureScannerInput(in); // velocity
// System.err.println("Init entity: " + entity);
int state = captureScannerInput(in);
if (entity instanceof Wizard) {
// 1 if the wizard is holding a Snaffle, 0 otherwise
((Wizard) entity).isHolding = state == 1;
// System.err.println("Is holding? " + state + " " + entity);
}
}
gameState.finalizeInit();
// System.err.println("Input init");
System.err.println(scannerInput);
}
static String scannerInput = "";
private static void initScanner() {
scannerInput = "";
}
private static String captureScannerStringInput(Scanner in) {
String next = in.next();
scannerInput += next + " ";
return next;
}
private static int captureScannerInput(Scanner in) {
int nextInt = in.nextInt();
scannerInput += nextInt + " ";
return nextInt;
}
}
// =================================
// State
// =================================
class Cache {
static int POINT_POOL_SIZE = 500000;
static int ACTION_POOL_SIZE = 200000;
static int WIZARD_POOL_SIZE = 200000;
static int SNAFFLE_POOL_SIZE = 100000;
static int BLUDGER_POOL_SIZE = 20000;
static int MAP_POINT_POOL_SIZE = 100000;
static int GAME_STATE_POOL_SIZE = 100000;
static final Wizard[] wizardPool = new Wizard[WIZARD_POOL_SIZE];
static final Snaffle[] snafflePool = new Snaffle[SNAFFLE_POOL_SIZE];
static final Bludger[] bludgerPool = new Bludger[BLUDGER_POOL_SIZE];
static final Point[] pointPool = new Point[POINT_POOL_SIZE];
static final GameState[] gameStatePool = new GameState[GAME_STATE_POOL_SIZE];
static final Action[] actionPool = new Action[ACTION_POOL_SIZE];
static final MapPoint[] mapPointPool = new MapPoint[MAP_POINT_POOL_SIZE];
static int wizardIndex, snaffleIndex, bludgerIndex, pointIndex, gameStateIndex, actionIndex, mapPointIndex;
static int totalGameStateLoops, totalActionLoops, totalPointLoops, totalWizardLoops, totalSnaffleLoops,
totalBludgerLoops, totalMapPointsLoops;
static int gameStateClones, collisions, collisionTest, evals;
static long lastCollision, totalCollisionTime, lastEval, totalEvalTime;
static void initEntities() {
for (int i = 0; i < WIZARD_POOL_SIZE; i++) {
wizardPool[i] = new Wizard();
}
for (int i = 0; i < SNAFFLE_POOL_SIZE; i++) {
snafflePool[i] = new Snaffle();
}
for (int i = 0; i < BLUDGER_POOL_SIZE; i++) {
bludgerPool[i] = new Bludger();
}
for (int i = 0; i < POINT_POOL_SIZE; i++) {
pointPool[i] = new Point();
}
for (int i = 0; i < GAME_STATE_POOL_SIZE; i++) {
gameStatePool[i] = new GameState();
}
for (int i = 0; i < ACTION_POOL_SIZE; i++) {
actionPool[i] = new Action();
}
for (int i = 0; i < MAP_POINT_POOL_SIZE; i++) {
mapPointPool[i] = new MapPoint();
}
}
public static void newTurn() {
gameStateIndex = 0;
totalGameStateLoops = 0;
actionIndex = 0;
totalActionLoops = 0;
pointIndex = 0;
totalPointLoops = 0;
wizardIndex = 0;
totalWizardLoops = 0;
snaffleIndex = 0;
totalSnaffleLoops = 0;
bludgerIndex = 0;
totalBludgerLoops = 0;
mapPointIndex = 0;
totalMapPointsLoops = 0;
gameStateClones = 0;
collisions = 0;
collisionTest = 0;
lastCollision = 0;
totalCollisionTime = 0;
lastEval = 0;
totalEvalTime = 0;
}
public static void endTurn() {
// System.err.println("Used " + (gameStateIndex + 10000 *
// totalGameStateLoops) + " game states");
// System.err.println("Used " + (actionIndex + ACTION_POOL_SIZE *
// totalActionLoops) + " actions");
// System.err.println("Used " + (pointIndex + POINT_POOL_SIZE *
// totalPointLoops) + " points");
// System.err.println("Used " + (wizardIndex + WIZARD_POOL_SIZE *
// totalWizardLoops) + " wizards");
// System.err.println("Used " + (snaffleIndex + SNAFFLE_POOL_SIZE *
// totalSnaffleLoops) + " snaffles");
// System.err.println("Used " + (bludgerIndex + BLUDGER_POOL_SIZE *
// totalBludgerLoops) + " bludgers");
// System.err.println("Used " + (mapPointIndex + MAP_POINT_POOL_SIZE *
// totalMapPointsLoops) + " map points");
// System.err.println("Cloned " + gameStateClones + " gamestates");
// System.err.println("Had " + collisions + " collisions");
// System.err.println("Tested for " + collisionTest + " collision
// tests");
// System.err.println("Spend " + totalCollisionTime / 1000000 + "ms
// testing for collisions");
// System.err.println(
// "\tWhich is " + 1.0 * totalCollisionTime / 1000 / collisionTest + "
// microseconds per collision test");
//
// System.err.println("Evaluated " + evals + " game states");
// System.err.println("Spend " + totalEvalTime / 1000000 + "ms
// evaluating game states");
// System.err.println("\tWhich is " + 1.0 * totalEvalTime / 1000 / evals
// + " microseconds per eval");
}
// TODO: remova the monitoring elements, as it takes some computing time
static Wizard newWizard() {
return new Wizard();
// wizardIndex++;
// if (wizardIndex >= WIZARD_POOL_SIZE) {
// wizardIndex = wizardIndex - WIZARD_POOL_SIZE;
// // totalWizardLoops++;
// }
// return wizardPool[wizardIndex].clear();
}
static Snaffle newSnaffle() {
return new Snaffle();
// snaffleIndex++;
// if (snaffleIndex >= SNAFFLE_POOL_SIZE) {
// snaffleIndex = snaffleIndex - SNAFFLE_POOL_SIZE;
// // totalSnaffleLoops++;
// }
// return snafflePool[snaffleIndex].clear();
}
static Bludger newBludger() {
return new Bludger();
// return bludgerPool[bludgerIndex++ % 1000].clear();
// bludgerIndex++;
// if (bludgerIndex >= BLUDGER_POOL_SIZE) {
// bludgerIndex = bludgerIndex - BLUDGER_POOL_SIZE;
// // totalBludgerLoops++;
// }
// return bludgerPool[bludgerIndex].clear();
}
static Point newPoint() {
return new Point();
// return pointPool[pointIndex++ % 10000].clear();
// pointIndex++;
// if (pointIndex >= POINT_POOL_SIZE) {
// pointIndex = pointIndex - POINT_POOL_SIZE;
// // Rmoving monitoring counters goes down from 21 ms to 14 ms to
// // instantiate 5M points
// // totalPointLoops++;
// }
//
// // Not calling .clear() means getting from 39 to 21 ms to instantiate
// 5M
// // points
// return pointPool[pointIndex];
}
static GameState newGameState() {
return new GameState();
// 93ms to get 1M game states
// gameStateIndex++;
// if (gameStateIndex >= GAME_STATE_POOL_SIZE) {
// gameStateIndex = gameStateIndex - GAME_STATE_POOL_SIZE;
// // totalGameStateLoops++;
// }
// // We only need to clear the snaffles, as they are the only entities
// // that can disappear. Down to 63 ms for 1M states
// return gameStatePool[gameStateIndex].clear();
}
static Action newAction() {
return new Action();
// actionIndex++;
// if (actionIndex >= ACTION_POOL_SIZE) {
// actionIndex = actionIndex - ACTION_POOL_SIZE;
// // totalActionLoops++;
// }
// return actionPool[actionIndex].clear();
}
public static Solution newSolution() {
return new Solution();
}
public static Step newStep() {
return new Step();
}
public static MapPoint newMapPoint() {
return new MapPoint();
// mapPointIndex++;
// if (mapPointIndex >= MAP_POINT_POOL_SIZE) {
// mapPointIndex = mapPointIndex - MAP_POINT_POOL_SIZE;
// // totalMapPointsLoops++;
// }
// // We only need to clear the snaffles, as they are the only entities
// // that can disappear. Down to 63 ms for 1M states
// return mapPointPool[mapPointIndex];
}
// public static void logClone() {
// gameStateClones++;
// }
//
// public static void logCollision() {
// collisions++;
// }
//
// public static void logCollisionTest() {
// collisionTest++;
// lastCollision = System.nanoTime();
// }
//
// public static void logCollisionEnd() {
// totalCollisionTime += System.nanoTime() - lastCollision;
// }
//
// public static void logEval() {
// evals++;
// lastEval = System.nanoTime();
// }
//
// public static void logEvalEnd() {
// totalEvalTime += System.nanoTime() - lastEval;
// }
}
class GameState {
int initialTotalEntities = -1;
int myTeamId;
Wizard[] myWizards = new Wizard[2];
Wizard[] opponentWizards = new Wizard[2];
Snaffle[] snaffles = new Snaffle[7];
Bludger[] bludgers = new Bludger[2];
Pole[] poles = new Pole[4];
int myMagic, opponentMagic;
int myScore, opponentScore;
boolean invalid;
GameState clear() {
// myWizards = new Wizard[2];
// opponentWizards = new Wizard[2];
for (int i = 0; i < 7; i++) {
snaffles[i] = null;
}
for (int i = 0; i < 4; i++) {
Pole pole = new Pole();
pole.entityId = -10;
pole.x = (i == 0 || i == 1) ? 0 : 16000;
pole.y = (i == 0 || i == 2) ? 1750 : 5750;
poles[i] = pole;
}
// snaffles = new Snaffle[7];
// bludgers = new Bludger[2];
// myMagic = 0;
// opponentMagic = 0;
// myScore = 0;
// opponentScore = 0;
return this;
}
public void setEntities(int entities) {
if (initialTotalEntities == -1) {
initialTotalEntities = entities;
}
}
public void finalizeInit() {
for (int i = 0; i < 2; i++) {
if (myWizards[i].isHolding) {
for (Snaffle snaffle : snaffles) {
if (snaffle == null) continue;
if (snaffle.x == myWizards[i].x && snaffle.y == myWizards[i].y) {
snaffle.grabbedBy = myWizards[i].entityId;
break;
}
}
}
}
for (int i = 0; i < 2; i++) {
if (opponentWizards[i].isHolding) {
for (Snaffle snaffle : snaffles) {
if (snaffle == null) continue;
if (snaffle.x == opponentWizards[i].x && snaffle.y == opponentWizards[i].y) {
snaffle.grabbedBy = opponentWizards[i].entityId;
break;
}
}
}
}
}
public String exportState() {
String result = "";
int entities = 0;
for (int i = 0; i < 2; i++) {
result += myWizards[i].entityId + " WIZARD " + myWizards[i].x + " " + myWizards[i].y + " "
+ myWizards[i].vx + " " + myWizards[i].vy + " " + (myWizards[i].isHolding ? 1 : 0) + " ";
entities++;
}
for (int i = 0; i < 2; i++) {
result += opponentWizards[i].entityId + " OPPONENT_WIZARD " + opponentWizards[i].x + " "
+ opponentWizards[i].y + " " + opponentWizards[i].vx + " " + opponentWizards[i].vy + " "
+ (opponentWizards[i].isHolding ? 1 : 0) + " ";
entities++;
}
for (int i = 0; i < 7; i++) {
if (snaffles[i] != null) {
result += snaffles[i].entityId + " SNAFFLE " + snaffles[i].x + " " + snaffles[i].y + " "
+ snaffles[i].vx + " " + snaffles[i].vy + " ";
entities++;
}
}
for (int i = 0; i < 2; i++) {
result += bludgers[i].entityId + " SNAFFLE " + bludgers[i].x + " " + bludgers[i].y + " " + bludgers[i].vx
+ " " + bludgers[i].vy + " ";
entities++;
}
result = entities + " " + result;
return result;
}
GameState fullClear() {
for (int i = 0; i < 2; i++) {
myWizards[i] = null;
}
for (int i = 0; i < 2; i++) {
opponentWizards[i] = null;
}
for (int i = 0; i < 7; i++) {
snaffles[i] = null;
}
for (int i = 0; i < 2; i++) {
bludgers[i] = null;
}
myMagic = 0;
opponentMagic = 0;
myScore = 0;
opponentScore = 0;
return this;
}
public void updateCounters() {
for (int i = 0; i < myWizards.length; i++) {
myWizards[i].countdownBeforeNextHold--;
}
for (int i = 0; i < opponentWizards.length; i++) {
opponentWizards[i].countdownBeforeNextHold--;
}
}
public void copyTransientValuesFrom(GameState expectedGameState) {
for (int i = 0; i < bludgers.length; i++) {
bludgers[i].lastCollidedWizard = expectedGameState.bludgers[i].lastCollidedWizard;
}
for (int i = 0; i < myWizards.length; i++) {
myWizards[i].countdownBeforeNextHold = expectedGameState.myWizards[i].countdownBeforeNextHold;
}
for (int i = 0; i < opponentWizards.length; i++) {
opponentWizards[i].countdownBeforeNextHold = expectedGameState.opponentWizards[i].countdownBeforeNextHold;
}
// for (int i = 0; i < snaffles.length; i++) {
// if (snaffles[i] == null || expectedGameState.snaffles[i] == null) {
// continue;
// }
// snaffles[i].grabbedBy = expectedGameState.snaffles[i].grabbedBy;
// }
myMagic = expectedGameState.myMagic;
opponentMagic = expectedGameState.opponentMagic;
myScore = expectedGameState.myScore;
opponentScore = expectedGameState.opponentScore;
}
public Entity findEntity(int id) {
for (int i = 0; i < 2; i++) {
if (myWizards[i].entityId == id) return myWizards[i];
}
for (int i = 0; i < 2; i++) {
if (opponentWizards[i].entityId == id) return opponentWizards[i];
}
for (int i = 0; i < 7; i++) {
if (snaffles[i] == null) continue;
if (snaffles[i].entityId == id) return snaffles[i];
}
for (int i = 0; i < 2; i++) {
if (bludgers[i].entityId == id) return bludgers[i];
}
return null;
}
public Wizard getWizard(int entityId) {
Wizard wizard = Cache.newWizard();
for (int i = 0; i < 2; i++) {
if (myWizards[i] == null) {
myWizards[i] = wizard;
break;
}
}
return wizard;
}
public Wizard getOpponentWizard(int entityId) {
Wizard wizard = Cache.newWizard();
for (int i = 0; i < 2; i++) {
if (opponentWizards[i] == null) {
opponentWizards[i] = wizard;
break;
}
}
return wizard;
}
public Snaffle getSnaffle(int entityId) {
Snaffle snaffle = Cache.newSnaffle();
for (int i = 0; i < snaffles.length; i++) {
if (snaffles[i] == null) {
snaffles[i] = snaffle;
break;
}
}
return snaffle;
}
public Bludger getBludger(int entityId) {
Bludger bludger = Cache.newBludger();
for (int i = 0; i < 2; i++) {
if (bludgers[i] == null) {
bludgers[i] = bludger;
break;
}
}
return bludger;
}
public static void assertEquals(GameState gameState, GameState expectedGameState) {
if (!Arrays.equals(gameState.myWizards, expectedGameState.myWizards)) {
System.err.println("Wizards (actual/simulated): " + Arrays.toString(gameState.myWizards) + " // "
+ Arrays.toString(expectedGameState.myWizards));
throw new AssertionError();
}
if (!Arrays.equals(gameState.opponentWizards, expectedGameState.opponentWizards)) {
System.err.println("Opponent Wizards (actual/simulated): " + Arrays.toString(gameState.opponentWizards)
+ " // " + Arrays.toString(expectedGameState.opponentWizards));
throw new AssertionError();
}
if (!Arrays.equals(gameState.bludgers, expectedGameState.bludgers)) {
System.err.println("Bludgers (actual/simulated): " + Arrays.toString(gameState.bludgers) + " // "
+ Arrays.toString(expectedGameState.bludgers));
throw new AssertionError();
}
if (!Arrays.equals(gameState.snaffles, expectedGameState.snaffles)) {
System.err.println("Snaffles (actual/simulated): " + Arrays.toString(gameState.snaffles) + " // "
+ Arrays.toString(expectedGameState.snaffles));
throw new AssertionError();
}
}
// 221 ms to clone 1M game states
public void cloneFrom(GameState gameState) {
// Cache.logClone();
clear();
myTeamId = gameState.myTeamId;
for (int i = 0; i < 2; i++) {
myWizards[i] = gameState.myWizards[i].clone();
}
for (int i = 0; i < 2; i++) {
opponentWizards[i] = gameState.opponentWizards[i].clone();
}
for (int i = 0; i < 2; i++) {
bludgers[i] = gameState.bludgers[i].clone();
}
for (int i = 0; i < gameState.snaffles.length; i++) {
if (gameState.snaffles[i] == null) {
continue;
}
snaffles[i] = gameState.snaffles[i].clone();
}
myScore = gameState.myScore;
opponentScore = gameState.opponentScore;
myMagic = gameState.myMagic;
opponentMagic = gameState.opponentMagic;
}
public void remove(Snaffle item) {
for (int i = 0; i < snaffles.length; i++) {
if (snaffles[i] == null) {
continue;
}
if (snaffles[i].entityId == item.entityId) {
snaffles[i] = null;
}
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(bludgers);
result = prime * result + (invalid ? 1231 : 1237);
result = prime * result + myMagic;
result = prime * result + myScore;
result = prime * result + myTeamId;
result = prime * result + Arrays.hashCode(myWizards);
result = prime * result + opponentMagic;
result = prime * result + opponentScore;
result = prime * result + Arrays.hashCode(opponentWizards);
result = prime * result + Arrays.hashCode(snaffles);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
GameState other = (GameState) obj;
if (!Arrays.equals(bludgers, other.bludgers)) return false;
if (invalid != other.invalid) return false;
if (myMagic != other.myMagic) return false;
if (myScore != other.myScore) return false;
if (myTeamId != other.myTeamId) return false;
if (!Arrays.equals(myWizards, other.myWizards)) return false;
if (opponentMagic != other.opponentMagic) return false;
if (opponentScore != other.opponentScore) return false;
if (!Arrays.equals(opponentWizards, other.opponentWizards)) return false;
if (!Arrays.equals(snaffles, other.snaffles)) return false;
return true;
}
@Override
public String toString() {
return "GameState [myTeamId=" + myTeamId + ", myWizards=" + Arrays.toString(myWizards) + ", opponentWizards="
+ Arrays.toString(opponentWizards) + ", snaffles=" + Arrays.toString(snaffles) + ", bludgers="
+ Arrays.toString(bludgers) + ", myMagic=" + myMagic + ", opponentMagic=" + opponentMagic
+ ", myScore=" + myScore + ", opponentScore=" + opponentScore + ", invalid=" + invalid + "]";
}
}
class Action {
String type;
float x, y;
int power;
// String debug;
public String debug;
public Action() {
}
public Action clear() {
type = null;
x = 0;
y = 0;
power = 0;
return this;
}
public Action(String type, float x, float y, int power) {
super();
this.type = type;
this.x = x;
this.y = y;
this.power = power;
}
public String execute() {
return type + " " + Utils.roundAwayFromZero(x) + " " + Utils.roundAwayFromZero(y) + " " + power;
}
public static Action buildMove(Point position, int power) {
Action action = Cache.newAction();
action.type = "MOVE";
action.x = position.x;
action.y = position.y;
action.power = power;
return action;
}
public static Action buildThrow(Point opponentGoal, int power) {
Action action = Cache.newAction();
action.type = "THROW";
action.x = opponentGoal.x;
action.y = opponentGoal.y;
action.power = power;
return action;
}
@Override
public Action clone() {
Action clone = Cache.newAction();
clone.type = type;
clone.x = x;
clone.y = y;
clone.power = power;
return clone;
}
@Override
public String toString() {
return "Action [type=" + type + ", x=" + x + ", y=" + y + ", power=" + power + ", debug=" + debug + "]";
}
}
// =================================
// Entities
// =================================
class Point {
protected float x, y;
public Point() {
super();
}
public Point(float x, float y) {
super();
this.x = x;
this.y = y;
}
public Point clear() {
x = 0;
y = 0;
return this;
}
public float distance2(Point target) {
return (x - target.x) * (x - target.x) + (y - target.y) * (y - target.y);
}
float distance(Point p) {
return (float) Math.sqrt(distance2(p));
}
Point closest(Point a, Point b) {
float da = b.y - a.y;
float db = a.x - b.x;
float c1 = da * a.x + db * a.y;
float c2 = -db * x + da * y;
float det = da * da + db * db;
Point point = Cache.newPoint();
if (det != 0) {
point.x = (da * c1 - db * c2) / det;
point.y = (da * c2 + db * c1) / det;
}
else {
// Le point est déjà sur la droite
point.x = x;
point.y = y;
}
return point;
}
@Override
public Point clone() {
Point clone = Cache.newPoint();
clone.x = x;
clone.y = y;
return clone;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Float.floatToIntBits(x);
result = prime * result + Float.floatToIntBits(y);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (getClass() != obj.getClass()) { return false; }
Point other = (Point) obj;
if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x)) { return false; }
if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y)) { return false; }
return true;
}
@Override
public String toString() {
return "Point [x=" + x + ", y=" + y + "]";
}
}
abstract class Entity extends Point {
protected int entityId;
protected float vx, vy;
protected final float friction, mass, radius;
private String debug;
// String debug;
public Entity(float friction, float mass, float radius) {
super();
this.friction = friction;
this.mass = mass;
this.radius = radius;
}
@Override
public Entity clear() {
super.clear();
entityId = -1;
vx = 0;
vy = 0;
// debug = null;
return this;
}
void applyNewSpeed(Point p, float thrust) {
applyNewSpeed(p.x, p.y, thrust);
}
public void applyNewSpeed(float px, float py, float thrust) {
float vectX = px - x;
float vectY = py - y;
float norm = (float) Math.sqrt(vectX * vectX + vectY * vectY);
float normVectX = vectX / norm;
float normVectY = vectY / norm;
// System.err.println("Applying new speed " + normVectX * thrust + " " +
// normVectY * thrust);
vx += normVectX * thrust / mass;
vy += normVectY * thrust / mass;
}
void move(float t) {
x += vx * t;
y += vy * t;
}
void end() {
x = Utils.roundAwayFromZero(x);
y = Utils.roundAwayFromZero(y);
vx = Utils.roundAwayFromZero(vx * friction);
vy = Utils.roundAwayFromZero(vy * friction);
}
// void play(Point p, int thrust) {
// applyNewSpeed(p, thrust);
// // this.rotate(p);
// // this.boost(thrust);
// move(1.0f);
// end();
// }
Collision collision(Entity u) {
// Cache.logCollisionTest();
float dist = distance2(u);
// Somme des rayons au carré
float sr = (radius + u.radius) * (radius + u.radius);
// Wizard and Snaffles don't collide when the Wizard doesn't hold a
// Snaffle
if (this instanceof Wizard && u instanceof Snaffle) {
if (!((Wizard) this).isHolding && ((Snaffle) u).grabbedBy == -1) {
sr = (radius - 1) * (radius - 1);
}
else {
return null;
}
}
if (u instanceof Wizard && this instanceof Snaffle) {
if (!((Wizard) u).isHolding && ((Snaffle) this).grabbedBy == -1) {
sr = (u.radius - 1) * (u.radius - 1);
}
else {
return null;
}
}
// On prend tout au carré pour éviter d'avoir à appeler un sqrt
// inutilement. C'est mieux pour les performances
if (dist < sr) {
// Les objets sont déjà l'un sur l'autre. On a donc une collision
// immédiate
// Cache.logCollisionEnd();
return new Collision(this, u, 0.0f);
}
// Optimisation. Les objets ont la même vitesse ils ne pourront jamais
// se rentrer dedans
if (vx == u.vx && vy == u.vy) {
// Cache.logCollisionEnd();
return null;
}
// On se met dans le référentiel de u. u est donc immobile et se trouve
// sur le point (0,0) après ça
// Our coordinates in u's referential
float x = this.x - u.x;
float y = this.y - u.y;
Point myp = Cache.newPoint();
myp.x = x;
myp.y = y;
// Our speed in u's referential
float vx = this.vx - u.vx;
float vy = this.vy - u.vy;
// u's coordinates in its own ref
Point up = Cache.newPoint();
up.x = 0;
up.y = 0;
// On cherche le point le plus proche de u (qui est donc en (0,0)) sur
// la droite décrite par notre vecteur de vitesse
Point point = Cache.newPoint();
point.x = x + vx;
point.y = y + vy;
Point p = up.closest(myp, point);
// Distance au carré entre u et le point le plus proche sur la droite
// décrite par notre vecteur de vitesse
float pdist = up.distance2(p);
// Distance au carré entre nous et ce point
float mypdist = myp.distance2(p);
// Si la distance entre u et cette droite est inférieur à la somme des
// rayons, alors il y a possibilité de collision
Collision result = null;
if (pdist < sr) {
// Our speed - we move along that droite
float length = (float) sqrt(vx * vx + vy * vy);
// On déplace le point sur la droite pour trouver le point d'impact
// C'est l'autre côté du triangle rectangle formé par l'hypothénuse
// sr et pdist. Il représente la distance entre le point le plus
// proche de la droite (p) et le point où se produira effectivement
// l'impact
float backdist = (float) sqrt(sr - pdist);
// On recalcule la position du point où se passe effectivement
// l'impact
p.x = p.x - backdist * (vx / length);
p.y = p.y - backdist * (vy / length);
// Si le point s'est éloigné de nous par rapport à avant, c'est que
// notre vitesse ne va pas dans le bon sens
if (myp.distance2(p) > mypdist) {
// Cache.logCollisionEnd();
return null;
}
pdist = p.distance(myp);
// Le point d'impact est plus loin que ce qu'on peut parcourir en un
// seul tour
if (pdist > length) {
// Cache.logCollisionEnd();
return null;
}
// Temps nécessaire pour atteindre le point d'impact
float t = pdist / length;
result = new Collision(this, u, t);
// System.err.println("Returing new collision between " + this + "
// and " + u + ": " + collision);
}
// Cache.logCollisionEnd();
return result;
}
public Collision edgeCollision() {
Entity u = getClosestEdgePoint();
// System.err.println("Closest edge for " + this + " is " + u);
return collision(u);
}
private MapPoint getClosestEdgePoint() {
float closestX, closestY;
if (y < 7500 - y) {
closestY = 0;
}
else {
closestY = 7500;
}
if (x < 16000 - x) {
closestX = 0;
}
else {
closestX = 16000;
}
MapPoint point = Cache.newMapPoint();
point.entityId = -2;
if (closestX <= closestY) {
point.x = x;
point.y = closestY;
point.vx = vx;
point.vy = 0;
}
else {
point.x = closestX;
point.y = y;
point.vy = vy;
point.vx = 0;
}
return point;
}
void bounce(Entity u) {
// Wizard and Snaffles don't collide when the Wizard doesn't hold a
// Snaffle
if (this instanceof Wizard && u instanceof Snaffle) {
if (!((Wizard) this).isHolding && ((Wizard) this).countdownBeforeNextHold <= 0) {
((Snaffle) u).becomesGrabbedBy(this);
}
return;
}
else if (u instanceof Wizard && this instanceof Snaffle) {
if (!((Wizard) u).isHolding && ((Wizard) u).countdownBeforeNextHold <= 0) {
((Snaffle) this).becomesGrabbedBy(u);
}
return;
}
else if (u instanceof MapPoint && this instanceof Snaffle) {
// System.err.println("Goal? " + ((MapPoint) u).isGoal() + " " + u);
if (((MapPoint) u).isGoal()) {
((Snaffle) this).scoredBy = ((MapPoint) u).getScorer();
return;
}
}
if (u instanceof MapPoint) {
// System.err.println("Initiating collision with edge " + this);
if (u.y == 0 || u.y == 7500) {
vy = -vy;
}
else {
vx = -vx;
}
this.debug = "Will collide with edge " + u;
// System.err.println("Collision done with edge, " + this + " // " +
// u);
return;
}
// Bludgers - store last attacked wizard to avoid a rebounce
// TODO: if Wizard is holding a Snaffle and gets recked, update Snaffle
// position too
if (this instanceof Bludger && u instanceof Wizard) {
// System.err.println("Bouncing bludget && wizard " + this + "//" +
// u);
((Bludger) this).lastCollidedWizard = u.entityId;
u.debug = "Will collide with Bludger " + this.entityId;
}
else if (this instanceof Wizard && u instanceof Bludger) {
// System.err.println("Bouncing bludget && wizard " + this + "//" +
// u);
((Bludger) u).lastCollidedWizard = entityId;
this.debug = "Will collide with Bludger " + u.entityId;
}
// System.err.println("Bouncing between " + this + " and " + u);
float m1 = mass;
float m2 = u.mass;
float mcoeff = (m1 + m2) / (m1 * m2);
float nx = x - u.x;
float ny = y - u.y;
// System.err.println("\tnew coords in u's ref: " + nx + ", " + ny);
// Distance au carré entre les 2 entities. TODO: Pourrait être calculée
// simplement comme la somme des carrés des rayons des deux entités
float nxnysquare = nx * nx + ny * ny;
// System.err.println("\tdistance between entities: " +
// sqrt(nxnysquare));
float dvx = vx - u.vx;
float dvy = vy - u.vy;
// System.err.println("\tnew speed in u's ref: " + dvx + ", " + dvy);
// fx et fy sont les composantes du vecteur d'impact. product est juste
// la pour optimiser
float product = nx * dvx + ny * dvy;
float fx = nx * product / (nxnysquare * mcoeff);
float fy = ny * product / (nxnysquare * mcoeff);
// System.err.println("\tvecteur d'impact: " + fx + ", " + fy);
// On applique une fois le vecteur d'impact à chaque entité
// proportionnellement à sa masse
vx -= fx / m1;
vy -= fy / m1;
// System.err.println("\tNew vx for us: " + vx + ", " + vy);
u.vx += fx / m2;
u.vy += fy / m2;
// System.err.println("\tNew vx for u: " + u.vx + ", " + u.vy);
// Si la norme du vecteur d'impact est inférieur à 100, on change sa
// norme pour le mettre à 100, sauf si on collisionne avec un bord
// TODO: map edge collisions
float impulse = (float) sqrt(fx * fx + fy * fy);
// System.err.println("Impulse: " + impulse);
if (impulse < GameEngine.MINIMUM_COLLISION_VELOCITY && !(u instanceof MapPoint)) {
fx = fx * GameEngine.MINIMUM_COLLISION_VELOCITY / impulse;
fy = fy * GameEngine.MINIMUM_COLLISION_VELOCITY / impulse;
// System.err.println("Multiplying by coeff: " +
// GameEngine.MINIMUM_COLLISION_VELOCITY / impulse);
}
// On applique une deuxième fois le vecteur d'impact à chaque entity
// proportionnellement à sa masse
// TODO: pourquoi ? Globalement je comprends pas l'application des
// vecteurs vitesses de l'algo, à creuser sur comment marchent les
// collisions elastiques
vx -= fx / m1;
vy -= fy / m1;
// System.err.println("\tFinal new vx for us: " + vx + ", " + vy);
u.vx += fx / m2;
u.vy += fy / m2;
// System.err.println("\tFinal new vx for u: " + u.vx + ", " + u.vy);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + entityId;
result = prime * result + Float.floatToIntBits(vx);
result = prime * result + Float.floatToIntBits(vy);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) { return true; }
if (!super.equals(obj)) { return false; }
if (getClass() != obj.getClass()) { return false; }
Entity other = (Entity) obj;
if (entityId != other.entityId) { return false; }
if (Float.floatToIntBits(vx) != Float.floatToIntBits(other.vx)) { return false; }
if (Float.floatToIntBits(vy) != Float.floatToIntBits(other.vy)) { return false; }
return super.equals(obj);
}
@Override
public String toString() {
return "Entity [entityId=" + entityId + ", vx=" + vx + ", vy=" + vy + ", friction=" + friction + ", mass="
+ mass + ", radius=" + radius + ", debug=" + debug + "] " + super.toString();
}
}
class Wizard extends Entity {
boolean friendly;
boolean isHolding;
// int grabbedSnaffle = -1;
int countdownBeforeNextHold;
public String debug;
public Wizard() {
super(0.75f, 1f, 400f);
}
@Override
public Wizard clear() {
super.clear();
friendly = false;
isHolding = false;
countdownBeforeNextHold = 0;
// grabbedSnaffle = -
return this;
}
protected Snaffle findClosestFreeSnaffle(GameState gameState) {
Snaffle result = null;
double distance = Double.MAX_VALUE;
for (Snaffle snaffle : gameState.snaffles) {
if (snaffle == null) {
continue;
}
boolean snaffleHeld = snaffle.grabbedBy != -1;
;
if (!snaffleHeld) {
double distanceToWizard = distance2(snaffle);
if (distanceToWizard < distance) {
result = snaffle;
distance = distanceToWizard;
}
}
}
// System.err.println("Closest snaffle to wizard " + result + " " + this
// + " " + gameState.snaffles);
return result;
}
protected double distanceToClosestFreeSnaffle(GameState gameState) {
double distance = Double.MAX_VALUE;
for (Snaffle snaffle : gameState.snaffles) {
if (snaffle == null) continue;
boolean snaffleHeld = snaffle.grabbedBy != -1;
if (snaffleHeld) continue;
double distanceToWizard = distance2(snaffle);
if (distanceToWizard < distance) {
distance = distanceToWizard;
}
}
// System.err.println("Closest snaffle to wizard " + result + " " + this
// + " " + gameState.snaffles);
return distance;
}
@Override
public Wizard clone() {
Wizard clone = Cache.newWizard();
clone.x = x;
clone.y = y;
clone.entityId = entityId;
clone.vx = vx;
clone.vy = vy;
clone.friendly = friendly;
clone.isHolding = isHolding;
clone.countdownBeforeNextHold = countdownBeforeNextHold;
clone.debug = debug;
return clone;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + (friendly ? 1231 : 1237);
// result = prime * result + (isHolding ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) { return true; }
if (!super.equals(obj)) { return false; }
if (getClass() != obj.getClass()) { return false; }
Wizard other = (Wizard) obj;
if (friendly != other.friendly) { return false; }
// if (isHolding != other.isHolding) { return false; }
return super.equals(obj);
}
@Override
public String toString() {
return "Wizard [friendly=" + friendly + ", isHolding=" + isHolding + ", countdownBeforeNextHold="
+ countdownBeforeNextHold + ", debug=" + debug + "] " + super.toString();
}
public Snaffle findHeldSnaffle(GameState gameState) {
for (Snaffle snaffle : gameState.snaffles) {
if (snaffle == null) {
continue;
}
if (snaffle.grabbedBy == this.entityId) { return snaffle; }
}
return null;
}
}
class MapPoint extends Entity {
public MapPoint() {
super(Float.MAX_VALUE / 2, Float.MAX_VALUE / 2, 0);
}
public int getScorer() {
return x == 0 ? 1 : 0;
}
public boolean isGoal() {
if (x == 0 || x == 16000) { return y > 1900 && y < 5600; }
return false;
}
@Override
public String toString() {
return "MapPoint [] " + super.toString();
}
}
class Pole extends Entity {
public Pole() {
super(Float.MAX_VALUE / 2, Float.MAX_VALUE / 2, 300);
}
@Override
public String toString() {
return "Pole [] " + super.toString();
}
}
class Snaffle extends Entity {
public int scoredBy = -1;
public int grabbedBy = -1;
public Snaffle() {
super(0.75f, 0.5f, 150f);
}
@Override
public Snaffle clear() {
super.clear();
scoredBy = -1;
grabbedBy = -1;
return this;
}
public void becomesGrabbedBy(Entity entity) {
x = entity.x;
y = entity.y;
vx = entity.vx;
vy = entity.vy;
((Wizard) entity).isHolding = true;
((Wizard) entity).debug = "" + entityId;
((Wizard) entity).countdownBeforeNextHold = 3;
// debug = "grabbed by " + entity;
grabbedBy = entity.entityId;
// entity.debug = toString();
// System.err.println("Grabbed by! " + this + " " + entity);
}
@Override
void move(float t) {
// if (!grabbed) {
x += vx * t;
y += vy * t;
// }
}
@Override
public Snaffle clone() {
Snaffle clone = Cache.newSnaffle();
clone.x = x;
clone.y = y;
clone.entityId = entityId;
clone.vx = vx;
clone.vy = vy;
clone.scoredBy = scoredBy;
// clone.debug = debug;
clone.grabbedBy = grabbedBy;
return clone;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
// result = prime * result + (grabbed ? 1231 : 1237);
result = prime * result + scoredBy;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!super.equals(obj)) return false;
if (getClass() != obj.getClass()) return false;
Snaffle other = (Snaffle) obj;
// if (grabbed != other.grabbed) return false;
if (scoredBy != other.scoredBy) return false;
return true;
}
@Override
public String toString() {
return "Snaffle [scoredBy=" + scoredBy + ", grabbedBy=" + grabbedBy + "] " + super.toString();
}
}
class Bludger extends Entity {
int lastCollidedWizard = -1;
public Bludger() {
super(0.9f, 8f, 200f);
}
@Override
public Bludger clear() {
super.clear();
lastCollidedWizard = -1;
return this;
}
public Wizard findClosestWizard(GameState gameState) {
Wizard result = null;
double distance = Double.MAX_VALUE;
for (Wizard wizard : gameState.myWizards) {
if (lastCollidedWizard == wizard.entityId) {
continue;
}
double distanceToWizard = distance2(wizard);
if (distanceToWizard < distance) {
result = wizard;
distance = distanceToWizard;
}
}
for (Wizard wizard : gameState.opponentWizards) {
if (lastCollidedWizard == wizard.entityId) {
continue;
}
double distanceToWizard = distance2(wizard);
if (distanceToWizard < distance) {
result = wizard;
distance = distanceToWizard;
}
}
return result;
}
@Override
public Bludger clone() {
Bludger clone = Cache.newBludger();
clone.x = x;
clone.y = y;
// clone.angle = angle;
clone.entityId = entityId;
clone.vx = vx;
clone.vy = vy;
clone.lastCollidedWizard = lastCollidedWizard;
return clone;
}
@Override
public String toString() {
return "Bludger [lastCollidedWizard=" + lastCollidedWizard + "] " + super.toString();
}
}
class Collision {
Entity a, b;
float t;
float timeInProcess;
public Collision(Entity a, Entity b, float t) {
super();
this.a = a;
this.b = b;
this.t = t;
}
@Override
public String toString() {
return "Collision [a=" + a + ", b=" + b + ", t=" + t + ", timeInProcess=" + timeInProcess + "]";
}
}
// =================================
// Utils
// =================================
class Utils {
public static int roundAwayFromZero(float f) {
// System.err.println("Rounding " + f + " to " + (int) (signum(f) *
// round(abs(f))));
return (int) (signum(f) * round(abs(f)));
}
}
// =================================
// AI
// =================================
abstract class AI {
public abstract Action[] computeActions(GameState gameState);
static Point goal0 = new Point(16000, 3750);
static Point goal1 = new Point(0, 3750);
protected Point findOpponentGoal(int myTeamId) {
switch (myTeamId) {
case 0:
return goal0;
case 1:
return goal1;
default:
return null;
}
}
}
class MCAI extends AI {
private static final Random RANDOM = new Random();
static int TIME_FOR_OWN_SIM = 55;
static int TIME_FOR_OPP_SIM = 10;
static int TIME_FOR_OWN_SIM_FIRST_TURN = 55;
static int SOLUTIONS_POOL_SIZE = 50000;
static int LOOK_AHEAD = 3;
static Solution[] solutions = new Solution[SOLUTIONS_POOL_SIZE];
static boolean SHOULD_CARRY_OVER = false;
static int CARRIED_OVER = 20;
static int CLONES = 5;
static Solution[] carriedOver = new Solution[CARRIED_OVER];
@Override
public Action[] computeActions(GameState gameState) {
solutions = new Solution[SOLUTIONS_POOL_SIZE];
// TODO: compute best opponentActions
Action[] opponentActions = null;
// while (System.currentTimeMillis() - start < TIME_FOR_OPP_SIM) {
//
// }
// start = System.currentTimeMillis();
Action[] chosenActions = new Action[2];
int index = 0;
// System.err.println("Starting MCAI after " +
// (System.currentTimeMillis() - Player.turnStart));
System.err.println("Starting carrying over from previous " + (System.currentTimeMillis() - Player.turnStart));
int carriedOverIndex = 0;
while (SHOULD_CARRY_OVER && index < CARRIED_OVER && carriedOver[carriedOverIndex] != null) {
Solution carriedOverSolution = carriedOver[carriedOverIndex++];
GameState initialState = Cache.newGameState();
initialState.cloneFrom(gameState);
GameState finalState = Cache.newGameState();
finalState.cloneFrom(initialState);
for (int i = 0; i < MCAI.LOOK_AHEAD - 1; i++) {
Action[] actions = carriedOverSolution.steps[i].actions;
GameEngine.simulateNextTurn(initialState, finalState, actions, opponentActions);
initialState = finalState;
}
for (int k = 0; k < CLONES; k++) {
GameState newFinalState = Cache.newGameState();
newFinalState.cloneFrom(initialState);
Solution offspring = carriedOverSolution.clone();
Action[] actions = buildRandomActions(initialState);
// System.err.println("Actions " + Arrays.toString(actions));
GameEngine.simulateNextTurn(initialState, newFinalState, actions, opponentActions);
initialState = newFinalState;
offspring.steps[MCAI.LOOK_AHEAD - 1].actions = actions;
offspring.steps[MCAI.LOOK_AHEAD - 1].score = evaluateFinalPosition(newFinalState);
offspring.computeScore();
solutions[index] = offspring;
index++;
}
}
System.err.println("Starting MCAI proper after " + (System.currentTimeMillis() - Player.turnStart));
float simTime = Player.firstTurnDone ? TIME_FOR_OWN_SIM : TIME_FOR_OWN_SIM_FIRST_TURN;
System.err.println("sim time " + simTime);
try {
while (System.currentTimeMillis() - Player.turnStart < simTime) {
// Build a solution that has valid steps
GameState cloneState = Cache.newGameState();
cloneState.cloneFrom(gameState);
GameState finalState = Cache.newGameState();
finalState.cloneFrom(cloneState);
Solution solution = buildSolution(cloneState, finalState, opponentActions);
GameState debugState = Cache.newGameState();
debugState.cloneFrom(finalState);
solution.expectedEndState = debugState;
// Store that solution
solutions[index++] = solution;
}
}
catch (Exception e) {
System.err.println(index + " solutions");
throw e;
}
System.err.println("Ended MCAI after " + (System.currentTimeMillis() - Player.turnStart));
// Sort solutions
Arrays.sort(solutions, new Comparator<Solution>() {
@Override
public int compare(Solution o1, Solution o2) {
if (o1 == null) { return 1; }
if (o2 == null) { return -1; }
if (o2.score == o1.score) { return 0; }
return o2.score > o1.score ? 1 : -1;
}
});
// Take the best one
int i = 0;
Solution best = null;
try {
do {
best = solutions[i];
chosenActions[0] = solutions[i].steps[0].actions[0];
chosenActions[1] = solutions[i].steps[0].actions[1];
if (i > 10) {
System.err.println("First 10 solutions are invalid, issue with the simulation");
System.err.println(Arrays.toString(gameState.myWizards));
System.err.println(solutions[0].steps[0].actions[0]);
System.err.println(solutions[0].steps[0].actions[1]);
System.err.println(gameState);
throw new RuntimeException();
}
}
while (!solutions[i++].steps[0].isValid(gameState));
}
catch (Exception e) {
System.err.println("Breaking at step " + i);
System.err.println(solutions[0].steps[0].actions[0]);
System.err.println(solutions[0].steps[0].actions[1]);
throw e;
}
// System.err.println("Best solution will lead us to game state in " +
// LOOK_AHEAD + " turns");
// System.err.println(best.expectedEndState);
// System.err.println(best);
i = 0;
if (SHOULD_CARRY_OVER) {
index = 0;
while (i < CARRIED_OVER) {
if (solutions[index].steps[0].isValid(gameState)) {
for (int j = 0; j < MCAI.LOOK_AHEAD - 1; j++) {
solutions[index].steps[j] = solutions[index].steps[j + 1];
}
carriedOver[i] = solutions[index];
i++;
}
index++;
}
}
System.err.println("Did " + index + " solutions after " + (System.currentTimeMillis() - Player.turnStart));
return chosenActions;
}
private Solution buildSolution(GameState initialState, GameState finalState, Action[] opponentActions) {
Solution solution = Cache.newSolution();
for (int i = 0; i < LOOK_AHEAD; i++) {
Action[] actions = buildRandomActions(initialState);
// System.err.println("Actions " + Arrays.toString(actions));
GameEngine.simulateNextTurn(initialState, finalState, actions, opponentActions);
initialState = finalState;
solution.steps[i].actions = actions;
solution.steps[i].score = evaluateFinalPosition(finalState);
initialState = finalState;
}
solution.computeScore();
return solution;
}
private Action[] buildRandomActions(GameState initialState) {
Action[] actions = new Action[2];
for (int i = 0; i < 2; i++) {
Action action = Cache.newAction();
action.debug = "" + initialState.myWizards[i].isHolding;
// 0 is MOVE, 1 is THROW
if (initialState.myWizards[i].isHolding) {
Snaffle held = initialState.myWizards[i].findHeldSnaffle(initialState);
if (held == null) {
System.err.println("Deciding on a throw while wizard is not holding!");
System.err.println(initialState);
throw new RuntimeException();
}
action.type = "THROW";
action.power = 500;// Math.min(500, 400 + RANDOM.nextInt(300));
// Always throw forward
if (initialState.myTeamId == 0) {
action.x = initialState.myWizards[i].x + RANDOM.nextInt(16000);
}
else {
action.x = RANDOM.nextInt((int) initialState.myWizards[i].x);
}
action.y = RANDOM.nextInt(7501);
// action.x = RANDOM.nextInt(16001);
// action.y = RANDOM.nextInt(7501);
// Point opponentGoal = findOpponentGoal(initialState.myTeamId);
// action.x = opponentGoal.x;
// action.y = opponentGoal.y;
}
else {
Snaffle held = initialState.myWizards[i].findHeldSnaffle(initialState);
if (held != null) {
System.err.println("Holding a snaffle, shuold throw instead!");
System.err.println(initialState);
throw new RuntimeException();
}
action.type = "MOVE";
action.power = 150; // Math.min(150, 120 + RANDOM.nextInt(100));
action.x = RANDOM.nextInt(16001);
action.y = RANDOM.nextInt(7501);
}
actions[i] = action;
}
return actions;
}
static float SCORE_WEIGHT = 1000;
static float CLOSEST_DIST_WEIGHT = 1.5f;
static float GLOBAL_DIST_TO_SNAFFLES_WEIGTH = 1f;
static float HOLDING_WEIGHT = 5;
static float WIN_LOSS_WEIGHT = 100000;
static float SPREAD_WEIGHT = 0.3f;
static float SPEED_WEIGHT = 0;
static float SNAFFLE_DIST_TO_GOAL_WEIGHT = 0;
static float MAX_DISTANCE = (float) 16000 * 16000 + 7500 * 7500;
static float WIZARD_TO_SNAFFLE_MIN_DIST = 400;
private float evaluateFinalPosition(GameState finalState) {
// Cache.logEval();
float score = 0;
int numberOfSnaffles = finalState.initialTotalEntities == 11 ? 7 : 5;
// Direct win/loss
if ((finalState.initialTotalEntities == 11 && finalState.myScore == 4)
|| (finalState.initialTotalEntities == 9 && finalState.myScore == 3)) {
score += WIN_LOSS_WEIGHT;
}
else if ((finalState.initialTotalEntities == 11 && finalState.opponentScore == 4)
|| (finalState.initialTotalEntities == 9 && finalState.opponentScore == 3)) {
score -= WIN_LOSS_WEIGHT;
}
score += SCORE_WEIGHT * finalState.myScore;
score -= SCORE_WEIGHT * finalState.opponentScore;
// Add the distance of ourselves to the snaffles
for (Wizard wizard : finalState.myWizards) {
if (wizard.isHolding) {
score += HOLDING_WEIGHT;
}
else {
float dist = (float) wizard.distanceToClosestFreeSnaffle(finalState);
float normalizedDist = (dist - WIZARD_TO_SNAFFLE_MIN_DIST) / MAX_DISTANCE;
score += CLOSEST_DIST_WEIGHT * (1 - normalizedDist);
}
}
// We want to be spread out to cover more ground instead of being all
// together
float wizardSpread = finalState.myWizards[0].distance2(finalState.myWizards[1]);
score += SPREAD_WEIGHT * wizardSpread / MAX_DISTANCE;
// float totalSpeed = 0;
// float totalDistanceToSnaffles = 0;
// for (Wizard wizard : finalState.myWizards) {
// totalSpeed += sqrt(wizard.vx * wizard.vx + wizard.vy * wizard.vy);
// for (Snaffle snaffle : finalState.snaffles) {
// if (snaffle == null) continue;
// totalDistanceToSnaffles += wizard.distance(snaffle);
// }
// }
// score += SPEED_WEIGHT * totalSpeed / 2;
// score -= GLOBAL_DIST_TO_SNAFFLES_WEIGTH * totalDistanceToSnaffles / (MAX_DISTANCE * 2 * numberOfSnaffles);
// Then basic heuristic: sum snaffle distance to the center of our
// opponent's goal
// TODO: account for distance to the goal itself, not only the center
float totalDistanceToMyGoal = 0;
for (Snaffle snaffle : finalState.snaffles) {
if (snaffle == null) {
continue;
}
Point myGoal = findOpponentGoal(finalState.myTeamId == 0 ? 1 : 0);
totalDistanceToMyGoal += snaffle.distance(myGoal);
}
score += SNAFFLE_DIST_TO_GOAL_WEIGHT * totalDistanceToMyGoal / (MAX_DISTANCE * numberOfSnaffles);
// Cache.logEvalEnd();
return score;
}
}
class Solution {
GameState expectedEndState;
float score;
Step[] steps = new Step[MCAI.LOOK_AHEAD];
public Solution() {
for (int i = 0; i < MCAI.LOOK_AHEAD; i++) {
steps[i] = Cache.newStep();
}
}
public void computeScore() {
for (int i = 0; i < MCAI.LOOK_AHEAD; i++) {
score += (1 - i * 0.15) * steps[i].score;
}
}
@Override
public Solution clone() {
Solution clone = Cache.newSolution();
clone.score = 0;
for (int i = 0; i < MCAI.LOOK_AHEAD; i++) {
clone.steps[i] = Cache.newStep();
clone.steps[i].actions[0] = steps[i].actions[0].clone();
clone.steps[i].actions[1] = steps[i].actions[1].clone();
}
return clone;
}
@Override
public String toString() {
return "Solution [score=" + score + ", steps=" + Arrays.toString(steps) + "]";
}
}
class Step {
public float score;
Action[] actions = new Action[2];
public boolean isValid(GameState gameState) {
for (int i = 0; i < 2; i++) {
if (actions[i].type == "MOVE" && gameState.myWizards[i].isHolding == true) { return false; }
if (actions[i].type == "THROW" && gameState.myWizards[i].isHolding == false) { return false; }
}
return true;
}
@Override
public String toString() {
return "Step [score=" + score + ", actions=" + Arrays.toString(actions) + "]";
}
}
// =================================
// Game Engine
// =================================
class GameEngine {
static final int MINIMUM_COLLISION_VELOCITY = 100;
// static final int MAX_COLLISION_SIM_TIME = 5;
public static void simulateNextTurn(GameState gameState, GameState expectedGameState, Action[] wizardActions,
Action[] opponentActions) {
Entity[] simulatedEntities = new Entity[17];
int counter = 0;
for (int i = 0; i < 2; i++) {
simulatedEntities[counter++] = expectedGameState.myWizards[i];
}
for (int i = 0; i < 2; i++) {
simulatedEntities[counter++] = expectedGameState.opponentWizards[i];
}
for (int i = 0; i < 2; i++) {
simulatedEntities[counter++] = expectedGameState.bludgers[i];
}
for (int i = 0; i < 7; i++) {
simulatedEntities[counter++] = expectedGameState.snaffles[i];
}
// for (int i = 0; i < 4; i++) {
// simulatedEntities[counter++] = expectedGameState.poles[i];
// }
simulateOurWizards(gameState, expectedGameState, wizardActions);
// if (expectedGameState.invalid) { return; }
simulateBludgers(gameState, expectedGameState);
// System.err.println("Simulating plays for entities " +
// simulatedEntities);
play(simulatedEntities);
// simulateCollisions(gameState, expectedGameState);
updateScore(expectedGameState, gameState);
}
private static void updateScore(GameState expectedGameState, GameState gameState) {
for (int i = 0; i < expectedGameState.snaffles.length; i++) {
Snaffle snaffle = expectedGameState.snaffles[i];
if (snaffle == null) {
continue;
}
if (snaffle.scoredBy == 0) {
if (expectedGameState.myTeamId == 0) {
// System.err.println("We scored a goal! ");
expectedGameState.myScore++;
// You can have goals without anyone holding the snaffle
if (expectedGameState.snaffles[i].grabbedBy != -1) {
((Wizard) expectedGameState.findEntity(expectedGameState.snaffles[i].grabbedBy)).isHolding = false;
}
expectedGameState.snaffles[i] = null;
gameState.remove(snaffle);
}
else if (expectedGameState.myTeamId == 1) {
// System.err.println("They scored a goal! ");
expectedGameState.opponentScore++;
if (expectedGameState.snaffles[i].grabbedBy != -1) {
((Wizard) expectedGameState.findEntity(expectedGameState.snaffles[i].grabbedBy)).isHolding = false;
}
expectedGameState.snaffles[i] = null;
gameState.remove(snaffle);
}
}
else if (snaffle.scoredBy == 1) {
if (expectedGameState.myTeamId == 1) {
// System.err.println("We scored a goal! ");
expectedGameState.myScore++;
if (expectedGameState.snaffles[i].grabbedBy != -1) {
((Wizard) expectedGameState.findEntity(expectedGameState.snaffles[i].grabbedBy)).isHolding = false;
}
expectedGameState.snaffles[i] = null;
gameState.remove(snaffle);
}
else if (expectedGameState.myTeamId == 0) {
// System.err.println("They scored a goal! ");
expectedGameState.opponentScore++;
if (expectedGameState.snaffles[i].grabbedBy != -1) {
((Wizard) expectedGameState.findEntity(expectedGameState.snaffles[i].grabbedBy)).isHolding = false;
}
expectedGameState.snaffles[i] = null;
gameState.remove(snaffle);
}
}
}
}
private static void simulateOurWizards(GameState gameState, GameState expectedGameState, Action[] actions) {
for (int i = 0; i < 2; i++) {
Action action = actions[i];
if ("MOVE".equals(action.type)) {
expectedGameState.myWizards[i].applyNewSpeed(action.x, action.y, action.power);
}
else if ("THROW".equals(action.type)) {
Snaffle held = expectedGameState.myWizards[i].findHeldSnaffle(expectedGameState);
// System.err.println("Applying new speed to thrown snaffle " +
// held + " " + action);
// TODO: incorrect simulation?
// if (held == null) {
// // System.err.println("WARN: incorrect simulation with
// // original action");
// actions[i].type = "MOVE";
// actions[i].power = Math.min(150, actions[i].power);
// expectedGameState.myWizards[i].applyNewSpeed(action.x,
// action.y, action.power);
// return;
// }
try {
held.applyNewSpeed(action.x, action.y, action.power);
}
catch (Exception e) {
System.err.println("No snaffle to throw! ");
System.err.println(expectedGameState);
throw e;
}
held.grabbedBy = -1;
expectedGameState.myWizards[i].isHolding = false;
// System.err.println("New snaffle " + held);
// expectedGameState.myWizards[i].applyNewSpeed(action.position,
// action.power);
}
}
}
private static void simulateBludgers(GameState gameState, GameState expectedGameState) {
for (Bludger bludger : expectedGameState.bludgers) {
// Bludger moves where the wizard was at the beginning of the turn
Wizard closestWizard = bludger.findClosestWizard(gameState);
// System.err.println("Simulating bludger move " + bludger + "
// towards " + closestWizard);
bludger.applyNewSpeed(closestWizard, 1000);
}
}
// TODO still issues when there are many collisions in a single step (like
// when a buldger sends a wizard against the edge, it rebounds on the
// buldget, etc)
private static void play(Entity[] entities) {
// Il faut conserver le temps où on en est dans le tour. Le but est
// d'arriver à 1.0
float t = 0.0f;
// long start = System.currentTimeMillis();
Collision[] previousCollisions = new Collision[20];
// Collision previousCollision = null;
int collisions = 0;
// TODO: reasonable? Try to avoid multi hits
while (t < 1.0) {
Collision firstCollision = null;
// On cherche toutes les collisions qui vont arriver pendant ce tour
// et on garde celle qui arrive en premier
for (int i = 0; i < entities.length; i++) {
if (entities[i] == null) {
continue;
}
// Collision avec une autre entité ?
for (int j = i + 1; j < entities.length; j++) {
if (entities[j] == null) {
continue;
}
Collision col = entities[i].collision(entities[j]);
// Si la collision arrive plus tôt que celle qu'on, alors on
// la garde
if (col != null && col.t + t < 1.0 && !hasPreviousCollision(previousCollisions, col)
&& (firstCollision == null || col.t < firstCollision.t)) {
// System.err.println("Keeping collision over " + col +
// " // " + firstCollision);
firstCollision = col;
firstCollision.timeInProcess = t;
}
}
}
for (int i = 0; i < entities.length; i++) {
if (entities[i] == null) {
continue;
}
Collision col = entities[i].edgeCollision();
if (col != null && col.t + t < 1.0 && !hasPreviousCollision(previousCollisions, col)
&& (firstCollision == null || col.t < firstCollision.t)) {
// System.err.println("Edge collision! " + col);
// System.err.println("Keeping collision over " + col + " //
// " + firstCollision);
firstCollision = col;
firstCollision.timeInProcess = t;
}
}
if (firstCollision != null) {
// Cache.logCollision();
// System.err.println("Collision! " + firstCollision);
// System.err.println(Arrays.toString(previousCollisions));
// On bouge les entities du temps qu'il faut pour arriver sur
// l'instant `t` de la collision
for (int i = 0; i < entities.length; ++i) {
if (entities[i] == null) {
continue;
}
// System.err.println("moving entities by " +
// firstCollision.t);
entities[i].move(firstCollision.t);
}
// On joue la collision
firstCollision.a.bounce(firstCollision.b);
t += firstCollision.t;
// System.err.println("setting t: " + t + " " +
// firstCollision.t);
for (int i = 0; i < previousCollisions.length; i++) {
if (previousCollisions[i] == null) {
previousCollisions[i] = firstCollision;
break;
}
}
// previousCollision = firstCollision;
collisions++;
}
else {
// Aucune collision, on peut juste déplacer les entities jusqu'à
// la fin du tour
for (int i = 0; i < entities.length; ++i) {
if (entities[i] == null) {
continue;
}
// System.err.println("moving entities by " + (1.0f - t));
entities[i].move(1.0f - t);
}
// Fin du tour
t = 1.0f;
}
}
// On arrondi les positions et on tronque les vitesses pour tout le
// monde
for (int i = 0; i < entities.length; ++i) {
if (entities[i] == null) {
continue;
}
// System.err.println("Rounding values for " + entities[i]);
entities[i].end();
// System.err.println("\tWhich gives " + entities[i]);
}
}
private static boolean hasPreviousCollision(Collision col, Collision collision) {
if (col == null) { return false; }
// System.err.println("\tComparing " + col + " and " + collision);
if (abs(collision.t) == 0 && col.a.entityId == collision.a.entityId && col.b.entityId == collision.b.entityId) { return true; }
return false;
}
private static boolean hasPreviousCollision(Collision[] previousCollisions, Collision collision) {
for (Collision col : previousCollisions) {
if (col == null) {
continue;
}
// System.err.println("\tComparing " + col + " and " + collision);
if (abs(collision.t) < 0.01 && col.a.entityId == collision.a.entityId
&& col.b.entityId == collision.b.entityId) { return true; }
}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment