An Introduction by Florian Fischer
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).
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.
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.
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 |
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 |
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.
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.
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 executedThere'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...
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.
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 unreachableCool, ain't it? However, as RoboCom is kind of an optimization competition, be warned that &CMP expands to five basic instructions...