Browse Source

initial commit

master
tilo 10 months ago
commit
807034a634
8 changed files with 740 additions and 0 deletions
  1. 29
    0
      .gitignore
  2. 85
    0
      src/Bot.java
  3. 140
    0
      src/CollectBot.java
  4. 29
    0
      src/DummyBot.java
  5. 67
    0
      src/EscapeBot.java
  6. 163
    0
      src/RumbleBot.java
  7. 28
    0
      src/SmartBot.java
  8. 199
    0
      src/SnakeBot.java

+ 29
- 0
.gitignore 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
- 0
src/Bot.java 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
- 0
src/CollectBot.java 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
- 0
src/DummyBot.java 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
- 0
src/EscapeBot.java 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
- 0
src/RumbleBot.java 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
- 0
src/SmartBot.java 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
- 0
src/SnakeBot.java 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;
}
}

Loading…
Cancel
Save