-
Notifications
You must be signed in to change notification settings - Fork 9
Programming
- To get started, you can play around with some of the example bots over at https://github.com/asmcup/bots.
- The SPEC.md file lists all instructions and IO codes, with some examples on top.
- When in doubt, consult the actual compiler and VM implementations.
- The Notepad++ syntax highlighting may be useful to have.
- Start asmcup.jar. You'll see a sandbox with a random world containing a robot loaded up, ready for you to mess with.
- Open the Code Editor from the Tools menu. You can also do this by pressing e.
- Write or paste some code. You can also save/load code to/from files, and if you want to use an external editor instead, there's the Reload Code option (Ctrl+R) that re-loads the last opened file from disk.
- In the code editor window, go to the Tools menu and click Compile. The compiler will run and either complain about problems with an error message, or finish compiling and leave a note at the bottom of the editor about the compiled program size.
- To actually run your program on the robot, select Tools -> Flash in the code editor. Your compiled program is now applied to the robot. Usually you'll want to do both these steps at once, which you can do with Compile & Flash (or Ctrl+E).
- The program is now running on the robot. To see what's going on inside it, open the debugger by selecting Tools -> Debugger in the main window (or just press d).
- In the debugger, you see the entire memory of the robot in the form of a memory pane, listing the current values of all memory cells (in hexadecimal). The memory cell with red text is where the program counter (i.e. the instruction to be executed next) is currently at, and the memory cell with a rectangle around it is the current stack pointer (i.e. where the next pushed byte will end up).
- You can see and manually change the motor, steering, laser strength and clock rate of the robot. Note that your changes will be overwritten whenever the robot manipulates these values.
- The debugger also shows how much battery the robot has left, what distance the last sensor beam was intercepted at, and how much gold your robot has collected since you started it.
- If your robot drove offscreen while you were looking at the debugger, you can press spacebar in the main window to re-center on it. You can also press c to lock the camera to the robot (press c again to toggle the lock).
- That's pretty much it for the programming workflow. Consider taking a look at the Evaluating page for an easy way to gauge the general prowess of your robot. And if all of this sounds/looks too complicated, there's the Genetics module where you never have to touch any code to create a robot!
We made a Notepad++ language definition that you can use!
asmLang.xml
It looks like this (indentation not included).
All instructions are bold and color-coded:
- Blue means elements are (overall) added to the stack (e.g.
push8
,dupf
). - Orange means elements are (overall) removed from the stack (e.g.
popf
,add8
). - Black means the amount of elements on the stack doesn't change (e.g.
jmp
,f2b
). - Red means "stack impact depends on the context" (only
io
).
Note that a float on the stack isn't treated differently from a byte: For example, the b2f
(byte to float) instruction is colored black because it takes one element off the stack and puts back another one back. It is very rare that you push/pop individual bytes of a float (or combine bytes into a float), hence this treatment.
The highlighting also recognizes the IO constants and valid numerals, as well as comments.
-
Needless to say, favor simple solutions and algorithms. Your program can only have < 256 instructions!
-
Find a way to keep your program organized. Since you only have jumps (i.e. the infamous gotos) to move around in the program, you can quickly end up with spaghetti code. The amount of labels (and comments) you can create is not limited, so use them to your advantage and keep them as descriptive as possible!
-
It may be worth it to allow your robot to do some dumb or imprecise thing (as long as it doesn't tend to get itself killed that way) by omitting edge case handling in favor of other, more substantial logic.
-
"Variables" (or "registers", depending on how you look at it) can be created by just labeling a part of the memory:
variableName: db8 #0
orfloatVariable: dbf 0.0
(or whatever initial values you would like). You can create as many as you like, but be aware that they (especially float ones) do take up your memory. Also: -
Since all functions and operations only interact with the stack, you shouldn't primarily rely on variables for your biddings. Compare these:
; Computes the sum of two bytes on the stack
sumFromStack:
add8 ; Yep, that's it! One byte of instruction(s).
a: db8 #0
b: db8 #0
; Computes the sum of two variables and stores it in the first
sumFromVariables:
push8 a ; 2 bytes
push8 b ; 2 bytes
add8 ; 1 byte
pop8 a ; 2 bytes
; This took 7 bytes in total!
Only use variables when it's necessary. The stack is your friend.
- It can be a powerful technique to use indirect jumps and label addresses as "function pointers" of sorts:
howToReact: db8 #0
...
eventReaction1:
<code>
eventReaction2:
<code>
... ; Determine which reaction you want to happen
push8 &eventReaction1
pop8 howToReact
... ; do stuff
jmp [howToReact]
-
You will probably branch and jump around a lot to create your desired program flow and decision making. Be very aware of what the stack looks like at every jump! If you jump back to your main loop/program start but left something on the stack, then your stack will slowly (or not so slowly) grow over time, until it spills into your program code and breaks things. Worse, if you pop too many things off the stack, your stack will "underflow" into the start of your program and quickly turn your entire memory into a mess. Finding the place where you went wrong can be annoying, so it's best to find a way to keep track of what you expect the stack to look like around every jump (or instruction). The Notepad++ language highlighting may help with this.
-
There's some example code for various ways to create function-like code over in the bots repository.
-
You can reduce the simulation speed to 0.5 in the World menu, or you can pause (p) the simulation and advance tick by tick (Single Tick in the World menu, or t). Note that an overclocked robot will still execute more than one instruction per (world) tick, but you should have an easier time finding out where things went wrong.
-
If you want to keep track of an individual variable but have trouble locating it, consider surrounding it with
db8 #0
or similar statements in your code, which should be easy to locate in the debugger window. -
As a last resort, you can attach a debugger to the actual jar (or, preferably, debug from e.g. eclipse) and trace the individual steps of the VM. You probably just wrote a
push8 var
where you meantpushf var
, but this way you'll find out for sure. Also, while we're pretty confident we have weeded out the bugs in the compiler/VM, it's not inconceivable that we missed something, so if you do find erroneous behavior, please raise an issue!
-
Once you start running low on program space, consider using relative instructions where possible:
push8r
,pop8r
,jnzr
. These take only 1 byte instead of 2, but they only work with addresses within 31 bits of themselves (in both directions). This method (and some of the ones below) are showcased in some example bots.-
For the sake of saving space, it may be useful to rearrange your code in such a way that (byte) variables are close to their usage (so you can
push8r
/pop8r
from/to them) and that branching jumps target nearby addresses (jnzr
). This may potentially make the code a lot harder to understand though, since the program flow may be harder to recognize. -
On this note, your loops are a great place to use
jnzr
. -
A byte constant takes just as much space when used directly (
push8 #VALUE
) as if it's extracted into a (nearby) labelled memory cell (CONSTANT_LABEL: db8 #VALUE
) and accessed viapush8r CONSTANT_LABEL
. The latter both eliminates a "magic number" and allows reusing the value. Remember, you can give the value as many names as you want (e.g.b00001100: TWELVE: MAX_STEPS: db8 #12
)! While you have to keep them close to their usage, you're never losing anything compared to the "direct usage" way. -
Creating a new byte variable (1 byte) near its usage with relative commands (1 byte per instruction) is likely cheaper than using one that is further away and needs absolute addressing (2 bytes per instruction).
-
-
If you want to push a float constant that is not predefined (i.e. one of the
c_f
functions) and can be approximated by a byte, usingpush8
with the closest byte and thenb2f
needs only 3 bytes of code instead of 5, it does take two instructions instead of one though. -
Also consider working in bytes where possible instead of floats to save variable size and stack space.