Advanced Cycle Tutorial

This tutorial follows on from the Basic Cycle Tutorial. If you haven't already read this, then you may wish to do so. In this Advanced Tutorial we will see how the definitions in cybot.cyc relate to the code written in the Basic Tutorial.

Hello World Mk II
The simplest program possible in any programming language is usually Hello World! Whilst this may be true, we can complicate it by including the definition for World in our code! As before our Hello World program will make Cybot's antennae LEDs flash, but we will add the definition for the antennae from cybot.cyc:

 1:  // Hello World Mk II
 3:  class LEDs
 4:  {
 5:      output toggle() : block( 9, "Toggle" );
 6:  }
 8:  LEDs antennae;
10:  proc main()
11:  {
12:      antennae.toggle();
13:  }

The first piece of code now is a declaration of a new class called LEDs. Classes are used to define aspects of a robot. In this case, we are describing an output device which is a set of LEDs. A new class is declared with the keyword class followed by a unique name for that class and a body enclosed in braces (curly brackets). By convention, all class names start with a capital letter. This helps distinguish them from procedure names, which begin with an initial lowercase letter.

The body of a class is made up of a list of member definitions, which describe what the class can do. Members can be inputs (sensors), outputs (things like motors) or processes (neither inputs nor outputs, but things which take time to execute, like wait). At line 5, we declare a new output called toggle. A member declaration is one of input, output or process, followed by a unique name (by which that member can later be referred), followed by a set of parenthesis (round brackets), which can contain parameter names, followed by a colon (':') and a member definition. The definition describes which type of block it output into the .03p file, and is written as block followed, in brackets, by a number indicating the type of block (in this case 9 for LEDs), and then a list of comma separated parameters specific to the block being created (in this case the string "Toggle"). The definition is terminated by a semicolon (';').

What does this mean? Well, where-ever we make a call to toggle(), the compiler will output an LEDs block with the action set to Toggle. So how do we call toggle()? We need an instance of LEDs to call it from.

Where a class declares a type of sensor or output, an instance declares a specific sensor on a particular robot. We can have more than one instance of LEDs, each relating to a particular set of LEDs, say one for the antennae and one for another set of LEDs in the center of each wheel hub. This approach lets us define each aspect of a robot once using a class, and the create instances for each use of that aspect e.g. one for each Sonar sensor.

In out example, line 8 declares a new instance of LEDs called antennae. An instance declaration is written as the name of the class from which to create an instance, followed by a unique name for the instance being created. Again, by convention, an instance name starts with a lowercase letter. We will see later how it is possible to parameterize this, so that each instance created is slightly different.

Finally, if we look at the main program, we see that at line 12, we use the instance of class LEDs called antennae to call the member toggle.

Expanding The Class Declaration
If we now look at the complete declaration of LEDs, we can see that it has two more output members, on, which turns the LEDs on, and off, which unsurprisingly turns them off again.

 1:  class LEDs
 2:  {
 3:      output on()     : block( 9, "On" );
 4:      output off()    : block( 9, "Off" );
 5:      output toggle() : block( 9, "Toggle" );
 6:  }

The way this works is that each member passes a different parameter to create a block set up to perform a particular action. By grouping these together into one logical unit which describes what actions you can perform on a set of LEDs, we can produce something which is useful and above all re-usable.

Passing Parameters To Members
We will now examine how we can vary what happens when we call a particular member by passing parameters. Take the call to delay.wait() that we used in the Basic Tutorial. Here we could pass in a number which was the number of seconds to pause for, before continuing. So how was this implemented? Well, we can alter what is used to create the block by using parameters:

1:  class Timer
2:  {
3:      process wait( secs ) : block( 3, secs );
4:  }
6:  Timer delay;

Here we see that at line 3, a process called wait is being declared. However, instead of the empty parenthesis we had in our example above we see that wait can take a single parameter. This parameter can be referred to through the name secs and we use it in the member definition so that it is placed in the Delay block in the .03p file. This means that is we call delay.wait( 2 ), then the value of secs will be 2, and the member definition effectively becomes block( 3, 2 ). What is more, if we used some other value than 2, that too would find its way into the block.

