The Captive Game Server (CGS) is a small Java application that contains the same game engine as the Web Game Server, but does not have a SQL database and does not communicate with clients using HTTP. It is to be started, as a child process, by the client program (such as a machine learning (ML) program that wants to play a game), and communicates with the client using standard input and standard output (i.e. UNIX pipes). For testing purposes, it can also be run directly from the console by a person who wants to play the game himself.
Since the Captive Game Server is run by the owner of the ML program, the latter has complete control how many times it's run, and with what data. It is a complete "honor system", so to speak. Thus, it is up to the owner of the ML program to keep track of how the CGS is used, how many game episodes were played, and how many moves were attempted in each episode.
To distinguish this version of the CGS from the socket-based version discussed later in this document, we will refer to it as the pipe-based.
If you're using CGS instaled on sapir, you can safely ignore this section. But if you are installing the CGS on your own machine, you also need to obtain a set of third-party JAR files that are used for working with JSON-format data. This is how: You can download a file such as jaxrs-ri-2.31.zip (from e.g. https://mvnrepository.com/artifact/org.glassfish.jersey.bundles/jaxrs-ri ) and unpack it on your machine in such a way that, for example, if you have the CGS code unpacked in ~/somedir/game (that's the directory in which various shell scripts are), then you also have the directory ~/somedir/jaxrs-ri.
For example: suppose you have downloaded both the CGS distribution file (e.g. captive-1.003-2020-08-07.zip, or a later version) and jaxrs-ri-2.31.zip into your home directory. Then you do the following to unpack the files into properly positioned directories:
cd mkdir captive cd captive unzip ~/captive-1.003-2020-08-07.zip unzip ~/jaxrs-ri-2.31.zip
In this way CGS can find relevant JAR files (used for handling JSON) in ../jaxrs-ri/ext/*.jar, relative to the directory where the scripts are. This is how the class path is set in the shell scripts (specifically, in set-var-captive.sh, which is included into captive-brief.sh and other scripts).
other-lib :
Elsewhere:
The CGS is presently installed on sapir, in the directory /home/vmenkov/captive. The CGS can play one game at a time; to choose a game, one needs to provide the CGS with a game rule file (a few of which can be found in /home/vmenkov/captive/game/rules/ ) and the initial number of pieces.
To use CGS, you need to add the directory /opt/jdk-14.0.2/bin into your path. Alternatively, you can modify the shell scripts mentioned below, so that they use /opt/jdk-14.0.2/bin/java rather than just java.
You can run the CGS from the command line as follows:
/home/vmenkov/captive/game/captive-full.sh /home/vmenkov/captive/game/rules/rules-02.txt 8In this example, you will start a session using the rule description from the specified file; each episode will use a random initial board with 8 pieces. During the session, you can play one or several episodes.
During the session, you will be able to type commands on the console, and the program will send the output to your screen. Type HELP to see the list of command, or EXIT to exit.
To use the CGS with your ML program, use the standard mechanism for spawning a child process, starting the script (captive-full.sh, captive-standard.sh, or captive-brief.sh), or the underlying Java program, as a child process from your program, with appropriate command-line arguments. You can capture the CGS's standard input and standard output via pipes, and communicate with it via writing into one pipe and reading from the other.
The command line arguments for the scripts such as captive-full.sh are the same as for the underlying Java program, edu.wisc.game.engine.Captive. These arguments are used to specify, directly or indirectly, two things:
In Game Server 2.*, the designers of human-subject experiments have a wide choice of experiment plan structures. Each parameter set in their trial lists may define a 6-paramter random board generator (specifying ranges of the number of pieces, the number of shapes, and the number of colors), with legacy or custom colors and shapes. Alternatively, a parameter set may define a sequence of predefined boards. Whichever structure the the parameter set has, using one of the methods described above will ensure that the captive server uses exactly the same random board generation process as is used by web-based Game Server in the human-subject experiments.
./captive-full.sh game-data/rules/farthest.txt game-data/boards/four-corners.jsonIf you do that, every episode in your session will start with the same identical initial board.
./captive-full.sh game-data/rules/farthest.txt 3In this example, every episode will start with a random board with the same number (3) pieces. The shapes and colors of each piece will be selected independently from each other from the entire set of shapes and colors available, which means that the number of shapes and colors will vary in each episode over the entire possible range.
./captive-full.sh game-data/rules/farthest.txt 2:4In the above example, for each episode the number of pieces will be selected randomly, with a uniform distribution, from the speciified range, meaning that, on average, one-third of all initial boards will have 2 pieces, one-third will have 3, and one-third will have 4.
./captive-full.sh game-data/rules/farthest.txt 5 2 3In the above example, every initial board will have 5 pieces, with exactly 2 distinct shapes and 3 distinct colors
Ranges and single numbers can be combined in arbitrary ways; essentially, a single number n is equivalent to the range n:n. E.g.
./captive-full.sh game-data/rules/farthest.txt 5 1:3 3:4In the above example, every initial board will have 5 pieces, with 1, 2, or 3 distinct shapes (with equal probability) and 3 or 4 distinct colors.
When using ranges, make sure that the upper bound of range of the number of shapes the upper bound of the range of the number of colors do not exceed the lower bound of the range of the number of pieces. Otherwise, the random board generator may be occasionally faced with an impossible task of creating a board which has more distinct colors or shapes than it has pieces!
To specify the sets from which colors and/or shapes will be drawn by the initial board generator, you can add additional parameters colors=... and/or shapes=... to the command line. The values of the parameters are semicolon-separated lists of colors and/or shapes, respectively. Make sure to use single quotes as shown below, since semicolons would be interpreted by the UNIX shell as command separators otherwise.
./captive-full.sh /opt/tomcat/game-data/rules/arrows/rule-01.txt 3 2 2 'colors=red;pink' 'shapes=arrows/arrow-up-left;arrows/arrow-up-right;arrows/arrow-down-right;arrows/arrow-down-left'
One is allowed to use * to mean "use all shapes for which SVG files exist in the appropriate subdirectory of the main shapes directory". Thus, 'shapes=arrows/*;weather/*' is equivalent to listing every shape from /opt/tomcat/game-data/shapes/arrows and /opt/tomcat/game-data/shapes/weather
If you're using custom shapes and/or custom colors, it is necessary that a list of colors and a set of shapes SVG files are found at the appropriate locations under /opt/tomcat/game-data, as explained in the document Using custom shapes and colors in Rule Game Server 2.*. If you're running your Captive Game Server on sapir (the server used for the human subjects), and are playing a game that human subjects are already playing, then you're all set, because the human-player experiment team has already set up the necessary files in /opt/tomcat/game-data. If you're running your Captive Game Server on another host, you may need to create a copy of sapir's /opt/tomcat/game-data directory on your host, either by copying the files directly, or (in the future) by checking out the experiment controle files from Aria's GitHub repository. (Ask Aria for details).
If the root of your server data directory is somewhere else than in /opt/tomcat/game-data directory (e.g. because you just copied the directory tree from sapir to somewhere under your home directory), you can specify this by an additional parameter on the command line, e.g. inputDir=/home/myname/my-game-data
This is the easiest way to emulate the behavior of the web-based Game Server (with which humans play). To do this, you can specify a trial list file and the (1-based) row number, e.g.
./captive-full.sh /opt/tomcat/game-data/trial-lists/vmColorTest/trial_1.csv 1The CGS figures the file type based on the extension: the ".csv" extension means you are providing a trial list file, while the ".txt" extension (as in items 1 thru 6, above) refers to a rule set file.
Any of the valid trial list files prepared by our human-subject experiment team can be used with the CGS. To decide which games you want to play, you may ask Gary or Aria about various trial list files that can be found in the trial list directory, /opt/tomcat/game-data/trial-lists on sapir.
The CGS will read the trial list file and extract the parameter set with the specified number. It will then create a game generator with the same parameters that the web-based Game Server would use when running on that parameter set; depending on the parameter set, that may involve random initial boards (with appropriate shapes, colors, and number of pieces) or predefined initial boards. As you play more episodes (starting each episode, after the first one, with the NEW command), the CGS will generate a sequence of initial boards in the same way as a web-based Game Server would, i.e. eiher randomly or by following a prescribed sequence of predefined boards. As in the web-based Game Server, the rule set specified in that parameter set will be used in each episode.
Since the trial list file implicitly refers to files in various directories under the server data directory (by default, /opt/tomcat/game-data), you must either have that directory, with all relevant files, on your computer, or use the inputDir=... parameter on the command line, just as in Example 6 above.
Starting in ver. 1.026, it is possible to specify the seed of the random number generator, so that the same random boards would be generated on repeated runs. To do that, insert -Dseed=N (where N is a positive integer) into the Java command found in a script such as captive-full.sh, or used during the child-process spawning in your ML application. For example, you can modify the last line of this script to look as follows:
java -Dseed=1 -Doutput=FULL edu.wisc.game.engine.Captive $argv[1-]
It is also possible to specify the seed on the command line as follows:
./captive-full.sh /opt/tomcat/game-data/rules/MLC/vm/test-05.txt 3 seed=5
If your ML application is in Java, you can use the ProcessBuilder API to spawn child processes and deal with their I/O. You create a Process object, write your commands to the stream returned by Process.getInputStream(), and read responses from the stream returned by Process.getOutputStream()
If your ML application is in Python, you can use subprocess.run for the same purpose.
For the convenience of ML researchers who may want their ML application written in Python to use the CGS, we provide a sample Python client that spawns a CGS and communicates with it via pipes.
To see how this works, run the script
./captive-python.shwith no arguments. It will set the CLASSPATH as needed to include all relevant JAR files, and will then invoke python/client.py with some arguments (rule file and piece count), which in its turn will spawn a CGS and play an episode of the specified game with it, communicating via pipes. The script python/client.py can take all other command-line arguments that the shell scripts discussed above do.
You may want to modify the script captive-python-socket.sh as needed to use a different rule file.
In the Python code, after python/client.py spawns the CGS process, it uses code from python/gameLoop.py to do the actual communication and playing. In the latter file, the method chooseMove() is the one deciding on the next move. In the sample code, the move is done by picking the first moveable piece and trying to put it to a randomly chosen bucket; in a real ML application, some appropriate learning logic would appear here instead.
Below is the console output from a sample session.
:~/w2020/game> ./captive-python.sh Rule file=./rules/rules-01.txt, #pieces=5 Received: 6 0 0 Received: # Hello. This is Captive Game Server ver. 1.003. Starting a new game (no. 1) Received: {"id":0,"value":[{"id":0,"color":"BLACK","shape":"SQUARE","x":2,"y":3,"buckets":[]},{"id":0,"color":"RED","shape":"SQUARE","x":6,"y":4,"buckets":[]},{"id":0,"color":"RED","shape":"SQUARE","x":5,"y":5,"bu ckets":[]},{"id":0,"color":"YELLOW","shape":"STAR","x":1,"y":6,"buckets":[0]},{"id":0,"color":"RED","shape":"TRIANGLE","x":3,"y":6,"buckets":[]}]} Code=6, status=0, stepNo=0 5 pieces still on the board Sending: MOVE 6 1 0 0 Received: 4 0 1 Received: {"id":0,"value":[{"id":0,"color":"BLACK","shape":"SQUARE","x":2,"y":3,"buckets":[]},{"id":0,"color":"RED","shape":"SQUARE","x":6,"y":4,"buckets":[]},{"id":0,"color":"RED","shape":"SQUARE","x":5,"y":5,"bu ckets":[]},{"id":0,"color":"YELLOW","shape":"STAR","x":1,"y":6,"buckets":[0]},{"id":0,"color":"RED","shape":"TRIANGLE","x":3,"y":6,"buckets":[]}]} Code=4, status=0, stepNo=1 5 pieces still on the board Sending: MOVE 6 1 0 0 Received: 4 0 2 Received: {"id":0,"value":[{"id":0,"color":"BLACK","shape":"SQUARE","x":2,"y":3,"buckets":[]},{"id":0,"color":"RED","shape":"SQUARE","x":6,"y":4,"buckets":[]},{"id":0,"color":"RED","shape":"SQUARE","x":5,"y":5,"bu ckets":[]},{"id":0,"color":"YELLOW","shape":"STAR","x":1,"y":6,"buckets":[0]},{"id":0,"color":"RED","shape":"TRIANGLE","x":3,"y":6,"buckets":[]}]} Code=4, status=0, stepNo=2 5 pieces still on the board Sending: MOVE 6 1 7 0 Received: 0 0 3 Received: {"id":0,"value":[{"id":0,"color":"BLACK","shape":"SQUARE","x":2,"y":3,"buckets":[]},{"id":0,"color":"RED","shape":"SQUARE","x":6,"y":4,"buckets":[]},{"id":0,"color":"RED","shape":"SQUARE","x":5,"y":5,"bu ckets":[]},{"id":0,"color":"RED","shape":"TRIANGLE","x":3,"y":6,"buckets":[0,1]}]} Code=0, status=0, stepNo=3 4 pieces still on the board .... .... .... 1 pieces still on the board Sending: MOVE 3 2 0 7 Received: 4 0 11 Received: {"id":0,"value":[{"id":0,"color":"BLACK","shape":"SQUARE","x":2,"y":3,"buckets":[3]}]} Code=4, status=0, stepNo=11 1 pieces still on the board Sending: MOVE 3 2 0 0 Received: 0 1 12 Received: # Game finished - the board is clear Received: {"id":0,"value":[]} Code=0, status=1, stepNo=12 Cleared board in 12 steps
This section is somewhat obsolete and mostly superfluos now, as a more detailed and updated description of the rule file syntax is now available elsewehere.
A few game rule description files have been supplied in /home/vmenkov/captive/game/rules/. The rule description language is mostly described in a document that Paul has posted on Google Docs; ask him for a share link. There are some extensions to this language though. In particular:
Order Manchu=[31, 25, 19, 13, 7, 1, 32, 26, 20, 14, 8, 2, ....] Order fromOutsideIn=[ [1,2,3,4,5,6, 7,13,19,25, 31,32,33,34,35,36, 30,24,18,12], [8,9,10,11, 14,20, 26,27,28,29, 23,17], [15,16,21,22]]The first line defines an order named Manchu, according to which one must first pick objects in the order in which one reads Manchu or Mongol script, i.e. by column, the columns being arranged from left to right. The second line defines an order named fromOutsideIn, according to which one must first pick objects from the cells adjacent to the edge of the square, etc, toward the center.
(counter,shape,color,positionList,bucketFunctions)For example, the rule
(*,star,*,fromInsideOut,[0])says that any number of stars of any color can be picked, as long as they are among the "most inside" pieces of all pieces on the board; once picked, they must be put into bucket no. 0.
For more information, see the detailed guide for the Rule file syntax and semantics.
The input to CGS consists of one-line commands, some with arguments. The output consists of "comment lines" (intended for debugging or human consumption), preceded with '#', and data lines, which do not have a '#'. Your ML program should ignore all comment lines.
Three output modes are available: brief, standard, and full. The full mode contains a large number of comment lines (in particular, a graphic board display); the other two modes don't have comment lines, or only have very few of them. The CGS can be started in any of these modes using one of the 3 scripts provided (captive-full.sh, captive-standard.sh, or captive-brief.sh).
The following are the commands you'll need
The implicit "NEW" command: when you start the CGS, an episode starts immediately, and you get 2 lines of output right away which are in the same format as for the "NEW" command (below).
Starts a new episode. You don't need this command at the very beginning of a session, since the first episode starts automatically. Use this command for every subsequent game. This command can be used after the previous episode has completed (with a "win" (clearing the board) or a stalemate (no piece can be moved anymore); it can also be used in the middle of an episode if you give up and want to start a new one.
Response: two lines. The first line contains 3 numbers, as described in the "MOVE" section below, reflecting the acceptance of the command and the current state. The second line describes the current display. It is in JSON format, and looks as follows:
{"id":0,"value":[{"id":0,"color":"BLUE","shape":"CIRCLE","x":4,"y":1,"buckets":[]},{"id":0,"color":"RED","shape":"CIRCLE","x":4,"y":3,"buckets":[0]},{"id":0,"color":"BLUE","shape":"SQUARE","x":3,"y":5,"buckets":[]},{"id":0,"color":"YELLOW","shape":"CIRCLE","x":3,"y":6,"buckets":[]}]}
Here, at the top level the only field that matters is "value". Its value is an array, each elements represents a piece. For example, the piece described as
{"id":0,"color":"RED","shape":"CIRCLE","x":4,"y":3,"buckets":[0]} has color RED, shape CIRCLE, is located in row 3, column 4. The field "buckets" is an array containing the list of buckets (see above for numbering!) into which this piece can be moved. If the array size is zero, it means that the piece is not moveable. It is up to your program and its "honor system" to which extent it wants to look into this array!
DISPLAY
Response: one line, displaying the current board in the JSON format, in the same format as in the NEW command.
MOVE row col B_row B_colThis command attempts picks the piece located at (row,col), and to move it to the bucket at (B_row, B_col). The values for the first two coordinates can be in the range [1..6]; the values for each of the last two coordinates must be 0 or 7.
Response: two lines (in the standard mode) or one line (in the brief mode). The first line contains 3 numbers, as follows:
response_code game_state move_countThe response_code is 0 for acceptance (successful move), positive for rejection (command is understood and is legal, but the move is denied), negative for an error (command arguments are not legal). More specifically:
public static class CODE { public static final int // move accepted and processed ACCEPT = 0, // Move rejected, and no other move is possible // (stalemate). This means that the rule set is bad, and we // owe an apology to the player STALEMATE=2, // move rejected, because there is no piece in the cell EMPTY_CELL= 3, // move rejected, because this destination is not allowed DENY = 4, // Exit requested EXIT = 5, // New game requested NEW_GAME = 6; public static final int INVALID_COMMAND= -1, INVALID_ARGUMENTS= -2, INVALID_POS= -3, // No game is on now. Start a game first! NO_GAME = -4, // Used in socket server GAME command INVALID_RULES = -5, // Used in web app, when trying to access a non-existent episode NO_SUCH_EPISODE = -6, // The number of preceding attempts does not match. This may indicate // that some HTTP requests have been lost, or a duplicate request ATTEMPT_CNT_MISMATCH = -7, // This code is returned on successful DISPLAY calls, to // indicate that it was a display (no actual move requested) // and not a MOVE JUST_A_DISPLAY = -8; }
The second number, the finish code reports the current state: 0=can play (there are pieces on the board, and some can be moved), 1=finish (no pieces left on the board), 2=stalemate (there are pieces on the board, but none of them can be moved anymore).
The third number is the move count, i.e. the total number of attempted and successful moves in this episode so far.
In the standard mode, the status line is followed by a line with the description of the current display, in the same format as in the NEW command. In the brief mode, this line is omitted. You can use the brief mode if your program keeps track of the board itself, and you want to reduce the cost of communication with the CGS.
EXIT
HELP
VERSION
In the "full" output mode, human-readable display is printed with each "DISPLAY", "NEW", or "MOVE" command, as a number of comment lines. In the other modes, the same display is available with the "DISPLAYFULL" command.
The following notation is used: O, #, T, * are circles, squares, triangle, and stars, respectively. The color is either not marked at all, or is expressed by a lowercase letter (bLACK, yELLOW, rED, and g for BLUE). Dots are empty cells. Round parentheses () surround pieces that can be moved presently. Square bracket surround the cell to which the last MOVE was applied; the cell will be empty [.] if the move was successful (the piece has been removed), or non-empty, e.g. [O], if the move was rejected.
There is no logging at present. The ML program is to count its games, and to record displays (e.g. the initial boards) if it so desires.
An alternative way of running CGS is as a process in a separate console window (or even by a different user, or on a different host) and communicating with it over the socket interface. Unlike the "normal" CGS, the socket game server can serve multiple clients, with a different game rule set for each one; however, the client-server session facing each client (and running in a separate thread) is still completely controlled by the client and thus can still be described as "captive".
In a new console window, run the script socket-server.sh with the desired port number as an argument, e.g.
~/w2020/game> ./socket-server.sh 7501
The socket server will normally not produce any messages, other than reports on rule set files being read at the beginning of each session, if everything goes on normally. Any error messages will go to the screen.
At this point (ver 1.003) there is no inactive session management in the server, so the clients ought to close their sessions as appropriate. When you don't need the server running anymore, you can kill it with Ctrl-C, or with a kill command.
The only difference from the normal (pipe-based) CGS API is that the socket CGS has one more command,
GAME "rule-file.txt" nPiecese.g.
GAME "rules/rules-01.txt" 5(The quotes around the file name are optional)
This command must be used as the first command of the session. It specifies the rule set file to use (the rule file name can specify the absolute file name on the server, or a path relative to the directory where the server was started), and the number of pieces. These arguments are analogous to those used on the command line when starting a pipe-based CGS; however, the rule file name must be double-quoted.
As of ver. 4.002, the GAME command in the socket CGS API can also use various other arguments, in the same way as they can be used on the command line of a pipe-based CGS (See Examples 1 thru 7 in Command-line parameters). For example:
GAME rules/rules-01.txt 5 1:2 1:3or
GAME /opt/tomcat/game-data/trial-lists/vmColorTest/trial_1.csv 1
After the GAME command is sent, the socket CGS behaves exactly like the pipe-based CGS does; it reads in a rule set file and any other required control files, and starts the first episode, sending back to the client the status line and the JSON display line. Thereafter, the same commands (MOVE, NEW, and finally EXIT) should be used by the client to play a desired number of episodes, exactly as their are in the pipe-based CGS.
You can test the socket CGS by running the telnet command on the appropriate port, e.g.
telnet localhost 7501
You can then enter the GAME command, followed by MOVE, NEW, and EXIT as appropriate. For example:
:~> telnet localhost 7501 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. GAME "rules/rules-01.txt" 2 6 0 0 # Hello. This is Captive Game Server ver. 1.003. Starting a new game (no. 1) {"id":0,"value":[{"id":0,"color":"BLUE","shape":"TRIANGLE","x":6,"y":5,"buckets":[]},{"id":0,"color":"RED","shape":"TRIANGLE","x":1,"y":6,"buckets":[0]}]} MOVE 6 1 7 0 0 0 1 {"id":0,"value":[{"id":0,"color":"BLUE","shape":"TRIANGLE","x":6,"y":5,"buckets":[1]}]} MOVE 5 6 7 7 0 1 2 # Game finished - the board is clear {"id":0,"value":[]} NEW 6 0 0 # Hello. This is Captive Game Server ver. 1.003. Starting a new game (no. 2) {"id":0,"value":[{"id":0,"color":"BLUE","shape":"TRIANGLE","x":5,"y":1,"buckets":[2]},{"id":0,"color":"YELLOW","shape":"CIRCLE","x":6,"y":5,"buckets":[1]}]} .... EXIT 5 0 0 # Goodbye Connection closed by foreign host.
For the convenience of ML researchers who may want their ML application written in Python communicate with the socket CGS, we provide a sample Python client doing exactly that.
To see how this works, run the script
./captive-python-socket.shwith no arguments. It will set the CLASSPATH as needed to include all relevant JAR files, and will then invoke python/client-socket.py with some arguments (host name, port number, rule file and piece count), which opens a socket connection and plays an episode of the specified game.
You may want to modify the script captive-python-socket.sh as needed to use a different host, port, or rule file.