wiki:DeveloperGuide

Developer Guide

The main feature of multistage is that the creation of new extensions should be simple. In this guide, all the resources needed to make extensions will be outlined. The main concepts of the multistage framework as well as the common implementations are covered in the Server-Side and Client-Side sections. Other topics such as asynchronous games, and other advanced topics have their own sections.

Prerequisites

In order to build Multistage extensions, a mixture of tools are needed.

  • The Multistage source tree
  • The API Documentation for Multistage
  • Java tools (compiler, IDE, etc.)

Server Side

The source code for the server-side extension implementations are located in multistage.server.control.gamecontrollers.* There are many examples already in this directory which can be examined to help the developer understand the capabilities of multistage.

Server Basics

Each server-side extension class has several requirements:

  1. It must extend ServerControl (located in edu.caltechUcla.sselCassel.projects.multistage.server.control)
  2. It must follow the naming convention %GameName%Control. This is hard-coded into the Multistage framework and is absolutely necessary.

Server Methods

There are several methods of the ServerControl class which need to be implemented in the extending class:

initialize: This method is the place to assign variables. Generally, what you would normally put in a constructor, you should put here instead. The extending class's constructor should remain empty. initialize() normally uses the accessing methods (described later) to retrieve information from a configuration file for later use during the round.

runSequence: This is where the bulk of the extension will be (for synchronous extensions). This is where information objects are created and sent to the client. It is also where information is processed after the client has moved, and payoffs are assigned. These tasks are outlined in later sections.

respondToChat: This helps handle chatting (discussed later) on the server side.

processRound: This is only used in asynchronous games. In an asynchronous game, this is where the bulk of the actions will be performed including sending/receiving information objects as well as determining when to advance a round. See the section on asynchronous games for more information.

Properties Files

In addition to the necessary classes, the extension must have a properties file. There is not strict convention for the file name or location, but a metadata directory is provided for most extensions. The name/location for the properties file is entered by the experimenter from the Server console by clicking the Load properties file button and selecting the file from the menu. The format of the properties file is simple key = value pairs:

        # a hash mark indicates a line to ignore, usually for comments

        # global parameters do not have any preceding text
        globalparameter = value

        # match specific parameters start with match.%matchNumber%
        match.matchNumber.matchspecificparam = othervalue

matchNumber ranges from 0 to maxRounds-1. If matchNumber of 0 is specified, it is the initial condition.

Multistage provides methods to read data for four different common types:

  • getBooleanProperty(...)
  • getFloatProperty(...)
  • getIntProperty(...)
  • getStringProperty(...)

For each of these types of method there are two standard forms:

  • get%Type%Property(String prop) where Type is the expected return type
  • get%Type%Property(String prop, defaultValue) where defaultValue is the value assigned if the property is not found in the configuration file.

Note: Just because there are only four types of methods to read data from the properties file, the software is not limited to just these types of data. More complex properties can be read in with the getStringProperty() method and easily parsed via the extension-specific game controller.

Client/Server? Communication

Introduction to Information Objects

Information Objects compose the bulk of the communication between the server and the clients. The Information object is wrapped around a hashtable; it is filled by using the add%Type%Info method. All of these methods are outlined in the Information object API documentation, but follow the form:

  • add%Type%Info(String key, Object ) where %Type% is the store type
  • get%Type%Info(String key) where %Type% is the return type

An important thing to note is that although a Type is passed in, when the information is stored in the hashtable, it does not retain its type information. Thus, it is crucial to specify compatible types when storing and retrieving information.

Add Methods:

  • addInfo
  • addBooleanInfo
  • addFloatInfo
  • addIntInfo
  • addStringInfo

Get Methods:

  • getInfo
  • getBooleanInfo
  • getFloatInfo
  • getIntInfo
  • getStringInfo

It is possible to pass any Java primitive array or object inside the information objects using the addInfo() and getInfo() methods. The following java code example shows how to insert a 2D integer array and a Hashtable object into an information object, and how to retrieve them:

