Skip to content
Snippets Groups Projects
Commit 56bacb63 authored by Adrian K.'s avatar Adrian K.
Browse files

Schatten-Bot

parent c8c36c0e
No related branches found
No related tags found
No related merge requests found
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
......@@ -8,7 +7,7 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_20" project-jdk-name="temurin-20" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_19" project-jdk-name="temurin-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
\ No newline at end of file
package de.fossag.hackatron;
public class AlgConfig {
public static final int maxDepth = 2; // How far we look into the future
public static final int dfsBestScore = 23; // Which score is considered "best" to stop searching early
}
package de.fossag.hackatron;
import java.util.*;
public class GameEngine {
public enum Move {
Up("up"), Down("down"), Left("left"), Right("right");
private final String name;
Move(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
public static Move generateNextMove(GameState gameState) {
ArrayList<Map.Entry<Move, Integer>> bestMoves = new ArrayList<>(AlgConfig.maxDepth);
double[] weightedScores = new double[AlgConfig.maxDepth];
for (int i = AlgConfig.maxDepth-1; i >= 0; i--) {
Map.Entry<Move, Integer> currentMove = dfs(gameState.ownX, gameState.ownY, gameState.getMap(), 0);
bestMoves.add(0, currentMove);
weightedScores[i] = currentMove.getValue()/(Math.min(Math.pow(2,i+1),AlgConfig.dfsBestScore));
gameState.cleanupMap(-i-2);
}
int highestScoreIndex = weightedScores.length-1;
for (int i = weightedScores.length - 2; i >= 0; i--) {
if (weightedScores[i] > weightedScores[highestScoreIndex]) {
highestScoreIndex = i;
}
}
return bestMoves.get(highestScoreIndex).getKey();
}
public static Map.Entry<Move, Integer> dfs(int x, int y, Integer[][] map, int depth) {
int bestMoveScore = 0;
Move bestMove = Move.Up;
Move[] moves = Move.values();
if(depth == 0) {
List<Move> listOfMoves = Arrays.asList(moves);
Collections.shuffle(listOfMoves);
moves = listOfMoves.toArray(new Move[0]);
}
for (Move m : moves) {
int[] nextPos = GameUtils.getNextPosition(map, x, y, m);
if (map[nextPos[0]][nextPos[1]] != null) continue;
map[nextPos[0]][nextPos[1]] = -1;
int score = dfs(nextPos[0], nextPos[1], map, depth+1).getValue() + 1;
map[nextPos[0]][nextPos[1]] = null;
if (score > bestMoveScore) {
if (score > AlgConfig.dfsBestScore) {
return Map.entry(m, score);
}
bestMoveScore = score;
bestMove = m;
}
}
return Map.entry(bestMove, bestMoveScore);
}
}
package de.fossag.hackatron;
import java.util.HashMap;
public class GameState {
public int ownID, ownX, ownY;
public GameEngine.Move lastMove = GameEngine.Move.Up;
private int mapWidth, mapHeight;
private final Integer[][] map; //[width][height]
private final HashMap<Integer, String> playerNames;
public GameState(int mapWidth, int mapHeight, int ownID) {
playerNames = new HashMap<>();
this.ownID = ownID;
map = new Integer[mapWidth][mapHeight];
this.mapWidth = mapWidth;
this.mapHeight = mapHeight;
}
public Integer[][] getMap() {
return map;
}
public void cleanupMap(int untilPriority) {
for (int x = 0; x < mapWidth; x++) {
for (int y = 0; y < mapHeight; y++) {
if (map[x][y] != null && map[x][y] <= untilPriority) map[x][y] = null;
}
}
}
public void addPos(int id, int x, int y) {
if (x < 0 | y < 0 | x >= mapWidth || y >= mapHeight) {
throw new UnsupportedOperationException("Position: (" + x + "," + y + ") out of bounds");
}
map[x][y] = id;
if (id == ownID) return;
addLookaheadPosRec(x, y, 1);
}
public void addLookaheadPosRec(int x, int y, int depth) {
if (depth >= AlgConfig.maxDepth) return;
for (GameEngine.Move m : GameEngine.Move.values()) {
int[] nextPos = GameUtils.getNextPosition(getMap(), x, y, m);
int newX = nextPos[0];
int newY = nextPos[1];
Integer currentCell = map[newX][newY];
if (currentCell == null || currentCell < -1 - depth) {
map[newX][newY] = -1 - depth;
addLookaheadPosRec(newX, newY, depth + 1);
}
}
}
public void setPlayerInfo(int playerId, String name) {
playerNames.put(playerId, name);
}
public void printMap() {
System.out.println("------------");
for (int y = 0; y < mapHeight; y++) {
System.out.print("|");
for (int x = 0; x < mapWidth; x++) {
Integer currentPixel = map[x][y];
if(currentPixel == null) {
System.out.print(" ");
} else if(currentPixel == ownID) {
System.out.print("X");
} else {
System.out.print((char) (map[x][y] + 64+1));
}
}
System.out.println("|");
}
System.out.println("------------");
}
public void playerDies(int playerId) {
playerNames.remove(playerId);
for (int i = 0; i < mapWidth; i++) {
for (int j = 0; j < mapHeight; j++) {
Integer val = map[i][j];
if (val != null && val == playerId) {
map[i][j] = null;
}
}
}
}
}
package de.fossag.hackatron;
public class GameUtils {
public static int[] getNextPosition(Integer[][] map, int x, int y, GameEngine.Move m) {
int mapWidth = map.length;
int mapHeight = map[0].length;
int newX = x;
int newY = y;
switch (m) {
case Up -> newY = (y - 1 + mapHeight) % mapHeight;
case Left -> newX = (x - 1 + mapWidth) % mapWidth;
case Right -> newX = (x + 1) % mapWidth;
case Down -> newY = (y + 1) % mapHeight;
}
return new int[]{newX, newY};
}
}
......@@ -2,30 +2,81 @@ package de.fossag.hackatron;
public class HackatronClient implements IHackatronClient {
private IMessageSender messageSender;
private static final String CLIENT_NAME = "dummyclient";
private static final String CLIENT_SECRET = "changeme";
private IMessageSender messageSender;
private static final String CLIENT_NAME = "Schatten";
private static final String CLIENT_SECRET = "ThisIsASuperSecretClientSecretToMakeSureNoOneStealsOurClient";
private int losses = 0;
private int wins = 0;
@Override
public void setMessageSender(IMessageSender messageSender) {
this.messageSender = messageSender;
}
GameState game;
@Override
public void onMessage(String message) {
String[] parts = message.split("\\|");
String messageType = parts[0];
@Override
public void setMessageSender(IMessageSender messageSender) {
this.messageSender = messageSender;
}
long lastTick = System.nanoTime();
int curTick = 0;
@Override
public void onMessage(String message) {
String[] parts = message.split("\\|");
String messageType = parts[0];
switch (messageType) {
case "motd":
messageSender.send("join|" + CLIENT_NAME + "|" + CLIENT_SECRET);
break;
case "tick":
messageSender.send("move|up");
break;
default:
System.out.println("Unknown message type :(");
System.exit(1);
switch (messageType) {
case "motd":
messageSender.send("join|" + CLIENT_NAME + "|" + CLIENT_SECRET);
break;
case "tick":
long timeStart = System.nanoTime();
game.lastMove = GameEngine.generateNextMove(game);
long timeEnd = System.nanoTime();
//System.out.println("Time spent: " + (timeEnd - timeStart) / 1_000_000.0 + "ms, going " + game.lastMove);
messageSender.send("move|" + game.lastMove);
game.cleanupMap(-2);
curTick++;
//System.out.println("Last tick time: "+(timeStart-lastTick)/1_000_000.0);
//System.out.println("Expected Tick: "+(1/(Math.ceil(curTick/10.d))));
lastTick = timeStart;
//if(game != null) game.printMap();
break;
case "game":
if (wins + losses > 0)
System.out.printf("Starting a new game, current stats: %d-%d, %d\n", wins, losses, wins / (wins + losses));
game = new GameState(Integer.parseInt(parts[1]), Integer.parseInt(parts[2]), Integer.parseInt(parts[3]));
curTick = 0;
break;
case "pos":
int id = Integer.parseInt(parts[1]);
int x = Integer.parseInt(parts[2]);
int y = Integer.parseInt(parts[3]);
game.addPos(id, x, y);
if (id == game.ownID) {
game.ownX = x;
game.ownY = y;
}
break;
case "player":
game.setPlayerInfo(Integer.parseInt(parts[1]), parts[2]);
break;
case "die":
System.out.println(parts[1] + " has died");
game.playerDies(Integer.parseInt(parts[1]));
break;
case "lose":
System.out.printf("LOST! Died at %d|%d, last move: %s\n", game.ownX, game.ownY, game.lastMove);
game.printMap();
losses++;
break;
case "win":
System.out.println("WON!");
wins++;
break;
case "error":
System.out.println("SERVER ERROR: " + parts[1]);
break;
default:
System.out.println("Unknown message type :(");
System.exit(1);
}
}
}
}
......@@ -14,46 +14,44 @@ import java.net.Socket;
*/
public class HackatronWrapper {
private String host;
private int port;
private IHackatronClient hackatronClient;
public HackatronWrapper(String host, int port, IHackatronClient hackatronClient) {
this.host = host;
this.port = port;
this.hackatronClient = hackatronClient;
}
public void run() throws IOException {
System.out.println("Connecting to " + host + ":" + port);
// Set up socket and wire up streams
Socket socket = new Socket(host, port);
OutputStream out = socket.getOutputStream();
PrintWriter writer = new PrintWriter(out, true);
InputStream in = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
System.out.println("Connection successful");
// The message sender logs the outgoing message and stuffs it into the output stream
hackatronClient.setMessageSender(s -> {
System.out.println("OUT MSG: " + s);
writer.println(s);
});
while (true) {
if (socket.isClosed()) {
return;
}
String line = reader.readLine();
if (line == null) {
return;
}
System.out.println("IN MSG: " + line);
hackatronClient.onMessage(line);
private String host;
private int port;
private IHackatronClient hackatronClient;
public HackatronWrapper(String host, int port, IHackatronClient hackatronClient) {
this.host = host;
this.port = port;
this.hackatronClient = hackatronClient;
}
}
public void run() throws IOException {
System.out.println("Connecting to " + host + ":" + port);
// Set up socket and wire up streams
Socket socket = new Socket(host, port);
OutputStream out = socket.getOutputStream();
PrintWriter writer = new PrintWriter(out, true);
InputStream in = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
System.out.println("Connection successful");
// The message sender logs the outgoing message and stuffs it into the output stream
hackatronClient.setMessageSender(s -> {
writer.println(s);
});
while (true) {
if (socket.isClosed()) {
return;
}
String line = reader.readLine();
if (line == null) {
return;
}
hackatronClient.onMessage(line);
}
}
}
......@@ -2,20 +2,20 @@ package de.fossag.hackatron;
public interface IHackatronClient {
/**
* Gets called one time after instance creation to set the {@link IMessageSender} instance. You
* can use that to send reply messages back to the game server.
*
* @param messageSender IMessageSender instance
*/
void setMessageSender(IMessageSender messageSender);
/**
* Gets called one time after instance creation to set the {@link IMessageSender} instance. You
* can use that to send reply messages back to the game server.
*
* @param messageSender IMessageSender instance
*/
void setMessageSender(IMessageSender messageSender);
/**
* Gets called every time a new message from the game server arrives.
*
* See PROTOCOL.md for details
*
* @param message Message string
*/
void onMessage(String message);
/**
* Gets called every time a new message from the game server arrives.
* <p>
* See PROTOCOL.md for details
*
* @param message Message string
*/
void onMessage(String message);
}
......@@ -2,5 +2,5 @@ package de.fossag.hackatron;
public interface IMessageSender {
void send(String message);
void send(String message);
}
......@@ -4,11 +4,11 @@ import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
HackatronWrapper client = new HackatronWrapper(
"game.hackatron.de",
4000,
new HackatronClient());
client.run();
}
public static void main(String[] args) throws IOException {
HackatronWrapper client = new HackatronWrapper(
"game.hackatron.de",
4000,
new HackatronClient());
client.run();
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment