Successful robots maintain a store of information, accessible at any time for making crucial decisions while in battle. This is useful for a variety of reasons, from enemy movement pattern analysis to determining whom to attack based on proximity and strength. This tip explains how you can implement an effective, fast enemy cache while having the convenience of an always up-to-date object using polymorphism. This code is slated for use in the Rapture 3.0 robot.
Polymorphism is a key component of proper object-oriented programming and is a powerful tool you can use when creating robots. Polymorphism basically means that you have one common method that is implemented
in different ways, so you can simply call that method without worrying about the actual
class you're calling it in. In other words, if you call food.eat(), it may
actually call steak.eat() or salad.eat(), which behave differently, but you don't have to worry about it. They are both types of food.
The following code samples showcase how to implement polymorphism in Robocode while adding a great deal of functionality and flexibility. Figure 1 outlines the object structure used by this framework:
Figure 1. UML diagram
The advantages of this structure are obvious: you can acquire a reference to an
Enemy object through the EnemyManager, which actually will
return an EnemyLog cast as an Enemy. Because the EnemyLog object
delegates all responsibility inherited by the Enemy interface to the first object in the log, you know that when you are working with this object, you will be accessing the most recent information.
Listings 1 to 4 use this structure. You would normally want to make your Enemy interface more elaborate, as well as add more functionality to the EnemyManager object, but for learning purposes, we will keep the interface at a minimum. Happy hunting!
Listing 1. Enemy interface
package example.enemy;
// This class is used as an interface
// to define the common methods needed
// to define enemy information.
public interface Enemy {
// Returns the enemy's name
public String getName();
// Gets the energy that our enemy currently has
public double getEnergy();
// Gets the velocity of our enemy
public double getVelocity();
}
|
Listing 2. EnemyImpl class
package example.enemy;
import robocode.*;
// A class to hold this package's local implementation
// of the enemy interface.
class EnemyImpl extends Object implements Enemy {
// Members
private String m_name = null;
private double m_energy = 0;
private double m_velocity = 0;
// Constructor
private EnemyImpl() {}
// Set methods
private void setName( String in_newName )
{ m_name = in_newName; }
private void setEnergy( double in_newEnergy )
{ m_energy = in_newEnergy; }
private void setVelocity( double in_newVelocity )
{ m_velocity = in_newVelocity; }
// Get methods
public String getName() { return m_name; }
public double getEnergy() { return m_energy; }
public double getVelocity() { return m_velocity; }
// Method used to create an instance of an enemy impl object.
public static final Enemy createEnemy(
ScannedRobotEvent in_event ) {
EnemyImpl out_enemy = new EnemyImpl();
out_enemy.setName( in_event.getName() );
out_enemy.setEnergy( in_event.getEnergy() );
out_enemy.setVelocity( in_event.getVelocity() );
return out_enemy;
}
};
|
Listing 3. EnemyCollection class
package example.enemy;
// A class that maintains a stack of all the enemy information
// objects that we have collected for a certain enemy. It is
// managed by the EnemyManager and can be referenced as an
// Enemy to maintain a reference to the most recent
// information for a certain enemy.
class EnemyCollection implements Enemy {
/* constant which holds our maximum stack size - the number
of Enemy information objects to maintain */
protected static final int MAX_STACK_SIZE = 500;
private Enemy[] m_information = new Enemy[ MAX_STACK_SIZE ];
private int next = 0;
private int size = 0;
/* our constructor, which takes in the first object in
our collection, this ensures that any collection always
has at least one entry. */
public EnemyCollection( Enemy in_newEnemy ) {
add( in_newEnemy );
}
// Adds an Enemy to our stack of objects.
public void add( Enemy in_new ) {
size = (size == MAX_STACK_SIZE) ? size : size + 1;
m_information[next] = in_new;
next = (next + 1) % MAX_STACK_SIZE;
}
// Method used to get a enemy represented by an index,
// where 0 is the last element inserted.
public Enemy get( int in_index ) {
int index = next - in_index - 1;
if( index < 0 ) index += MAX_STACK_SIZE;
return m_information[ index ];
}
// Method that returns the number of Enemy object
// that we currently are storing.
public int size() { return size; }
// Enemy interface methods
/* NOTE: By having this class implement enemy, we can use it
* as an enemy object and be ensured that whenever it is
* updated, we always have the most recent information */
public String getName() { return get(0).getName(); }
public double getEnergy() { return get(0).getEnergy(); }
public double getVelocity() { return get(0).getVelocity(); }
}
|
Listing 4. EnemyManager class
package example.enemy;
import robocode.*;
import java.util.*;
// a class which is used to manage and maintain
// all of our enemy information objects.
public class EnemyManager {
/* holds a reference to a SINGLETON instance of this class
to ensure that it only ever gets instantiated once */
private static EnemyManager SINGLETON = new EnemyManager();
/* holds a map of all our enemy information objects
as values with the enemies name's used as keys. */
private Map m_allInformation = null;
// private constructor used to hide unnecessary instantiation
private EnemyManager() {
// We want to use a Hashtable, since it is synchronized so
// we need not worry about data integrity for the most part.
m_allInformation = new Hashtable();
}
// Method which is used to get an instance of this class, as
// it exists.
public static EnemyManager getInstance()
{ return SINGLETON; }
// Method use to log an enemy object in the manager
public Enemy log( ScannedRobotEvent in_event ) {
Enemy newEnemy = EnemyImpl.createEnemy( in_event );
EnemyCollection col = (EnemyCollection)m_allInformation.get(
newEnemy.getName() );
if( null == col ) {
col = new EnemyCollection( newEnemy );
m_allInformation.put( newEnemy.getName(), col );
} else {
col.add( newEnemy );
};
return col;
}
// Method used to get a dynamic enemy object. By dynamic
// this means, that calls to any of the methods will always
// get the most recent data. This does not necessary imply
// that the data is current, it's just the most current.
public Enemy get( String in_name ) {
return (Enemy)m_allInformation.get( in_name );
}
// Method used to get a reference to an enemy at a certain
// point in time.
public Enemy get( String in_name, int in_time ) {
EnemyCollection col = (EnemyCollection)m_allInformation.get(
in_name );
return (null == col) ? null : col.get( in_time );
}
// Method used to return the maximum number of cached items
// we store on any Enemy at any given time.
public static final int getCacheSize() {
return EnemyCollection.MAX_STACK_SIZE; }
};
|
- Read all of the
Secrets from the Robocode masters
. This page will be updated as new tips become available.
- Robocode's creator, Mathew Nelson, maintains the official Robocode site. This should be the first stop for anyone serious about Robocode.
-
RoboLeague by Christian Schnell is a league and season manager for Robocode. It ensures that all possible groupings indeed play their matches, manages the results, and produces HTML status reports.
- "Rock 'em, sock 'em Robocode" (developerWorks, January 2002) disarms Robocode and starts you on your way to building your own customized lean, mean, fighting machine.
- In "Rock 'em, sock 'em Robocode: Round 2" (developerWorks, May 2002), Sing Li looks at advanced robot construction and team play.
- New to Java? Check out "Introduction to Java programming" (developerWorks, November 2004), a tutorial that steps you through the fundamentals of Java language programming.
-
developerWorks: Hundreds of articles about every aspect of Java programming.





