Defines: Making RoboCom programs prettier

An Introduction by Florian Fischer

About this document

This document explains defines, a new language concept in RoboCom 3, and shows some of their uses.
There's also a really useful application of defines at the end of this page which makes your life easier when programming comparisons (a replacement for the xCOMP instructions).

What are Defines?

Defines are RoboCom's implementation of a pre-processor, and they're similar to the macros in programming languages like C. That means that robots don't know about defines and never execute them, instead, defines are replaced by their contents during program assembly. Defines can be used to give constants (e.g. bank numbers, ID codes, or variables used for scanning) names, but they can also hold complete functions, and they can even accept parameters.

The theory

To use defines in RoboCom programs, they need to be declared first. This is done with the Define command, which can be used above the first bank, or between any two banks. An example:

...
Published Country Wherever

Define &ScanVar { #5 }

Bank Main
...
This example declares the define &ScanVar (Defines always start with the & character) to its value #5 (or whatever you write inside the brackets). Later on, the define can be used:
...
Scan    &ScanVar
Comp    &ScanVar,1
...
To use the previously declared define, just insert its name into the program. RoboCom will replace it with its contents (here #5) during assembly, and your robot will just execute Scan #5, as before.
Note: Just like labels, defines must be unique in the whole ROB file, i.e. they cannot be redefined later.

Some practice

Let's start with some simple uses of defines. Here's an example flooder robot, first in a version without defines:
Listing of FlooderOldStyle.rob
Published Name Old-Style Flooder
Published Author Robert Robot
Published Country Nomansland

Bank FlooderMain
@Begin
 Scan   #5          ; Scan for enemies
 Comp   #5,1
  Jump  @NoEnemy    ; No enemy detected
 Trans  2,1         ; Kill enemy
 Turn   0
 Jump   @Begin      ; Look around
@NoEnemy
 Create 2,2,1       ; Create a new bot
 Trans  1,1         ; Transfer main bank
 Trans  2,2         ; Transfer kill bank
 Set    %Active,1   ; Activate new bot
 Turn   0
 Jump   @Begin      ; Look around

Bank Kill
Die
You can see that the program is not exactly verbose about what it's doing. For example, you have to remember yourself what each bank is doing and where it is located, what purpose your variables have, what Turn 0 means, etc.
And if you'd like to reorganize your bot at some later time to run on bank two and have his killing bank elsewhere, you've got to go all through the code and replace the numbers.

Now, let's use defines to rewrite the bot:
Listing of FlooderDefines.rob
Published Name Flooder with Defines
Published Author Robert Robot
Published Country Nomansland

Define &NumBanks { 2 }
Define &RunBank { 1 }
Define &KillBank { 2 }
Define &ScanVar { #5 }
Define &Left { 0 }

Bank FlooderMain
@Begin
 Scan   &ScanVar             ; Scan for enemies
 Comp   &ScanVar,1
  Jump  @NoEnemy             ; No enemy detected
 Trans  &KillBank,1          ; Kill enemy
 Turn   &Left
 Jump   @Begin               ; Look around
@NoEnemy
 Create 2,&NumBanks,1        ; Create a new bot
 Trans  &RunBank,&RunBank    ; Transfer main bank
 Trans  &KillBank,&KillBank  ; Transfer kill bank
 Set    %Active,1            ; Activate new bot
 Turn   &Left
 Jump   @Begin               ; Look around

Bank Kill
Die
Now if you'd like to move the kill bank somewhere else within the robot, or give it more banks, all you've got to do is changing the define.

Longer Defines and Macros

While one-line (actually one-number) defines are certainly useful, there's so much more that can be done using defines. So let's have a look at multi-line defines.

...
Define &Reboot { BJump 1, 1 }
Define &ChangeRow { Turn 1
            Move
            Turn 0 }

Bank Test
 Move
 Move
 Move
 &ChangeRow
 &Reboot
...
While this example probably isn't really useful, it demonstrates that defines can consist not only of numbers, but of whole instructions or even of several instructions.
You should keep in mind, however, that defines are not functions: Whatever you write inside the brackets, the RoboCom assembler will just replace your define with it. That's why the bank Test will still have 7 instructions inside your robot (you can prove that using the debugger).

To make defines even more configurable, they can have parameters, and they can be nested. As an example, let's reimplement the MUL instruction using only RoboCom 2 commands:

...
; This define is nested.
Define &Left { 0 }
Define &TurnAround {
  Turn &Left
  Turn &Left
  Turn &Left
  Turn &Left
}

Define &MyConst { 7 }

; This define requires two parameters.
Define &MUL(?var, ?factor) {
  Set    #20, ?factor ; Count down in #20
  Set    #19, ?var    ; Store original value in #19
  Add    ?var, #19    ; Add #variable to itself
  Sub    #20, 1       ; Count down
  Comp   #20, 1       ; repeat ($factor - 1) times
   Jump  -3
}

Bank One
  Set #1, 5           ; Calculate 5*MyConst in #1
  &MUL(#1, &MyConst)
  &TurnAround
...
Nested defines are quite simple: just use another define inside a define. If your recursion becomes too deep, the RoboCom assembler will not accept your program.

Defines with parameters are declared just like functions in many other programming languages: after the define name, inside a pair of brackets, the parameter list is given.
They are used in the same way (unlike normal instructions): their parameters are enclosed in a pair of brackets. The brackets are needed because you can have nested parameter lists with nested defines.
All Define parameter names have to start with a question mark, and the parameter names should only consist of letters and numbers.
Wherever these parameter names occur inside the Define, they are replaced with the words after the define call.
So in the example above, ?var will be replaced with #1, and ?factor with &MyConst, which will in turn be replaced with 7.
Up to ten parameters are allowed for each define.

Final words

Using defines, programs in RoboCom 3 can be written prettier, are easier to read and easier to modify. However, you should watch for the following pitfalls:

 

Bonus

Are you still there? Having read all these lengthy explanations? Or have you just accidentally scrolled down here? Never mind, let's continue with something really useful.

The problem: COMP et al.

Have you ever got annoyed about the cumbersome comparison instructions RoboCom provides? Well, it happens to me about every time I've got to use them.
Unlike most other programming languages, where comparisons (usually called IF) happen to execute the next line of code if they are true, in RoboCom, the next line is only executed if the comparison is false. Another problem is that while there are six useful numerical comparisons (less than, less or equal, equal, not equal, greater or equal, and greater than), RoboCom has only got four comparison instructions.

The solution: &IFxx

Using Defines, it is quite easy to emulate the normal IF behaviour. The following defines provide the six mentioned comparisons:
Define &IFEQ (?op1, ?op2) { NCOMP ?op1, ?op2 }
Define &IFNE (?op1, ?op2) { COMP ?op1, ?op2 }
Define &IFLT (?op1, ?op2) { GCOMP ?op1, ?op2 }
Define &IFLE (?op1, ?op2) { LCOMP ?op2, ?op1 }
Define &IFGT (?op1, ?op2) { LCOMP ?op1, ?op2 }
Define &IFGE (?op1, ?op2) { GCOMP ?op2, ?op1 }
To use them in your programs, just copy them between the headers and the first bank. Now, for example, you can write the following:
&IfEq(#1, 2)
  Scan #5  ; this instruction only if #1 == 2
Trans 2, 2 ; this instruction is always executed
There's still a little problem: &IFxx only give you one instruction to do your comparison-dependent stuff. Mostly, you will need more than one, so you'll have to jump elsewhere in your program. Many assembler languages (RoboCom is quite like Assembler) have conditional jumps for this purpose. Well, just read on...

Conditional jumps: &Jxx

Conditional jumps are really easy to do now: let's just combine the comparison and the jump into one define.
Define &JEQ (?c1, ?c2, ?label) {
  &IfEq(?c1, ?c2)
    Jump ?label                }
Define &JNE (?c1, ?c2, ?label) {
  &IfNe(?c1, ?c2)
    Jump ?label                }
Define &JLT (?c1, ?c2, ?label) {
  &IfLt(?c1, ?c2)
    Jump ?label                }
Define &JLE (?c1, ?c2, ?label) {
  &IfLe(?c1, ?c2)
    Jump ?label                }
Define &JGT (?c1, ?c2, ?label) {
  &IfGt(?c1, ?c2)
    Jump ?label                }
Define &JGE (?c1, ?c2, ?label) {
  &IfGe(?c1, ?c2)
    Jump ?label                }
Now you can simply write &JEq(#1, 1, @enemyfound), and assuming that #1 holds a scan result, you have just checked if there's an enemy robot in front of you.
There's a small problem, however: &Jxx defines are actually two instructions. So if you don't use a label for the ?label parameter, but a number, you'll probably have to think twice about it...

The king of comparisons: &CMP

Now there's just one thing missing: the king of comparisons, which allows to distinguish between the three cases "less-than", "equal" and "greater-than" in one line. Even most higher programming languanges don't have this comparison, yet it is easy to implement in RoboCom:
Define &CMP (?c1, ?c2, ?less, ?equal, ?greater) {
  &JLt(?c1, ?c2, ?less)
  &JGt(?c1, ?c2, ?greater)
  Jump ?equal                                   }
Now you can handle a scan result with one line of code:
Scan #1
&Cmp(#1, 1, @nobody, @enemy, @own)
; this line of code is unreachable
Cool, ain't it? However, as RoboCom is kind of an optimization competition, be warned that &CMP expands to five basic instructions...