Captive Game Server

Updated 2021-11-15 for ver 4.002

What is the Captive Game Server?

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.

Third-part JAR files

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:

Starting the Captive Game Server

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 8
      
In 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.

Command-line parameters: specifying the rule set and the initial board

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:

There are several ways to provide this information, as illustraed below. In examples 1 thru 6, the first argument is the rule set file, specified as either a relative path (relative to the current directory), or an absolute path; it is followed by arguments specifying the initial board(s) for the session's episodes, in one of several ways. In example 7, the first argument is a trial list file, which contains information that both specifies the rule set and the initial board generation process.

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.

1. Specify a pre-defined board file identified by name

./captive-full.sh game-data/rules/farthest.txt game-data/boards/four-corners.json 
	
If you do that, every episode in your session will start with the same identical initial board.

2. Specify only the number of pieces, as a single number

./captive-full.sh game-data/rules/farthest.txt 3
	
In 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.

3. Specify only the number of pieces, as a single number or a range

./captive-full.sh game-data/rules/farthest.txt 2:4
      
In 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.

4. Specify the number of pieces, the number of shapes, and the number of colors, as single numbers

 ./captive-full.sh game-data/rules/farthest.txt 5 2 3
	
In the above example, every initial board will have 5 pieces, with exactly 2 distinct shapes and 3 distinct colors

5. Same as the above, but with using ranges for any of the three quantities.

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:4
	
In 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!

6. Same as the above, but with custom shapes and/or custom colors.

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

7. Use a trial list file

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 1
The 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.

Specifying the random generator seed

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

Talking to the CGS from a Java application

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()

Talking to the CGS from a Python application

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.sh
      
with 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

Game description

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:

For more information, see the detailed guide for the Rule file syntax and semantics.

Commands (input) and output

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 API

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).

NEW

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_col
This 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_count
The 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

Human-readable output

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.

Logging

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.

Socket interface

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".

Starting the socket CGS

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 socket CGS API

The only difference from the normal (pipe-based) CGS API is that the socket CGS has one more command,

GAME "rule-file.txt" nPieces
e.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:3
or
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.

Talking to the socket CGS from the console

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.

Talking to the socket CGS from a Python client

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.sh
      
with 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.