Refactoring, Bug Prediction, Shortest Paths prediction
Chase bug and FarChase bug simulators implemented. Codes faster reachable by opponent get lowered values.
This commit is contained in:
parent
a0ced36a12
commit
56a7cf597b
17 changed files with 573 additions and 184 deletions
|
@ -5,7 +5,6 @@ import java.util.regex.Pattern;
|
||||||
|
|
||||||
import farbot.ai.AI;
|
import farbot.ai.AI;
|
||||||
import farbot.game.GameState;
|
import farbot.game.GameState;
|
||||||
import farbot.game.Settings;
|
|
||||||
|
|
||||||
public class FarbotIO {
|
public class FarbotIO {
|
||||||
private static Scanner in;
|
private static Scanner in;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package farbot.game;
|
package farbot;
|
||||||
|
|
||||||
public class Settings {
|
public class Settings {
|
||||||
private Integer timebank;
|
private Integer timebank;
|
|
@ -1,11 +1,12 @@
|
||||||
package farbot.ai;
|
package farbot.ai;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import farbot.game.GameState;
|
import farbot.game.GameState;
|
||||||
import farbot.game.MoveResult;
|
import farbot.game.Move;
|
||||||
import farbot.game.map.Move;
|
|
||||||
|
|
||||||
import static farbot.FarbotIO.logln;
|
import static farbot.FarbotIO.logln;
|
||||||
|
|
||||||
|
@ -21,28 +22,46 @@ public class AI {
|
||||||
|
|
||||||
public String calcMove(Integer time) {
|
public String calcMove(Integer time) {
|
||||||
logln("Calculating move in "+time+"ms");
|
logln("Calculating move in "+time+"ms");
|
||||||
|
|
||||||
|
Set<Move> validMoves = getValidMoves();
|
||||||
|
List<MoveValue> valuedMoves = getValuedMoves(validMoves);
|
||||||
|
MoveValue maxMove = getMaxMove(valuedMoves);
|
||||||
|
|
||||||
|
this.lastMove = maxMove;
|
||||||
|
|
||||||
|
logln("Max.move '"+maxMove.move+"': "+maxMove.value);
|
||||||
|
return maxMove.move.name().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Move> getValidMoves(){
|
||||||
Set<Move> validMoves = currentState.selfMoves();
|
Set<Move> validMoves = currentState.selfMoves();
|
||||||
logln("Valid moves: "+validMoves.toString());
|
logln("Valid moves: "+validMoves.toString());
|
||||||
|
return validMoves;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MoveValue> getValuedMoves(Set<Move> validMoves){
|
||||||
ArrayList<MoveValue> valuedMoves = new ArrayList<>();
|
ArrayList<MoveValue> valuedMoves = new ArrayList<>();
|
||||||
|
|
||||||
for(Move m: validMoves) {
|
for(Move m: validMoves) {
|
||||||
logln("Checking move: "+m.name());
|
logln("Checking move: "+m.name());
|
||||||
GameState fork = currentState.fork();
|
|
||||||
MoveResult mr = fork.moveSelf(m);
|
GameTree gt = new GameTree(currentState.fork(), m);
|
||||||
GameTree gt = new GameTree(fork);
|
|
||||||
gt.build(treeDepth);
|
gt.build(treeDepth);
|
||||||
Double value = gt.getValue();
|
Double value = gt.getValue();
|
||||||
value += mr.getCollectedCodes()*Math.pow((treeDepth+1), 1.6);
|
|
||||||
value -= mr.getKilledBugs()*8*Math.pow((treeDepth+1), 1.6);
|
|
||||||
|
|
||||||
logln("Move '"+m.name()+"': "+value);
|
logln("Move '"+m.name()+"': "+value);
|
||||||
valuedMoves.add(new MoveValue(m, value));
|
valuedMoves.add(new MoveValue(m, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
valuedMoves.sort((mv1, mv2) -> -mv1.value.compareTo(mv2.value));
|
return valuedMoves;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MoveValue getMaxMove(List<MoveValue> valuedMoves) {
|
||||||
|
valuedMoves.sort(Comparator.comparingDouble(m -> -m.value));
|
||||||
MoveValue maxMove = valuedMoves.get(0);
|
MoveValue maxMove = valuedMoves.get(0);
|
||||||
if(this.lastMove != null && this.lastMove.value <= maxMove.value) {
|
|
||||||
|
// Prevent fallback loop
|
||||||
|
if(this.lastMove != null && this.lastMove.value >= maxMove.value) {
|
||||||
switch(this.lastMove.move) {
|
switch(this.lastMove.move) {
|
||||||
case LEFT:
|
case LEFT:
|
||||||
if(maxMove.move == Move.RIGHT) maxMove = valuedMoves.get(1);
|
if(maxMove.move == Move.RIGHT) maxMove = valuedMoves.get(1);
|
||||||
|
@ -61,10 +80,7 @@ public class AI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastMove = maxMove;
|
return maxMove;
|
||||||
|
|
||||||
logln("Max.move '"+maxMove.move+"': "+maxMove.value);
|
|
||||||
return maxMove.move.name().toLowerCase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseUpdate(String[] update) {
|
public void parseUpdate(String[] update) {
|
||||||
|
|
|
@ -2,33 +2,42 @@ package farbot.ai;
|
||||||
|
|
||||||
import static farbot.FarbotIO.logln;
|
import static farbot.FarbotIO.logln;
|
||||||
|
|
||||||
import farbot.game.map.Point;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
|
||||||
import farbot.game.GameState;
|
import farbot.game.GameState;
|
||||||
import farbot.game.MoveResult;
|
import farbot.game.Move;
|
||||||
|
import farbot.game.Point;
|
||||||
|
import farbot.game.map.util.ShortestPaths;
|
||||||
|
import farbot.game.token.Code;
|
||||||
|
|
||||||
public class GameTree {
|
public class GameTree {
|
||||||
private GameState node;
|
private GameState node;
|
||||||
private Double value;
|
private Double value;
|
||||||
private HashSet<Point> visited;
|
private HashSet<Point> visited;
|
||||||
private Integer totalLevel;
|
private Integer totalLevel;
|
||||||
|
private Move initMove;
|
||||||
|
|
||||||
public GameTree(GameState root) {
|
public GameTree(GameState root, Move initMove) {
|
||||||
this.node = root;
|
this.node = root;
|
||||||
this.value = 0.0;
|
this.value = 0.0;
|
||||||
this.totalLevel = 1;
|
this.totalLevel = 1;
|
||||||
this.visited = new HashSet<>();
|
this.visited = new HashSet<>();
|
||||||
|
this.initMove = initMove;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void build(int level) {
|
public void build(int level) {
|
||||||
this.totalLevel = level;
|
this.totalLevel = level;
|
||||||
|
|
||||||
Queue<GameState> children = makeChildren(node, this, level);
|
MoveResult mr = Simulator.move(initMove, node);
|
||||||
|
value += valueCodes(mr, level, node, this);
|
||||||
|
value += valueBugs(mr, level, node);
|
||||||
|
|
||||||
|
Queue<GameState> children = makeChildren(node, this, --level);
|
||||||
while(level > 0) {
|
while(level > 0) {
|
||||||
level--;
|
level--;
|
||||||
|
|
||||||
|
@ -62,16 +71,10 @@ public class GameTree {
|
||||||
|
|
||||||
for(Point selfMove: selfMoves) {
|
for(Point selfMove: selfMoves) {
|
||||||
GameState child = state.fork();
|
GameState child = state.fork();
|
||||||
MoveResult mr = child.moveSelf(selfMove);
|
MoveResult mr = Simulator.move(selfMove, child);
|
||||||
|
|
||||||
if(mr.getCollectedCodes() != 0){
|
gtRoot.value += valueCodes(mr, level, child, gtRoot);
|
||||||
logln("Got "+mr.getCollectedCodes()+" codes at "+selfMove.toString()+" in level "+level);
|
gtRoot.value += valueBugs(mr, level, child);
|
||||||
gtRoot.value += Math.pow(level*mr.getCollectedCodes(), 1.3);
|
|
||||||
}
|
|
||||||
if(mr.getKilledBugs() != 0) {
|
|
||||||
logln("Killed "+mr.getKilledBugs()+" bugs at "+selfMove.toString()+" in level "+level);
|
|
||||||
gtRoot.value -= Math.pow(level*4*mr.getKilledBugs(), level/totalLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
gtRoot.visited.add(selfMove);
|
gtRoot.visited.add(selfMove);
|
||||||
|
|
||||||
|
@ -80,4 +83,50 @@ public class GameTree {
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double valueBugs(MoveResult moveResult, int level, GameState state) {
|
||||||
|
double value = 0.0;
|
||||||
|
if(moveResult.lenKilledBugs() != 0) {
|
||||||
|
value = -1 * Math.pow(level*4*moveResult.lenKilledBugs(), (double) level/totalLevel);
|
||||||
|
if (level >= totalLevel-1) value *= 2;
|
||||||
|
if (level >= 5) value /= 2;
|
||||||
|
|
||||||
|
logln("Killed "+moveResult.lenKilledBugs()+" bugs at "+state.getSelfPosition()+" in level "+level+" @ "+value);
|
||||||
|
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double valueCodes(MoveResult moveResult, int level, GameState state, GameTree gtRoot) {
|
||||||
|
double value = 0.0;
|
||||||
|
if(moveResult.lenCollectedCodes() != 0){
|
||||||
|
|
||||||
|
double codeValSum = moveResult.getCollectedCodes().stream()
|
||||||
|
.map(c -> codeVal(c, gtRoot))
|
||||||
|
.reduce(Double::sum).get();
|
||||||
|
|
||||||
|
value = Math.pow(level*codeValSum, 1.7);
|
||||||
|
logln("Got "+moveResult.lenCollectedCodes()+" codes at "+state.getSelfPosition()+" in level "+level+ " @ "+value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Double codeVal(Code code, GameTree gtRoot) {
|
||||||
|
List<Point> opponentShortestPath = ShortestPaths.shortestPath(
|
||||||
|
gtRoot.node.getOpponentPosition(), code.getPosition());
|
||||||
|
|
||||||
|
List<Point> selfShortestPath = ShortestPaths.shortestPath(
|
||||||
|
gtRoot.node.getSelfPosition(), code.getPosition());
|
||||||
|
|
||||||
|
logln("Shortest path for Code at "+code.getPosition()+
|
||||||
|
": Opponent="+opponentShortestPath.size()+
|
||||||
|
", Self="+selfShortestPath.size());
|
||||||
|
|
||||||
|
|
||||||
|
if(opponentShortestPath.size() < selfShortestPath.size()) {
|
||||||
|
if(opponentShortestPath.size() <= 3) return 0.0;
|
||||||
|
return 1.0/(selfShortestPath.size() - opponentShortestPath.size()+0.5);
|
||||||
|
}
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
40
farbot/src/farbot/ai/MoveResult.java
Normal file
40
farbot/src/farbot/ai/MoveResult.java
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package farbot.ai;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import farbot.game.Player;
|
||||||
|
import farbot.game.token.Bug;
|
||||||
|
import farbot.game.token.Code;
|
||||||
|
|
||||||
|
public class MoveResult {
|
||||||
|
private Player player;
|
||||||
|
private List<Bug> killedBugs;
|
||||||
|
private List<Code> collectedCodes;
|
||||||
|
|
||||||
|
public MoveResult(Player player, List<Code> collectedCodes, List<Bug> killedBugs) {
|
||||||
|
this.player = player;
|
||||||
|
this.killedBugs = killedBugs;
|
||||||
|
this.collectedCodes = collectedCodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayer() {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Bug> getKilledBugs() {
|
||||||
|
return killedBugs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int lenKilledBugs() {
|
||||||
|
return killedBugs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Code> getCollectedCodes() {
|
||||||
|
return collectedCodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int lenCollectedCodes() {
|
||||||
|
return collectedCodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,19 +1,24 @@
|
||||||
package farbot.ai;
|
package farbot.ai;
|
||||||
|
|
||||||
import farbot.game.map.Point;
|
import farbot.Settings;
|
||||||
|
|
||||||
import farbot.game.GameState;
|
import farbot.game.GameState;
|
||||||
import farbot.game.Settings;
|
import farbot.game.Point;
|
||||||
import farbot.game.token.Bug;
|
import farbot.game.token.Bug;
|
||||||
import farbot.game.token.Bug.BugAI;
|
import farbot.game.token.Bug.BugAI;
|
||||||
import farbot.game.token.Code;
|
import farbot.game.token.Code;
|
||||||
import farbot.game.token.Mine;
|
import farbot.game.token.Mine;
|
||||||
|
|
||||||
|
import static farbot.FarbotIO.logln;
|
||||||
|
import static farbot.FarbotIO.log;
|
||||||
|
|
||||||
public class Parser {
|
public class Parser {
|
||||||
public static void parse(String[] update, GameState state) {
|
public static void parse(String[] update, GameState state) {
|
||||||
switch(update[2]) {
|
switch(update[2]) {
|
||||||
case "round":
|
case "round":
|
||||||
state.setRound(Integer.parseInt(update[3]));
|
state.setRound(Integer.parseInt(update[3]));
|
||||||
|
log("\n");
|
||||||
|
logln("******* Round "+state.getRound()+" *******");
|
||||||
|
log("\n");
|
||||||
break;
|
break;
|
||||||
case "field":
|
case "field":
|
||||||
parseMap(update[3].split(","), state);
|
parseMap(update[3].split(","), state);
|
||||||
|
|
103
farbot/src/farbot/ai/Simulator.java
Normal file
103
farbot/src/farbot/ai/Simulator.java
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package farbot.ai;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import farbot.game.GameState;
|
||||||
|
import farbot.game.Move;
|
||||||
|
import farbot.game.Player;
|
||||||
|
import farbot.game.Point;
|
||||||
|
import farbot.game.token.Bug;
|
||||||
|
import farbot.game.token.Code;
|
||||||
|
|
||||||
|
public class Simulator {
|
||||||
|
public static MoveResult move(Move move, GameState state) {
|
||||||
|
Point movePoint = state.calcMovePoint(state.getSelfPosition(), move);
|
||||||
|
return move(movePoint, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MoveResult move(Point position, GameState state) {
|
||||||
|
state.moveSelf(position);
|
||||||
|
List<Bug> bugs = state.removeBugsAt(state.getSelfPosition());
|
||||||
|
List<Code> codes = state.removeCodesAt(state.getSelfPosition());
|
||||||
|
|
||||||
|
predictBugs(state);
|
||||||
|
|
||||||
|
bugs.addAll(state.removeBugsAt(state.getSelfPosition()));
|
||||||
|
|
||||||
|
state.addSelfSnippets(codes.size());
|
||||||
|
state.removeSelfSnippets(bugs.size()*4);
|
||||||
|
|
||||||
|
return new MoveResult(state.getSelfClone(), codes, bugs);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void predictBugs(GameState state) {
|
||||||
|
List<Bug> bugs = state.getBugs();
|
||||||
|
state.clearBugs();
|
||||||
|
|
||||||
|
for(Bug bug: bugs) {
|
||||||
|
Point movePoint = bug.getPosition();
|
||||||
|
switch(bug.getAiType()) {
|
||||||
|
case CHASE:
|
||||||
|
movePoint = chaseAI(bug, state);
|
||||||
|
break;
|
||||||
|
case FAR_CHASE:
|
||||||
|
movePoint = farChaseAI(bug, state);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
state.putToken(new Bug(movePoint.x, movePoint.y, bug.getAiType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Point chaseAI(Bug chaseBug, GameState state) {
|
||||||
|
Player nearestPlayer = state.getPlayerClones().stream()
|
||||||
|
.min(Comparator.comparingDouble(p -> p.distance(chaseBug.getPosition())))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
return euclideanBugMove(chaseBug.getPosition(),
|
||||||
|
nearestPlayer.getPosition(),
|
||||||
|
state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Point farChaseAI(Bug farChaseBug, GameState state) {
|
||||||
|
// only difference to chaseAI: uses .max
|
||||||
|
Player farthestPlayer = state.getPlayerClones().stream()
|
||||||
|
.max(Comparator.comparingDouble(p -> p.distance(farChaseBug.getPosition())))
|
||||||
|
.get();
|
||||||
|
|
||||||
|
return euclideanBugMove(farChaseBug.getPosition(),
|
||||||
|
farthestPlayer.getPosition(),
|
||||||
|
state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Point euclideanBugMove(Point from, Point to, GameState state) {
|
||||||
|
ArrayList<Point> movePoints = new ArrayList<Point>();
|
||||||
|
movePoints.addAll(bugMovePoints(from, state));
|
||||||
|
Collections.shuffle(movePoints);
|
||||||
|
Point nearestPoint = movePoints.stream()
|
||||||
|
.min(Comparator.comparingDouble(to::distance))
|
||||||
|
.get(); // .min returns an Optional<Point>
|
||||||
|
|
||||||
|
return nearestPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<Point> bugMovePoints(Point from, GameState state){
|
||||||
|
Set<Point> movePoints = state.movePoints(from);
|
||||||
|
|
||||||
|
// Bugs can't use Gates
|
||||||
|
// TODO: Move this to GameMap
|
||||||
|
if(from.equals(new Point(0,7))) {
|
||||||
|
movePoints.remove(new Point(18,7));
|
||||||
|
}else if(from.equals(new Point(18,7))) {
|
||||||
|
movePoints.remove(new Point(0,7));
|
||||||
|
}
|
||||||
|
|
||||||
|
return movePoints;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,32 @@
|
||||||
package farbot.game;
|
package farbot.game;
|
||||||
|
|
||||||
import farbot.game.map.Point;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import farbot.Settings;
|
||||||
|
import farbot.ai.MoveResult;
|
||||||
import farbot.game.map.GameMap;
|
import farbot.game.map.GameMap;
|
||||||
import farbot.game.map.Move;
|
import farbot.game.map.util.Movement;
|
||||||
import farbot.game.token.Bug;
|
import farbot.game.token.Bug;
|
||||||
import farbot.game.token.Code;
|
import farbot.game.token.Code;
|
||||||
import farbot.game.token.Mine;
|
import farbot.game.token.Mine;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manager for the GameState.
|
* The public interface for managing the game state
|
||||||
*
|
*
|
||||||
* Any data retrieved from GameState belongs to the caller.
|
* Users may not alter player, map or token
|
||||||
|
* states directly, but only through the API provided by
|
||||||
|
* this class.
|
||||||
|
*
|
||||||
|
* Any data retrieved from GameState belongs to the caller
|
||||||
|
* and is safe to use. You cannot change the GameState by
|
||||||
|
* altering data returned from GameState methods. Use
|
||||||
|
* the appropriate mutating methods instead.
|
||||||
|
*
|
||||||
|
* GameState can create a fast, lazy clone of itself
|
||||||
|
* by calling fork(). Forking itself is as cheap as possible.
|
||||||
|
* Deep field copies are created lazily, i.e. only if needed.
|
||||||
*/
|
*/
|
||||||
public class GameState {
|
public class GameState {
|
||||||
private Integer round;
|
private Integer round;
|
||||||
|
@ -44,33 +56,17 @@ public class GameState {
|
||||||
this.forked = forked;
|
this.forked = forked;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<GameState> fork(Integer times) {
|
|
||||||
ArrayList<GameState> forks = new ArrayList<>();
|
public Integer getRound() {
|
||||||
for (int i=0; i<times; i++) forks.add(fork());
|
return round;
|
||||||
return forks;
|
}
|
||||||
|
public void setRound(Integer round) {
|
||||||
|
this.round = round;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GameState fork() {
|
/*
|
||||||
GameState fork = new GameState(self.clone(), opponent.clone(), map.fork(), round, true);
|
* Player Management
|
||||||
fork.map.setForked(true);
|
*/
|
||||||
return fork;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putToken(Bug bug) {
|
|
||||||
map.putToken(bug);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putToken(Code code) {
|
|
||||||
map.putToken(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putToken(Mine mine) {
|
|
||||||
map.putToken(mine);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearTokens() {
|
|
||||||
map.clearTokens();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSnippets(String playerName, Integer snippets) {
|
public void setSnippets(String playerName, Integer snippets) {
|
||||||
Settings s = Settings.getInstance();
|
Settings s = Settings.getInstance();
|
||||||
|
@ -82,8 +78,12 @@ public class GameState {
|
||||||
return self.getSnippets();
|
return self.getSnippets();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getOpponentSnippets() {
|
public void addSelfSnippets(Integer snippets) {
|
||||||
return opponent.getSnippets();
|
self.addSnippets(snippets);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeSelfSnippets(Integer snippets) {
|
||||||
|
self.removeSnippets(snippets);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBombs(String playerName, Integer bombs) {
|
public void setBombs(String playerName, Integer bombs) {
|
||||||
|
@ -102,64 +102,107 @@ public class GameState {
|
||||||
return self.getPosition();
|
return self.getPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoveResult moveSelf(Point position) {
|
public Point getOpponentPosition() {
|
||||||
return makeMove(self, position);
|
return opponent.getPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoveResult moveSelf(Move move) {
|
public void moveSelf(Point position) {
|
||||||
return makeMove(self, calcMovePoint(self.getPosition(), move));
|
self.setPosition(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoveResult moveOpponent(Point position) {
|
public void moveSelf(Move move) {
|
||||||
return makeMove(opponent, position);
|
self.setPosition(calcMovePoint(self.getPosition(), move));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoveResult moveOpponent(Move move) {
|
public Player getSelfClone() {
|
||||||
return makeMove(self, calcMovePoint(opponent.getPosition(), move));
|
return self.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
private MoveResult makeMove(Player player, Point position) {
|
public List<Player> getPlayerClones() {
|
||||||
player.setPosition(position);
|
ArrayList<Player> players = new ArrayList<>();
|
||||||
|
players.add(self.clone());
|
||||||
List<Code> codes = map.removeCodesAt(position);
|
players.add(opponent.clone());
|
||||||
List<Bug> bugs = map.removeBugsAt(position);
|
return players;
|
||||||
|
|
||||||
player.addSnippets(codes.size());
|
|
||||||
player.removeSnippets(bugs.size()*4);
|
|
||||||
|
|
||||||
return new MoveResult(player.clone(), codes.size(), bugs.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getRound() {
|
/*
|
||||||
return round;
|
* Token Management
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void putToken(Bug bug) {
|
||||||
|
map.putToken(bug);
|
||||||
}
|
}
|
||||||
public void setRound(Integer round) {
|
|
||||||
this.round = round;
|
public void putToken(Code code) {
|
||||||
|
map.putToken(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void putToken(Mine mine) {
|
||||||
|
map.putToken(mine);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Code> removeCodesAt(Point position){
|
||||||
|
return map.removeCodesAt(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Bug> removeBugsAt(Point position){
|
||||||
|
return map.removeBugsAt(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Bug> getBugs(){
|
||||||
|
return map.getBugs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearBugs() {
|
||||||
|
map.clearBugs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearTokens() {
|
||||||
|
map.clearTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Movements Management
|
||||||
|
*/
|
||||||
|
|
||||||
|
public Set<Point> selfMovePoints(){
|
||||||
|
return movePoints(self.getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Move> selfMoves(){
|
||||||
|
return Movement.validMoves(self.getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point calcMovePoint(Point from, Move move) {
|
||||||
|
return Movement.calcMovePoint(from, move);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Point> movePoints(Point from){
|
||||||
|
return Movement.validMovePoints(from);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fork Managment
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public List<GameState> fork(Integer times) {
|
||||||
|
ArrayList<GameState> forks = new ArrayList<>();
|
||||||
|
for (int i=0; i<times; i++) forks.add(fork());
|
||||||
|
return forks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameState fork() {
|
||||||
|
GameState fork = new GameState(self.clone(), opponent.clone(), map.fork(), round, true);
|
||||||
|
fork.map.setForked(true);
|
||||||
|
return fork;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isForked() {
|
public boolean isForked() {
|
||||||
return forked;
|
return forked;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<Point> selfMovePoints(){
|
|
||||||
return map.validMovePoints(self.getPosition());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Move> selfMoves(){
|
|
||||||
return map.validMoves(self.getPosition());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Point> opponentMovePoints(){
|
|
||||||
return map.validMovePoints(opponent.getPosition());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Move> opponentMoves(){
|
|
||||||
return map.validMoves(opponent.getPosition());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Point calcMovePoint(Point from, Move move) {
|
|
||||||
return map.calcMovePoint(from, move);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
|
14
farbot/src/farbot/game/Move.java
Normal file
14
farbot/src/farbot/game/Move.java
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package farbot.game;
|
||||||
|
|
||||||
|
public enum Move {
|
||||||
|
UP (0,-1),
|
||||||
|
DOWN (0,1),
|
||||||
|
LEFT (-1,0),
|
||||||
|
RIGHT (1,0);
|
||||||
|
|
||||||
|
private final int x;
|
||||||
|
private final int y;
|
||||||
|
Move(int x, int y) { this.x = x; this.y = y; }
|
||||||
|
public int getX() { return x; }
|
||||||
|
public int getY() { return y; }
|
||||||
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
package farbot.game;
|
|
||||||
|
|
||||||
public class MoveResult {
|
|
||||||
private Player player;
|
|
||||||
private int killedBugs;
|
|
||||||
private int collectedCodes;
|
|
||||||
|
|
||||||
public MoveResult(Player player, int collectedCodes, int killedBugs) {
|
|
||||||
this.player = player;
|
|
||||||
this.killedBugs = killedBugs;
|
|
||||||
this.collectedCodes = collectedCodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Player getPlayer() {
|
|
||||||
return player;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getKilledBugs() {
|
|
||||||
return killedBugs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCollectedCodes() {
|
|
||||||
return collectedCodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,7 +1,5 @@
|
||||||
package farbot.game;
|
package farbot.game;
|
||||||
|
|
||||||
import farbot.game.map.Point;
|
|
||||||
|
|
||||||
public class Player implements Cloneable {
|
public class Player implements Cloneable {
|
||||||
private Integer bombs;
|
private Integer bombs;
|
||||||
private Integer snippets;
|
private Integer snippets;
|
||||||
|
@ -53,6 +51,10 @@ public class Player implements Cloneable {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double distance(Point point) {
|
||||||
|
return this.position.distance(point);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Player clone() {
|
public Player clone() {
|
||||||
return new Player(this.bombs, this.snippets, new Point(this.position), this.id);
|
return new Player(this.bombs, this.snippets, new Point(this.position), this.id);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package farbot.game.map;
|
package farbot.game;
|
||||||
|
|
||||||
public class Point extends java.awt.Point{
|
public class Point extends java.awt.Point{
|
||||||
|
|
||||||
|
@ -10,6 +10,11 @@ public class Point extends java.awt.Point{
|
||||||
|
|
||||||
public Point(int x, int y) { super(x,y); }
|
public Point(int x, int y) { super(x,y); }
|
||||||
|
|
||||||
|
|
||||||
|
public Integer taxiDistance(Point to) {
|
||||||
|
return Math.abs(this.x - to.x) + Math.abs(this.y - to.y);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "(x=" + x + ",y=" + y + ")";
|
return "(x=" + x + ",y=" + y + ")";
|
|
@ -1,6 +1,5 @@
|
||||||
package farbot.game.map;
|
package farbot.game.map;
|
||||||
|
|
||||||
import farbot.game.map.Point;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
@ -10,7 +9,9 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import farbot.game.Settings;
|
import farbot.game.Move;
|
||||||
|
import farbot.game.Point;
|
||||||
|
import farbot.Settings;
|
||||||
import farbot.game.token.Bug;
|
import farbot.game.token.Bug;
|
||||||
import farbot.game.token.Code;
|
import farbot.game.token.Code;
|
||||||
import farbot.game.token.Mine;
|
import farbot.game.token.Mine;
|
||||||
|
@ -85,37 +86,6 @@ public final class GameMap {
|
||||||
return new GameMap(this);
|
return new GameMap(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<Move> validMoves(Point from) {
|
|
||||||
EnumSet<Move> moves;
|
|
||||||
|
|
||||||
switch(base[from.x][from.y]) {
|
|
||||||
case GATE_L:
|
|
||||||
case GATE_R:
|
|
||||||
moves = EnumSet.of(Move.LEFT, Move.RIGHT);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
moves = EnumSet.allOf(Move.class);
|
|
||||||
moves.removeIf( move -> from.x+move.x < 0
|
|
||||||
|| from.y+move.y < 0
|
|
||||||
|| from.x+move.x >= s.getFieldWidth()
|
|
||||||
|| from.y+move.y >= s.getFieldHeight()
|
|
||||||
|| base[from.x+move.x][from.y+move.y] == Field.WALL );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return moves;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Point> validMovePoints(Point from){
|
|
||||||
return validMoves(from).stream()
|
|
||||||
.map(move -> calcMovePoint(from, move))
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Point calcMovePoint(Point from, Move move) {
|
|
||||||
return new Point(Math.floorMod(from.x+move.x, s.getFieldWidth()),
|
|
||||||
Math.floorMod(from.y+move.y, s.getFieldHeight()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putToken(Bug bug) {
|
public void putToken(Bug bug) {
|
||||||
if(dirtyBugs) {
|
if(dirtyBugs) {
|
||||||
|
@ -148,6 +118,12 @@ public final class GameMap {
|
||||||
mineMap.computeIfAbsent(mine.getPosition(), k -> new ArrayList<>()).add(mine);
|
mineMap.computeIfAbsent(mine.getPosition(), k -> new ArrayList<>()).add(mine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearBugs() {
|
||||||
|
this.bugs = new ArrayList<>();
|
||||||
|
this.bugMap = new HashMap<>();
|
||||||
|
this.dirtyBugs = false;
|
||||||
|
}
|
||||||
|
|
||||||
public void clearTokens() {
|
public void clearTokens() {
|
||||||
this.bugs = new ArrayList<>();
|
this.bugs = new ArrayList<>();
|
||||||
this.codes = new ArrayList<>();
|
this.codes = new ArrayList<>();
|
||||||
|
@ -300,4 +276,8 @@ public final class GameMap {
|
||||||
|
|
||||||
return Collections.unmodifiableList(tokens);
|
return Collections.unmodifiableList(tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Field[][] getBase() {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
package farbot.game.map;
|
|
||||||
|
|
||||||
public enum Move {
|
|
||||||
UP (0,-1),
|
|
||||||
DOWN (0,1),
|
|
||||||
LEFT (-1,0),
|
|
||||||
RIGHT (1,0);
|
|
||||||
|
|
||||||
protected final int x;
|
|
||||||
protected final int y;
|
|
||||||
Move(int x, int y) { this.x = x; this.y = y; }
|
|
||||||
}
|
|
53
farbot/src/farbot/game/map/util/Movement.java
Normal file
53
farbot/src/farbot/game/map/util/Movement.java
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package farbot.game.map.util;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import farbot.Settings;
|
||||||
|
import farbot.game.Move;
|
||||||
|
import farbot.game.Point;
|
||||||
|
import farbot.game.map.Field;
|
||||||
|
import farbot.game.map.GameMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility functions working on the invariant base map (GameMap.base).
|
||||||
|
*
|
||||||
|
* Obey functional contract. Functions must not have side-effects.
|
||||||
|
*/
|
||||||
|
public class Movement {
|
||||||
|
private static Field[][] base = GameMap.getBase();
|
||||||
|
|
||||||
|
public static Set<Move> validMoves(Point from) {
|
||||||
|
EnumSet<Move> moves;
|
||||||
|
|
||||||
|
switch(base[from.x][from.y]) {
|
||||||
|
case GATE_L:
|
||||||
|
case GATE_R:
|
||||||
|
moves = EnumSet.of(Move.LEFT, Move.RIGHT);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
moves = EnumSet.allOf(Move.class);
|
||||||
|
moves.removeIf( move ->
|
||||||
|
from.getX()+move.getX() < 0
|
||||||
|
|| from.y+move.getY() < 0
|
||||||
|
|| from.x+move.getX() >= Settings.getInstance().getFieldWidth()
|
||||||
|
|| from.y+move.getY() >= Settings.getInstance().getFieldHeight()
|
||||||
|
|| base[from.x+move.getX()][from.y+move.getY()] == Field.WALL );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return moves;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<Point> validMovePoints(Point from){
|
||||||
|
return validMoves(from).stream()
|
||||||
|
.map(move -> calcMovePoint(from, move))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Point calcMovePoint(Point from, Move move) {
|
||||||
|
return new Point(Math.floorMod(from.x+move.getX(), Settings.getInstance().getFieldWidth()),
|
||||||
|
Math.floorMod(from.y+move.getY(), Settings.getInstance().getFieldHeight()));
|
||||||
|
}
|
||||||
|
}
|
119
farbot/src/farbot/game/map/util/ShortestPaths.java
Normal file
119
farbot/src/farbot/game/map/util/ShortestPaths.java
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package farbot.game.map.util;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.PriorityQueue;
|
||||||
|
|
||||||
|
import farbot.game.Point;
|
||||||
|
|
||||||
|
import static farbot.FarbotIO.logln;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility functions working on the invariant base map (GameMap.base).
|
||||||
|
*
|
||||||
|
* Obey functional contract. Functions must not have side-effects.
|
||||||
|
*
|
||||||
|
* Memoized shortest paths calculation.
|
||||||
|
*/
|
||||||
|
public class ShortestPaths {
|
||||||
|
private static HashMap<Points, List<Point>> memo = new HashMap<>();
|
||||||
|
|
||||||
|
public static List<Point> shortestPath(Point a, Point b){
|
||||||
|
assert(a != null); assert(b != null);
|
||||||
|
Points points = new Points(a,b);
|
||||||
|
|
||||||
|
if(memo.containsKey(points)) return memo.get(points);
|
||||||
|
|
||||||
|
List<Point> path = aStarSearch(a,b);
|
||||||
|
memo.put(new Points(a,b), path);
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Point> aStarSearch(Point start, Point goal){
|
||||||
|
logln("Calculating aStar from "+start+" to "+goal);
|
||||||
|
|
||||||
|
PriorityQueue<ANode> frontier = new PriorityQueue<>(Comparator.comparingInt(a -> a.priority));
|
||||||
|
HashMap<Point, Point> cameFrom = new HashMap<>();
|
||||||
|
HashMap<Point, Integer> cost = new HashMap<>();
|
||||||
|
|
||||||
|
frontier.add(new ANode(start,0));
|
||||||
|
cost.put(start, 0);
|
||||||
|
|
||||||
|
while(!frontier.isEmpty()) {
|
||||||
|
ANode current = frontier.poll();
|
||||||
|
Point currentP = current.point;
|
||||||
|
|
||||||
|
if(currentP.equals(goal)) break;
|
||||||
|
|
||||||
|
for(Point nextP: Movement.validMovePoints(currentP)) {
|
||||||
|
int newCost = cost.get(currentP) + 1;
|
||||||
|
|
||||||
|
if(!cost.containsKey(nextP) || newCost < cost.get(nextP)) {
|
||||||
|
cost.put(nextP, newCost);
|
||||||
|
int priority = newCost + goal.taxiDistance(nextP);
|
||||||
|
frontier.add(new ANode(nextP, priority));
|
||||||
|
cameFrom.put(nextP, currentP);
|
||||||
|
|
||||||
|
//logln("Added "+nextP+" with cost "+newCost+" and priority "+priority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkedList<Point> path = new LinkedList<>();
|
||||||
|
Point last = goal;
|
||||||
|
path.add(last);
|
||||||
|
Point newLast;
|
||||||
|
while( (newLast = cameFrom.get(last)) != null) {
|
||||||
|
cameFrom.remove(last);
|
||||||
|
path.add(newLast);
|
||||||
|
last = newLast;
|
||||||
|
}
|
||||||
|
Collections.reverse(path);
|
||||||
|
|
||||||
|
logln("Shortest path: "+path.toString());
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ANode {
|
||||||
|
private Point point;
|
||||||
|
private Integer priority;
|
||||||
|
|
||||||
|
private ANode(Point point, Integer priority) {
|
||||||
|
this.point = point;
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Points {
|
||||||
|
private Point a;
|
||||||
|
private Point b;
|
||||||
|
|
||||||
|
private Points(Point a, Point b) {
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return 31 * (31 + a.hashCode()) + b.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (obj == null) return false;
|
||||||
|
if (getClass() != obj.getClass()) return false;
|
||||||
|
|
||||||
|
Points other = (Points) obj;
|
||||||
|
|
||||||
|
if(!a.equals(other.a) || !b.equals(other.b)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package farbot.game.token;
|
package farbot.game.token;
|
||||||
|
|
||||||
import farbot.game.map.Point;
|
import farbot.game.Point;
|
||||||
|
|
||||||
public abstract class Token {
|
public abstract class Token {
|
||||||
Point position;
|
Point position;
|
||||||
|
|
Loading…
Reference in a new issue