// initialize a new Information object
Information info = new Information();

// add a new 2D integer array to the information object
int[][] array = new int[5][16];
info.addInfo("array", array);

// add a java Hashtable object to the information object
Hashtable table = new Hashtable();
info.addInfo("table", table);

// retrieve the array from the information object
int[][] retrievedArray = (int[][])info.getInfo("array");

// retrieve the Hashtable from the information object
Hashtable retrievedTable = (Hashtable)info.getInfo("table");

The other add and get methods are implemented as a convenience to the developer. getStringInfo(...) removes the need to use a cast to retrieve Strings from the Information object, and the other get methods allow the developer to add and get java primitives from the Information object without having to wrap them in classes like Integer or Float. So, instead of having to do this:

// add int to Information
int x = 5;
Integer x2 = new Integer(x);
info.addInfo("x", x2);

// retrieve int from Information
Integer receivedX2 = (Integer)info.getInfo("x");
int receivedX = receivedX2.intValue();

The developer can simply do this:

// add int to Information
int x = 5;
info.addIntInfo("x", x);

// retrieve int from Information
int receivedX = info.getIntInfo("x");

Passing Information Objects

The Information object is passed to the client side through a number of methods. Specifically, the methods are: For Synchronous Extensions:

  • askAllPlayers(Information[]) - sends Information[i] to player number i.
  • askPlayer(Information, int partnerNum) - sends a single Information object to player number parterNum.

For Asynchronous Extensions: Asynchronous extensions are described in a special section below, but the method used is:

  • askAsynchronous(Information []) - sends Information[i] to player number i as indexed by ServerControl.

Asynchronous games can have a time limit, which is discussed in the Asynchronous section.

Assigning Payoffs

Two methods are used to assign payoffs to players.

  • addPayoff( int partnerNum, float payoff) - gives a single payoff to a player
  • addPayoffs( float[] payoffs) - gives each member of a float array to each player according to partnerNum.

Aborting the round

The extension writer has little to do with this in staged games, however, there are some issues in the code that will be necessary to make certain that everything aborts correctly. Never catch AbortRoundExceptions. If you wish to catch other Exceptions, use a double catch clause like thus:

        try{
            //code
        }catch(AbortRoundException abort){
            throw abort;
        }catch(Exception e){ 
            //do something 
        }

Aborting rounds in asynchronous extensions is explained in the section about asynchronous extensions.

Outputting data to the output file

To output data to the output file, use the methods:

  • addOutput - group specific, round specific output
  • addMatchOutput - group specific, whole match output

See the API Documentation for specifics on the parameters and the XML output format used by the outputService. Remember that questions can automatically write to the output file, one only needs to record player interactions.

Extra Server-Side Features

Client History Panels

These objects are inserted into the GUI of the client (discussed later). Once they are added to the client side, they are accessed through methods on the server side of the form:

  • addColumnData( int partnerNum, String column, String data [, Color color])

Note: the color parameter is optional.

This will set the header "column" with the value of "data." If a column does not already exist by that name, a new column will be created. This means that in order for data to appear in continuous columns/rows, the headers need to be identical (case sensitive). A common request is to start out with a blank row before a round has occurred. To do this, simply write to the specified columns with an empty String "". The first round will overwrite this and continue correctly.

Messaging:

It is often desirable to send messages to the players to make announcements or give reminders. To do this, use one of the following methods:

  • displayMessage - sends a message immediately to a single player
  • displayUniversalMessage - sends a message immediately to all players
  • displayMessagePostRound - stores the message to be sent after the current round is over for all other ServerControl? objects.

Each of these has several method signatures, which correspond to various situations. The decision to use a particular signature of these methods is beyond the scope of this document. Please consult the API Documentation for ServerControl?.

Chatting

