Agent.java (7504B)
1 import java.io.*; 2 import java.net.*; 3 import java.nio.*; 4 import java.util.*; 5 6 import hex.*; 7 8 public class Agent { 9 private static Net net; 10 private static State state; 11 private static Board board; 12 private static HexPlayer player, opponent, winner; 13 private static int gameSecs, threadLimit, memLimitMib; // unused 14 15 public static void main(String[] args) { 16 if (args.length < 2) { 17 System.err.println("Not enough args: <host> <port>"); 18 return; 19 } 20 21 String host = args[0]; 22 int port = Integer.parseInt(args[1]); 23 24 try { 25 net = new Net(host, port); 26 } catch (IOException ex) { 27 System.err.println("Failed to initialise network"); 28 return; 29 } 30 31 state = State.START; 32 33 boolean gameOver = false, firstRound = true; 34 while (!gameOver) { 35 switch (state) { 36 case START -> { 37 Optional<NetMessage> msg = net.recvMsg(); 38 39 if (!msg.isPresent()) { 40 System.err.println("Failed to receive message from hex server"); 41 return; 42 } 43 44 if (msg.get() instanceof StartMessage start) { 45 player = start.player(); 46 47 gameSecs = start.gameSecs(); 48 threadLimit = start.threadLimit(); 49 memLimitMib = start.memLimitMib(); 50 51 board = new Board(start.boardSize()); 52 53 switch (player) { 54 case BLACK -> { opponent = HexPlayer.WHITE; state = State.SEND; } 55 case WHITE -> { opponent = HexPlayer.BLACK; state = State.RECV; } 56 } 57 } else { 58 System.err.println("Invalid message received from server"); 59 return; 60 } 61 } 62 63 case RECV -> { 64 Optional<NetMessage> msg = net.recvMsg(); 65 66 if (!msg.isPresent()) { 67 System.err.println("Failed to receive message from hex server"); 68 return; 69 } 70 71 if (msg.get() instanceof MoveMessage move) { 72 board.play(opponent, move.boardX(), move.boardY()); 73 74 if (firstRound && new Random().nextBoolean()) { 75 board.swap(); 76 77 if (!net.sendMsg(new SwapMessage())) { 78 System.err.println("Failed to send message to hex server"); 79 return; 80 } 81 82 state = state.RECV; 83 } else { 84 state = state.SEND; 85 } 86 } else if (msg.get() instanceof SwapMessage swap) { 87 board.swap(); 88 state = State.SEND; 89 } else if (msg.get() instanceof EndMessage end) { 90 winner = end.winner(); 91 state = State.END; 92 } else { 93 System.err.println("Invalid message received from server"); 94 return; 95 } 96 97 firstRound = false; 98 } 99 100 case SEND -> { 101 Optional<Move> nextMove = board.next(); 102 103 if (!nextMove.isPresent()) { 104 System.err.println("Failed to generate next board move"); 105 return; 106 } 107 108 Move move = nextMove.get(); 109 110 board.play(player, move.x(), move.y()); 111 112 if (!net.sendMsg(new MoveMessage(move.x(), move.y()))) { 113 System.err.println("Failed to send message to hex server"); 114 return; 115 } 116 117 state = State.RECV; 118 firstRound = false; 119 } 120 121 case END -> { 122 gameOver = true; 123 } 124 } 125 } 126 127 return; 128 } 129 } 130 131 enum State { 132 START, RECV, SEND, END, 133 } 134 135 record Move(int x, int y) {} 136 137 enum Cell { 138 BLACK(HexPlayer.BLACK.value), 139 WHITE(HexPlayer.WHITE.value), 140 EMPTY(2); 141 142 public final int value; 143 144 private Cell(int value) { 145 this.value = value; 146 } 147 } 148 149 class Board { 150 private final int size; 151 private final ArrayList<Cell> cells; 152 private final ArrayList<Move> moves; 153 154 public Board(int size) { 155 this.size = size; 156 this.cells = new ArrayList<>(size * size); 157 this.moves = new ArrayList<>(size * size); 158 159 for (int j = 0; j < size; j++) { 160 for (int i = 0; i < size; i++) { 161 this.cells.add(Cell.EMPTY); 162 this.moves.add(new Move(i, j)); 163 } 164 } 165 166 Collections.shuffle(this.moves); 167 } 168 169 public boolean play(HexPlayer player, int x, int y) { 170 int idx = y * this.size + x; 171 172 Cell cell = this.cells.get(idx); 173 if (cell != Cell.EMPTY) return false; 174 175 switch (player) { 176 case BLACK -> this.cells.set(idx, Cell.BLACK); 177 case WHITE -> this.cells.set(idx, Cell.WHITE); 178 } 179 180 this.moves.remove(new Move(x, y)); 181 182 return true; 183 } 184 185 public void swap() { 186 this.moves.clear(); 187 188 for (int j = 0; j < this.size; j++) { 189 for (int i = 0; i < this.size; i++) { 190 int idx = j * this.size + i; 191 192 Cell cell = this.cells.get(idx); 193 194 switch (cell) { 195 case BLACK -> this.cells.set(idx, Cell.WHITE); 196 case WHITE -> this.cells.set(idx, Cell.BLACK); 197 case EMPTY -> this.moves.add(new Move(i, j)); 198 } 199 } 200 } 201 202 Collections.shuffle(this.moves); 203 } 204 205 public Optional<Move> next() { 206 if (this.moves.isEmpty()) return Optional.empty(); 207 208 return Optional.of(this.moves.remove(this.moves.size() - 1)); 209 } 210 } 211 212 sealed interface NetMessage {} 213 214 record StartMessage(HexPlayer player, int boardSize, int gameSecs, int threadLimit, int memLimitMib) implements NetMessage {} 215 record MoveMessage(int boardX, int boardY) implements NetMessage {} 216 record SwapMessage() implements NetMessage {} 217 record EndMessage(HexPlayer winner) implements NetMessage {} 218 219 class Net { 220 private final Socket sock; 221 private final OutputStream out; 222 private final InputStream in; 223 224 public static final int MESSAGE_SIZE = 32; 225 226 public Net(String host, int port) throws IOException { 227 this.sock = new Socket(host, port); 228 229 this.out = this.sock.getOutputStream(); 230 this.in = this.sock.getInputStream(); 231 } 232 233 public Optional<NetMessage> recvMsg() { 234 byte[] buf = new byte[MESSAGE_SIZE]; 235 236 int nbytes_recv = 0; 237 238 do { 239 int curr; 240 try { 241 curr = this.in.read(buf, nbytes_recv, buf.length - nbytes_recv); 242 } catch (IOException ex) { 243 return Optional.empty(); 244 } 245 246 if (curr <= 0) return Optional.empty(); 247 nbytes_recv += curr; 248 } while (nbytes_recv < buf.length); 249 250 return deserialiseMsg(ByteBuffer.wrap(buf)); 251 } 252 253 public boolean sendMsg(NetMessage msg) { 254 byte[] buf = new byte[MESSAGE_SIZE]; 255 256 serialiseMsg(msg, ByteBuffer.wrap(buf)); 257 258 try { 259 this.out.write(buf); 260 this.out.flush(); 261 } catch (IOException ex) { 262 return false; 263 } 264 265 return true; 266 } 267 268 private static void serialiseMsg(NetMessage msg, ByteBuffer buf) { 269 buf.order(ByteOrder.BIG_ENDIAN); 270 271 if (msg instanceof StartMessage start) { 272 buf.putInt(HexMessageType.START.value); 273 buf.putInt(start.player().value); 274 buf.putInt(start.boardSize()); 275 buf.putInt(start.gameSecs()); 276 buf.putInt(start.threadLimit()); 277 buf.putInt(start.memLimitMib()); 278 } else if (msg instanceof MoveMessage move) { 279 buf.putInt(HexMessageType.MOVE.value); 280 buf.putInt(move.boardX()); 281 buf.putInt(move.boardY()); 282 } else if (msg instanceof SwapMessage swap) { 283 buf.putInt(HexMessageType.SWAP.value); 284 } else if (msg instanceof EndMessage end) { 285 buf.putInt(HexMessageType.END.value); 286 buf.putInt(end.winner().value); 287 } 288 } 289 290 private static Optional<NetMessage> deserialiseMsg(ByteBuffer buf) { 291 buf.order(ByteOrder.BIG_ENDIAN); 292 293 int type = buf.getInt(); 294 295 if (type == HexMessageType.START.value) { 296 HexPlayer player = HexPlayer.fromRaw(buf.getInt()); 297 int boardSize = buf.getInt(); 298 int gameSecs = buf.getInt(); 299 int threadLimit = buf.getInt(); 300 int memLimitMib = buf.getInt(); 301 return Optional.of(new StartMessage(player, boardSize, gameSecs, threadLimit, memLimitMib)); 302 } else if (type == HexMessageType.MOVE.value) { 303 int boardX = buf.getInt(); 304 int boardY = buf.getInt(); 305 return Optional.of(new MoveMessage(boardX, boardY)); 306 } else if (type == HexMessageType.SWAP.value) { 307 return Optional.of(new SwapMessage()); 308 } else if (type == HexMessageType.END.value) { 309 HexPlayer winner = HexPlayer.fromRaw(buf.getInt()); 310 return Optional.of(new EndMessage(winner)); 311 } else { 312 return Optional.empty(); 313 } 314 } 315 }