@@ -0,0 +1,29 @@ | |||
### IntelliJ IDEA ### | |||
out/ | |||
!**/src/main/**/out/ | |||
!**/src/test/**/out/ | |||
### Eclipse ### | |||
.apt_generated | |||
.classpath | |||
.factorypath | |||
.project | |||
.settings | |||
.springBeans | |||
.sts4-cache | |||
bin/ | |||
!**/src/main/**/bin/ | |||
!**/src/test/**/bin/ | |||
### NetBeans ### | |||
/nbproject/private/ | |||
/nbbuild/ | |||
/dist/ | |||
/nbdist/ | |||
/.nb-gradle/ | |||
### VS Code ### | |||
.vscode/ | |||
### Mac OS ### | |||
.DS_Store |
@@ -0,0 +1,85 @@ | |||
import java.io.BufferedReader; | |||
import java.io.InputStreamReader; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.net.Socket; | |||
import java.net.InetSocketAddress; | |||
public abstract class Bot { | |||
// Ein Bot ist ein Programm, das sich mit einem Server verbindet und | |||
// mit diesem kommuniziert. Der Server sendet dem Bot eine Zeichenkette, | |||
// die das Spielfeld repräsentiert. Der Bot sendet dem Server ein Zeichen, | |||
// das die nächste Bewegung des Bots repräsentiert. | |||
private final String host; // Hostname oder IP-Adresse des Servers | |||
private final int port; // Port des Servers | |||
protected Bot(String[] args) { | |||
host = args.length > 0 ? args[0] : "localhost"; | |||
port = args.length > 1 ? Integer.parseInt(args[1]) : 63187; | |||
} | |||
// Diese Methode stellt die Verbindung zum Server her und startet die | |||
// Kommunikation mit dem Server. Die Methode wird von der main-Methode | |||
// aufgerufen. | |||
protected void run() { | |||
try (Socket socket = new Socket()) { | |||
socket.connect(new InetSocketAddress(host, port)); | |||
OutputStream out = socket.getOutputStream(); | |||
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); | |||
View view = new View(); | |||
while (true) { | |||
view.read(in); | |||
view.print(); | |||
try { | |||
char ch = nextMove(view); | |||
out.write(ch); | |||
} | |||
catch (Exception e) { break; } | |||
} | |||
socket.close(); | |||
} catch (IOException e) { | |||
System.err.println("Error: " + e.getMessage()); | |||
} | |||
} | |||
// Diese Methode ermittelt den nächsten Zug des Bots. Sie wird von der | |||
// run-Methode aufgerufen, nachdem der Server das Spielfeld gesendet hat. | |||
// Subklassen müssen diese Methode implementieren. | |||
abstract protected char nextMove(View view) throws Exception; | |||
// Diese Klasse repräsentiert das Spielfeld. Sie wird von der run-Methode | |||
// verwendet, um das Spielfeld zu lesen und auszugeben. | |||
// Subklassen können diese Klasse verwenden, um das Spielfeld zu analysieren. | |||
public static class View { | |||
protected String data; | |||
protected int width; | |||
// Diese Methode liest das Spielfeld vom Server. | |||
private void read(BufferedReader in) throws IOException { | |||
StringBuilder sb = new StringBuilder(); | |||
data = in.readLine(); | |||
if (data == null) { | |||
return; | |||
} | |||
sb.append(data); | |||
width = data.length(); | |||
for (int i = 1; i < width; ++i) { | |||
sb.append(in.readLine()); | |||
} | |||
data = sb.toString(); | |||
} | |||
// Diese Methode gibt das Spielfeld aus. | |||
protected void print() { | |||
if (data == null || width < 1) { | |||
return; | |||
} | |||
for (int i = 0, len = data.length(); i < len; i += width) { | |||
System.out.println(data.substring(i, i + width)); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,140 @@ | |||
import java.util.Stack; | |||
public class CollectBot extends SmartBot { | |||
private int maxNumberOfTilesToMove = 15; | |||
private int tilesMoved; | |||
private char directionBias; | |||
private final Stack<Character> moves; | |||
protected CollectBot(String[] args) { | |||
super(args); | |||
tilesMoved = 0; | |||
directionBias = LEFT; | |||
moves = new Stack<Character>(); | |||
} | |||
public static void main(String[] args) { | |||
Bot bot = new CollectBot(args); | |||
bot.run(); | |||
} | |||
@Override | |||
protected char nextMove(View view) throws Exception { | |||
char move = '^'; | |||
// Search larger areas after a time | |||
if (moves.size() != 0 && moves.size() % 100 == 0 && maxNumberOfTilesToMove <=30) { | |||
maxNumberOfTilesToMove += 5; | |||
} | |||
// Look for Gems | |||
int turn = scanView(view); | |||
if (turn == 3) { | |||
tilesMoved++; | |||
// Change up direction after not encountering anything for a while | |||
if (tilesMoved >= maxNumberOfTilesToMove) { | |||
tilesMoved = 0; | |||
move = switchBias(true); | |||
} | |||
} else { | |||
tilesMoved = 0; | |||
move = switch (turn) { | |||
case -1 -> LEFT; | |||
case 0 -> FORWARD; | |||
case 1 -> RIGHT; | |||
default -> move; | |||
}; | |||
} | |||
moves.push(move); | |||
return move; | |||
} | |||
/*** | |||
* This Method can be either used to detect potential spiral collision paths or to periodically change the direction bias | |||
* @param numberOfTurns The number of turns that should be equal | |||
* @return true, if the last n turns were equal in the given distance covered | |||
*/ | |||
private boolean evaluateNumberOfEqualTurns(int numberOfTurns) { | |||
if (this.moves.isEmpty() || this.moves.size() < 3) { | |||
return false; | |||
} | |||
Stack<Character> moves = (Stack<Character>) this.moves.clone(); | |||
int numberOfEqualTurns = 0; | |||
int distanceCovered = 0; | |||
char directionToCompare = '#'; | |||
while (numberOfEqualTurns <= numberOfTurns && !moves.isEmpty()) { | |||
char lastMove = moves.pop(); | |||
if (lastMove == FORWARD) { | |||
distanceCovered++; | |||
continue; | |||
} | |||
if (numberOfEqualTurns == 0) { | |||
numberOfEqualTurns++; | |||
directionToCompare = lastMove; | |||
} else { | |||
if (lastMove != directionToCompare) { | |||
return false; | |||
} | |||
numberOfEqualTurns++; | |||
} | |||
} | |||
return numberOfEqualTurns == 3; | |||
} | |||
/*** | |||
* Changes up the direction bias every two turns which achieves a rough zig-zag pattern | |||
* @return the next direction bias | |||
*/ | |||
protected char switchBias(boolean forcedSwitch) { | |||
if(!evaluateNumberOfEqualTurns(2) && !forcedSwitch) { | |||
return directionBias; | |||
} | |||
directionBias = switch (directionBias) { | |||
case LEFT -> { | |||
directionBias = RIGHT; | |||
yield RIGHT; | |||
} | |||
case RIGHT -> { | |||
directionBias = LEFT; | |||
yield LEFT; | |||
} | |||
default -> directionBias; | |||
}; | |||
return directionBias; | |||
} | |||
protected char switchBias() { | |||
return switchBias(false); | |||
} | |||
protected int scanView(View view) { | |||
if (view.data.contains("@")) { | |||
int stone = view.data.indexOf("@") + 1; | |||
// Check Front: | |||
switch (stone) { | |||
case 3, 8: | |||
return 0; | |||
} | |||
// If possible, prefer direction Bias | |||
if (directionBias == LEFT) { | |||
switch (stone) { | |||
case 11, 12: return -1; // left | |||
case 14, 15: return 1; // right | |||
} | |||
} else { | |||
switch (stone) { | |||
case 14, 15: return 1; // right | |||
case 11, 12: return -1; // left | |||
} | |||
} | |||
} | |||
return 3; | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
import java.util.Random; | |||
public class DummyBot extends SmartBot { | |||
Random random; | |||
protected DummyBot(String[] args) { | |||
super(args); | |||
random = new Random(); | |||
} | |||
public static void main(String[] args) { | |||
Bot bot = new DummyBot(args); | |||
bot.run(); | |||
} | |||
@Override | |||
protected char nextMove(View view) throws Exception { | |||
int move = random.nextInt(0, 7); | |||
return switch (move) { | |||
case 1,2,3 -> FORWARD; | |||
case 4 -> LEFT; | |||
case 5 -> RIGHT; | |||
case 6 -> SHOOT; | |||
default -> 0; | |||
}; | |||
} | |||
} |
@@ -0,0 +1,67 @@ | |||
public class EscapeBot extends Bot { | |||
int unitsMoved; | |||
int unitsToMove; | |||
int timesTurned; | |||
protected EscapeBot(String[] args) { | |||
super(args); | |||
unitsMoved = 0; | |||
unitsToMove = 5; | |||
timesTurned = 0; | |||
} | |||
public static void main(String[] args) { | |||
Bot bot = new EscapeBot(args); | |||
bot.run(); | |||
} | |||
@Override | |||
protected char nextMove(View view) throws Exception { | |||
//boolean found = false; | |||
if (unitsMoved % 5 == 0) { | |||
char c = checkView(view); | |||
switch (c) { | |||
case '0': break; | |||
case '1': throw new Exception("found!"); | |||
default: return c; | |||
} | |||
} | |||
if (unitsMoved < unitsToMove) { | |||
unitsMoved++; | |||
return '^'; | |||
} else if (unitsMoved == unitsToMove) { | |||
unitsMoved = 0; | |||
timesTurned++; | |||
if (timesTurned % 2 == 0) { | |||
unitsToMove += 5; | |||
} | |||
return '>'; | |||
} | |||
throw new Exception(); | |||
} | |||
private char checkView(View view) { | |||
if (view.data.contains("o")) { | |||
int spaceship = view.data.indexOf("o") + 1; | |||
if (spaceship == 13) { | |||
return '1'; | |||
} else if (spaceship <= 10) { | |||
return '^'; | |||
} else if (spaceship >= 15) { | |||
return 'v'; | |||
} else if (spaceship%5 < 3) { | |||
return '<'; | |||
} else if (spaceship%5 > 3) { | |||
return '>'; | |||
} | |||
} | |||
return '0'; | |||
} | |||
} |
@@ -0,0 +1,163 @@ | |||
import java.util.Random; | |||
public class RumbleBot extends SmartBot { | |||
private int tilesToMove; | |||
private int escapeMove; | |||
private final Random random; | |||
private final int[] leftVectorIndizes = new int[] {36,37,38,39}; | |||
private final int[] rightVectorIndizes = new int[] {23,24,25,26}; | |||
private final int[] frontVectorIndizes = new int[] {4,13,22,31}; | |||
protected RumbleBot(String[] args) { | |||
super(args); | |||
this.random = new Random(); | |||
this.escapeMove = 0; | |||
this.tilesToMove = random.nextInt(5, 15); | |||
} | |||
public static void main(String[] args) { | |||
Bot bot = new RumbleBot(args); | |||
bot.run(); | |||
} | |||
@Override | |||
protected char nextMove(View view) throws Exception { | |||
char move = FORWARD; | |||
// if (tilesToMove >= tilesMoved) { | |||
// tilesMoved = 0; | |||
// tilesToMove = random.nextInt(5, 15); | |||
// move = directionBias; | |||
// } | |||
if (escapeMove != 0) { | |||
escapeMove--; | |||
return FORWARD; | |||
} | |||
// Check for enemies | |||
Enemy enemy = evaluateEnemy(view); | |||
if (enemy != null) { | |||
move = calculateOptimalMove(view, enemy); | |||
escapeMove = 3; | |||
} | |||
if (directionBlocked(view,FORWARD)) { | |||
if (directionBlocked(view,RIGHT)) { | |||
move = LEFT; | |||
} else { | |||
move = RIGHT; | |||
} | |||
} | |||
return move; | |||
} | |||
private Enemy evaluateEnemy(View view) { | |||
int enemyPos; | |||
if (view.data.contains("^")) { | |||
enemyPos = view.data.indexOf('^'); | |||
} else if (view.data.contains("v")) { | |||
enemyPos = view.data.indexOf('v'); | |||
} else if (view.data.contains("<")) { | |||
enemyPos = view.data.indexOf('<'); | |||
} else if (view.data.contains(">")) { | |||
enemyPos = view.data.indexOf('>'); | |||
} else { | |||
return null; | |||
} | |||
return new Enemy(enemyPos + 1, view.data.charAt(enemyPos)); | |||
} | |||
public boolean directionBlocked(View view, char direction) { | |||
if (direction == FORWARD) { | |||
for (int i = 4; i < 32; i+=9) { | |||
if (view.data.charAt(i) == 'X') { | |||
return true; | |||
} | |||
} | |||
} else if (direction == LEFT) { | |||
for (int i = 36; i < 40; i++) { | |||
if (view.data.charAt(i) == 'X') { | |||
return true; | |||
} | |||
} | |||
} else if (direction == RIGHT) { | |||
for (int i = 41; i < 45; i++) { | |||
if (view.data.charAt(i) == 'X') { | |||
return true; | |||
} | |||
} | |||
} | |||
return false; | |||
} | |||
private char calculateOptimalMove(View view, Enemy enemy) { | |||
int pos = enemy.position; | |||
char dir = enemy.direction; | |||
// Top Sector | |||
if (pos <= 18) { | |||
// Left Half | |||
if (pos % 9 < 5) { | |||
return !directionBlocked(view,RIGHT) ? RIGHT : SHOOT; | |||
} | |||
// Right Half | |||
else if (pos % 9 > 5) { | |||
return !directionBlocked(view,LEFT) ? LEFT : SHOOT; | |||
} | |||
// Center | |||
else { | |||
return SHOOT; | |||
} | |||
} | |||
// Bottom Sector | |||
if (pos >= 24) { | |||
// Left or Right Half | |||
if (pos % 9 != 5) { | |||
return !directionBlocked(view,FORWARD) ? FORWARD : randomDirection(view); | |||
} | |||
// Center | |||
else { | |||
return randomDirection(view); | |||
} | |||
} | |||
// Left or Right | |||
return randomDirection(view); | |||
} | |||
private char randomDirection(View view) { | |||
int rand = random.nextInt(0, 1); | |||
char dir = rand == 0 ? '<' : '>'; | |||
return !directionBlocked(view,dir) ? dir : flipDirection(dir); | |||
} | |||
private char flipDirection(char direction) { | |||
return switch (direction) { | |||
case RIGHT -> LEFT; | |||
case LEFT -> RIGHT; | |||
case FORWARD -> BACKWARD; | |||
default -> FORWARD; | |||
}; | |||
} | |||
private static class Enemy { | |||
final int position; | |||
final char direction; | |||
public Enemy(int position, char direction) { | |||
this.position = position; | |||
this.direction = direction; | |||
} | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
import java.util.Stack; | |||
public abstract class SmartBot extends Bot { | |||
protected static final char FORWARD = '^'; | |||
protected static final char BACKWARD = 'v'; | |||
protected static final char LEFT = '<'; | |||
protected static final char RIGHT = '>'; | |||
protected static final char SHOOT = 'f'; | |||
protected int tilesMoved; | |||
protected final Stack<Character> moves; | |||
protected SmartBot(String[] args) { | |||
super(args); | |||
this.tilesMoved = 0; | |||
this.moves = new Stack<>(); | |||
} | |||
@Override | |||
protected char nextMove(View view) throws Exception { | |||
return 0; | |||
} | |||
} |
@@ -0,0 +1,199 @@ | |||
import java.util.Stack; | |||
public class SnakeBot extends SmartBot { | |||
private int maxNumberOfTilesToMove = 15; | |||
private int tilesMoved; | |||
private int tailLength; | |||
private int escapeSequence; | |||
private char directionBias; | |||
private final Stack<Character> moves; | |||
protected SnakeBot(String[] args) { | |||
super(args); | |||
tilesMoved = 0; | |||
tailLength = 0; | |||
escapeSequence = 0; | |||
directionBias = LEFT; | |||
moves = new Stack<Character>(); | |||
} | |||
public static void main(String[] args) { | |||
Bot bot = new SnakeBot(args); | |||
bot.run(); | |||
} | |||
@Override | |||
protected char nextMove(View view) throws Exception { | |||
char move = '^'; | |||
// Avoid potential spiral paths | |||
if (evaluateNumberOfEqualTurns(3, tailLength)) { | |||
moves.push(switchBias()); | |||
System.out.println("Potential Spiral detected. Next Move: " + directionBias); | |||
escapeSequence = 2; | |||
return directionBias; | |||
} | |||
// Way must be free | |||
if (!checkFrontCollisions(view)) { | |||
escapeSequence = 1; | |||
moves.push(switchBias()); | |||
System.out.println("Collision ahead. Next Move: " + directionBias); | |||
return directionBias; | |||
} | |||
// Escape after potential collision or spiral turn loops | |||
if (escapeSequence != 0) { | |||
switch (escapeSequence) { | |||
case 1: | |||
escapeSequence = 0; | |||
moves.push(FORWARD); | |||
return FORWARD; | |||
case 2: | |||
escapeSequence = 1; | |||
moves.push(switchBias()); | |||
return directionBias; | |||
} | |||
} | |||
// Search larger areas after a time | |||
if (moves.size() != 0 && moves.size() % 100 == 0 && maxNumberOfTilesToMove <=30) { | |||
maxNumberOfTilesToMove += 5; | |||
} | |||
// Look for Gems | |||
int turn = scanView(view); | |||
if (turn == 3) { | |||
tilesMoved++; | |||
// Change up direction after not encountering anything for a while | |||
if (tilesMoved >= maxNumberOfTilesToMove) { | |||
tilesMoved = 0; | |||
move = switchBias(true); | |||
} | |||
} else { | |||
tilesMoved = 0; | |||
move = switch (turn) { | |||
case -1 -> LEFT; | |||
case 0 -> FORWARD; | |||
case 1 -> RIGHT; | |||
default -> move; | |||
}; | |||
} | |||
moves.push(move); | |||
return move; | |||
} | |||
/*** | |||
* This Method can be either used to detect potential spiral collision paths or to periodically change the direction bias | |||
* @param numberOfTurns The number of turns that should be equal | |||
* @param maxDepth The distance in wich the turns must have occurred to be considered | |||
* @return true, if the last n turns were equal in the given distance covered | |||
*/ | |||
private boolean evaluateNumberOfEqualTurns(int numberOfTurns, int maxDepth) { | |||
if (this.moves.isEmpty() || this.moves.size() < 3) { | |||
return false; | |||
} | |||
Stack<Character> moves = (Stack<Character>) this.moves.clone(); | |||
int numberOfEqualTurns = 0; | |||
int distanceCovered = 0; | |||
char directionToCompare = '#'; | |||
while (numberOfEqualTurns <= numberOfTurns && distanceCovered <= maxDepth && !moves.isEmpty()) { | |||
char lastMove = moves.pop(); | |||
if (lastMove == FORWARD) { | |||
distanceCovered++; | |||
continue; | |||
} | |||
if (numberOfEqualTurns == 0) { | |||
numberOfEqualTurns++; | |||
directionToCompare = lastMove; | |||
} else { | |||
if (lastMove != directionToCompare) { | |||
return false; | |||
} | |||
numberOfEqualTurns++; | |||
} | |||
} | |||
return numberOfEqualTurns == 3; | |||
} | |||
private boolean evaluateNumberOfEqualTurns(int numberOfTurns) { | |||
return evaluateNumberOfEqualTurns(numberOfTurns, 32); | |||
} | |||
/*** | |||
* Changes up the direction bias every two turns which achieves a rough zig-zag pattern | |||
* @return the next direction bias | |||
*/ | |||
protected char switchBias(boolean forcedSwitch) { | |||
if(!evaluateNumberOfEqualTurns(2) && !forcedSwitch) { | |||
return directionBias; | |||
} | |||
directionBias = switch (directionBias) { | |||
case LEFT -> { | |||
directionBias = RIGHT; | |||
yield RIGHT; | |||
} | |||
case RIGHT -> { | |||
directionBias = LEFT; | |||
yield LEFT; | |||
} | |||
default -> directionBias; | |||
}; | |||
return directionBias; | |||
} | |||
protected char switchBias() { | |||
return switchBias(false); | |||
} | |||
private boolean checkFrontCollisions(View view) { | |||
return view.data.charAt(2) != '*' && | |||
view.data.charAt(7) != '*'; | |||
} | |||
private boolean checkSideCollisions(View view, int side) { | |||
if (side == -1) { | |||
return view.data.charAt(11) != '*'; | |||
} else if (side == 1) { | |||
return view.data.indexOf(13) != '*'; | |||
} | |||
return false; | |||
} | |||
protected int scanView(View view) { | |||
if (view.data.contains("@")) { | |||
int stone = view.data.indexOf("@") + 1; | |||
// Check Front: | |||
switch (stone) { | |||
case 3: | |||
tailLength++; | |||
return 0; | |||
case 8: | |||
return 0; | |||
} | |||
// If possible, prefer direction Bias | |||
if (directionBias == LEFT) { | |||
switch (stone) { | |||
case 11, 12: return checkSideCollisions(view, -1)? -1 : 3; // left | |||
case 14, 15: return checkSideCollisions(view, 1)? 1 : 3; // right | |||
} | |||
} else { | |||
switch (stone) { | |||
case 14, 15: return checkSideCollisions(view, 1)? 1 : 3; // right | |||
case 11, 12: return checkSideCollisions(view, -1)? -1 : 3; // left | |||
} | |||
} | |||
} | |||
return 3; | |||
} | |||
} |