Many games require players to be able to communicate amongst themselves to cooperate or compete. Multistage has a built in customizable chat system. The methods used to control the chatting system are:

  • activateGroupChatRouting - allows everyone to talk to everyone else
  • clearChatRoutingTable - removes all chat routes
  • insertChatRoute(int sender, int receiver) - allows sender to talk to receiver but does not necessarily allow receiver to talk to sender.
  • insertChatRoutingTable( Vector[] ) - given an array of Vectors, this will allow player #i to talk to the players corresponding to integers in Vector[i].

Surveys (see Surveys for more info)

Many games need to question subjects to obtain enough information for the experiment to be complete. There are 28 method signatures to choose from for this purpose. For details on each of these, consult the API Documentation. Note that all of these questions automatically record data to the output file (unless testing for correctness). Here is short rundown of the signatures to choose from:

  • questionSubject: sends a Vector of questions to a single subject
  • questionSubjects: sends a Vector of questions to all subjects
  • questionSubjectsPostMatch: stores a Vector of questions to be answered after the current match
  • questionSubjectsPostRound: stores a Vector of questions to be answered after the current round

Each method has its own specification, but some features to notice are:

  • Timelimit: a time limit can be set
  • Blocking: This specifies if the round/match can continue while the question is being asked. If blocking is set to true, then a game will pause while the question is being asked. If blocking is false, the game will continue.
  • Customized Button/Label? texts: The labels and buttons are fully customizable with the proper method calls.

The question Vectors are constructed from Question objects. Some question types to investigate are (found in multistage.shared.data):

  • SliderQuestion
  • TextQuestion
  • EssayQuestion
  • DegreeQuestion
  • ChoiceQuestion

Client-Side

The Client-Side is controls all interaction with the user and is responsible for collecting the data which is then sent to the server. The class files for the client implementation are located in edu.caltechUcla.sselCassel.multistage.client.interfaces.gameinterfaces.* Again, there are numerous examples already in this directory to use as a guide.

Client Basics

The client-side interface class is devoted largely to GUI building and manipulating Information objects. There are several properties that the client class must have:

  1. It extends ClientGUI (located at edu.caltechUcla.sselCassel.projects.multistage.client.interfaces.ClientGUI)
  2. It must have a class name of the form %GameName%GUI. This is a similar convention as used by the server-side controller class.

Client Methods

There are several methods in the ClientGUI class which must be supplied by the extending client-side interface class:

  • constructLayout: - create the layout of the GUI, usually using input from the Information object passed by the server.
  • initialize: pulls in variables from the information object and initialize any variables needed. This method is for the same purpose as its counterpart in ServerControl.
  • constructWaitingLayout: - construct the layout for the waiting screen
  • processMessage: - used to change the default message processing. For example to send a postRound message, one could use the sendUpdatePostRound method. It is important to note that no matter what is put in this function, it should always super(arguments) as it's default.
  • testingProcedures: - This method is for use in integrated testing. It is called to determine the response of the client for testing. Generally, random data generation occurs here to make sure that the data is safely processed.
  • respond: - for asynchronous (will be discussed in the Asynchronous section)
  • close: - perform any actions to shut down the client. Generally, this is used to remove any actionListeners or GUI components that need to be garbage collected.
  • reauthenticate: - used to restart a client when a failure occurs. This is almost never used for staged games, as the default procedure restarts most staged games correctly. This is important to asynchronous games (and will be discussed in the asynchronous section).

Thread Safety Precautions

If Swing is used for the GUI, there is a specific procedure to use to make sure that Swing does not crash due to simultaneous transactions. There is a small chance that these errors will occur if this procedure is not followed, and generally the errors are incredibly hard to track down. However, if the following procedure is used, no error can occur. Use this format:

        Runnable doUpdate = new Runnable() {
            public void run() {
        	// code to set up GUI
            }
        }
        SwingUtilities.invokeAndWait(doUpdate); // or invokeLater(doUpdate)

Sending and Receiving Information Objects

