2.4. Building Classes

All objects in the simulated world correspond to programming objects in steve. In order to define an agent in the simulated world, you'll start by constructing a programming object, or class. This class will serve as a template which defines the agent's behaviors. This section describes how to construct and use these classes.

The section Defining Classes (Section 2.4.1) describes how to define an empty class.

All classes have two major components: methods, which define a class's behavior and variables which define the data that the class can hold. This data can be used both to store information about the state of the agent, or information required for computation. The section Defining Instance Variables (Section 2.4.2) details how variables can be added to objects, while the section Defining Class Methods (Section 2.4.3) shows how method are defined.

Two special methods are critical for an agents behavior: one that gets called automatically when the agent is created, init, and another which is run automatically at every step of the simulation, iterate. These methods, and a few other special methods are discussed in the section Special Method Names (Section 2.4.4)

Even after the class is defined, it will still not be present in the simulation. This is because a class is nothing more than a "template" for an agent. In order to bring agents into the simulation, you must use the template to create instances of the class. The section on Creating and Destroying Instances (Section 2.4.5) describes how instances of classes are created and destroyed.

2.4.1. Defining Classes

When building a class, you typically don't start from scratch—instead, you make the new class the child of an existing class. This is called creating a subclass. By subclassing a class, the new class will inherit all of the parent's methods and variables. This approach means that most of the difficult work will already be done by an existing breve class, and we can simply override and customize the behaviors that we need to.

For example, if we're building an object which will move through the 3D world, we'd like to have an object that understands the relationship between position, velocity and acceleration. Instead of implementing such a class ourselves, we can subclass Mobile.tz which is included with breve. Our custom subclass will contain the custom behaviors we desire, while the parent class takes care of the details.

When building a class, you must first decide the class name and its parent class. The parent class is the class from which the new class will inherit its behaviors. Classes which are to be used primarily for computation and do not require any special inherited behaviors, will typically use the generic root class Object. Classes which move around the world will inherit behaviors from Mobile, while immobile objects in the world will inherit behaviors from Stationary. A full list of classes is available in the appendix (Appendix A).

An empty class is simply defined by the following steve code:
parent_class_name : class_name {

}
Because we often deal with classes in their plural form (like when creating multiple instances of an object), it can be useful to give a class an alias which will allow us to refer to the class in its plural form. This is not required but may make code easier to read. This alias is defined by adding the text (aka alias_name) after the class name.

As an example of defining a class, both with and without an alias, consider a class called myMobile which will be a child of the class Mobile:
# first without the alias...

Mobile : myMobile {

}

# now with the alias...

Mobile : myMobile (aka myMobiles) {

}

This code above defines an empty class with no variables and no methods. This means that it will behave exactly as its parent class does. The next step is to customize the class' behavior by adding in methods and variables.

2.4.2. Defining Instance Variables

An instance variable is a variable associated with a class. Each instance of the class will have it's own private copies of the class' instance variables.

Once the empty class declaration has been written, variables can be added using the heading + variables, followed by the list of instance variables. Variables are listed in the format variable_name (variable_type).

The variable name must start with a letter, but afterwards may contain any alphanumeric characters, as well as the characters _ and -.

Multiple variables of the same type can also be declared on the same line:
    variable1, variable2, variable3, ... (variableType). 
Variable types are covered in detail in the section Types (Section 2.5).

As an example, we'll add some variables to the simple class we created in the previous section:
Mobile : myMobile {
	+ variables:
		myInt, myOtherInt (int).
		myObject (object).
		myFloat (float).
}

2.4.3. Defining Methods

The most simple method call in steve is a call to a method which takes no arguments. The definition of such an method is simple. Inside the definition of instanceName, we create a line:
+ to methodName:
The statements which follow this line will be part of the newly defined method until either the end of the object definition, or until the next method definition.

To define an method which takes arguments we will need the keyword, variable name and type of each argument. The keyword identifies the variable when it is being called, while the variable name is how the variable will be referenced from within the method. Finally, the type is simply the type of variable that will be passed in. The layout of this information is keyword variable_name (type), such that a method which takes one variable could be defined by the following line:
+ to set-velocity to-value theValue (float):
If the method takes two variables, we add another keyword/name/type triplet:
+ to set-rotation of-joint theJoint (Object) to-value theValue (float):

The code associated with the second method would then use the variables theJoint and theValue: "of-joint" and "to-value" are not actual variables, but instead the keywords which indicate which variables follows.

The calling convention of these methods is simple. After the instance name and method name, we give a list of keywords and values to be passed in. The order of the keyword/value pairs does not affect how the code is executed, though it may effect the readability of the code. The following lines call the set-rotation method which we defined above:
# the following lines are equivalent

myObject set-rotation of-joint myJoint to-value 200.
myObject set-rotation to-value 200 of-joint myJoint.

Methods may also have local variables associated with them. These variable definitions look just like class variable definitions, except that they follow after the method definition line, and not after the variable definition line. Method variables are automatically initialized to zero every time the method is called. Variable declarations in a method must precede all statements in the method.

For example, here is a simple method which uses local variables:
+ to find-closest-creature in creatureList (list):
	item (object).
	closestItem (object).
	distance (float).

	# we start with a unreasonably high "closestDistance" so that 
	# we are sure to find something closer.

	closestDistance = 1000000.

	foreach item in creatureList: {
		distance = |(self get-location) - (item get-location)|.

		if distance < closestDistance: {
			closestItem = item.
			closestDistance = distance.
		}
	}

	return closestItem.

NoteFor developer use only
 

When examining the internal classes included with the breve distribution, you might notice some methods defined using a minus sign instead of a plus sign:
- to methodName:
This syntax simple means that the method should be treated as a non-public method and that the method should not be documented. Though these methods function as all other methods, their use in user simulations is discouraged.

2.4.4. Special Method Names

Certain method names have special meaning in steve, in that they are called automatically by the simulation at special times. These methods, in particular init and iterate are critical, as they are the entry-point into how your agents are initialized and how they will behave. These special method names are outlined below:

2.4.5. Creating and Destroying Instances

Creating a new instance of an object is called instantiating the object. Instantiating in steve is done using the new command. Instantiation creates a single new instance if no number is given, or as many objects as you want by preceding the command with a number. The syntax follows:
new object_type.
number new object_type.

If a single object is created, it is returned as an object type. If several are created, they are returned as a list. For example:

myBird (object).
myBugs (list).

myBird = new Bird.
myBugs = 100 new Bugs.

The method init is called automatically for a class and all of its superclasses when the class is instantiated.

Destroying instances is simply accomplished using the command free:
free instance.
free accepts both single instances and lists of instances.

If an instance frees itself, then execution of the code is stopped immediately, as though the free command was followed by a return.

When an object is freed, the destroy method is automatically called for the instance. Prior to version 1.9, destroy would automatically be called for all superclasses. This is no longer the case.