initial commit

This commit is contained in:
tilo 2024-02-07 14:20:55 +01:00
commit 807034a634
8 changed files with 740 additions and 0 deletions

29
.gitignore vendored Normal file
View File

@ -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

85
src/Bot.java Normal file
View File

@ -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));
}
}
}
}

140
src/CollectBot.java Normal file
View File

@ -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;
}
}

29
src/DummyBot.java Normal file
View File

@ -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;
};
}
}

67
src/EscapeBot.java Normal file
View File

@ -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';
}
}

163
src/RumbleBot.java Normal file
View File

@ -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;
}
}
}

28
src/SmartBot.java Normal file
View File

@ -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;
}
}

199
src/SnakeBot.java Normal file
View File

@ -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;
}
}