Information objects were covered on the server side. There are only a few minor comments on how to handle them on the client side. Generally, GUI events are what one wants to record. A large portion of games add information during the actionListener section of the code. Once all this information is collected, it can be sent back to the server at the end of the round by calling makeMove(); makeMove returns true if the Information was successfully sent and false if there was a problem. In a unistage game, there is a single call to makeMove at the end of the round. In a multistage game, there can be multiple calls back and forth between the client and server. However, for each call to askAllPlayers (or equivalent methods) there is exactly one makeMove. This is what is meant by staged games being one-to-one transactions. The functions inside ClientGUI responsible for adding data to the information object passed back to the server side are:

  • addInfo
  • addBooleanInfo
  • addFloatInfo
  • addIntInfo
  • addStringInfo

These are local functions built into the ClientGUI object, and do not require the extension to make an Information object to return.

GUI Addons

History Panel

To include the history panel in the client GUI, add a ClientHistoryPanel to the GUI where it is needed. By default the ClientGUI object contains an instance of this object called clientHistory. It can be used by adding this object to any container displayed in the GUI. In order to update a ClientHistoryPanel manually (i.e. before a round is over), use the method updateHistory (see the API Documentation for details).

Chat Panel

To allow clients to chat with each other, a ClientMessagePanel should be added to the GUI layout. The ClientGUI has a built in instance of this object called clientMessages. To display the message panel on the screen, add this object to a container displayed in the GUI.

Asynchronous Extensions

The explanation of Asynchronous Extensions has been moved to its own page.

Advanced Topics

These are topics generally unneeded in most games that require some extra effort and precaution to implement.

Sharing Info across groups/matches:

There are two Hashtables that resides in ServerVariables that are specifically for this use:

  • sharedData - stores data that can be accessed by all groups. It is flushed between matches, so it is not suitable for cross-match data storage.
  • experimentData - stores data that can be accessed by all groups and is maintained between matches.

There are a few precautions when using these hashtables, as when they are used improperly, it will result in thread errors. The proper, thread-safe way to be sure that sharing violations do not occur is to follow a simple procedure:

  1. Always put in data before ask* methods
  2. Always retrieve data after ask* methods

Keyword Parsing

Sometimes messages and other elements need customization, such as inserting player names or extra descriptors. This can be achieved by using the built in parsing capability of Multistage. You can read more about Keyword Parsing by clicking here: Keyword Parsing

Appendix

This section is to explain some concepts of game play and how it relates to writing the extension.

  • Round / Match: A match consists of one or more rounds. A round is conducted by a ServerControl object (the extension-specific class which extends ServerControl) within a group. Client groups are reshuffled before the beginning of each match, but not each round.
  • Group: The extension (a ServerControl object) administers a group. Multistage will run as many Extension objects as necessary to accomodate the number of groups. Groups are constant throughout all of the rounds of a match, but can change between matches.
  • Thread Safety: Multistage uses a large number of threads. This makes it easier to manage, but requires some precautions to make sure that two threads aren't writing to the same space at the same time.
  • Flow: Flow is the termed used to describe the organization and order of advancement. Currently, two types of flow are possible in multistage. The players can advance synchronously (staged) or asynchronously.
  • Synchronous (Unistage): All the groups begin each round at the same time. This means that if a group finishes a round before the other groups, that group will wait until every other group is finished. In general, a single Information object is sent to each client and a single Information object is returned (containing the events of the round). Importantly, there is a one-to-one relationship between the number of Information objects sent and received, and in the case of Unistage, there is only one Information object in each direction.
  • Synchronous (Multistage): This is identical to synchronous unistage except that more than one information object can be sent back and forth. It still will maintain a one-to-one relationship between objects sent and received.
  • Asynchronous: With synchronous flow, there is a one-to-one relationship between Information objects sent and received. This is not so with asynchronous. Asynchronous allows any number of Information objects sent in either direction. Objects will be sent back and forth until the certain conditions are met, so the extension writer has complete control over the flow of interaction between the clients and server.
Last modified 11 months ago Last modified on 10/18/2013 04:14:47 PM