package animals;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
/*public*/ class DeepWolf extends Animal {
private static final int NUM_WOLF_SPECIES
= (int) Math.
round(Math.
pow( ((double) MAP_SIZE) / 20, 2) - 3) - 3;
private static final int POPULATION = 100;
private static int MAP_AREA;
private static final char EMPTY = ' ';
private static final char STONE = 'S';
private static final char BEAR = 'B';
private static final char LION = 'L';
private static final char WOLF = 'W';
private static final char ENEMY_WOLF = 'w';
private static final char FRIENDLY_WOLF = '@';
private static final Attack[] ATTACKS = { Attack.ROCK, Attack.PAPER,
Attack.SCISSORS };
private static final float EXPECTED_WIN_PERCENTAGE = 1f / ATTACKS.length;
private static final float POTENTIAL_DANGER_DECAY = 63 / 64f;
private static final float PROXIMITY_DANGER_DECAY = 3 / 4f;
private static final float UNSEEN_DEATH_BASE_CHANCE = 1 / 16f;
private static final FightIntel FIGHT_INTEL_OVERRIDE = new FightIntel();
static {
FIGHT_INTEL_OVERRIDE.registerWin(STONE, Attack.PAPER);
FIGHT_INTEL_OVERRIDE.registerWin(BEAR, Attack.SCISSORS);
FIGHT_INTEL_OVERRIDE.registerWin(LION, Attack.SCISSORS);
FIGHT_INTEL_OVERRIDE.registerWin(LION, Attack.SCISSORS);
FIGHT_INTEL_OVERRIDE.registerWin(LION, Attack.SCISSORS);
FIGHT_INTEL_OVERRIDE.registerLoss(LION, Attack.SCISSORS);
}
private static final FightIntel fightIntel = new FightIntel();
private static final PopulationIntel populationIntel = new PopulationIntel();
static {
populationIntel.addMany(STONE, POPULATION);
populationIntel.addMany(BEAR, POPULATION);
populationIntel.addMany(LION, POPULATION);
populationIntel.addMany(WOLF, (NUM_WOLF_SPECIES - 1) * POPULATION);
}
private static MoveIntel movesThisTurn;
private static final Collection<DeepWolf> us = new ArrayList<DeepWolf>();
private static int turn = -1;
private static boolean movePhase = false;
private boolean calledToMoveThisTurn = false;
private final List<Fight> fightsLastTurn = new ArrayList<Fight>();
private int y = 0;
private int x = 0;
private final PositionIntel positionIntel = new PositionIntel();
// for debug purposes
private BetterMove moveThisTurn;
private Map<BetterMove, List<Double>> dangerThisTurn;
private List<Double> dangerTemp;
public DeepWolf() {
super(WOLF);
us.add(this);
populationIntel.addOne(FRIENDLY_WOLF);
MAP_AREA = MAP_SIZE * MAP_SIZE;
}
@Override
public Attack fight(final char opponent) {
if (movePhase) {
enterFightPhase();
}
Attack attack = doFight(opponent);
fightsLastTurn.add(new Fight(opponent, attack));
return attack;
}
private Attack doFight(final char opponent) {
return getFightIntel(opponent).getBest();
}
private static SimpleFightIntel getFightIntel(final char opponent) {
if (FIGHT_INTEL_OVERRIDE.hasData(opponent)) {
return FIGHT_INTEL_OVERRIDE.getOpponentIntel(opponent);
} else {
return fightIntel.getOpponentIntel(opponent);
}
}
@Override
public Move move() {
if (calledToMoveThisTurn) {
enterFightPhase();
}
if (!movePhase) {
enterMovePhase();
}
calledToMoveThisTurn = true;
surroundings[1][1] = EMPTY;
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
positionIntel.add(y + (row - 1), x + (col - 1), surroundings[row][col]);
}
}
BetterMove move = doMove();
movesThisTurn.add(move);
moveThisTurn = move;
y = clip(y + move.dy);
x = clip(x + move.dx);
return move.toMove();
}
private BetterMove doMove() {
double leastDanger
= Float.
POSITIVE_INFINITY; BetterMove leastDangerousMove = null;
dangerThisTurn = new HashMap<BetterMove, List<Double>>();
for (BetterMove move : BetterMove.values()) {
dangerTemp = new ArrayList<Double>(5);
for (int i = 0; i < 5; i++) {
dangerTemp.add(0d);
}
double danger = positionIntel.assessDangerOnTurn(y + move.dy, x + move.dx, turn + 1);
if (move.hasOpposite()) {
double friendlyWolfCollideProbability = (movesThisTurn.percentageOf(move.getOpposite()) * (us.size() - 1))
/ MAP_AREA;
dangerTemp.set(4, friendlyWolfCollideProbability * getOpponentDanger(WOLF));
danger += friendlyWolfCollideProbability * getOpponentDanger(WOLF);
}
dangerThisTurn.put(move, dangerTemp);
if (danger < leastDanger) {
leastDanger = danger;
leastDangerousMove = move;
}
}
if (leastDangerousMove != null) {
return leastDangerousMove;
} else {
throw new AssertionError();
}
}
private static void enterMovePhase() {
movePhase = true;
turn++;
movesThisTurn = new MoveIntel();
}
private static void enterFightPhase() {
movePhase = false;
Iterator<DeepWolf> wolves = us.iterator();
while (wolves.hasNext()) {
DeepWolf wolf = wolves.next();
Iterator<Fight> fights = wolf.fightsLastTurn.iterator();
while (fights.hasNext()) {
Fight fight = fights.next();
char opponent = fight.getOpponent();
Attack attack = fight.getAttack();
if (fights.hasNext() || wolf.calledToMoveThisTurn) {
fightIntel.registerWin(opponent, attack);
// TODO: don't assume that we killed an unfriendly wolf here?
populationIntel.removeOne(opponent);
} else {
fightIntel.registerLoss(opponent, attack);
populationIntel.removeOne(FRIENDLY_WOLF);
wolves.remove();
}
}
wolf.calledToMoveThisTurn = false;
wolf.fightsLastTurn.clear();
}
}
private static int clip(final int n) {
return ((n % MAP_SIZE) + MAP_SIZE) % MAP_SIZE;
}
private static int posDist(final int m, final int n) {
return clip(n - m);
}
private static int dist(final int m, final int n) {
int posDist = posDist(m, n);
return posDist < (MAP_SIZE / 2) ? posDist : MAP_SIZE - posDist;
}
private boolean isVisible(final int y, final int x) {
return (dist(y, this.y) <= 1) && (dist(x, this.x) <= 1);
}
private static double getOpponentDanger(final char opponent) {
double winPercentage;
if (opponent == FRIENDLY_WOLF) {
winPercentage = 0;
} else {
SimpleFightIntel opponentFightIntel = getFightIntel(opponent);
winPercentage = opponentFightIntel.getWinPercentage(opponentFightIntel.getBest());
if (winPercentage == 0) {
winPercentage = EXPECTED_WIN_PERCENTAGE;
}
if (opponent == WOLF) {
winPercentage *= (double) (NUM_WOLF_SPECIES - 1) / NUM_WOLF_SPECIES;
}
}
return (1 / winPercentage) - 1;
}
private static double decayPotentialDanger(final double danger, final int turnsStale) {
return decayDanger(danger, turnsStale, POTENTIAL_DANGER_DECAY);
}
private static double decayProximityDanger(final double danger, final int proximity) {
return decayDanger(danger, proximity, PROXIMITY_DANGER_DECAY);
}
private static double decayDanger(final double danger, final int decay, final double factor) {
return danger
* Math.
pow(factor, decay
); }
private static class Fight {
private final char opponent;
private final Attack attack;
Fight(final char opponent, final Attack attack) {
this.opponent = opponent;
this.attack = attack;
}
private char getOpponent() {
return opponent;
}
private Attack getAttack() {
return attack;
}
}
private static class WinLossIntel {
private int total = 0;
private int wins = 0;
WinLossIntel() {}
private int getTotal() {
return total;
}
private int getWins() {
return wins;
}
private int getLosses() {
return total - wins;
}
private double getWinPercentage() {
return total == 0 ? 0 : (double) wins / total;
}
private void registerWin() {
total++;
wins++;
}
private void registerLoss() {
total++;
}
private void registerResult(final boolean won) {
if (won) {
registerWin();
} else {
registerLoss();
}
}
@Override
return wins + "/" + total;
}
}
private static class SimpleFightIntel {
private final Map<Attack, WinLossIntel> attackIntel;
SimpleFightIntel() {
attackIntel = new HashMap<Attack, WinLossIntel>();
for (Attack attack : ATTACKS) {
get(attack);
}
}
private WinLossIntel get(final Attack attack) {
WinLossIntel result = attackIntel.get(attack);
if (result == null) {
result = new WinLossIntel();
attackIntel.put(attack, result);
}
return result;
}
private int getTotal(final Attack attack) {
return get(attack).getTotal();
}
private int getWins(final Attack attack) {
return get(attack).getWins();
}
private int getLosses(final Attack attack) {
return get(attack).getLosses();
}
private double getWinPercentage(final Attack attack) {
return get(attack).getWinPercentage();
}
private Attack getBest() {
Attack bestAttack = null;
double bestPercentage = -1;
List<Attack> leastUsedAttacks = new ArrayList<Attack>(ATTACKS.length);
for (Entry<Attack, WinLossIntel> entry : attackIntel.entrySet()) {
Attack attack = entry.getKey();
WinLossIntel intel = entry.getValue();
double percentage = entry.getValue().getWinPercentage();
if (percentage > bestPercentage) {
bestAttack = entry.getKey();
bestPercentage = percentage;
}
int uses = intel.getTotal();
if (uses <= leastUses) {
leastUsedAttacks.add(attack);
leastUses = uses;
}
}
if (((bestPercentage - EXPECTED_WIN_PERCENTAGE) / (1 - EXPECTED_WIN_PERCENTAGE)) < (1.0 / (leastUses + 2 + random
.nextDouble()))) {
return leastUsedAttacks.get(random.nextInt(leastUsedAttacks.size()));
} else {
return bestAttack;
}
}
private void registerWin(final Attack attack) {
registerResult(attack, true);
}
private void registerLoss(final Attack attack) {
registerResult(attack, false);
}
private void registerResult(final Attack attack, final boolean won) {
get(attack).registerResult(won);
}
private boolean hasData() {
for (WinLossIntel intel : attackIntel.values()) {
if (intel.getTotal() > 0) {
return true;
}
}
return false;
}
@Override
return attackIntel.toString();
}
}
private static class FightIntel {
private final Map
<Character, SimpleFightIntel
> fightIntel
= new HashMap
<Character, SimpleFightIntel
>();
FightIntel() {}
private SimpleFightIntel get
(final Character opponent
) { SimpleFightIntel result = fightIntel.get(opponent);
if (result == null) {
result = new SimpleFightIntel();
fightIntel.put(opponent, result);
}
return result;
}
private SimpleFightIntel getOpponentIntel
(final Character opponent
) { return get(opponent);
}
private WinLossIntel get
(final Character opponent,
final Attack attack
) { return get(opponent).get(attack);
}
private int getTotal
(final Character opponent,
final Attack attack
) { return get(opponent, attack).getTotal();
}
private int getWins
(final Character opponent,
final Attack attack
) { return get(opponent, attack).getWins();
}
private int getLosses
(final Character opponent,
final Attack attack
) { return get(opponent, attack).getLosses();
}
private double getWinPercentage
(final Character opponent,
final Attack attack
) { return get(opponent, attack).getWinPercentage();
}
private Attack getBest
(final Character opponent
) { return get(opponent).getBest();
}
private void registerWin
(final Character opponent,
final Attack attack
) { registerResult(opponent, attack, true);
}
private void registerLoss
(final Character opponent,
final Attack attack
) { registerResult(opponent, attack, false);
}
private void registerResult
(final Character opponent,
final Attack attack,
final boolean won
) { get(opponent, attack).registerResult(won);
}
private boolean hasData
(final Character opponent
) { return get(opponent).hasData();
}
@Override
return fightIntel.toString();
}
}
private static class Coordinate {
private final int x;
private final int y;
Coordinate(final int y, final int x) {
this.x = clip(x);
this.y = clip(y);
}
private int getX() {
return x;
}
private int getY() {
return y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (prime * result) + x;
result = (prime * result) + y;
return result;
}
@Override
public boolean equals
(final Object obj
) { if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Coordinate other = (Coordinate) obj;
return (x == other.x) && (y == other.y);
}
@Override
return "[" + y + "," + x + "]";
}
}
private static class LionIntel {
private final Map<Coordinate, Integer> lionIntel = new HashMap<Coordinate, Integer>();
LionIntel() {}
private static Coordinate getCoordinateOnTurn(final Coordinate coordinate, final int onTurn) {
return getCoordinateOnTurn(coordinate.getY(), coordinate.getX(), onTurn);
}
private static Coordinate getCoordinateOnTurn(final int y, final int x, final int onTurn) {
return getCoordinateOnTurnRelativeToTurn(y, x, onTurn, 0);
}
private static Coordinate getCoordinateOnTurnRelativeToTurn(final int y, final int x, final int onTurn,
final int relativeToTurn) {
return new Coordinate(y + ((onTurn / 2) - (relativeToTurn / 2)), x
+ (((onTurn + 1) / 2) - ((relativeToTurn + 1) / 2)));
}
private void addOnTurn(final int y, final int x, final int onTurn) {
lionIntel.put(getCoordinateOnTurnRelativeToTurn(y, x, 0, onTurn), onTurn);
}
private void add(final int y, final int x) {
addOnTurn(y, x, turn);
}
private void add(final int y, final int x, final boolean isPresent) {
if (isPresent) {
add(y, x);
} else {
remove(y, x);
}
}
private void remove(final int y, final int x) {
lionIntel.remove(getCoordinateOnTurnRelativeToTurn(y, x, 0, turn));
}
private int size() {
return lionIntel.size();
}
private double assessDangerOnTurn(final int y, final int x, final int onTurn) {
return assessDangerOnTurn(y, x, onTurn, false);
}
private double assessDangerOnTurnWithFuture(final int y, final int x, final int onTurn) {
return assessDangerOnTurn(y, x, onTurn, true);
}
private double assessDangerOnTurn(final int y, final int x, final int onTurn, final boolean withFuture) {
double danger = 0;
for (Entry<Coordinate, Integer> entry : lionIntel.entrySet()) {
Coordinate lion = entry.getKey();
int turnLastSeen = entry.getValue();
Coordinate lionWhenLastSeen = getCoordinateOnTurn(lion, turnLastSeen);
int dy = posDist(lionWhenLastSeen.getY(), y);
int dx = posDist(lionWhenLastSeen.getX(), x);
if ((dy == 0) && (dx == 0)) {
dy = dx = MAP_SIZE;
} else if ((dy == 0) && (dx == (MAP_SIZE - 1))) {
dy = MAP_SIZE;
} else if ((dx == 0) && (dy == (MAP_SIZE - 1))) {
dx = MAP_SIZE;
}
int turnsUntilClosestEncounterY = (dy * 2) - (turnLastSeen % 2);
int turnsUntilClosestEncounterX = ((dx * 2) - ((turnLastSeen + 1) % 2));
int turnsUntilClosestEncounter
= Math.
min(turnsUntilClosestEncounterY, turnsUntilClosestEncounterX
) + 1; Coordinate lionAtClosestEncounter = getCoordinateOnTurn(lion, turnLastSeen + turnsUntilClosestEncounter);
int closestEncounterDist = dist(lionAtClosestEncounter.getY(), y)
+ dist(lionAtClosestEncounter.getX(), x);
if (((turnLastSeen + turnsUntilClosestEncounter) - onTurn) < closestEncounterDist) {
turnsUntilClosestEncounter
= Math.
max(turnsUntilClosestEncounterY, turnsUntilClosestEncounterX
); lionAtClosestEncounter = getCoordinateOnTurn(lion, turnLastSeen + turnsUntilClosestEncounter);
closestEncounterDist = dist(lionAtClosestEncounter.getY(), y)
+ dist(lionAtClosestEncounter.getX(), x);
}
if (withFuture || (((turnsUntilClosestEncounter == 1)) && (closestEncounterDist == 0))) {
double lionDanger = decayProximityDanger(
decayPotentialDanger(getOpponentDanger(LION), turnsUntilClosestEncounter),
closestEncounterDist);
danger += lionDanger;
}
}
return danger;
}
@Override
public int hashCode() {
return lionIntel.hashCode();
}
@Override
public boolean equals
(final Object obj
) { if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
LionIntel other = (LionIntel) obj;
return lionIntel == other.lionIntel;
}
@Override
return LionIntel.class.getSimpleName() + "[" + lionIntel.toString() + "]";
}
}
private static class WolfIntel {
private final Map<Coordinate, Integer> wolfIntel = new HashMap<Coordinate, Integer>();
WolfIntel() {}
private void addOnTurn(final int y, final int x, final int onTurn) {
wolfIntel.put(new Coordinate(y, x), onTurn);
}
private void add(final int y, final int x) {
addOnTurn(y, x, turn);
}
private void add(final int y, final int x, final boolean isPresent) {
if (isPresent) {
add(y, x);
} else {
remove(y, x);
}
}
private void remove(final int y, final int x) {
wolfIntel.remove(new Coordinate(y, x));
}
private int size() {
return wolfIntel.size();
}
private double assessDangerOnTurn(final int y, final int x, final int onTurn) {
double danger = 0;
for (Entry<Coordinate, Integer> entry : wolfIntel.entrySet()) {
Coordinate wolf = entry.getKey();
int turnsStale = onTurn - entry.getValue();
int dist = dist(x, wolf.getX()) + dist(y, wolf.getY());
// if (turnsStale >= dist) {
// danger += decayPotentialDanger(getOpponentDanger(WOLF) * Math.pow(.75, dist - 1), turnsStale - 1);
// }
if ((turnsStale >= dist) && (dist <= 1)) {
danger
+= decayPotentialDanger
(getOpponentDanger
(WOLF
) * Math.
pow(PROXIMITY_DANGER_DECAY, dist
),
turnsStale - 1);
}
}
return danger;
}
@Override
public int hashCode() {
return wolfIntel.hashCode();
}
@Override
public boolean equals
(final Object obj
) { if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
WolfIntel other = (WolfIntel) obj;
return wolfIntel == other.wolfIntel;
}
@Override
return WolfIntel.class.getSimpleName() + "[" + wolfIntel.toString() + "]";
}
}
private static class MoveIntel {
private final Map<BetterMove, Integer> moves = new HashMap<BetterMove, Integer>();
private int total = 0;
MoveIntel() {}
private int get(final BetterMove move) {
if (result == null) {
return 0;
} else {
return result;
}
}
private void add(final BetterMove move) {
moves.put(move, get(move) + 1);
total++;
}
private int size() {
return total;
}
private double percentageOf(final BetterMove move) {
int moveCount = get(move);
return moveCount == 0 ? 0 : ((double) moveCount) / total;
}
}
private class PositionIntel {
private final LionIntel lionIntel = new LionIntel();
private final WolfIntel wolfIntel = new WolfIntel();
PositionIntel() {}
private void add(final int y, final int x, final char animal) {
lionIntel.add(y, x, animal == LION);
wolfIntel.add(y, x, animal == WOLF);
}
private double assessDangerOnTurn(final int y, final int x, final int onTurn) {
LionIntel potentialLionIntel = new LionIntel();
WolfIntel potentialWolfIntel = new WolfIntel();
for (BetterMove move : BetterMove.values()) {
int potentialY = y + move.dy;
int potentialX = x + move.dx;
if (!isVisible(potentialY, potentialX)) {
potentialLionIntel.addOnTurn(potentialY, potentialX, onTurn - 1);
potentialWolfIntel.addOnTurn(potentialY, potentialX, onTurn - 1);
}
}
double potentialLionDanger = potentialLionIntel.assessDangerOnTurn(y, x, onTurn)
* (Math.
max(0, populationIntel.
guessOnTurn(LION, onTurn
- 1) - lionIntel.
size()) / MAP_AREA
); double potentialWolfDanger = potentialWolfIntel.assessDangerOnTurn(y, x, onTurn)
0,
populationIntel.guessOnTurn(WOLF, onTurn - 1)
+ (populationIntel.guessOnTurn(FRIENDLY_WOLF, onTurn - 1) - 1)) / MAP_AREA);
double lionDanger = lionIntel.assessDangerOnTurnWithFuture(y, x, onTurn);
double wolfDanger = wolfIntel.assessDangerOnTurn(y, x, onTurn);
dangerTemp.set(0, potentialLionDanger);
dangerTemp.set(1, potentialWolfDanger);
dangerTemp.set(2, lionDanger);
dangerTemp.set(3, wolfDanger);
return potentialLionDanger + potentialWolfDanger + lionDanger + wolfDanger;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (prime * result) + lionIntel.hashCode();
result = (prime * result) + wolfIntel.hashCode();
return result;
}
@Override
public boolean equals
(final Object obj
) { if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PositionIntel other = (PositionIntel) obj;
return lionIntel.equals(other.lionIntel) && wolfIntel.equals(other.wolfIntel);
}
@Override
return PositionIntel.class.getSimpleName() + "[" + lionIntel.toString() + ", " + wolfIntel.toString() + "]";
}
}
private static class PopulationIntel {
PopulationIntel() {}
private int get(final char animal) {
Integer result
= populations.
get(animal
); if (result == null) {
return 0;
} else {
return result;
}
}
private int getMax(final char animal) {
return get(animal);
}
private void addOne(final char animal) {
addMany(animal, 1);
}
private void addMany(final char animal, final int n) {
populations.put(animal, get(animal) + n);
}
private void removeOne(final char animal) {
removeMany(animal, 1);
}
private void removeMany(final char animal, final int n) {
populations.put(animal, get(animal) - n);
}
private double guess(final char animal) {
return guessOnTurn(animal, turn);
}
private double guessOnTurn(final char animal, final int onTurn) {
int max = get(animal);
switch (animal) {
case FRIENDLY_WOLF:
return max;
default:
return max
* Math.
pow(1 - Math.
pow(UNSEEN_DEATH_BASE_CHANCE,
(getOpponentDanger
(animal
) + 1)), onTurn
); }
}
@Override
public int hashCode() {
return populations.hashCode();
}
@Override
public boolean equals
(final Object obj
) { if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
PopulationIntel other = (PopulationIntel) obj;
return populations.equals(other.populations);
}
@Override
return PopulationIntel.class.getSimpleName() + "[" + populations + "]";
}
}
private static enum BetterMove {
UP(0, -1), RIGHT(1, 0), DOWN(0, 1), LEFT(-1, 0), HOLD(0, 0);
public final int dx;
public final int dy;
BetterMove(final int dx, final int dy) {
this.dx = dx;
this.dy = dy;
}
public Move toMove() {
return toMove(this);
}
public static Move toMove(final BetterMove move) {
return Move.valueOf(move.name());
}
public static BetterMove fromMove(final Move move) {
return BetterMove.valueOf(move.name());
}
public boolean hasOpposite() {
return hasOpposite(this);
}
public static boolean hasOpposite(final BetterMove move) {
return getOpposite(move) != null;
}
public BetterMove getOpposite() {
return getOpposite(this);
}
public static BetterMove getOpposite(final BetterMove move) {
switch (move) {
case UP:
return DOWN;
case RIGHT:
return LEFT;
case DOWN:
return UP;
case LEFT:
return RIGHT;
case HOLD:
return null;
default:
throw new AssertionError();
}
}
}
}