cybot
Cycle Tutorial

Cycle is a simple Java (or C/C++) programming language for producing programs for Cybot from Ultimate Real Robots. It is a direct alternative to using the graphical programming "language" supplied on the CDs.

Hello World
The simplest program possible in any programming language is usually Hello World! Cycle is no exception, but as Cybot has no graphical display, we will make do with just making his antennae LEDs flash. The code for this is as follows:

// Hello World!

include "cybot.cyc"

proc main()
{
    antennae.toggle();
    delay.wait( 5 );
}

That's it! Not very long is it! In fact it could be shorter still, as line 1 is simply a comment telling you what the program is or does and the blank lines are simply there to make the code more readable.

The code proper begins at line 3, whereby we tell the Cycle compiler that we wish to use the definitions for Cybot. These simple tell the compiler what inputs (sensors we can interrogate) and outputs (things we can control) our robot has. These are stored in the file cybot.cyc. The reason these are in a separate file is because they are shared between all Cycle programs - if they need to be changed, then you simply update that file, you don't have to update all your programs individually.

All Cycle programs contain a procedure called main. The program starts with the first statement inside main. The start of the procedure is at line 5 and takes the form proc followed by the procedure name, followed by a set of parenthesis (round brackets). The body of the procedure (and because this is main, the program) is enclosed in braces (curly brackets). So in our Hello World example the body of main runs from line 6 to line 9.

The body of Hello World consists of a single statement, antennae.toggle(). All statements in Cycle are followed by a semicolon (;), unless the use braces ({}). This helps the compiler work out where one statement ends and the next begins. antennae.toggle() simply sets the antennae LEDs flashing (toggle is the name used in the graphical programmer for the flashing state).

The second statement delay.wait( 5 ) simply pauses for 5 seconds, after which the program terminates. Without the delay, the program would exit immediately without ever flashing the LEDs once!

To compile this program, simply type it into a text editor (e.g. Notepad) and save it as hello.cyc in the same folder as you installed the cycle compiler. Note that you should not type the line numbers, they are just to make the code to read. Having saved the file, open a DOS or Command Prompt and compile it with:

cycle -ocycle.03p hello.cyc

This will produce a new file called hello.03p. This file can be opened by Programmer 03 on CD2. You will need to copy it into <path_to_real_robots_2>\program files\Real Robots\Robot Programs. You should then be able to open it in Programmer 03 and you will see a graphical representation of our code and run it in the simulator.

Windows users can alternatively use the following command, where the -i switch causes the output to be installed into the Robot Programs folder directly:

cycle -i -ocycle.03p hello.cyc

Be careful using this, as like most compilers, any file with the same name will be overwritten without warning.

Loops
Most Cybot programs need to do things in a loop. For example following a line repeatedly reads the line following sensor and adjusts the speed of the motors to turn it to the left or the right. We'll start with the simplest form of loop, the infinite loop. Here the program repeats a set of actions over and over, for ever. The program will have the same function as Hello World, but will make the LEDs flash more slowly. Here's the code:

// Flash Cybot's antennae slowly

include "cybot.cyc"

proc main()
{
    while( true )
    {
        antennae.on();
        delay.wait( 1 );
        antennae.off();
        delay.wait( 1 );
    }
}

Save this as flash.cyc, compile it and run it. You should see that Cybot's LEDs flash on and off as 1 second intervals. So how does it work?

As before we include the Cybot definitions from cycbot.cyc and start the program with a main procedure. This time however the body of the program contains a while loop (line 7). This executes whatever is in the body of the loop while the condition is true. In our case the condition is always true, as it is the constant true, so the loop will continue forever.

The body of the loop (lines 9 to 12) is executed from top to bottom each time the loop repeats. The first statement, antennae.on(), at line 9 turns Cybot's antennae LEDs on, and the third statement, antennae.off() at line 11, turns them off again. The second and fourth statements, as lines 10 and 12 respectively, cause the program to wait. The 1 between the parenthesis is a parameter indicating how many seconds to wait for, in this case it is for 1 second each time.

Variables
Cycle programs can contain variables. These are "boxes" into which a number can be placed and later retrieved. Each "box" is denoted by a letter from 'a' to 'l' (lowercase 'L'). Variables can be assigned a new value, which can be either a number, or the contents of another variable. They can also be assigned a value calculated using a simple expression. An expression can be a simple addition e.g. a + 1, or subtraction, multiplication and division.

