hex

hex.git
git clone git://git.lenczewski.org/hex.git
Log | Files | Refs

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 }