First implementation of a stronger AI

This commit is contained in:
Jonas Suess 2019-03-24 14:23:43 +01:00
parent f6bed8fbda
commit e95d6e168a
3 changed files with 393 additions and 2 deletions

View file

@ -38,6 +38,7 @@ public class GameConstants {
public static final Class<?> PLAYER_TYPES[] = { public static final Class<?> PLAYER_TYPES[] = {
Human.class, Human.class,
BasicAI.class, BasicAI.class,
StrongAI.class
// TODO: Add more Player types, like different AIs // TODO: Add more Player types, like different AIs
}; };
} }

View file

@ -25,7 +25,6 @@ public class Kingdom {
this.type = type; this.type = type;
this.center = center; this.center = center;
center.setKingdom(this); center.setKingdom(this);
this.castles.add(center);
} }
/** /**
@ -33,7 +32,8 @@ public class Kingdom {
* @param castle die Burg, die hinzugefügt werden soll * @param castle die Burg, die hinzugefügt werden soll
*/ */
public void addCastle(Castle castle) { public void addCastle(Castle castle) {
this.castles.add(castle); if(!this.castles.contains(castle))
this.castles.add(castle);
} }
/** /**

View file

@ -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<Castle> g, Player p, Optional<Castle> change) {
Player owner = null;
if(change.isPresent()) {
owner = change.get().getOwner();
change.get().setOwner(p);
}
double value = 0;
for(Player player : game.getPlayers()) {
List<Castle> 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<Castle> g, Player p, Castle c) {
Node<Castle> 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<Castle> g, Player p, Castle c) {
Node<Castle> 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<Castle> 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<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
* @throws InterruptedException
*/
private void chooseInitialCastles(Game game) throws InterruptedException {
List<Castle> 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<Castle> g = game.getMap().getGraph();
List<Castle> 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<Castle> g = game.getMap().getGraph();
List<Castle> 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<Castle> 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<Castle> g = game.getMap().getGraph();
List<Castle> 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<Castle> na = g.getNode(a);
List<Castle> 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<Castle> g, Castle castle) {
Node<Castle> 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;
}
}