Contents


Secrets from the Robocode masters

Tracking your opponents' movement

Select the best movement algorithm for each opponent

Comments

This article demonstrates a technique for selecting movement state based on past performance. To keep things as simple as possible, the example does not include long-term persistence of data (although you could add this functionality without too much effort). I'll be limiting the scope of this tip to a high-level overview of the code. If you would like a more detailed explanation of the classes and methods, refer to the source code comment documentation. The complete source code is available in Related topics.

The math utility class

Typically a bot relies heavily on a few mathematical algorithms. A good practice is to create a utility class with static methods for the most commonly used algorithms.

The utility class for this example is the BotMath class. This class contains a single method, calculateDamage(), for calculating the damage caused by a bullet based on the bullet's power.

Extending the AdvancedRobot class to provide publisher/subscriber support

As we add states, state managers, and other supporting classes to our bot, they will need access to different events. We could pass these events to the state managers and let them pass the events on to the individual states, but it would be more efficient to pass only relevant events directly to the objects that need them.

The ExtendedRobot class allows objects to sign up to receive only the events in which they are interested. The EventRegistry class contains the constants used to sign up for the different events. The abstract EventListener class defines the methods required to receive these events.

In addition to the standard Robocode events, the ExtendedRobot class generates three events required by both state managers and states. The enable, disable, and execute events are passed directly to any registered state managers, which are responsible to pass the events on to the active states. The enable() method is called at the beginning of each round; implementations of this method should initialize variables and sign up for any relevant Robocode events. The disable() method is called at the end of each round; implementations of this method should release resources and unsubscribe from any Robocode events for which the class is currently subscribed. The execute() method is called each turn, allowing the state managers and states to execute turn-based algorithms. Any class that needs to receive these events must implement the CommandListener interface.

Managing states

The StateManager class is responsible for choosing the best state. Each turn, its execute() method calls the isValid() method of the currently active state. If the state's isValid() method returns false, a new state is selected (this process may be used to switch states based on the number of opponents, etc.).

Most of the work involved in selecting a new state is handled by the StateManager's selectNewState() method. This method requests statistics data from each candidate state and evaluates the data to select the best state. The StateManager uses a two-stage approach to select the best state. First it attempts to use the state with the best win/loss ratio; if no suitable state is found, the StateManager selects the state with the lowest damage/time ratio.

States

The abstract State class defines the methods required for each state implementation. It includes the standard enable(), disable(), and execute() methods as well as some specialized methods for communicating with the state manager. The getStatistics() method returns a Statistics object with data about the state's past performance (for this example, the state manages a single Statistics object, but it could easily be modified to use a HashMap to look up statistics by opponent). The getName() method returns the friendly name of the state for use in debug messages. The isValid() method is responsible for determining when the state should be used -- it is called both during state selection and each turn after a state is selected.

Each State class is responsible for tracking how well it performed and for logging this data to its Statistics object.

I have included two simple example states in the source code: the TrackState and CannonFodderState classes provide example implementations of the abstract State class.

Statistics

The Statistics class is a simple container class for tracking how well a state performed. It contains variables for damage ratio (damage/time), the number of encounters in which the state has been used, the number of losses, and the number of wins. It also contains three helper methods for setting and retrieving this data. The update() method provides an easy means of setting the variables in the Statistics object. The getDamageRatio() and getWinLossRatio() methods return the statistics data for the state.

To prevent a single bad round from disqualifying a potentially effective state, statistics data is not considered valid unless the state has been used in at least three encounters (the Statistics object returns default data values during this evaluation period).

Putting it all together

The main robot class carries much of the responsibility for setting up the state managers and states. Each round it must create the state managers, add states to the state managers, register the state managers as command listeners, and call the ExtendedRobotenable() method to activate the command listeners. The main robot class is also responsible for calling the ExtendedRobotexecuteTurn() method every turn (this method calls the execute() method of all registered command listeners). At the end of each round, the main robot class must call the disable() method to allow command listeners to shut down gracefully.

A typical run() method for a multi-stated bot is shown in Listing 1:

Listing 1. Typical run() method for a multi-stated bot
public void run() {
        try {
            // Set up and enable the state manager
            StateManager navigation = new StateManager(this);
            navigation.addState(new CannonFodderState(this));
            navigation.addState(new TrackState(this));
            addCommandListener(navigation);
            enable();

            // Set turret to move independent of body
            setAdjustGunForRobotTurn(true);

            // Main bot execution loop
		    while(true) {
                // Spin gun
                setTurnGunRightRadians(Math.PI);
                // Allow StateManager to do it's thing
		    	executeTurn();
                // Finish the turn
                execute();
	    	}
        } finally {
            // Disable the State Manager
            disable();
        }
    }

There it is. I realize this is rather complex to implement, but it offers two large ongoing advantages. The architecture is very extensible -- you can enhance the functionality just by adding states. In addition, the advantage of always choosing the best state makes a bot much more dynamic -- not to mention difficult to defeat.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java development
ArticleID=204588
ArticleTitle=Secrets from the Robocode masters: Tracking your opponents' movement
publish-date=05012002