Parameters can be used with inputs, outputs or processes and there is no limit to the number of parameters you can use, as long as the number of parameters passed in the call is the same as the number of parameters in the member declaration. Parameters are not limited to being number either, they can be strings as well.

Inputs are very similar to outputs and processes, but they have an optional part which helps the compiler to produce valid output for switch statements, particularly those that use a default clause. Take the following input declaration:

1:  class Light
2:  {
3:      input getStatus() : block( 10, 1, 1 );
4:  }
6:  Light light;

Here we have declared an input called getStatus, which would normally access with code like this:

 1:  proc main()
 2:  {
 3:      while( true )
 4:      {
 5:          switch( light.getStatus() )
 6:          {
 7:          case 1:
 8:               motors.setSpeed( -2, 2 );
 9:               break;
10:          case 2:
11:               motors.setSpeed( 0, 0 );
12:               break;
             // ...
14:          }
15:      }
16:  }

How does the compiler know how may input values there are, so that it can both determine valid values for the case statements. Also if we don't supply enough case values for all the possible inputs, or we use a default clause, how does the compiler know which inputs to link to the next statement? The answer is to tell the compiler the maximum and minimum acceptable values:

1:  class Light
2:  {
3:      input{1,3} getStatus() : block( 10, 1, 1 );
4:  }
6:  Light light;

At line 3, you can now see the addition of a pair of numbers in braces after the input keyword. The first is the minimum value, the second is the maximum, so in this example the values 1, 2, and 3 are all permissible.

There is one other kind of member which it is possible to declare, and that is the constant. A constant is simply a number or a string which is accessible through a unique name. Constants are useful as they prevent code from depending upon the absolute values used, so if at some later date the numbers or strings change, only the constant declaration needs to be changed and not all of your code. Constants are declared using the const keyword:

1:  class Light
2:  {
3:      const LEFT  = 1;
4:      const SAME  = 2;
5:      const RIGHT = 3;
7:      input{1,3} getStatus() : block( 10, 1, 1 );
8:  }

Here LEFT is defined to be 1, and where you would normally use a number, you could equally use Light.LEFT, in this case it would be in a switch statement, but constants can also be used in place of parameters. Note that the name of the class is used to denote where the constant is defined, and not the name of an instance.

Passing Parameters To Classes
You may have asked yourself earlier how, if we create two instances of a class, they can come to represent two different physical sensors. Well, in short, they can't, at least not without using some kind of parameters, as we saw using members. This is exactly how they work, when we create an instance we can specify a list of parameters which may be used in some or all of the members of the class.

In the same way that a member declaration has a list of parameters, a class declaration may optionally have a parameter list too. A simple example of this is an alternative way of implementing Cybot's Sonar sensors. The existing code is as follows:

1:  class Sonar
2:  {
3:      input{1,4} getLeftRange()  : block( 6, "Left" );
4:      input{1,4} getRightRange() : block( 6, "Right" );
5:  }
7:  Sonar sonar;

This can be modified to use class parameters, so the same code can now be written as:

1:  class Sonar( side )
2:  {
3:      input{1,4} getRange() : block( 6, side );
4:  }
6:  Sonar leftSonar( "Left" );
7:  Sonar rightSonar( "Right" );

As you can see, line 3 has been changed so that the class now takes a parameter, side, which will be either "Left" or "Right". Now, to create an instance of the class Sonar we pass a list of parameters in parenthesis after the instance name. So in lines 6 and 7, we are creating an instance, leftSonar, in which side will be "Left", and another instance, rightSonar, where it will be "Right". The code which makes use of these input members is now slightly different. Instead of calling sonar.getLeftRange() we now call leftSonar.getRange(). Both of these are perfectly valid ways of implementing the same thing, but the former was chosen as it more closely mimics the use of the motors.