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.game.GameState;
|
||||
import farbot.game.Settings;
|
||||
|
||||
public class FarbotIO {
|
||||
private static Scanner in;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package farbot.game;
|
||||
package farbot;
|
||||
|
||||
public class Settings {
|
||||
private Integer timebank;
|
|
@ -1,11 +1,12 @@
|
|||
package farbot.ai;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import farbot.game.GameState;
|
||||
import farbot.game.MoveResult;
|
||||
import farbot.game.map.Move;
|
||||
import farbot.game.Move;
|
||||
|
||||
import static farbot.FarbotIO.logln;
|
||||
|
||||
|
@ -21,28 +22,46 @@ public class AI {
|
|||
|
||||
public String calcMove(Integer time) {
|
||||
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();
|
||||
logln("Valid moves: "+validMoves.toString());
|
||||
return validMoves;
|
||||
}
|
||||
|
||||
private List<MoveValue> getValuedMoves(Set<Move> validMoves){
|
||||
ArrayList<MoveValue> valuedMoves = new ArrayList<>();
|
||||
|
||||
for(Move m: validMoves) {
|
||||
logln("Checking move: "+m.name());
|
||||
GameState fork = currentState.fork();
|
||||
MoveResult mr = fork.moveSelf(m);
|
||||
GameTree gt = new GameTree(fork);
|
||||
|
||||
GameTree gt = new GameTree(currentState.fork(), m);
|
||||
gt.build(treeDepth);
|
||||
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);
|
||||
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);
|
||||
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) {
|
||||
case LEFT:
|
||||
if(maxMove.move == Move.RIGHT) maxMove = valuedMoves.get(1);
|
||||
|
@ -61,10 +80,7 @@ public class AI {
|
|||
}
|
||||
}
|
||||
|
||||
this.lastMove = maxMove;
|
||||
|
||||
logln("Max.move '"+maxMove.move+"': "+maxMove.value);
|
||||
return maxMove.move.name().toLowerCase();
|
||||
return maxMove;
|
||||
}
|
||||
|
||||
public void parseUpdate(String[] update) {
|
||||
|
|
|
@ -2,33 +2,42 @@ package farbot.ai;
|
|||
|
||||
import static farbot.FarbotIO.logln;
|
||||
|
||||
import farbot.game.map.Point;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.function.BinaryOperator;
|
||||
|
||||
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 {
|
||||
private GameState node;
|
||||
private Double value;
|
||||
private HashSet<Point> visited;
|
||||
private Integer totalLevel;
|
||||
private Move initMove;
|
||||
|
||||
public GameTree(GameState root) {
|
||||
public GameTree(GameState root, Move initMove) {
|
||||
this.node = root;
|
||||
this.value = 0.0;
|
||||
this.totalLevel = 1;
|
||||
this.visited = new HashSet<>();
|
||||
this.initMove = initMove;
|
||||
}
|
||||
|
||||
public void build(int 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) {
|
||||
level--;
|
||||
|
||||
|
@ -62,16 +71,10 @@ public class GameTree {
|
|||
|
||||
for(Point selfMove: selfMoves) {
|
||||
GameState child = state.fork();
|
||||
MoveResult mr = child.moveSelf(selfMove);
|
||||
MoveResult mr = Simulator.move(selfMove, child);
|
||||
|
||||
if(mr.getCollectedCodes() != 0){
|
||||
logln("Got "+mr.getCollectedCodes()+" codes at "+selfMove.toString()+" in level "+level);
|
||||
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.value += valueCodes(mr, level, child, gtRoot);
|
||||
gtRoot.value += valueBugs(mr, level, child);
|
||||
|
||||
gtRoot.visited.add(selfMove);
|
||||
|
||||
|
@ -80,4 +83,50 @@ public class GameTree {
|
|||
|
||||
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;
|
||||
|
||||
import farbot.game.map.Point;
|
||||
|
||||
import farbot.Settings;
|
||||
import farbot.game.GameState;
|
||||
import farbot.game.Settings;
|
||||
import farbot.game.Point;
|
||||
import farbot.game.token.Bug;
|
||||
import farbot.game.token.Bug.BugAI;
|
||||
import farbot.game.token.Code;
|
||||
import farbot.game.token.Mine;
|
||||
|
||||
import static farbot.FarbotIO.logln;
|
||||
import static farbot.FarbotIO.log;
|
||||
|
||||
public class Parser {
|
||||
public static void parse(String[] update, GameState state) {
|
||||
switch(update[2]) {
|
||||
case "round":
|
||||
state.setRound(Integer.parseInt(update[3]));
|
||||
log("\n");
|
||||
logln("******* Round "+state.getRound()+" *******");
|
||||
log("\n");
|
||||
break;
|
||||
case "field":
|
||||
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;
|
||||
|
||||
import farbot.game.map.Point;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import farbot.Settings;
|
||||
import farbot.ai.MoveResult;
|
||||
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.Code;
|
||||
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 {
|
||||
private Integer round;
|
||||
|
@ -44,33 +56,17 @@ public class GameState {
|
|||
this.forked = forked;
|
||||
}
|
||||
|
||||
public List<GameState> fork(Integer times) {
|
||||
ArrayList<GameState> forks = new ArrayList<>();
|
||||
for (int i=0; i<times; i++) forks.add(fork());
|
||||
return forks;
|
||||
|
||||
public Integer getRound() {
|
||||
return round;
|
||||
}
|
||||
public void setRound(Integer round) {
|
||||
this.round = round;
|
||||
}
|
||||
|
||||
public GameState fork() {
|
||||
GameState fork = new GameState(self.clone(), opponent.clone(), map.fork(), round, true);
|
||||
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();
|
||||
}
|
||||
/*
|
||||
* Player Management
|
||||
*/
|
||||
|
||||
public void setSnippets(String playerName, Integer snippets) {
|
||||
Settings s = Settings.getInstance();
|
||||
|
@ -82,8 +78,12 @@ public class GameState {
|
|||
return self.getSnippets();
|
||||
}
|
||||
|
||||
public Integer getOpponentSnippets() {
|
||||
return opponent.getSnippets();
|
||||
public void addSelfSnippets(Integer snippets) {
|
||||
self.addSnippets(snippets);
|
||||
}
|
||||
|
||||
public void removeSelfSnippets(Integer snippets) {
|
||||
self.removeSnippets(snippets);
|
||||
}
|
||||
|
||||
public void setBombs(String playerName, Integer bombs) {
|
||||
|
@ -102,64 +102,107 @@ public class GameState {
|
|||
return self.getPosition();
|
||||
}
|
||||
|
||||
public MoveResult moveSelf(Point position) {
|
||||
return makeMove(self, position);
|
||||
public Point getOpponentPosition() {
|
||||
return opponent.getPosition();
|
||||
}
|
||||
|
||||
public MoveResult moveSelf(Move move) {
|
||||
return makeMove(self, calcMovePoint(self.getPosition(), move));
|
||||
public void moveSelf(Point position) {
|
||||
self.setPosition(position);
|
||||
}
|
||||
|
||||
public MoveResult moveOpponent(Point position) {
|
||||
return makeMove(opponent, position);
|
||||
public void moveSelf(Move move) {
|
||||
self.setPosition(calcMovePoint(self.getPosition(), move));
|
||||
}
|
||||
|
||||
public MoveResult moveOpponent(Move move) {
|
||||
return makeMove(self, calcMovePoint(opponent.getPosition(), move));
|
||||
public Player getSelfClone() {
|
||||
return self.clone();
|
||||
}
|
||||
|
||||
private MoveResult makeMove(Player player, Point position) {
|
||||
player.setPosition(position);
|
||||
|
||||
List<Code> codes = map.removeCodesAt(position);
|
||||
List<Bug> bugs = map.removeBugsAt(position);
|
||||
|
||||
player.addSnippets(codes.size());
|
||||
player.removeSnippets(bugs.size()*4);
|
||||
|
||||
return new MoveResult(player.clone(), codes.size(), bugs.size());
|
||||
public List<Player> getPlayerClones() {
|
||||
ArrayList<Player> players = new ArrayList<>();
|
||||
players.add(self.clone());
|
||||
players.add(opponent.clone());
|
||||
return players;
|
||||
}
|
||||
|
||||
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() {
|
||||
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
|
||||
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;
|
||||
|
||||
import farbot.game.map.Point;
|
||||
|
||||
public class Player implements Cloneable {
|
||||
private Integer bombs;
|
||||
private Integer snippets;
|
||||
|
@ -53,6 +51,10 @@ public class Player implements Cloneable {
|
|||
this.id = id;
|
||||
}
|
||||
|
||||
public double distance(Point point) {
|
||||
return this.position.distance(point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player clone() {
|
||||
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{
|
||||
|
||||
|
@ -10,6 +10,11 @@ public class Point extends java.awt.Point{
|
|||
|
||||
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
|
||||
public String toString() {
|
||||
return "(x=" + x + ",y=" + y + ")";
|
|
@ -1,6 +1,5 @@
|
|||
package farbot.game.map;
|
||||
|
||||
import farbot.game.map.Point;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
|
@ -10,7 +9,9 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
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.Code;
|
||||
import farbot.game.token.Mine;
|
||||
|
@ -85,37 +86,6 @@ public final class GameMap {
|
|||
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) {
|
||||
if(dirtyBugs) {
|
||||
|
@ -148,6 +118,12 @@ public final class GameMap {
|
|||
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() {
|
||||
this.bugs = new ArrayList<>();
|
||||
this.codes = new ArrayList<>();
|
||||
|
@ -300,4 +276,8 @@ public final class GameMap {
|
|||
|
||||
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;
|
||||
|
||||
import farbot.game.map.Point;
|
||||
import farbot.game.Point;
|
||||
|
||||
public abstract class Token {
|
||||
Point position;
|
||||
|
|
Loading…
Reference in a new issue