OK, so we can put a number in a box, very useful that! Well, there's slightly more to it than that - we can test the value in the box to see if some condition is true. We can test to see if the value is equal to another value, or greater than, less than, not equal to, etc. If we look back to the while loop example, we can see that the loop is executed while the condition is true, or to put it another way, the loop will stop executing when the condition becomes false.

Well, that's the theory, let' try an example. Say we want to flash Cybot's LEDs four times. We could use a variable to count the number of times we have flashed and stop when this reaches 4. In Cycle this can be written:

// Flash Cybot's antennae four times

include "cybot.cyc"

proc main()
{
    a = 0;

    while( a < 4 )
    {
        antennae.on();
        delay.wait( 1 );
        antennae.off();
        delay.wait( 1 );

        a = a + 1;
    }
}

This looks a little more complicated, but we have really only added three lines, and changed one, from the previous example. At line 7 we put the number zero in the box marked 'a'. In our while statement our condition now tests to see that the contents of variable 'a' are less than four. This will be true, as 'a' initially contains zero (from the previous statement). We therefore execute the body of the while statement, which will flash the LEDs once. At the end of the loop body we add one to the contents of 'a' and place the result back in box 'a' (line 16). This as the net effect of incrementing 'a' by one. We then go back to the beginning of the loop and test to see whether 'a' is still less than four - it is, so we flash the LEDs again and increment 'a' again. This happens until the value in 'a' reaches four. The condition of the while loop then evaluates to false and execution jumps to the next statement after the end of the body of the while, i.e. line 18. In this case, this is the end of the procedure, so it simply causes the program to terminate.

For Loops
Our while loop example is all well and good, but is a little long winded. Instead we can re-write this as a for loop, which does exactly the same thing, but in less code. A for statement consists of an initial assignment, followed by a condition, followed by an increment assignment, all of which are written at the start of the loop. So, our above example now becomes:

// Flash Cybot's antennae four times using 'for'

include "cybot.cyc"

proc main()
{
    for( a = 0; a < 4; a++ )
    {
        antennae.on();
        delay.wait( 1 );
        antennae.off();
        delay.wait( 1 );
    }
}

I'm sure you'll agree this is much more compact way of writing things. You may have spotted that the increment has been written a++ instead of a = a + 1 - this is an common abbreviation inherited from C.

If Statement
Sticking with our flash program, we could re-write the code again to use an if statement to control the proceedings. The for example is about as compact as we can make it, but to illustrate the use of if, we can write the same program as:

// Flash Cybot's antennae four times using 'if'

include "cybot.cyc"

proc main()
{
    a = 0;

    while( true )
    {
        if( a == 4 )
        {
            cybot.stop();
        }

        antennae.on();
        delay.wait( 1 );
        antennae.off();
        delay.wait( 1 );

        a = a + 1;
    }
}

The only difference here is that we are testing the inverse of the while condition using if and that we exit the loop with cybot.stop(), which simple terminates the program. Programmers of other languages should note that the braces ({}) are mandatory on if, for and while statements in Cycle.

Controlling Outputs
Cybot has other outputs that it's antennae LEDs, the most important being the two motors. These are controlled using motors.setSpeed( left, right ). Here is a simple program to drive forward for 1 second, turn left slowly for 1 second and finally turn sharp right for 1 second before stopping:

// Motor control

include "cybot.cyc"

proc main()
{
    motors.setSpeed( 2, 2 );
    delay.wait( 1 );
    motors.setSpeed( 1, 2 );
    delay.wait( 1 );
    motors.setSpeed( 2, 0 );
    delay.wait( 1 );
}

Line 7 simply sets both the left and right motors to speed 2. If you remember from CD1, in Programmer 02 the motors can have speeds from -4 (full speed backwards) to 4 (full speed forwards). These same values apply in Cycle, so we are setting both motors to about half speed forwards. Line 8 waits for 1 second before line 9 sets the left motor to run slightly slower than the right, resulting in a slow turn to the left. After another wait at line 10, we set the left-hand motor to run at half speed and stop the right-hand motor, resulting in a tight turn to the right. Note that we need another wait at line 12, otherwise we would immediately exit the program!

