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 (the section called “Defining Classes”) 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 (the section called “Defining Instance Variables”) details how variables can be added to objects, while the section Defining Class Methods (the section called “Defining Methods”) 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 (the section called “Special Method Names”)
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 (the section called “Creating and Destroying Instances”) describes how instances of classes are created and destroyed.
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 (???).
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
after the class
name.
alias_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.
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 afterwords 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 (the section called “Types in "steve"”).
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). }
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
, such that a method which
takes one variable could be defined by the following line:
keyword
, variable_name
, (type
)
+ 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.
![]() | For 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. |
Methods definitions may also specify optional arguments. Optional arguments are arguments which are given default values, and therefore do not need to be provided when calling the method. Optional arguments can greatly simplify writing code in steve.
To make an argument optional, you need to provide it with a default
value. To do so, you'll need to modify the argument definition to
include the text =
after the argument name.
For example, a variable called value
theHeight
with keyword with-height
could be given a
default value like this: with-height theHeight =
100 (int)
. Default values for optional arguments must be literal
values (and not expressions or variables).
Below is an example of a method defined with a set of optional arguments.
# Create a new agent, with some default values. + to create-new-agent with-color color = (1, 0, 0) (vector) with-energy energy = 100 (int) with-radius radius = 10 (int) with-name name = "agent" (string):
The method above could be called in a number of ways, optionally including or excluding each of the arguments:
# no arguments self create-new-agent. # some of the arguments self create-new-agent with-energy 10 with-name "Becky". # all of the arguments self create-new-agent with-color (1, 1, 1) with-energy 100 with-radius 20 with-name "Robert".
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:
init
, if it exists, is called
automatically when a class is instantiated. The method is called
not only for the class being instantiated, but also for its
superclass and all other ancestors up to the root object. Though
you should implement an init method for your class which will set
up the instance when the class is instantiated, the init method
should never be called manually.
iterate
, if it exists, is called
automatically during every iteration of the breve engine. If your
class must perform a task during each iteration, then you may
choose to implement an iterate method. The order in which the
objects in the simulation are iterated cannot be
controlled—if you need to control the order in which
actions are performed, consider using iterate in conjunction with
the post-iterate method described below.
Unlike the init
and destroy
methods, iterate
is not automatically called for the
superclasses of an instance. This means that your iterate method
must call super iterate if you wish to incorporate the parent's
iterate method. This is absolutely necessary for subclasses of
Control.
post-iterate
, if it exists, is
called automatically during every iteration of the breve engine
after the iterate methods for all objects have been called. It is
occasionally desirable to perform an action during each
iteration, which requires data produced during that very same
iteration from other objects. If this action is to be performed
in the iterate method, than object A cannot be certain that
object B has been iterated yet (and vice-versa). To solve this
problem, objects may implement a post-iterate method which is
automatically called after all objects have been iterated. The
PatchLife demo uses this technique.
destroy
, if it exists, is called
automatically when a class is being freed. However, unlike init,
and like iterate, you must explicitly call the super class
destroy method if you need it to be called as well. If your class
needs to perform certain tasks before being destroyed, you should
implement this method. Be warned the you need to be carefull not
to free an object referenced in the base class if it is needed
for the base class destroy method.
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—you must call "super
destroy" if you wish for the superclass destroy method to be run.