wiki:Java Client Tutorial

Version 6 (modified by jherwitz, 13 years ago) (diff)

--

Java Client Tutorial

This tutorial deals with development and usage of the Quagents Java platform. For further information on classes, member functions, etc., see the Javadoc located in the Quagents3 download.

Front-end Development

This section details front-end Quagents development. This mostly takes place in the quagents3.main package.

Creating a Scenario

All scenarios must extend the abstract Scenario class. This implies that several functions and a constructor are required for every new scenario.

First let us discuss the ocnstructor. The Scenario constructor takes two arguments, a map name and a set of eligible maps to execute the scenario on. The set of maps is in the form of a string array, with each string being the name of the map as quake will recognize it. If the developer wishes to take advantage of the Quagents random map generator, simply put "randommap" as an element of this array.

public static final String[] maps = {"randommap", "delta","firstroom", "hurdles", "largeroom",
	"medroom", "smallroom", "maze1"};
private static final String scenarioname = "Sandbox";
//...
public Sandbox(){
		super(scenarioname,maps);
	}

The developer must also define a few specific functions for the Scenario class to be implemented. These are procedural, and the Scenario object will execute them automatically in a logical order to initialize the scenario correctly.

The configure function allows the developer to make server modifications, in order to enable any fundamental rules. For example, to enable data logging in the CaptureTheFlag? scenario, we have:

	protected void configure() throws IOException{
		File configfile = new File(System.getenv("HOME") + "/.quagents3/quagents3/quagents-config.cfg");
		if(!configfile.isFile())
			configfile.createNewFile();
		else{
			configfile.delete();
			configfile.createNewFile();
		}
		Writer config = new FileWriter(configfile);
		config.write("LOC_FLAG 1");
		config.flush();
		if(logfilename!=""){
			config.write("LOC_FILENAME " + logfilename);
			config.flush();
		}
		if(lograte!=-1){
			config.write("NUM_FRAMES_PER_LOG " + lograte);
			config.flush();
		}
	}

This makes modifications to the server's configuration file, enabling the data logging changes in the server code.

The setGUI function allows the user to insert a new GUI to run alongside the Quagents3 scenario execution. If no GUI is desired, simply define this as a blank function. The CaveExploration? handles this function as such:

protected void setGUI() {
		if(explorers.length==0){
			Quagents.frame.setVisible(false);
			return;
		}
		Quagents.next = new CaveExplorationGUI(explorers,survivors,director);
		Quagents.frame.setName("Quagents3 " + scenarioname);
		Quagents.frame.add(Quagents.next);
		Quagents.frame.pack();
		Quagents.frame.setVisible(true);
		}
	}

The runQuagentCode function allows the developer an opportunity to start his quagents executing. Many times, this consists of a call to the Quagent interface's executeCode function. The CaveExploration? scenario once again provides an example:

protected void runQuagentCode(){
		for(int i=0; i<explorers.length; i++)
			explorers[i].executeCode();
	}

Finally, the initialize function allows the developer an opportunity to instantiate and initialize Quagents or any other miscellaneous scenario components. For instance, in CaveExplorer? we have:

protected void initialize() {
		for(int i=0; i<explorers.length; i++){
			explorers[i] = new CaveExplorer(i);
		}
		for(int i=0; i<survivors.length; i++){
			if(randwidth > 0)
				survivors[i] = new TrappedSurvivor(i,randwidth,randheight);
			else
				survivors[i] = new TrappedSurvivor(i);
		}
	}

These functions are executed automatically in the following order at startup:

  • configure
  • initialize
  • setGUI
  • runQuagentCode

See the previously created scenarios in quagents3.main for more example code.

Writing Quagent Code

Note: The following may vary depending on QuagentEntity? type, but the steps necessary are invariant and will only differ syntactically.

The first step in creating a Quagent is construction. This is accomplished by calling the constructor with a parameter of the Quagents3 server IP address, in string form. That is:

Client quagent = new Client("127.0.0.1");

Next, we have the initialization phase. The quagent does not spawn on construction. Instead, we have a buffered connection to the server in which we may modify certain initial attributes. Among these are in-game name, spawn location, and team. When these attributes are set (for information on syntax, see javadoc) the developer may call the ready function. This finalizes all initial attributes and spawns the Quagent in-game. Trivially, it is implemented as:

