From e95d6e168a417f10c37fadbe8760b2e6c65568a4 Mon Sep 17 00:00:00 2001 From: Jonas Suess Date: Sun, 24 Mar 2019 14:23:43 +0100 Subject: [PATCH] First implementation of a stronger AI --- Projektgruppe_175/src/game/GameConstants.java | 1 + Projektgruppe_175/src/game/map/Kingdom.java | 4 +- .../src/game/players/StrongAI.java | 390 ++++++++++++++++++ 3 files changed, 393 insertions(+), 2 deletions(-) create mode 100644 Projektgruppe_175/src/game/players/StrongAI.java diff --git a/Projektgruppe_175/src/game/GameConstants.java b/Projektgruppe_175/src/game/GameConstants.java index 291df2b..fb98dca 100644 --- a/Projektgruppe_175/src/game/GameConstants.java +++ b/Projektgruppe_175/src/game/GameConstants.java @@ -38,6 +38,7 @@ public class GameConstants { public static final Class PLAYER_TYPES[] = { Human.class, BasicAI.class, + StrongAI.class // TODO: Add more Player types, like different AIs }; } diff --git a/Projektgruppe_175/src/game/map/Kingdom.java b/Projektgruppe_175/src/game/map/Kingdom.java index 84cce8b..f20534d 100644 --- a/Projektgruppe_175/src/game/map/Kingdom.java +++ b/Projektgruppe_175/src/game/map/Kingdom.java @@ -25,7 +25,6 @@ public class Kingdom { this.type = type; this.center = center; center.setKingdom(this); - this.castles.add(center); } /** @@ -33,7 +32,8 @@ public class Kingdom { * @param castle die Burg, die hinzugefügt werden soll */ public void addCastle(Castle castle) { - this.castles.add(castle); + if(!this.castles.contains(castle)) + this.castles.add(castle); } /** diff --git a/Projektgruppe_175/src/game/players/StrongAI.java b/Projektgruppe_175/src/game/players/StrongAI.java new file mode 100644 index 0000000..c0c9ea8 --- /dev/null +++ b/Projektgruppe_175/src/game/players/StrongAI.java @@ -0,0 +1,390 @@ +package game.players; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import base.Graph; +import base.Node; +import game.AI; +import game.Game; +import game.Player; +import game.map.Castle; +import gui.AttackThread; + +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; + + 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 ATTACK_F1 = 1.0; + + private static double EVAL_CASTLES = 1.0; + + /** + * Evaluates the current game state + * @param game the current game + * @param graph the graph to be evaluated + * @param p the player + * @return the state value + */ + public static double evaluateState(Game game, Graph g, Player p, Optional change) { + Player owner = null; + if(change.isPresent()) { + owner = change.get().getOwner(); + change.get().setOwner(p); + } + 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; + for(Castle c : castles) { + value += s * utilityCastle(g, player, c) * EVAL_CASTLES; + } + } + if(change.isPresent()) { + change.get().setOwner(owner); + } + return value; + } + + /** + + /** + * Analyzes the utility of a given castle to the player p + * @param g the game graph + * @param p the player + * @param c the castle to be analyzed + * @return the utility value + */ + public static double utilityCastle(Graph g, Player p, Castle c) { + Node node = g.getNode(c); + + // Number of adjacent friendly castles + double f1 = g.getEdges(node).stream() + .filter(x->x.getOtherNode(node).getValue().getOwner() != null) + .filter(x->x.getOtherNode(node).getValue().getOwner() == p) + .count(); + // Number of adjacent enemy castles + double f2 = g.getEdges(node).stream() + .filter(x->x.getOtherNode(node).getValue().getOwner() != null) + .filter(x->x.getOtherNode(node).getValue().getOwner() != p) + .count(); + + // Number of connected edges + double f3 = g.getEdges(node).size() - 1; + + // Is surrounded by opponents castles + double f4 = (g.getEdges(node).stream() + .filter(x->x.getOtherNode(node).getValue().getOwner() != null) + .filter(x->x.getOtherNode(node).getValue().getOwner() != p) + .count() == g.getEdges(node).size() - 1)? 1.0 : 0; + + // Size of the castles batch + double f5 = getBatch(g, p, c, new ArrayList<>()).size(); + + // 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; + } + + /** + * Analyzes the utility to attack a certain castle + * @param g + * @param p + * @param c + * @return + */ + public static double utilityReinforce(Graph g, Player p, Castle c) { + Node node = g.getNode(c); + + // Number of adjacent enemy castles + double f1 = g.getEdges(node).stream() + .filter(x->x.getOtherNode(node).getValue().getOwner() != null) + .filter(x->x.getOtherNode(node).getValue().getOwner() != p) + .count(); + + // Is surrounded by opponents castles + double f2 = (g.getEdges(node).stream() + .filter(x->x.getOtherNode(node).getValue().getOwner() != null) + .filter(x->x.getOtherNode(node).getValue().getOwner() != p) + .count() == g.getEdges(node).size() - 1)? 1.0 : 0; + + // Castle utility + double f3 = utilityCastle(g, p, c); + + // 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("-----"); + } + return REINFORCE_F1 * f1 + REINFORCE_F2 * f2 + f3 * REINFORCE_F3 + + f4 * REINFORCE_F4 + f5 * REINFORCE_F5; + } + + /** + * Analyzes the utility to attack a certain castle + * @param g the graph + * @param p the attacking player + * @param a the attacking castle + * @param t the target castle + * @return + */ + public static double utilityAttack(Game game, Graph g, Player p, Castle a, Castle t) { + double f1 = evaluateState(game, g, p, Optional.of(t)); + + double ratio = ((double)a.getTroopCount())/t.getTroopCount(); + + return ratio * (ATTACK_F1 * f1); + } + + /** + * Returns a batch of connected castles around some start castle that belong to 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 + * @param game the game + * @throws InterruptedException + */ + private void chooseInitialCastles(Game game) throws InterruptedException { + List availableCastles = game.getMap().getCastles().stream() + .filter(c -> c.getOwner() == null) + .collect(Collectors.toList()); + + while(availableCastles.size() > 0 && getRemainingTroops() > 0) { + + sleep(3); + + 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; + } + } + availableCastles.remove(best); + game.chooseCastle(best, this); + } + } + + /** + * Distribute new troops to castles + * @param game the game + * @throws InterruptedException + */ + private void distributeTroops(Game game) throws InterruptedException { + Graph g = game.getMap().getGraph(); + 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; + for(Castle c : castles) { + double score = utilityReinforce(g, this, c); + if(score > bestScore) { + bestScore = score; + best = c; + } + } + sleep(5); + game.addTroops(this, best, 1); + + } + } + + /** + * Reinforces the castles with troops + * @param game the game + */ + private void reinforceCastles(Game game) { + Graph g = game.getMap().getGraph(); + List distributors = game.getMap().getCastles().stream() + .filter(x->x.getOwner() == this) + .filter(x->x.getTroopCount() > 1) + .filter(x->isBorder(g, x) == false) + .collect(Collectors.toList()); + + List receivers = game.getMap().getCastles().stream() + .filter(x->x.getOwner() == this) + .filter(x->isBorder(g, x) == true) + .collect(Collectors.toList()); + + if(receivers.size() == 0 || distributors.size() == 0) return; + + for(Castle d : distributors) { + while(d.getTroopCount() > 1) { + double bestScore = 0; + Castle best = receivers.get(0); + + for(Castle c : receivers) { + double score = utilityReinforce(g, this, c); + if(score > bestScore) { + bestScore = score; + best = c; + } + } + if(best == d) break; + + game.moveTroops(d, best, 1); + } + } + } + + /** + * Attacks opponents castles + * @param game the game + * @throws InterruptedException + */ + private boolean attackCastles(Game game) throws InterruptedException { + Graph g = game.getMap().getGraph(); + List attackers = g.getAllValues().stream() + .filter(x->x.getOwner() == this) + .filter(x->isBorder(g, x)) + .filter(x->x.getTroopCount() > 1) + .collect(Collectors.toList()); + + for(Castle a : attackers) { + Node na = g.getNode(a); + List targets = g.getEdges(na).stream() + .map(x->x.getOtherNode(na).getValue()) + .filter(x->x.getOwner() != this) + .collect(Collectors.toList()); + + if(targets.isEmpty()) + continue; + + Castle best = targets.get(0); + double bestScore = 0; + for(Castle t : targets) { + double score = utilityAttack(game, g, this, a, t); + if(score > bestScore) { + bestScore = score; + best = t; + } + } + if(bestScore > evaluateState(game, g, this, Optional.empty())) { + AttackThread attackThread = game.startAttack(a, best, a.getTroopCount()); + if(fastForward) + attackThread.fastForward(); + + attackThread.join(); + return true; + }else { + return false; + } + + } + return false; + } + + @Override + protected void actions(Game game) throws InterruptedException { + if(game.getRound() == 1) { + chooseInitialCastles(game); + }else { + 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 + * @param castle the castle to be checked + * @return true, if castle is on border + */ + private static boolean isBorder(Graph g, Castle castle) { + Node n = g.getNode(castle); + int a = (int) g.getEdges(n).stream() + .filter(x->x.getOtherNode(n).getValue().getOwner() != null) + .filter(x->x.getOtherNode(n).getValue().getOwner() != castle.getOwner()) + .count(); + + return (a > 0)? true : false; + } + +}