Improved AI with documentation
This commit is contained in:
parent
99f5e8d59d
commit
378ed2c2c0
1 changed files with 99 additions and 94 deletions
|
|
@ -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;
|
|
||||||
|
|
||||||
private static double UTILITY_F1 = 3.0;
|
// These values were determined mostly by trial-and-error
|
||||||
private static double UTILITY_F2 = 5.0;
|
|
||||||
private static double UTILITY_F3 = 1.0;
|
|
||||||
private static double UTILITY_F4 = -10.0;
|
|
||||||
private static double UTILITY_F5 = 3.0;
|
|
||||||
private static double UTILITY_F6 = 2.0;
|
|
||||||
|
|
||||||
private static double REINFORCE_F1 = 1.0;
|
private static double UTILITY_F1 = 4.0; // Number of adjacent friendly castles
|
||||||
private static double REINFORCE_F2 = -5.0;
|
private static double UTILITY_F2 = 1.0; // Number of adjacent enemy castles
|
||||||
private static double REINFORCE_F3 = 0.5;
|
private static double UTILITY_F3 = 3.0; // Number of connected edges
|
||||||
private static double REINFORCE_F4 = -100.0;
|
private static double UTILITY_F4 = -10.0; // Is surrounded by opponents castles
|
||||||
private static double REINFORCE_F5 = -2.0;
|
private static double UTILITY_F5 = 3.0; // Size of the castles batch
|
||||||
|
private static double UTILITY_F6 = 4.0; // Castles missing to a full kingdom
|
||||||
|
|
||||||
private static double ATTACK_F1 = 1.0;
|
private static double REINFORCE_F1 = 1.0; // Number of adjacent enemy castles
|
||||||
|
private static double REINFORCE_F2 = -5.0; // Is surrounded by opponents castles
|
||||||
|
private static double REINFORCE_F3 = 0.5; // Castle utility
|
||||||
|
private static double REINFORCE_F4 = -100.0; // Is border castle
|
||||||
|
private static double REINFORCE_F5 = -2.0; // Number of troops
|
||||||
|
|
||||||
private static double EVAL_CASTLES = 1.0;
|
private static double ATTACK_F1 = 1.0; // Castle utility
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
@ -104,21 +119,7 @@ public class StrongAI extends AI {
|
||||||
// Castles missing to a full kingdom
|
// Castles missing to a full kingdom
|
||||||
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
|
||||||
|
|
@ -386,5 +367,29 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue