### 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 |
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)); | |||||
} | |||||
} | |||||
} | |||||
} |
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; | |||||
} | |||||
} |
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; | |||||
}; | |||||
} | |||||
} |
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'; | |||||
} | |||||
} |
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; | |||||
} | |||||
} | |||||
} |
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; | |||||
} | |||||
} |
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; | |||||
} | |||||
} |