hex

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

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 ```