Creating your own Environment Interface

Step 4: Create EIS Interface Package

We now will create a package within our project that contains all classes for the interface. It might be useful to choose the name of the package in such a way that it is clear that it contains the interface classes. Within this package we will create the 'main' class of the interface, and several classes for controllable entities. For instance we could name the package for the Hanoi interface hanoiei.

Step 5: Create Environment Interface

Within the package (created in Step 4) we now create a new file. This file will be the main class of the interface. Preferably name it in such a way that it's clear that it's the main file. In the case of the Hanoi game we have named it HanoiInterface. This class will implement all required functionalities for the Environment Interface.

First make sure that the class HanoiInterface extends AbstractEnvironment. Your class should be looking something like this;

package <name_of_package>;

// Imports, etc.

/**
 * Provides an interface to the Hanoi Tower game.
 *
 * @author <Name here>
 */
public class HanoiInterface extends AbstractEnvironment {
    // Methods and stuff go here.
}

The first methods to implement

Now we need to implement a couple of methods; initresetkill. These methods control the actual Environment. init is used to start the Interface for the first time. It should perform all actions required to set-up an environment and register the entities to it. reset should reset the environment to a start-state (think of resetting a level in a game). At last, there's the kill method which handles the killing of the interface. We will illustrate the use of these methods using the Hanoi interface.

Here's a typical init implementation. Notice that the Entity class is not yet created so you will get an error at this point.

public void init(Map<String, Parameter> parameters) throws ManagementException {
    // Prepare the game.
    reset(parameters);

    // Try creating and registering an entity.
    try {
        registerEntity("entity", new Entity(game));
    } catch (EntityException e) {
        throw new ManagementException("Could not create an entity", e);
    }
}

What happens here is that the reset method is called from within the init method. Since we simply want Hanoi to get to its starting position, we can call reset to achieve that. After that the game is in its starting position, we assign an entity named entity to the interface. We do this by creating a new Entity instance, which models a controllable entity (more about this in Step 6).

public void reset(Map<String, Parameter> parameters)
       throws ManagementException {

    // Build the world based on the provided parameters.
    List<Integerstart = new ArrayList<Integer>();
    Parameter p = parameters.get("discs");

    // Prepare game initialisation data.

    // ... [Parameters are being processed here, see the original file].

    // Instantiate the game.
    if (controller == null) {
        controller = new Hanoi(start);
          // ... [Additional actions to execute on launch].
    } else {
        controller.reset(start);
    }

    setState(EnvironmentState.PAUSED);
}

The reset function reads out the Parameters, which might influence the setup of the game. For the sake of clarity, we've left that specific part out from the guide (you can see it in the Hanoi repository). After everything is set, the data is passed to the constructor of the game (a new instance of Hanoi is made).

NB: Please note how the new Hanoi instance is being assigned to a private variable called controller. This allows the interface to easily perform actions within the game instance.

Caution: It's important to set the state of the Environment to PAUSED at the end, the environment should not yet start before the Observers have been attached. Whenever the environment starts too soon, errors might occur.

Another important method is the kill method; this method handles the event in which the Interface must be killed. This can be exiting the game, a click on the kill button in the IDE running the Interface, etc. In this method you most likely want to take care of closing the (optional) GUI, and closing down all other elements of the environment. An example is shown below (for the Hanoi game);

@Override
public void kill() throws ManagementException {
    if (controller != null) {
        controller.exitGame();
        controller = null;
    }
    setState(EnvironmentState.KILLED);
}

The most important step here is to set the state of the Environment to killed. This should be the last action to execute. Close the GUI / environment before setting the state to killed. As you can see controller.exitGame() is called, this method does the actual closing of the game, since this completely depends on the way the game/environment is implemented, we won't discuss this here. The sourcecode can be found in the original repository.

Additional methods to implement

Since we're extending AbstractEnvironment we need to implement two additional methods; isSupportedByEnvironment and isSupportedByType. These two methods can (for example) be implemented as following;

/**
 * Returns true if the action is supported by the environment.
 *
 * @return true if the action is supported by the environment
 */
@Override
protected boolean isSupportedByEnvironment(Action action) {
    if (action.getName().equals("move") && action.getParameters().size() == 2) {
        return true;
    }
    if (...) {
        return true;
    }
    return false;
}

Basically what this method does, is defining what actions are supported within the Environment. In other words; it defines whether triggering a specific action (for instance, move) cans be understood (and executed) by the environment. In the case of this example, we can see that we have one action specified, named move. We also see that there's another check on the size of the Parameters. This way it is ensured that upon execution of move, two Parameters are present.

Please note: it's not required to use the same structure as in the example. Main points is to return true whenever an Action is supported, and false otherwise.

The last method is isSupportedByType. In the case of Hanoi, this simply returns the result of the isSupportedByEnvironment method. However, there might be situations in which the type of an Entity determines the availability of an Action. An example is given below (from the carriage environment on eishub);

@Override
public boolean isSupportedByType(Action action, String type) {
    if (type.equals("robot") &&  action.getName().equals("push")) {
        return true;
    }
    if (...) {
        return true;
    }
    return false;
}

Handling environment windows

If your environment creates windows, special care should be taken when the user clicks the close button of the window. First of all, do not exit the JVM. Exiting the JVM will salso kill the agent platform if that happens to be running on the same JVM as your environment. Second, make sure you properly close down the environment including switching to the KILLED state, closing the windows and taking care of multi-threading.

Also, if your environment uses windows, it is considered user friendly to remember environment window positions and size (to avoid having a user to do this again and again when an environment is launched.)