Improved AI with documentation

This commit is contained in:
Jonas Suess 2019-03-26 17:20:38 +01:00
parent 99f5e8d59d
commit 378ed2c2c0

View file

@ -13,53 +13,68 @@ import game.AI;
import game.Game; import game.Game;
import game.Player; import game.Player;
import game.map.Castle; import game.map.Castle;
import game.map.PathFinding;
import gui.AttackThread; import gui.AttackThread;
import gui.components.MapPanel.Action;
public class StrongAI extends AI { public class StrongAI extends AI {
private static boolean DEBUG = false; // These values were determined mostly by trial-and-error
private static double UTILITY_F1 = 3.0; private static double UTILITY_F1 = 4.0; // Number of adjacent friendly castles
private static double UTILITY_F2 = 5.0; private static double UTILITY_F2 = 1.0; // Number of adjacent enemy castles
private static double UTILITY_F3 = 1.0; private static double UTILITY_F3 = 3.0; // Number of connected edges
private static double UTILITY_F4 = -10.0; private static double UTILITY_F4 = -10.0; // Is surrounded by opponents castles
private static double UTILITY_F5 = 3.0; private static double UTILITY_F5 = 3.0; // Size of the castles batch
private static double UTILITY_F6 = 2.0; private static double UTILITY_F6 = 4.0; // Castles missing to a full kingdom
private static double REINFORCE_F1 = 1.0; private static double REINFORCE_F1 = 1.0; // Number of adjacent enemy castles
private static double REINFORCE_F2 = -5.0; private static double REINFORCE_F2 = -5.0; // Is surrounded by opponents castles
private static double REINFORCE_F3 = 0.5; private static double REINFORCE_F3 = 0.5; // Castle utility
private static double REINFORCE_F4 = -100.0; private static double REINFORCE_F4 = -100.0; // Is border castle
private static double REINFORCE_F5 = -2.0; private static double REINFORCE_F5 = -2.0; // Number of troops
private static double ATTACK_F1 = 1.0; private static double ATTACK_F1 = 1.0; // Castle utility
private static double EVAL_CASTLES = 1.0; private static double EVAL_OWN = 3.0; // Weight of own utilities
private static double EVAL_OPP = -10.0; // Weight of opponents utilities
private boolean playedJoker1 = false;
private boolean playedJoker2 = false;
public StrongAI(String name, Color color) {
super(name, color);
}
/** /**
* Evaluates the current game state * Evaluates the current game state
* @param game the current game * @param game the current game
* @param graph the graph to be evaluated * @param graph the graph to be evaluated
* @param p the player * @param p the player
* @param change the castle that should be considered to belong to p
* @return the state value * @return the state value
*/ */
public static double evaluateState(Game game, Graph<Castle> g, Player p, Optional<Castle> change) { public static double evaluateState(Game game, Graph<Castle> g, Player p, Optional<Castle> change) {
// Temporarily give castle "change" to p
Player owner = null; Player owner = null;
if(change.isPresent()) { if(change.isPresent()) {
owner = change.get().getOwner(); owner = change.get().getOwner();
change.get().setOwner(p); change.get().setOwner(p);
} }
// Calculate total state utility
double value = 0; double value = 0;
for(Player player : game.getPlayers()) { for(Player player : game.getPlayers()) {
List<Castle> castles = g.getAllValues().stream() List<Castle> castles = g.getAllValues().stream()
.filter(x->x.getOwner() == player) .filter(x->x.getOwner() == player)
.collect(Collectors.toList()); .collect(Collectors.toList());
double s = (p == player)? 3.0 : -10.0; double s = (p == player)? EVAL_OWN : EVAL_OPP;
for(Castle c : castles) { for(Castle c : castles) {
value += s * utilityCastle(g, player, c) * EVAL_CASTLES; value += s * utilityCastle(g, player, c);
} }
} }
// Hand "change" back to its previous owner
if(change.isPresent()) { if(change.isPresent()) {
change.get().setOwner(owner); change.get().setOwner(owner);
} }
@ -105,20 +120,6 @@ public class StrongAI extends AI {
double f6 = c.getKingdom().getCastles().stream() double f6 = c.getKingdom().getCastles().stream()
.filter(x->x.getOwner() == p).count() - c.getKingdom().getCastles().size() + 2; .filter(x->x.getOwner() == p).count() - c.getKingdom().getCastles().size() + 2;
if(DEBUG) {
System.out.println("-----");
System.out.println("Castle: " + c.getName());
System.out.println("F1: " + f1 * UTILITY_F1);
System.out.println("F2: " + f2 * UTILITY_F2);
System.out.println("F3: " + f3 * UTILITY_F3);
System.out.println("F4: " + f4 * UTILITY_F4);
System.out.println("F5: " + f5 * UTILITY_F5);
System.out.println("F6: " + f6 * UTILITY_F6);
System.out.println("Total: " + (UTILITY_F1 * f1 + UTILITY_F2 * f2
+ UTILITY_F3 * f3 + UTILITY_F4 * f4 + f5 * UTILITY_F5
+ f6 * UTILITY_F6));
System.out.println("-----");
}
return UTILITY_F1 * f1 + UTILITY_F2 * f2 return UTILITY_F1 * f1 + UTILITY_F2 * f2
+ UTILITY_F3 * f3 + UTILITY_F4 * f4 + f5 * UTILITY_F5 + UTILITY_F3 * f3 + UTILITY_F4 * f4 + f5 * UTILITY_F5
+ f6 * UTILITY_F6; + f6 * UTILITY_F6;
@ -126,10 +127,10 @@ public class StrongAI extends AI {
/** /**
* Analyzes the utility to attack a certain castle * Analyzes the utility to attack a certain castle
* @param g * @param g the graph
* @param p * @param p the current player
* @param c * @param c the castle
* @return * @return the reinforcement utility
*/ */
public static double utilityReinforce(Graph<Castle> g, Player p, Castle c) { public static double utilityReinforce(Graph<Castle> g, Player p, Castle c) {
Node<Castle> node = g.getNode(c); Node<Castle> node = g.getNode(c);
@ -152,21 +153,9 @@ public class StrongAI extends AI {
// Is border castle // Is border castle
double f4 = isBorder(g, c)? 0.0 : 1.0; double f4 = isBorder(g, c)? 0.0 : 1.0;
// Number of troops exceeding 4 // Number of troops exceeding
double f5 = c.getTroopCount()-4; double f5 = c.getTroopCount();
if(DEBUG) {
System.out.println("-----");
System.out.println("Castle: " + c.getName());
System.out.println("F1: " + f1 * REINFORCE_F1);
System.out.println("F2: " + f2 * REINFORCE_F2);
System.out.println("F3: " + f3 * REINFORCE_F3);
System.out.println("F4: " + f4 * REINFORCE_F4);
System.out.println("F5: " + f5 * REINFORCE_F5);
System.out.println("Total: " + (REINFORCE_F1 * f1 + REINFORCE_F2 * f2 + f3 * REINFORCE_F3
+ f4 * REINFORCE_F4 + f5 * REINFORCE_F5));
System.out.println("-----");
}
return REINFORCE_F1 * f1 + REINFORCE_F2 * f2 + f3 * REINFORCE_F3 return REINFORCE_F1 * f1 + REINFORCE_F2 * f2 + f3 * REINFORCE_F3
+ f4 * REINFORCE_F4 + f5 * REINFORCE_F5; + f4 * REINFORCE_F4 + f5 * REINFORCE_F5;
} }
@ -184,40 +173,11 @@ public class StrongAI extends AI {
double ratio = ((double)a.getTroopCount())/t.getTroopCount(); double ratio = ((double)a.getTroopCount())/t.getTroopCount();
return ratio * (ATTACK_F1 * f1); return ATTACK_F1 * f1 * ratio;
} }
/** /**
* Returns a batch of connected castles around some start castle that belong to the player p * Chooses the initial castles in round one
* @param g the graph
* @param p the owner of the castles
* @param castle the start castle
* @param checked a list of checked castles, initialize as new ArrayList
* @return a list of castles
*/
public static List<Castle> getBatch(Graph<Castle> g, Player p, Castle castle, List<Castle> checked) {
Node<Castle> n = g.getNode(castle);
Set<Castle> adjacent = g.getEdges(n).stream()
.map(x->x.getOtherNode(n).getValue())
.filter(x->x.getOwner() == p)
.filter(x->checked.contains(x) == false)
.collect(Collectors.toSet());
if(!checked.contains(castle))
checked.add(castle);
for(Castle c : adjacent) {
checked.addAll(getBatch(g, p, c, checked).stream()
.filter(x->checked.contains(x) == false)
.collect(Collectors.toSet()));
}
return checked;
}
public StrongAI(String name, Color color) {
super(name, color);
}
/**
* Chooses the initial castles in round 1
* @param game the game * @param game the game
* @throws InterruptedException * @throws InterruptedException
*/ */
@ -227,15 +187,12 @@ public class StrongAI extends AI {
.collect(Collectors.toList()); .collect(Collectors.toList());
while(availableCastles.size() > 0 && getRemainingTroops() > 0) { while(availableCastles.size() > 0 && getRemainingTroops() > 0) {
sleep(1000);
sleep(3);
Castle best = availableCastles.get(0); Castle best = availableCastles.get(0);
double bestScore = 0; double bestScore = 0;
double score = 0; double score = 0;
for(Castle c : availableCastles) { for(Castle c : availableCastles) {
score = evaluateState(game, game.getMap().getGraph(), this, Optional.of(c)); score = evaluateState(game, game.getMap().getGraph(), this, Optional.of(c));
//score = evaluateLookahead(game, game.getMap().getGraph(), this, 10);
if(score > bestScore) { if(score > bestScore) {
bestScore = score; bestScore = score;
best = c; best = c;
@ -256,6 +213,7 @@ public class StrongAI extends AI {
List<Castle> castles = game.getMap().getCastles().stream() List<Castle> castles = game.getMap().getCastles().stream()
.filter(x->x.getOwner() == this) .filter(x->x.getOwner() == this)
.collect(Collectors.toList()); .collect(Collectors.toList());
while(this.getRemainingTroops() > 0) { while(this.getRemainingTroops() > 0) {
Castle best = castles.get(0); Castle best = castles.get(0);
double bestScore = 0; double bestScore = 0;
@ -266,7 +224,7 @@ public class StrongAI extends AI {
best = c; best = c;
} }
} }
sleep(5); sleep(500);
game.addTroops(this, best, 1); game.addTroops(this, best, 1);
} }
@ -295,12 +253,16 @@ public class StrongAI extends AI {
while(d.getTroopCount() > 1) { while(d.getTroopCount() > 1) {
double bestScore = 0; double bestScore = 0;
Castle best = receivers.get(0); Castle best = receivers.get(0);
// Do not cheat like evil BasicAI,
// only move troops if there is a path
PathFinding path = new PathFinding(g, d, Action.MOVING, this);
for(Castle c : receivers) { for(Castle c : receivers) {
double score = utilityReinforce(g, this, c); double score = utilityReinforce(g, this, c);
if(score > bestScore) { if(score > bestScore && path.getPath(c) != null) {
bestScore = score; if(path.getPath(c).size() > 0) {
best = c; bestScore = score;
best = c;
}
} }
} }
if(best == d) break; if(best == d) break;
@ -342,7 +304,8 @@ public class StrongAI extends AI {
best = t; best = t;
} }
} }
if(bestScore > evaluateState(game, g, this, Optional.empty())) { // Attack, if the chance of taking the castle is more useful than not attacking
if(bestScore > evaluateState(game, g, this, Optional.empty()) && a.getTroopCount() > best.getTroopCount()) {
AttackThread attackThread = game.startAttack(a, best, a.getTroopCount()); AttackThread attackThread = game.startAttack(a, best, a.getTroopCount());
if(fastForward) if(fastForward)
attackThread.fastForward(); attackThread.fastForward();
@ -357,20 +320,38 @@ public class StrongAI extends AI {
return false; return false;
} }
/**
* Play some jokers
* @param game the current game
*/
private void playJokers(Game game) {
if(!playedJoker1) {
if(this.getCastles(game).size() <= game.getMap().getCastles().size()/2) {
playedJoker1 = true;
System.out.println("StrongAI played joker 1.");
this.addTroops(5);
}
}
if(!playedJoker2) {
// TODO: Play joker 2
}
}
@Override @Override
protected void actions(Game game) throws InterruptedException { protected void actions(Game game) throws InterruptedException {
if(game.getRound() == 1) { if(game.getRound() == 1) {
chooseInitialCastles(game); chooseInitialCastles(game);
}else { }else {
playJokers(game);
distributeTroops(game); distributeTroops(game);
boolean shouldAttack = false; boolean shouldAttack = false;
do { do {
//fastForward();
shouldAttack = attackCastles(game); shouldAttack = attackCastles(game);
reinforceCastles(game); reinforceCastles(game);
}while(shouldAttack); }while(shouldAttack);
} }
} }
/** /**
* Checks, whether a given castle has adjacent opponent castles * Checks, whether a given castle has adjacent opponent castles
* @param g the graph * @param g the graph
@ -387,4 +368,28 @@ public class StrongAI extends AI {
return (a > 0)? true : false; return (a > 0)? true : false;
} }
/**
* Returns a batch of connected castles around some start castle that belong to the player p
* @param g the graph
* @param p the owner of the castles
* @param castle the start castle
* @param checked a list of checked castles, initialize as new ArrayList
* @return a list of castles
*/
public static List<Castle> getBatch(Graph<Castle> g, Player p, Castle castle, List<Castle> checked) {
Node<Castle> n = g.getNode(castle);
Set<Castle> adjacent = g.getEdges(n).stream()
.map(x->x.getOtherNode(n).getValue())
.filter(x->x.getOwner() == p)
.filter(x->checked.contains(x) == false)
.collect(Collectors.toSet());
if(!checked.contains(castle))
checked.add(castle);
for(Castle c : adjacent) {
checked.addAll(getBatch(g, p, c, checked).stream()
.filter(x->checked.contains(x) == false)
.collect(Collectors.toSet()));
}
return checked;
}
} }