From 807034a634a7eaa355df12a67b8d2c67fdc3fa8a Mon Sep 17 00:00:00 2001 From: tilo Date: Wed, 7 Feb 2024 14:20:55 +0100 Subject: [PATCH] initial commit --- .gitignore | 29 +++++++ src/Bot.java | 85 +++++++++++++++++++ src/CollectBot.java | 140 +++++++++++++++++++++++++++++++ src/DummyBot.java | 29 +++++++ src/EscapeBot.java | 67 +++++++++++++++ src/RumbleBot.java | 163 ++++++++++++++++++++++++++++++++++++ src/SmartBot.java | 28 +++++++ src/SnakeBot.java | 199 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 740 insertions(+) create mode 100644 .gitignore create mode 100644 src/Bot.java create mode 100644 src/CollectBot.java create mode 100644 src/DummyBot.java create mode 100644 src/EscapeBot.java create mode 100644 src/RumbleBot.java create mode 100644 src/SmartBot.java create mode 100644 src/SnakeBot.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f68d109 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/src/Bot.java b/src/Bot.java new file mode 100644 index 0000000..110fe85 --- /dev/null +++ b/src/Bot.java @@ -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)); + } + } + } +} diff --git a/src/CollectBot.java b/src/CollectBot.java new file mode 100644 index 0000000..8ebcb96 --- /dev/null +++ b/src/CollectBot.java @@ -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 moves; + + protected CollectBot(String[] args) { + super(args); + tilesMoved = 0; + directionBias = LEFT; + moves = new Stack(); + } + + 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 moves = (Stack) 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; + } +} diff --git a/src/DummyBot.java b/src/DummyBot.java new file mode 100644 index 0000000..447e93b --- /dev/null +++ b/src/DummyBot.java @@ -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; + }; + } + +} diff --git a/src/EscapeBot.java b/src/EscapeBot.java new file mode 100644 index 0000000..3fc1ca2 --- /dev/null +++ b/src/EscapeBot.java @@ -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'; + } +} diff --git a/src/RumbleBot.java b/src/RumbleBot.java new file mode 100644 index 0000000..3187763 --- /dev/null +++ b/src/RumbleBot.java @@ -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; + } + + } + +} diff --git a/src/SmartBot.java b/src/SmartBot.java new file mode 100644 index 0000000..014c0f5 --- /dev/null +++ b/src/SmartBot.java @@ -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 moves; + + + protected SmartBot(String[] args) { + super(args); + this.tilesMoved = 0; + this.moves = new Stack<>(); + } + + @Override + protected char nextMove(View view) throws Exception { + return 0; + } + + +} diff --git a/src/SnakeBot.java b/src/SnakeBot.java new file mode 100644 index 0000000..bc19b17 --- /dev/null +++ b/src/SnakeBot.java @@ -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 moves; + + protected SnakeBot(String[] args) { + super(args); + tilesMoved = 0; + tailLength = 0; + escapeSequence = 0; + directionBias = LEFT; + moves = new Stack(); + } + + 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 moves = (Stack) 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; + } +}