quagent.ready();

The quagent has now entered the live phase of the game, during which most commands are unlocked for use. While many of these exist, the usage is constant throughout. Generally, the user must create an object for the Command they are about to execute. Then, the developer must execute it through a quagent. There are two options for this, the execute function which blocks until the command is returned from the server, or the start function which is non-blocking. For instance,

Rotate r = new Rotate(180);
quagent.execute(r);

or

Rotate r = new Rotate(180);
quagent.start(r);

are both valid. Additionally, the Client class provides many convenience functions, which allow execution directly through the quagent, of the form:

quagent.rotate(180);

Data and exit codes returned by these functions may be accessed through member getter functions. Note that these do not block, so usage of the Command or Quagent waitForTerminate (which blocks for return) function will ensure updated data.

It is also possible to enable client-based periodic functions. These execute every specified period. In order to enable this execution, simply append the desired period (in milliseconds) to the end of the constructor or function. For example,

Facing facing = new Facing(100);

will execute a Facing command every 100ms. This functionality is useful for sensor simulator or obtaining periodic data on the scenario execution.

Back-end Development

This section describes back-end Quagents development

Modifying internal structures

Much of the client-server interaction and multithreading control is handled internally by the Java client. Any modification to these controllers (mostly located in Command, ProtocolZero, and ReaderThread?) must ensure that the server will accept/recognize the changes will equivalent functionality.

Commands

The developer may desire to create a new Command. There are two types of commands, ProtocolZero commands, which carry a one-to-one correspondence with the preexisting ProtocolZero commands within the server. Any modification to these must, in most cases, also include server modification, as all currently recognized server actions are spanned by the current set of client P0 commands. Most uses will be for ProtocolOne commands, which abstract away from the server and can provide arbitrary complexity. To implement a new Command, the developer must extend either ProtocolZero or ProtocolOne.

For P0 commands, the user must define four things:

  • A set of constructors which will be used to instantiate the command, specifically the parameters sent to the server.
  • A setOP function which will create the protocol message to send to the server. NOTE: This must be consistent with server protocol exceptions, defined in ProtocolZero.
  • A set of functions used by external sources to access data or exit codes returned by the server.
  • A setData function, used by the socket reader to send server return data. This is only valid for functions which have an associated data response. For example, for the Facing command we have:
public void setData(String params) {
		datastring = params;
		paramscan = new Scanner(params);
		viewangle[YAW] = paramscan.nextDouble();
		viewangle[PITCH] = paramscan.nextDouble();
		viewangle[ROLL] = paramscan.nextDouble();
	}

ProtocolOne comamnds are slightly different in their implementation. By design, these commands have no direct server interaction, handling all interaction through nested ProtocolZero commands. Only three components need to be implemented for this class of command:

  • A set of constructors which initialize the command and any subcommands.
  • An addChildren function, which adds all nested P0 commands to the children list. This is necessary for internal client verification and execution. An example function looks like:
protected void addChildren(){
		children.add(lookforward);  //rangefinder
		children.add(lookbackward); //rangefinder
		children.add(turnright);    //rotate
		children.add(turnleft);     //rotate
	}
  • A run function, which acts as a black-box executor for external processes.

For more information, see the CartesianSensor2D source, as well as its implementation in the CaveExplorer? class.

Implementing a new Entity

All entities must implement the same functions.

  • A getWriter function which returns the entity's socket writer.
  • A waitForTerminate function which blocks until server response.
  • An execute function which executes a command and blocks until server response.
  • A start command which executes a command but does not block for server response.
  • An addEventListener command which is fired when a command is returned.
  • Several internally-used functions. Many of these are trivial, but necessary for external black-box access. For examples, see the implementation in the Client class.

QuagentEntity?

In addition to the above, the QuagentEntity? must implement:

  • Another addEventListener command, which is used to flag significant in-game events, such as death and messages heard.
  • A getEventListener, which returns the in-game event listener.
  • A setQuagentID, which assigns an integer id to the quagent given by the server
  • A getQuagentID, which returns the above.
  • The initial phase commands.

GODEntity

The GOD entity must implement all Entity functions, as well as an addEventListener and a getEventListener function which is directly analogous to those used for the QuagentEntity? (with a different listener type).

The Client and GOD classes provide example implementations.