README.txt (10999B)
1 hex-server: A Simple 'Hex' Server 2 ============================================================================== 3 A simple 'Hex' game server, for running 1 round of a tournament between 2 4 player agents. Supports the PIE rule. Includes example agents written in C, 5 C++, Java, and Python3. 6 7 To run tournaments, there exists a helper script written for Python3.11 (see 8 `tournament-host.py`), which takes a comma-delimited agent-pair tournament 9 schedule, runs the tournament, and collects the resulting statistics in the 10 given output file. 11 12 NOTE: currently, the Java agent (written for Java 17) requires ~16 threads to 13 load a jar and execute, thanks to the JVM requirements. since the default 14 thread limit is 4, this agent cannot be used without increasing the limit to 15 16 16 17 hex-server: Building 18 ------------------------------------------------------------------------------ 19 To build the server and all agents, run the following shell command in the 20 project's root directory: 21 22 ```sh 23 $ ./build.sh 24 $ WERROR=1 ./build.sh # alternatively, to build all binaries with -Werror 25 ``` 26 27 To clean all built artefacts, run the following shell command: 28 29 ```sh 30 $ ./clean.sh 31 ``` 32 33 Before running the hex-server, or the tournament-host.py helper script, a 34 number of hex agent runner users must be added to the system. This is done 35 to enable hard limits on the number of threads and memory any given agent is 36 able to use (avoiding starving out other agents, or killing the server or 37 opponent agent). To install these users, run the following shell command: 38 39 ```sh 40 $ sudo ./install # this must be ran as root, to be able to add users 41 ``` 42 43 To remove the previously added users, run the followind shell command: 44 45 ```sh 46 $ sudo ./uninstall # must be ran as root, to remove added users 47 ``` 48 49 hex-server: Usage 50 ------------------------------------------------------------------------------ 51 To run a tournament, you can use the included `tournament-host.py` script. To 52 log debug output from the server, ensure that you pass the `-v` flag. The 53 scripts usage is as follows: 54 55 ```sh 56 $ sudo ./tournament-host.py [-hv] <tournament-schedule.txt> <output.log> \ 57 [-d <board-dimensions>] [-s <time-limit-secs>] \ 58 [-t <agent-thread-limit>] [-m <agent-mem-limit-mib>] \ 59 [--concurrent-matches {1,2,3,4,5,6,7,8}] 60 ``` 61 62 NOTE: the `output.log` file will be created if it does not exist. Otherwise, it 63 will be overwritten 64 65 NOTE: the tournament host MUST be ran as root (or by a user with the Linux 66 CAP_SETUID capability), due to the hex game server using setuid() to enforce 67 process resource limits. The tournament host will by default use all users 68 with usernames of the form `hex-agent-$id`, where `$id` is an integer, in a 69 round-robin fashion. To generate these users, please run the `install.sh` 70 script as root (or via `sudo`). 71 72 During initial agent development, it may be desirable to simply start the 73 server with two known agents. For this, the `test-server.sh` script can be 74 used as follows: 75 76 ```sh 77 $ sudo ./test-server agents/hexes/run-random.sh <your-agent-cmd> 78 ``` 79 80 NOTE: instead of using the existing `agents/hexes/run-random.sh` agent, you can 81 of course substitute any other agent run command you would like. For early 82 development, `agents/hexes/run-random.sh` is recommended to ensure your agent 83 successfully connects to the server and can respond to random moves, but for 84 developing stronger play or benchmarking your agent, you might want to 85 use `agents/hexes/run.sh` to play against a MCTS agent instead. 86 87 NOTE: the `test-server.sh` script must be ran as root (or by a user with the 88 Linux CAP_SETUID capability) to allow it to use setuid() for process resource 89 limiting. 90 91 To directly invoke the server, use the following command: 92 93 ```sh 94 $ sudo ./server/bin/hex-server -a <agent-1> -ua <uid> -b <agent-2> -ub <uid> \ 95 [-d <board-dimensions>] [-s <time-limit-secs>] \ 96 [-t <agent-thread-limit>] [-m <agent-mem-limit-mib>] [-v] 97 ``` 98 99 NOTE: The server MUST be ran as root (i.e. as a privileged process), or by a 100 user with the Linux CAP_SETUID capability. This is due to the use of setuid() 101 to set the user id for the agents, and thus maintain process limits which work 102 based on the (effective) user id of a given process. Note that despite the 103 server running as root, the user agents run as the given users (via -ua/-ub). 104 105 Server Options: 106 +-----+-----------------------------------------------+-----------+-----------+ 107 | Opt | Description | Optional | Default | 108 +-----+-----------------------------------------------+-----------+-----------+ 109 | -a | The first agent (black) | Required | N/A | 110 | -ua | The uid to set for the first agent (black) | Required | N/A | 111 | -b | The second agent (white) | Required | N/A | 112 | -ub | The uid to set for the second agent (white) | Required | N/A | 113 | -d | Board dimensions | Optional | 11 | 114 | -s | Per-Agent game timer (seconds) | Optional | 300 | 115 | -t | Per-Agent thread hard-limit | Optional | 4 | 116 | -m | Per-Agent memory hard-limit (MiB) | Optional | 1024 | 117 | -v | Verbose output | Optional | N/A | 118 +-----+-----------------------------------------------+-----------+-----------+ 119 120 Each agent will be invoked using the following shell command: 121 122 ```sh 123 <agent-string> <server-host> <server-port> 124 ``` 125 126 For maximum flexibility and ease of use, it is recommended to write a wrapper 127 shell script to be passed as the `agent-string`, to allow for a more 128 specialised run command structure (e.g. using a compiled agent with 129 differently named options, or having an interpreted agent and having to pass 130 the agent source to the interpreter). 131 132 An example of such a wrapper script is as follows: 133 134 ```sh 135 #!/bin/sh 136 137 exec /usr/bin/env python3 $(dirname $0)/my_agent.py $@ # forward all args 138 ``` 139 140 Writing such a wrapper shell script also means that agent-specific commandline 141 options (e.g. a verbose logging mode, an optimised "release" mode or 142 unoptimised "debug" mode, or passing the server host and port via named 143 commandline arguments instead of as positional ones) can be implemented, 144 without any special handling from the server. 145 146 For examples of both compiled and interpreted agents, as well as for an 147 example of the wrapper scripts used to invoke the agents, please see the 148 `run.sh` wrapper scripts in the example agent directories (under `agents/`). 149 150 NOTE: the wrapper script, if used, must be made executable. This can be done 151 using the following shell command (replacing `/tmp/my_agent/` with your 152 specific agent's directory): 153 154 ```sh 155 $ chmod +x /tmp/my_agent/my_wrapper_script.sh 156 ``` 157 158 hex-server: Protocol 159 ------------------------------------------------------------------------------ 160 The server uses a simple binary protocol for all messages between the server 161 and individual agents, and will communicate between itself and an agent using 162 a socket. 163 164 Server Flow 165 1) Create processes for both agents (setting process limits) 166 2) accept() both agents (within a timeout) 167 3) send() a MSG_START to both agents 168 4) recv() a MSG_MOVE (or MSG_SWAP on round 1 as white only) 169 5) Make said move and test the board for a winner 170 a) If there is a winner, goto 7) 171 b) Otherwise, goto 4) 172 6) send() the received message to the other agent, goto 4) 173 7) send() a MSG_END to both agents 174 175 NOTE: if at any point in this flow an agent sends a malformed message, plays 176 an invalid move (e.g. attempts to move out-of-bounds, swaps except as player 2 177 on the first turn, moves onto a spot with an existing piece), or causes the 178 socket connection to close, the game is over and the other agent wins by 179 default. 180 181 Agent Flow 182 1) connect() to the server given by the commandline args (host/port) 183 2) recv() a MSG_START from the server 184 a) If playing as black (agent 1), goto 3) 185 b) If playing as white (agent 2), goto 4) 186 3) send() a MSG_MOVE (or MSG_SWAP on round 1 as white only) 187 4) recv() a MSG_MOVE, MSG_SWAP, or MSG_END 188 a) If received MSG_MOVE or MSG_SWAP, update internal state, goto 3) 189 b) If received MSG_END, goto 5) 190 5) close() connection to server 191 192 hex-server: Protocol Wire Format 193 ------------------------------------------------------------------------------ 194 The wire format consists of a fixed 32-byte packet, with a simple 195 (type:u32,params:u32[]) packet structure. 196 197 The wire format if oriented around 32-bit unsigned words for simplicity, and 198 values for the packet type and all parameters, will all be of this type. 199 200 NOTE: for implementations, this boils down into a single recv() or send() of 201 32 bytes, followed by parsing the received packet based on the `type`, 202 extracting all the required parameters. Agents should also make sure to follow 203 this exact packet structure when sending messages, as otherwise they will be 204 seen by the server as having sent malformed messages and the server will 205 consider this a forfeit. Please see the example agents under `agents/` for 206 specific example implementations. 207 208 hex-server: Protocol Messages 209 ------------------------------------------------------------------------------ 210 Below is a listing of the messages in the protocol, their IDs and parameters, 211 and the relationships between the server and 2 agents. 212 213 Protocol Messages: 214 +-------+-----------+---------------------------------------------------------+ 215 | ID | Name | Params | 216 +-------+-----------+---------------------------------------------------------+ 217 | 0 | MSG_START | player:u32, board_size:u32, game_secs:u32 | 218 | | | thread_limit:u32, mem_limit_mib:u32 | 219 +-------+-----------+---------------------------------------------------------+ 220 | 1 | MSG_MOVE | board_x:u32, board_y:u32 | 221 +-------+-----------+---------------------------------------------------------+ 222 | 2 | MSG_SWAP | N/A | 223 +-------+-----------+---------------------------------------------------------+ 224 | 3 | MSG_END | winner:u32 | 225 +-------+-----------+---------------------------------------------------------+ 226 227 An example of this protocol defined in a C-like language is as follows: 228 229 ```c 230 enum player_type : u32 { 231 PLAYER_BLACK = 0, 232 PLAYER_WHITE = 1, 233 }; 234 235 enum msg_type : u32 { 236 MSG_START = 0, 237 MSG_MOVE = 1, 238 MSG_SWAP = 2, 239 MSG_END = 3, 240 }; 241 242 union msg_data { 243 struct { 244 enum player_type player; 245 u32 board_size; 246 u32 game_secs; 247 u32 thread_limit; 248 u32 mem_limit_mib; // NOTE: in units of MiB 249 } start; 250 251 struct { 252 u32 board_x; 253 u32 board_y; 254 } move; 255 256 /* struct { } swap; */ // NOTE: swap has no parameters 257 258 struct { 259 enum player_type winner; 260 } end; 261 }; 262 263 struct msg { 264 enum msg_type type; 265 union msg_data data; 266 }; 267 ```