The motor speeds are quite easy to remember, but if you prefer, you can use names for the numbers -4 to 4. These are Motors (with a capital 'M') followed by FFAST (4), FMED (3), FSLOW (2), FVSLOW (1) and STOP (0). To go backwards the names are the same, but prefixed by a 'B' instead of an 'F'. So the above program becomes:

// Motor control 2

include "cybot.cyc"

proc main()
{
    motors.setSpeed( Motors.FSLOW, Motors.FSLOW );
    delay.wait( 1 );
    motors.setSpeed( Motors.FVSLOW, Motors.FSLOW );
    delay.wait( 1 );
    motors.setSpeed( Motors.FSLOW, Motors.STOP );
    delay.wait( 1 );
}

For controlling the motors, this is not as readable, but names can be used in place of values almost anywhere, and are often a good idea to improve the readability of your programs.

Reading Inputs
Having mastered simple control of the robot, we would now like our programs to be able to react to the various sensors (light, sonar and line following) on the robot. Lets start with a rather unsophisticated program which steers towards light:

// Follow light (simple)

include "cybot.cyc"

proc main()
{
    while( true )
    {
        switch( light.getStatus() )
        {
        case Light.LEFT:
            motors.setSpeed( -2, 4 );
            break;
        case Light.SAME:
            motors.setSpeed( 4, 4 );
            break;
        case Light.RIGHT:
            motors.setSpeed( 4, -2 );
            break;
        }
    }
}

Here we use a new kind of statement, the switch statement and its associated parts, case and break. What switch does is take a value (in the brackets) and compare it against each of the case clauses in turn. When it finds a match, the code between the ':' of the case and the break is executed. After that, execution continues at the next statement after the closing brace (}) of the switch.

In the above example we are reading the light sensor using light.getStatus() and depending upon its value, we decide what to do. If the value is LEFT (1), then we turn to the left (lines 11 to 13), if it is RIGHT (3), we turn to the right (lines 17 to 19), and if it is SAME (2), we drive straight on (lines 14 to 16). As we do this in a loop, the program will make Cybot continually hunt for the brightest light and drive towards it.

.

The fundamental flaw with the above program is that Cybot will keep driving even if there is an obstacle in the way. Time to look at using Cybot's Sonar sensors to avoid objects.

Reading More Than One Input
In order to cope with Sonar, we need to be able to read more than one input at a time, as the Sonar is organized as two separate sensors. This is not difficult, but requires that we place one switch inside another:

// Avoid.cycle

include "cybot.cyc";

proc main()
{
    while( true )
    {
        switch( sonar.getRightRange() )
        {
        case 1:
            switch( sonar.getLeftRange() )
            {
            case 1: motors.setSpeed( -2, -2 ); break;
            case 2: motors.setSpeed( -2, 0 ); break;
            case 3: motors.setSpeed( -2, 1 ); break;
            case 4: motors.setSpeed( -2, 2 ); break;
            }
            break;
        case 2:
            switch( sonar.getLeftRange() )
            {
            case 1: motors.setSpeed( 0, -2 ); break;
            case 2: motors.setSpeed( 0, 2 ); break;
            case 3: motors.setSpeed( 2, 3 ); break;
            case 4: motors.setSpeed( 2, 4 ); break;
            }
            break;
        case 3:
            switch( sonar.getLeftRange() )
            {
            case 1: motors.setSpeed( 1, -2 ); break;
            case 2: motors.setSpeed( 2, 1 ); break;
            case 3: motors.setSpeed( 2, 3 ); break;
            case 4: motors.setSpeed( 3, 4 ); break;
            }
            break;
        case 4:
            switch( sonar.getLeftRange() )
            {
            case 1: motors.setSpeed( 2, -2 ); break;
            case 2: motors.setSpeed( 3, 2 ); break;
            case 3: motors.setSpeed( 4, 3 ); break;
            case 4: motors.setSpeed( 4, 4 ); break;
            }
            break;
        }
    }
}

Here we use sonar.getRightRange() in an outer case statement, and for every value, we have an inner switch statement which uses sonar.getLeftRange(). In both cases the range goes from 1 (the nearest) to 4 (the furthest). From this you should be able to see that every combination of left and right range is accounted for, just as it was in Programmer 02 on CD1.

Well that's the basics, if you want to know more, then read the Advanced Tutorial as well. This explains the contents of cybot.cyc and how to write your own robot definitions.