This manual is for PyBot. PyBot's author is called Trafalgar on the DarwinBots forum, SL on the various IRC channels he's on, shadowlord in some other places, and shadowlord13 on sourceforge and gmail.
PyBot is a python script/program designed to accomplish several goals:
closingRate when (robage==0) = floor(ceil(closingRate+rnd(2)-1,30),1) becomes *.closingRate 2 rnd add 1 sub 30 ceil 1 floor .closingRate *.robage 0 = store dropbool.
threatMode when (((eyef>0) and (refkills>0) and (not targFriendlyComp)) or ((tiepres==tienum) and (trefkills>0) and (not tieFriendlyComp)) or (shang!=0)) = threatMode or 1
Note that the threatMode or 1 actually does *.threadMode 1 | (unless it's in a condition, like the when (stuff) section)
There are a few caveats:
Like Python, the syntax of .pyr robot scripts has indentation as an important factor. Overall, the syntax is similar to Python, but with a DarwinBots-specific set of operators, functions, etc, and with DarwinBots-specific additions. The pyr files are NOT run through a python interpreter, and you cannot access Python libraries or use many python language constructs. The language syntax was simply designed to be somewhat similar to python's.
Comments
Single-line comments begin with an # symbol anywhere in the line. However, an # at the beginning of the line followed by a metacommand tells the compiler to change its behavior in some way after that line.
Genes
Genes are created from code which is tabbed over and inside a zero-indentation if/else. The zero-indentation is very important. If the if/elif/else is tabbed over instead, it will be translated to conditional code inside the gene it is in. A proper if statement consists of if condition:, and code which is supposed to be run if that condition is true should be tabbed over one more level than the if statement. As with python, code below the if statement which is at the tab level of the if statement, or less, will end the if block.
You can optionally include parentheses around condition, like so: if (condition):. That is functionally identical to the same thing without parens.
Example of gene defintion and translation:
if ((eye5 > 0) and (refeye!=myeye)): becomes
cond
*.eye5 0 > *.refeye *.myeye != andstartYou can also have an else: statement (and code inside its block (I mean tabbed over after it)) at the end of the if's block. This will be translated into an else statement in the txt file.
Stop statements are automatically placed prior to any new cond or start statements.
if (): is valid syntax, and results in an empty cond.
Statements
Assignment
First, two special operators are used in these examples: $ means the value of a memory address or variable or sysvar, and ∧ means the address of a variable or sysvar or the like.
The second thing to note is that variable names or sysvars on the right side of an = are by default assumed to refer to the contents, not the address. To refer to the address, prepend ∧ to the variable/sysvar/etc's name. In contrast, variable names etc on the left side of an = are by default assumed to refer to the address, not the value. To refer to the value instead, prepend it with $. These examples show both of these:
foo = 7 is translated to 7 .foo store
$foo = -4 is translated to -4 *.foo store
$8 = 1 is translated to 1 8 store
8 = 1 would not be valid, since numbers are not assignable.
foo = bar is translated to *.bar .foo store
foo = &bar is translated to .bar .foo store
Inc and dec
To use DB's inc operator, write foo ++. This becomes .foo inc The space before the ++ is optional.
To use DB's dec operator, write foo --. This becomes .foo dec The space before the -- is optional.
The 'when' operator
The 'when' operator is used to create conditional store/inc/dec commands. It is written foo when (someCondition) = bar. The () after when is required, and your condition must be inside it. Preferably use boolean logic operators (==, !=, <, >, <=, >=, true, false, and, or, not, xor) in the condition. The contents of the () are processed like what's after an =. That is, variable names etc are assumed to refer to the value of that variable, rather than the address of it. To refer to an address, use &.
The store/inc/dec will only be run by DarwinBots if the when's condition is true, AND, if the statement is inside other if/elif/else statements, only if it should be getting executed. (PyBot handles all the mess for nested branching conditions and when and such automatically)
IMPORTANT: You MUST NOT write a condition which isn't fully resolved into boolean values - for example, this is bad: foo when (multi) = 5 The 'multi' would never make it onto the boolean stack because you didn't compare it to anything. PyBot will try to warn you if you do this, but it isn't infallible. (For the curious, that would be translated to (with storechecking off) 5 .foo *.multi store dropbool. *.multi isn't compared against anything, so it doesn't go onto the boolean stack. That screws up the store and results in your robot trying to drop a bool when none was added to the stack in that line.)
Storechecking
If storechecking is enabled (it is by default, you use #disable-storecheck to turn it off), then any store statement (but NOT inc or dec) will have an extra condition as well. PyBot will insert code to duplicate the value to be stored, and to compare it to the value in the destination. If they do not match, the store will proceed. If not, the store will not be executed, since it would have wasted energy and changed nothing.
This saves energy (if stores cost energy) at the expense of code size, readability of the .txt file, and it may make the bot's code slightly slower to execute.
+=, -=, *=, etc
These are currently not supported. (I thought they were, but it appears not to be so)
If/elif/else
You can write if/elif/else statements to have easier control over execution of store/inc/dec, usually with less DNA produced than by repeating the same condition in several when conditions. The if/elif/else statements can even be nested. You cannot have an operation (store/inc/dec/etc) on the same line as an if/elif/else, they must be on separate lines.
A proper if statement consists of if condition: (condition must be boolean: true or false, or returned by and, or, xor, not, ==, !=, <, >, <=, >=, etc), and code which is supposed to be run if that condition is true should be tabbed over one more level than the if statement. As with python, code below the if statement which is at the tab level of the if statement, or less, will end the if block.
IMPORTANT: You MUST NOT write a condition which isn't fully resolved into boolean values - for example, this is bad: foo when (multi) = 5 The 'multi' would never make it onto the boolean stack because you didn't compare it to anything. PyBot will try to warn you if you do this, but it isn't infallible. (For the curious, that would be translated to (with storechecking off) 5 .foo *.multi store dropbool. *.multi isn't compared against anything, so it doesn't go onto the boolean stack. That screws up the store and results in your robot trying to drop a bool when none was added to the stack in that line.)
You can optionally include parentheses around condition, like so: if (condition):. That is functionally identical to the same thing without parens.
Example:
if (robage>1): if ((eyef>27) and (not targFriendlyComp)): if (tiepres==0): tie = rnd(32000) tienum = tie elif ((tiepres>0) and (tieNumAfter==tiepres)): if ((tieFriendlyComp) or ((tieVeggieComp) and (not targVeggieComp))): #We can tie to another tie = rnd(32000) tienum = tie else: tienum = tiepres #Destroy them, my robots tieloc = &strvenom tieval = 10000Variable names, sysvars, and such inside the condition of an if or elif are read as referring to the value of the variable/sysvar, not its address. If you want it to refer to its address, use &.
What this compiles to is rather complicated, so it isn't presented here. Suffice it to say that it's accomplished by using the boolean stack and boolean operators. If there is a cap on how many entries can be in the boolean stack, you may run into trouble if your nested if/elif/elses are too deep. Code inside an 'if' or an 'else' has one value on the stack from the if or else, unless that if/else is inside another if/else, in which case it has two (in addition to the 1 or 2 more from the other if/else). Code inside an elif has three from the elif. The 'when' operator and storecheck may require up to 2 free slots, but these are only temporary (for the statement the when and/or storecheck is affiliated with).
Operators
Operators are written as they would be in python, c++, java, c#, etc, except that PyBot does not respect order of operations at this time. Instead, you will probably want to use parens to show how operations should be ordered. (If you don't, PyBot will translate things from left to right)
Unary operators: inc, dec, rnd or rand, sgn or sign, abs, not or !, ~, ++, --, -, sqr or sqrt, andbits (translates to &), orbits (translates to |), xorbits(translates to ^). These can be used with any of these forms: not(foo) or not foo or !foo. The final form can only be used with symbols, not named operators (notfoo would not work).
Binary operators: + (becomes add), - (becomes sub), * (becomes mult), / (becomes div), % (becomes mod), and, or, xor, >, <, >=, <=, == (becomes =), !=, %=, !%=, >>, <<, store. These are used like so: foo = bar + zort, which becomes *.bar *.zort add .foo store
Function operators: angle(2 args), dist(2 args), ceil(2 args), floor(2 args), pow(2 args), pyth(2 args). Additionally, floor has min as a synonym, and ceil has max as a synonym. You call these like so: floor(foo, bar), which becomes *.foo *.bar floor.
Unsupported: PyBot will not recognize * for pointer dereferencing - you should use $ instead. PyBot also doesn't support ~= or !~=, since they are deprecated (according to the DarwinBots wiki). PyBot does not have a ternary operator (foo?bar:zort), but neither does DarwinBots. PyBot also currently does not allow access to: clearbool, dropbool, dupbool, swapbool, or overbool, mainly because the state of the boolean stack must remain unchanged for if/elif/else, when, and storecheck to function properly. However, it DOES allow you to write store, inc, dec, and dup, so you can already screw things up...
Important: In some cases, PyBot will transform some operators into forms which work on the main int stack (instead of the boolean stack (formerly referred to as SGizing)). Effectively, it will only NOT do so inside the conditions () of if statements, elif statements, and when statements, but that means it's mostly only going to occur if you have said operators in the part of an assignment statement where variable names go, or after the =. The following operators are changed: not or !, and, or, xor, >, <, >=, <=, == (becomes =), !=, %=, !%=, >>, <<. One benefit from this is that you can write foo = bar and zort and it will do bitwise and instead of boolean and: *.bar *.zort & .foo store. However, the SGized forms of not, !, and the comparison operators were only designed to work when the values they operate on are either 0 or 1.
Constants
True and false are both usable in conditions (if (true), when (true), etc). These, as well as numbers, are passed directly into the output txt file unchanged (unless you use a $ prefix on a number, in which case it's a memory address instead of a number).
You can use dup or dupe like a constant, and it will be inserted literally - ((refypos-ypos)+refveldn)*dup, for example, would become *.refxpos *.ypos sub *.refveldn add dup mult. That's basically saying "multiply this times itself" without having to actually calculate it twice.
Currently, if you use true or false in somewhere where conditional operators get SGized, they will become 1 or 0 to avoid screwing up the boolean stack.
If a condition in an if, elif, or when statement is "1" or "0" and nothing else (parens are still allowed though), then it will be converted to "true" or "false" respectively. Example:
if (1): one2 = 1 elif 0: zero2 = 1Those will be processed as if they said if (true):, and elif false:.
Macros
Macros are essentially named definitions, which, when you write code which names them, are inserted in there to replace the name. Macros can reference other macros, and so on. Example:
define nextrefxpos: refxpos+((refveldx*dist(refxpos,refypos))/39) define nextrefypos: refypos-((refvelup*dist(refxpos,refypos))/39) define betternextrefxpos: refxpos+((refveldx*dist(nextrefxpos,nextrefypos))/39) define betternextrefypos: refypos-((refvelup*dist(nextrefxpos,nextrefypos))/39) define fixedangle: angle(betternextrefxpos, betternextrefypos) # Later, inside an if block, and with storecheck enabled: aimshoot = -fixedangle + aimThat becomes: *.refxpos *.refveldx *.refxpos *.refveldx *.refxpos *.refypos dist mult 39 div add *.refypos *.refvelup *.refxpos *.refypos dist mult 39 div sub dist mult 39 div add *.refypos *.refvelup *.refxpos *.refveldx *.refxpos *.refypos dist mult 39 div add *.refypos *.refvelup *.refxpos *.refypos dist mult 39 div sub dist mult 39 div sub angle - *.aim add dupbool dup *.aimshoot != and .aimshoot store dropbool
Macros will not work very well if they're something like (900 + thisgene) if that's supposed to be an address to read or write. This is how you can do that (without macros): (900+$thisgene) when (abs($(900+thisgene) - 101)>100) = 200 becomes: 200 900 *.thisgene add 900 *.thisgene add * 101 sub abs 100 > store dropbool
[+]Metacommands
Metacommands apply only to the file they are in, and only to lines which come after them. They are executed as PyBot translates the pyr file from top-to-bottom. They generally affect how PyBot processes the rest of the file, and you would use most at the beginning of the file. Some metacommands are paired so that one turns on a feature and another turns it off (for example, #include-original-code and #omit-original-code). Some metacommands cannot be turned back off once turned on, but PyBot will turn them off once it finishes translating the file they are in (they NEVER affect other files).
The most frequently used metacommands are #enable-storecheck and #disable-storecheck. If storecheck is enabled, PyBot will automatically add conditions to every store command which will make the store command not execute if it is already set to whatever it was going to be set to (saving energy if there are costs for store commands but not for boolean logic).
Name Default? Purpose #include-original-code Yes Turns on original code echoing to the txt file. From this point on, PyBot will insert each original line from the pyr, commented out, into the robot's outputted txt file. The commented out line(s) will be written just before the line(s) for the translated robot code. (Use #omit-original-code to turn it back off) #omit-original-code Turns off original code echoing to the txt file. From this point on, PyBot will no longer echo lines from the pyr into the robot's outputted txt file. (Use #include-original-code to turn it back on) #combine-lines No PyBot will begin omitting most (but not all) endlines from the output file. This reduces the output filesize a bit (but also makes it less readable). Cannot be disabled once on. #inline-defs No Any defs which come after this point will be inlined: The def will not be written to the txt file, and instead, any reference to the def will be translated to the memory address which the def refers to, instead of to the name of the def. Cannot be disabled once on. #sg-more No This tells PyBot to SGize boolean logic operators (and, or, ==, !=, >, etc) in when conditions and in storecheck conditions. To turn this back off, use #sg-normal. This does not affect how if/elif/else statements are translated. #sg-normal Yes This tells PyBot to go back to its normal behavior of using the boolean stack and actual boolean logic operators (and, or, ==, !=, >, etc) when you write them in a when condition and for storecheck conditions. #enable-storecheck Yes PyBot will begin automatically adding conditions to every store command which will make the store command not execute if the destination is already set to whatever it was going to be set to (saving energy if there are costs for store commands but not for boolean logic). This is translated to something like [value] dup *destination != destination store, or to a more complicated form if you also used 'when', or if it's affected by other conditional logic, e.g. inside an if/elif/else. #disable-storecheck No This turns off the storecheck-adding code. You will probably want to do this in a gene that you want to be as small as possible (say, for a virus), or if you know that the variable wasn't already set to that, or if you don't care if it is set again. Custom Variables
You can associate variables with memory locations by writing def variableName at memoryLocation with no indentation - example: def reproducedLastCycle at 962. This is translated to def reproducedLastCycle 962 in the outputted txt file, unless you turned on inline-defs earlier in the file than the def.
Maintainability
PyBot's syntax is unlikely to change much. Guardian 0.9 was originally written with a version of PyBot which automatically did SGizing on when(conditions), written before conditionals and the boolean stack were made to work in standard start/stop genes in DarwinBots. PyBot has been updated to actually use conditionals and the boolean stack instead, and Guardian 0.9 still compiles - but the output is different.
However, PyBot will not automatically enable the use of new operators which are added to DarwinBots. Fortunately for anyone who does want to use them, if I'm absent, you can still fairly easily make PyBot allow using them if they are a standard kind of operator (unary, binary, function).
You'd probably only have to edit Patterns.py to add the new operators. The "normal" patterns in there are used in conditions (if, elif, when, and in non-tabbed-over if statements which are turned into cond/start), and the "sg" patterns are used everywhere else inside genes. If #sg-more is on, then the "sg" patterns would also be used in when and storecheck conditions instead of the "normal" patterns.