From 378ed2c2c0d785df7050e5ea28927f642a12b2ac Mon Sep 17 00:00:00 2001 From: Jonas Suess Date: Tue, 26 Mar 2019 17:20:38 +0100 Subject: [PATCH] Improved AI with documentation --- .../src/game/players/StrongAI.java | 193 +++++++++--------- 1 file changed, 99 insertions(+), 94 deletions(-) diff --git a/Projektgruppe_175/src/game/players/StrongAI.java b/Projektgruppe_175/src/game/players/StrongAI.java index 4b6fa03..61f3b97 100644 --- a/Projektgruppe_175/src/game/players/StrongAI.java +++ b/Projektgruppe_175/src/game/players/StrongAI.java @@ -13,53 +13,68 @@ import game.AI; import game.Game; import game.Player; import game.map.Castle; +import game.map.PathFinding; import gui.AttackThread; +import gui.components.MapPanel.Action; public class StrongAI extends AI { - - private static boolean DEBUG = false; - private static double UTILITY_F1 = 3.0; - 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; + // These values were determined mostly by trial-and-error - private static double REINFORCE_F1 = 1.0; - private static double REINFORCE_F2 = -5.0; - private static double REINFORCE_F3 = 0.5; - private static double REINFORCE_F4 = -100.0; - private static double REINFORCE_F5 = -2.0; + private static double UTILITY_F1 = 4.0; // Number of adjacent friendly castles + private static double UTILITY_F2 = 1.0; // Number of adjacent enemy castles + private static double UTILITY_F3 = 3.0; // Number of connected edges + private static double UTILITY_F4 = -10.0; // Is surrounded by opponents castles + 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 * @param game the current game * @param graph the graph to be evaluated * @param p the player + * @param change the castle that should be considered to belong to p * @return the state value */ public static double evaluateState(Game game, Graph g, Player p, Optional change) { + // Temporarily give castle "change" to p Player owner = null; if(change.isPresent()) { owner = change.get().getOwner(); change.get().setOwner(p); } + // Calculate total state utility double value = 0; for(Player player : game.getPlayers()) { List castles = g.getAllValues().stream() .filter(x->x.getOwner() == player) .collect(Collectors.toList()); - double s = (p == player)? 3.0 : -10.0; + double s = (p == player)? EVAL_OWN : EVAL_OPP; 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()) { change.get().setOwner(owner); } @@ -104,21 +119,7 @@ public class StrongAI extends AI { // Castles missing to a full kingdom double f6 = c.getKingdom().getCastles().stream() .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 + UTILITY_F3 * f3 + UTILITY_F4 * f4 + f5 * UTILITY_F5 + f6 * UTILITY_F6; @@ -126,10 +127,10 @@ public class StrongAI extends AI { /** * Analyzes the utility to attack a certain castle - * @param g - * @param p - * @param c - * @return + * @param g the graph + * @param p the current player + * @param c the castle + * @return the reinforcement utility */ public static double utilityReinforce(Graph g, Player p, Castle c) { Node node = g.getNode(c); @@ -152,21 +153,9 @@ public class StrongAI extends AI { // Is border castle double f4 = isBorder(g, c)? 0.0 : 1.0; - // Number of troops exceeding 4 - double f5 = c.getTroopCount()-4; - - 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("-----"); - } + // Number of troops exceeding + double f5 = c.getTroopCount(); + return REINFORCE_F1 * f1 + REINFORCE_F2 * f2 + f3 * REINFORCE_F3 + f4 * REINFORCE_F4 + f5 * REINFORCE_F5; } @@ -184,40 +173,11 @@ public class StrongAI extends AI { 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 - * @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 getBatch(Graph g, Player p, Castle castle, List checked) { - Node n = g.getNode(castle); - Set 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 + * Chooses the initial castles in round one * @param game the game * @throws InterruptedException */ @@ -227,15 +187,12 @@ public class StrongAI extends AI { .collect(Collectors.toList()); while(availableCastles.size() > 0 && getRemainingTroops() > 0) { - - sleep(3); - + sleep(1000); Castle best = availableCastles.get(0); double bestScore = 0; double score = 0; for(Castle c : availableCastles) { score = evaluateState(game, game.getMap().getGraph(), this, Optional.of(c)); - //score = evaluateLookahead(game, game.getMap().getGraph(), this, 10); if(score > bestScore) { bestScore = score; best = c; @@ -256,6 +213,7 @@ public class StrongAI extends AI { List castles = game.getMap().getCastles().stream() .filter(x->x.getOwner() == this) .collect(Collectors.toList()); + while(this.getRemainingTroops() > 0) { Castle best = castles.get(0); double bestScore = 0; @@ -266,7 +224,7 @@ public class StrongAI extends AI { best = c; } } - sleep(5); + sleep(500); game.addTroops(this, best, 1); } @@ -295,12 +253,16 @@ public class StrongAI extends AI { while(d.getTroopCount() > 1) { double bestScore = 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) { double score = utilityReinforce(g, this, c); - if(score > bestScore) { - bestScore = score; - best = c; + if(score > bestScore && path.getPath(c) != null) { + if(path.getPath(c).size() > 0) { + bestScore = score; + best = c; + } } } if(best == d) break; @@ -342,7 +304,8 @@ public class StrongAI extends AI { 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()); if(fastForward) attackThread.fastForward(); @@ -357,20 +320,38 @@ public class StrongAI extends AI { 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 protected void actions(Game game) throws InterruptedException { if(game.getRound() == 1) { chooseInitialCastles(game); }else { + playJokers(game); distributeTroops(game); boolean shouldAttack = false; do { - //fastForward(); shouldAttack = attackCastles(game); reinforceCastles(game); }while(shouldAttack); } } + /** * Checks, whether a given castle has adjacent opponent castles * @param g the graph @@ -386,5 +367,29 @@ public class StrongAI extends AI { 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 getBatch(Graph g, Player p, Castle castle, List checked) { + Node n = g.getNode(castle); + Set 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; + } }