Subroutines are callable sections of Robotic code, somewhat like functions but not as fully featured. They are accessed the same way as labels, and as such can't explicitly accept parameters or return values, like true functions could. Even so, they are an extremely useful way of organizing and managing Robotic, and are a very real extension of MZX capabilities, not just a shortcut for cleaner code. Subroutines were added in MZX version 2.65
How To Use
Subroutines are basically labels with advanced functionality; any label beginning with the character '#' is treated as a subroutine. They can be called from any place in Robotic as a label might be, including goto, send, if statements, failure branches (e.g. take 4 GEMS "#toopoor"), and the '?' textbox command (see quirks below). Subroutines are different from normal labels in that each robot has a call stack that keeps track of which line was being executed when a subroutine is called. Using the special subroutine label "#return" sends a robot one level up its call stack, to the code it was previously executing. Since it is a stack, subroutine calls can be arbitrarily nested. One other special subroutine label, "#top", sends the robot to the very top of the stack, where the first subroutine was called.
This is best understood through an example.
set "$str" "I am a robot with subroutines." goto "#write" goto "#sub1" goto "#sub2" set "$str" "The end." goto "#write" end : "#sub1" set "$str" "This is subroutine 1. It calls:" goto "#write" goto "#suba" goto "#subb" goto "#subc" set "$str" "This line is never seen." goto "#write" goto "#return" : "#sub2" set "$str" "This is subroutine 2. It calls:" goto "#write" goto "#subb" goto "#suba" goto "#subd" set "$str" "This line IS seen." goto "#write" goto "#return" : "#suba" set "$str" "Subroutine A" goto "#write" goto "#return" : "#subb" set "$str" "Subroutine B" goto "#write" goto "#return" : "#subc" set "$str" "Subroutine C" goto "#write" goto "#top" : "#subd" set "$str" "Subroutine D" goto "#write" goto "#return" : "#write" write overlay c0f "&$str&" at 0 "local" inc "local" by 1 goto "#return"
This produces the following output, written to the overlay.
I am a robot with subroutines. This is subroutine 1. It calls: Subroutine A Subroutine B Subroutine C This is subroutine 2. It calls: Subroutine B Subroutine A Subroutine D This line IS seen. The end.
Besides demonstrating the function of subroutine nesting and repeated calling, this example shows how to practically write a piece of code to perform a specific function. In this case, a short subroutine to write a string to the next available overlay row. It also demonstrates the use of a string (or a counter) as an implicit parameter, so that subroutines can be used somewhat like functions.
Quirks and Special Notes
- When subroutines were first introduced, they required the robot's call stack to be explicitly initialized. For this reason, in games made between version 2.65 and the port, robots that use subroutines contain lines like this:
- The MZX interpreter would use the first comment string in the robot beginning with #* as a static call stack, storing the jump locations in that string in two bytes each. The "-1-2-3..." portion of the string was a convention to remind the programmer how many levels deep the stack was, and that any subroutine nesting beyond that point would break the code. Since the port, each robot's stack has been dynamically handled internally by MZX, and can be arbitrarily large.
- When a robot sends itself to a subroutine (including when it uses a send or a send "all" command to do so), the stored return point is the command after the one which triggered the send. Otherwise a goto would cause an infinite loop on return, which would not be very useful. When a robot sends ANOTHER robot to a subroutine, the stored return point in that robot is the command it is currently on and would have executed otherwise. Otherwise there would be errors with subroutines causing robots to skip commands.
- When robots are sent to several subroutines during the same cycle, they react to all of them, in the reverse order in which they were received. This is a natural side-effect of the way cross-robot label sending and subroutines are handled, and is actually a very powerful feature. Basically what happens is when a robot is sent to a label, its current command is changed to be that label. When it is sent to multiple labels at the same time, the last label it received will be the one it ultimately executes. But when a robot is sent to a subroutine, it stores its current command location on the stack before moving to the new label. So when it receives multiple subroutine calls at the same time, it stores all of them on the stack, and then starts executing from the last subroutine received. Each goto "#return" moves up the stack to the previous subroutine received, until all of them are executed. Try this example with two robots, to see this in action:
. "@robot1" send "robot2" "#sub1" send "robot2" "#sub2" send "robot2" "#sub3" send "robot2" "#sub2" end --------------------- . "@robot2" end : "#sub1" set "$str" "Subroutine 1" goto "#write" goto "#return" : "#sub2" set "$str" "Subroutine 2" goto "#write" goto "#return" : "#sub3" set "$str" "Subroutine 3" goto "#write" goto "#return" : "#write" write overlay c0f "&$str&" at 0 "local" inc "local" by 1 goto "#return"
- This results in the following printed to the overlay:
Subroutine 2 Subroutine 3 Subroutine 2 Subroutine 1
- When a robot is sent to a subroutine when the program is processing a command that takes multiple cycles to complete ("go south for 5" for example), even if that command runs for only a single cycle, the command will be re-executed from the beginning when returning from that subroutine. This leads to one major drawback: since wait is a command that takes multiple cycles to complete. Execution does not move to the next command until the robot has finished waiting. Because the most common way to end a cycle is "wait for 1", a robot sent to a subroutine during this cycle break will return from that subroutine at the beginning of the wait command, and wait again. So, a robot running a continuous engine loop will be interrupted for a cycle every time it is told to handle another task. This is unlikely to be changed any time soon. A few solutions have been proposed to get around this (making a specific exception for wait 1, adding an "end cycle" command, etc.). The best solution found is to use the command "cycle 1", as it ends the cycle without ending program execution, and has a similar form to wait 1.