c-gomoku-cli is a command line interface for gomoku/renju engines that support Gomocup protocol. It is a derived project from c-chess-cli, originally authored by Lucas Braesch (lucasart).
Different from computer chess community where a lot of interface tools have been developed in the passed 20 years, computer gomoku/renju is still lack of tools for engine-vs-engine matches. Most of the existing tools are GUI based, which is not suitable for distributed systems like high performance clusters. The motivation of c-gomoku-cli is to provide an open-source command line tool that can run large scale engine-vs-engine matches on multi-core workstations or cluster systems in parallel. The large-scale engine-vs-engine testing is common approach for quantifying the strength improvements of computer game engines, like chess. With this tool, engine designers can concisely measure the new engine ELO gain by letting it play with the old version for enough number of games.
Instead of sticking on the C language to avoid the dependency hell in c-chess-cli, C++ is chosen to be the primary programming language in this project, because my target is to build a usable tool and C++ contains some features that can bring more convenience to achieving this target. Similar to c-chess-cli, c-gomoku-cli is primarily developed for Unix/Linux, it should work on all POSIX operating systems, including MacOS and Android. Windows support is added by Haobin (dhbloo).
Under the root directory of project, run cd src
, then run make
.
c-gomoku-cli [-each [eng_options]] -engine [eng_options] -engine [eng_options] ... [options]
c-gomoku-cli -each tc=180/30 \
-engine name=Wine cmd=./example-engine/pbrain-wine \
-engine name=Rapfi cmd=./example-engine/pbrain-rapfi1 \
-rule 0 -boardsize 20 -rounds 2 -games 4000 -debug -repeat \
-concurrency 8 -drawafter 200 \
-pgn my_games.pgn -sgf my_games.sgf -msg my_games.txt \
-openings file=opening_examples/offset_freestyle_20x20.txt order=random
engine OPTIONS
: Add an engine defined byOPTIONS
to the tournament.each OPTIONS
: ApplyOPTIONS
to each engine in the tournament.concurrency N
: Set the maximum number of concurrent games to N (default value 1).drawafter N
: Adjudicate the game as a draw, if the number of moves in one game reachesN
ply.N
must be greater then0
to be effective.rule RULE
: Set the game rule with Gomocup rule codeRULE
.RULE=0
: Play with gomoku rule and winner wins by five or longer connection.RULE=1
: Play with gomoku rule but winner only wins by exact-5 connection (longer connections will be ignored).RULE=4
: Play with renju rule.- No other rule code is acceptable.
boardsize SIZE
: Set the board size toSIZE
XSIZE
. ValidSIZE
range is[5..22]
.games N
: Play N games per encounter (default value 1). This value should be set to an even number in tournaments with more than two players to make sure that each player plays an equal number of games with black and white colors.rounds N
: Multiply the number of rounds to play byN
(default value 1). This only makes sense to use for tournaments with more than 2 engines.gauntlet
: Play a gauntlet tournament (first engine against the others). The default is to play a round-robin (plays all pairs).- with
n=2
engines, both gauntlet and round-robin just play the number of-games
specified. - gauntlet for
n>2
:G(e1, ..., en) = G(e1, e2) + G(e1, e3) + ... + G(e1, en)
. There aren-1
pairs. - round-robin for
n>2
:RR(e1, ..., en) = G(e1, ..., en) + RR(e2, ..., en)
. There aren(n-1)/2
pairs. - using
-rounds
repeats the tournament-rounds
times. The number of games played for each pair is therefore-games * -rounds
.
- with
loseonly
: In a gauntlet tournament, only save games, messages and samples that first engine loses. This option is only effective when specifyinggauntlet
.repeat
: Repeat each opening twice, with each engine playing both sides.transform
: Transform openings by using rotating and flip. There are 8 types of transform (identity, rotate90, rotate180, rotate270, flipX, flipY, flipXY, flipYX). After using all openings each time, a new transform type is used, and this process repeats for all transform types.sprt [elo0=E0] elo1=E1 [alpha=A] [beta=B]
: Performs a Sequential Probability Ratio Test forH1: elo=E1
vsH0: elo=E0
, wherealpha
is the type I error probability (false positive), andbeta
is type II error probability (false negative). Default values areelo0=0
, andalpha=beta=0.05
. This can only be used in matches between two players.log
: Write all I/O communication with engines to file(s). This producesc-gomoku-cli.id.log
, whereid
is the thread id (range1..concurrency
). Note that all communications (including error messages) starting with[id]
mean within the context of thread numberid
, which tells you which log file to inspect (id = 0 is the main thread, which does not product a log file, but simply writes to stdout).debug
: Turn on debug mode. In debug mode, more detailed information about game and engines will be printed, and-log
will also be turned on automatically.sendbyboard
: Send full position usingBOARD
command before each move. If not specified, continuous position are sent usingTURN
. Some engines might behave differently when receivingBOARD
rather thanTURN
.fatalerror
: Consider "engine crashed before answering to START", "engine timeout after tolerance before answering to START", "engine output ERROR before answering to START", "engine crashed before answering to MOVE", "engine timeout after tolerance before answering to MOVE" as fatal error, which causes c-gomoku-cli to terminate with a failure exit code. By default this is turned off thus such engine failure is considered as crash loss or time loss (Error messages will still be printed to stderr).openings file=FILE [type=TYPE] [order=ORDER] [srand=N]
:- Read opening positions from
FILE
, inTYPE
format.type
can beoffset
(default value) orpos
. See "Openings File Format" section below about details of different formats. order
can berandom
orsequential
(default value).srand
sets the seed of the random number generator toN
. The default valueN=0
will set the seed automatically to an unpredictable number. Any non-zero number will generate a unique, reproducible random sequence.
- Read opening positions from
pgn FILE
: Save a dummy game toFILE
, in PGN format. PGN format is for chess games. We replace the moves with some random chess moves but only keep the game result and player names. This dummy PGN file can be input by BayesianElo to compute ELO scores.sgf FILE
: Save a game toFILE
, in SGF format.msg FILE
: Save engine messages toFILE
, in TXT format. Messages in each games are grouped by game index.sample
. See below.
-
cmd=COMMAND
: Set the command to run the engine.- The current working directory will be set automatically, if a
/
is contained inCOMMAND
. For example,cmd=../Engines/Wine2.0
, will run./Wine2.0
from../Engines
. If no/
is found, the command is executed as is. Without/
, for examplecmd=demoengine
will rundemoengine
, which only works ifdemoengine
command was inPATH
. - Arguments can be provided as part of the command. For example
"cmd=../fooEngine -foo=1"
. Note that the""
are needed here, for the command line interpreter to parse the whole string as a single token. - Command line is escaped using backslash (
\
). If you would like to use backslash in engine path on Windows, use a double backslash (\\
) to replace all original backslash in path.
- The current working directory will be set automatically, if a
-
name=NAME
: Set the engine's name. If omitted, the name will be taken from theABOUT
values sent by the engine. -
tc=TIMECONTROL
: Set the time control toTIMECONTROL
. The format ismatch_time/turn_time+increment
ormatch_time
, wherematch_time
is the total time of this match (in seconds),turn_time
is the max time limit per move (in seconds), andincrement
is time increment per move (in seconds). Ifturn_time
is omitted, then it will be the same withmatch_time
by default. Ifincrement
is omitted, then it will be set to0
by default. Ifmatch_time
is0
, then there will no limit on match time andturn_time
will be the only limitation. -
depth=N
: Depth limit per move. This is an extension option1, may not be supported by all engines. -
nodes=N
: Node limit per move (N
is recommended to be have a granularity more than1000
). This is an extension option1, may not be supported by all engines. -
maxmemory=MAXMEMORY
: Set the max memory toMAXMEMORY
bytes. Default memory limit is 350MB (same as Gomocup) if omitted. -
thread=N
: Number of threads a engine can use. Default value is1
. This is an extension option1, may not be supported by all engines. -
tolerance=N
: Tolerance (in seconds) to determine when an engine hangs (which is an unrecoverable error at this point). Default value isN=3
. -
option.O=V
: Set a raw protocol info. CommandINFO [O] [V]
will be sent to the engine before each game starts.
So far c-gomoku-cli only accept openings in plaintext format (*.txt
). In a plaintext opening file, each line is an opening position. Currently there are two notation types for a position: offset
and pos
.
Offset opening notation is denoted by the move sequence: <black_move>, <white_move>, <black_move>
... . Each move is separated with a comma ",
" and a space "". The last move will not be followed by any comma or space. Each move is in format <x-offset>,<y-offset>
, no space in between. Note that here each coordinate is the offset from the center of the board, which is different from the move coordinate of Gomocup protocol where (0,0) is up-left corner of the board. Offset value can be negative.
Assuming that a board is in size SIZE
, the conversion between plaintext offsets <x-offset>,<y-offset>
and Gomocup move <x>,<y>
is:
HALF_SIZE = floor(SIZE / 2)
<x> = <x-offset> + HALF_SIZE
<y> = <y-offset> + HALF_SIZE
The following is an example position in "offset" notation:
8,-3, 6,-4, 5,-4, 4,-3, 2,-8, -1,-5
You can see more examples from Gomocup 2020 result page (download the results+openings from the link at the very bottom of that page).
Pos opening notation is denoted by a sequence of move position, with no space or any forms of delimiter in between: <black_move><white_move><block_move>
... . Each move is in format <x-coord><y-coord>
, while <x-coord>
is a letter starting from "a
", <y-coord>
is a number starting from "1
". Coordinate is the offset from the upper-left corner of the board. This notation is only suitable for board size less than 27
.
The following is an example position in "pos" notation:
b7d6e6f7h2k5
This notation is common among many Gomoku/Renju applications. (For example, you can acquire a pos notation text by using "getpos" command in Yixin-Board).
Sampling is used to record various position and engine outputs in a game, as well as the final game result. These can be used as training data, which can be used to fit the parameters of a gomoku engine evaluation, otherwise known as supervised learning. Sample record is usually in binary format easy for engine to process, meanwhile a human readable and easily parsable CSV file can also be generated. Note that only game which result is not "win by time forfeit" or "win by opponent illegal move" will be recorded.
Syntax is -sample [freq=%f] [format=csv|bin|bin_lz4] [file=%s]
. Example -sample freq=0.25 format=csv file=out.csv
.
freq
is the sampling frequency (floating point number between0
and1
). Defaults to1
if omitted.file
is the name of the file where samples are written. Defaults tosample.[csv|bin|bin.lz4]
if omitted.format
is the format in which the file is written. Defaults tocsv
, which is human readable:Position,Move,Result
.Position
is the board position in "pos" notation.Move
is the move in "pos" notation output by the engine.Result
is the game outcome from perspective of current side to move, values forResult
are0=loss
,1=draw
,2=win
. For binary formatbin
see the section below for details.bin_lz4
is the same asbin
format, but the whole file stream is compressed using LZ4 to save disk space (This is suitable for huge training dataset containing millions of positions). Engines are recommended to use LZ4 "Auto Framing" API (example) to decompress the training data.
Binary format uses variable length encoding show below, which is easy to parse for engines. Each entry has a length of 4+ply
bytes. Position is represented by a move sequence that black plays first. Move sequence is guaranteed to have the same order as the actual game record.
struct Entry {
uint16_t result : 2; // game outcome: 0=loss, 1=draw, 2=win (side to move pov)
uint16_t ply : 9; // current number of stones on board
uint16_t boardsize : 5; // board size in [5-22]
uint16_t rule : 3; // game rule: 0=freestyle, 1=standard, 4=renju
uint16_t move : 13; // move output by the engine
uint16_t position[ply]; // move sequence that representing a position
};
Each move is represented by a 16bit unsigned integer. It's lower 10 bits are constructed with two index x
and y
using uint16_t move = (x << 5) | y;
. Below is a code snippet that does transform between the packed move and two coordinates.
uint16_t Move(int x, int y) { return (x << 5) | y; }
int CoordX(uint16_t move) { return (move >> 5) & 0x1f; }
int CoordY(uint16_t move) { return move & 0x1f; }
Thanks to lucasart for developing the c-chess-cli project. His prior work provides a perfect starting point for the development of c-gomoku-cli. Thanks to Haobin for contributing the support on Windows. It makes c-gomoku-cli avaliable to all Windows based engines.
External library used:
Lucas Braesch (lucasart) Chao Ma (nkg114mc) Haobin Duan (dhbloo)
Footnotes
-
Yixin-Board extension protocol: https://github.com/accreator/Yixin-protocol/blob/master/protocol.pdf ↩ ↩2 ↩3