Here is a rudimentary tutorial, built around sample types. It assumes you already know how to load sides and run rounds, and have some idea how a stack-based language works.
You probably won't go through this tutorial in order. You probably won't build sides very similar to the examples. But the examples will show you what's important, what you should learn, and what you can imitate. Not everything is explained, so don't hesitate to look things up.
Additional examples and explanations (or even just suggestions) are eagerly accepted.
The simplest side that reproduces:
#side Tutorial #color FD4 #type Vegetable #hardware solar-cells .4 constructor .4 armor 50 processor 2 #code do autoconstruct forever #end
do ... forever is an infinite loop. Most types have at least one of these. autoconstruct turns the constructor on and off as energy is available.
Vegetables are delicious, and many sides will kill and eat them. Let's give them a weapon.
#type Killer Vegetable #color F0E #hardware solar-cells .3 constructor .3 armor 100 processor 5 energy 100 0 grenades 30 15 34 robot-sensor 15 #code do autoconstruct grenades-reload-time periodic-robot-sensor if robot-found if robot-position robot-velocity lead-grenade then then forever
Obviously you need something to shoot at, so load up some other sides if you haven't already.
test if body then is a conditional, written in a different order than in most languages.
periodic-robot-sensor takes an argument, and fires the robot sensor if that many frames have passed. Using grenades-reload-time as the argument gives the weapon just enough time to reload between scans.
lead-grenade is a convenient operator for shooting at moving targets. If you replace robot-position robot-velocity lead-grenade with robot-distance robot-direction fire-grenade and test against some moving targets, you can see why shot-leading matters.
Vegetable grows very slowly, especially if it spends energy buying weapons. Let's make a type which eats manna, so it can grow faster.
#type Animal #color #hardware engine .05 constructor 1 eater 2 energy 250 25 food-sensor 8 processor 5 armor 100 #code do autoconstruct 44 periodic-food-sensor drop food-found if food-position seek-location else 0 engine-power! then forever
periodic-food-sensor returns a boolean indicating whether it fired the sensor, but since we don't care, we use drop to stop the results from piling up on the stack.
seek-location sets the engine to move toward the coordinates given as arguments. It's the most common movement operator. engine-power! is a lower-level way of controlling the engine, which we use here to turn it off when there's nothing to eat.
Animals tend to starve. When there's no food nearby, waiting for some to appear doesn't work very quickly. Why not go looking instead?
#code #vector dest new-dest: 0 world-width random 0 world-height random dest! return #start new-dest do autoconstruct 44 periodic-food-sensor drop food-found if food-position seek-location else dest seek-location position dest 3 in-range new-dest& ifc then forever
new-dest defines a label, which can be called as a user-defined operator, or passed as an argument to other operators, like ifc.
If execution started at the beginning of this type, it would blunder into new-dest and fail when trying to return from it. #start makes execution start elsewhere.
Moving and fightingEdit
#type Mineral #color FFF #hardware processor 10 robot-sensor 10 blaster 25 3 25 engine .1 energy 200 5 solar-cells .12 armor 300 #code do 0 engine-power! energy 20 > if 57 periodic-robot-sensor and-if robot-found and-if attack^ then armor max-armor < energy 10 > and repair-rate! forever attack: robot-position robot-velocity seek-moving-location do blaster-reload-time periodic-robot-sensor if robot-found if robot-position robot-velocity lead-blaster robot-position robot-velocity seek-moving-location else return ;nobody here - give up then then energy armor min 20 > while-loop return
Read other sides. (But bear in mind that they don't always do things in the best way. In particular, old sides don't use newer features.) Try to figure out how they work. Modify them and see if you can do better.
Don't be afraid to learn math. In particular, vector arithmetic is easy and very useful. (It's useful for other things than Grobots, too.)
Don't try to make a side perfect from the beginning. Just try to make it do one thing well. You can improve the rest later.
Keep it simple. Complicated code is hard to debug and probably won't work much better. Don't try too hard to make it behave properly in all circumstances. For Grobots, code that works most of the time is almost as good as code that works all the time.
You can control what type a baby will be with constructor-type!. Types are numbered (not named, sorry) in order from 1. If you set constructor-type when there's already a baby in progress, it will abort, so it's usually set only when constructor-type is 0.
Most sides want to build whatever type they're short on. You can find out the populations with type-population:
constructor-type nif 2 type-population 3 type-population < 2 3 ifev constructor-type! then
nif is short for not if. ifev is a value conditional often used for this purpose.
Mineral ignores incoming shots, so an enemy with a long-range weapon can safely kill it. Most sides avoid this by watching for incoming shots, and following them back to their origin.
The simplest way to adapt Mineral to chase shots is to generalize attack to chase an arbitrary location, not just robot-position. Then when we see a shot, we can look at its velocity and attack the area it came from.
do 0 engine-power! energy 20 > if 57 periodic-robot-sensor and-if robot-found and-if robot-position attack^ then energy armor min 50 > if ;only attack if we're healthy 15 periodic-shot-sensor if shot-found and-if shot-velocity unitize -20 vs* shot-position v+ attack^ then then armor max-armor < energy 10 > and repair-rate! forever #vector target ;;Go to a location and kill any enemies seen ;takes target as an argument attack: ; tx ty -- target! do robot-found if target robot-velocity seek-moving-location else target seek-location then blaster-reload-time periodic-robot-sensor if robot-found if robot-position robot-velocity lead-blaster ;stay a short distance away from the target: position robot-position v- unitize 2.5 vs* robot-position v+ robot-velocity 10 vs* v+ target! else position target 3 in-range ifr ;nobody here - give up then then robot-found nif 13 periodic-shot-sensor and-if shot-found and-if shot-velocity unitize -20 vs* shot-position v+ target! then energy armor min 20 > while-loop return
Notice that a boolean is being used as repair-rate. Unlike real hardware, Grobots hardware is forgiving about out-of-range arguments, so when repair-rate is set to 1, it's treated as max-repair-rate.
One fighter tends to die when facing a group. So let's make the fighters call for help when they see something. When they receive a call, they treat it just like chasing a shot:
#const call-ch 5 do 0 engine-power! energy 20 > if 57 periodic-robot-sensor and-if robot-found and-if robot-position 2 call-ch send robot-position attack^ then energy armor min 50 > if ;only attack if we're healthy 15 periodic-shot-sensor if shot-found and-if shot-velocity unitize -20 vs* shot-position v+ 2dup 2 call-ch send attack^ then call-ch receive attack& ifc else call-ch clear-messages ;so we don't respond to old calls then armor max-armor < energy 10 > and repair-rate! forever
Attack doesn't change at all.
Places where there's been a recent call for help are often dangerous. It might be useful to make Animals avoid them when wandering.
Animal, like any gatherer, tends to wander near enemies and get killed. We can fix this by making it run away when it sees a shot. This is like chasing shots, but in the opposite direction.
Many sides run to somewhere well-defended (a stationary colony? a group of fighters?) instead of just running away, to avoid running into things. It's also possible to run away when damaged instead of watching for shots.
Animals could also call for help when they see a shot, or get hurt. This is easy to add to their shot-fleeing code.
Exercise: Make a long-range gunner which uses a forcefield to make the target easier to hit. This has been tried a few times (in Flyswatter and early versions of Untouchable) but never with much success.
The center of the world is a dangerous place to be. Productive therefore goes to the nearest corner at the beginning of the round. Unfortunately it often runs into another side along the way and dies. More cautious hiding could be a large advantage in the early game.
Shepherds runs away as a group, and tends to end up in corners without trying. So does MicroAlgae. Unproductive accelerates this by deliberately moving next to a wall if one is close.
Syphons let you separate energy production from consumption, giving more flexibility in side design. There are several ways to use them. The most popular is to have the energy producers carry the syphon, and hungry cells announce their locations in messages. The syphoners listen and point the syphons wherever they're asked.
Avoiding friendly fireEdit
Blasters can be hard to use in groups, because it's easy to hit your allies instead of enemies. One way to use them is to organize cells so they're unlikely to be in each other's way. If they avoid each other, or stay in a formation facing the enemy, they won't suffer much from friendly fire. Very short-range blasters avoid the problem by not having enough room for anyone to get in the way.
Noffee has a brute-force solution: it looks at nearby friendly cells and checks to see if they will be in the line of fire. This involves a lot of vector math. If you're willing to be more conservative about when you shoot, you can make this much simpler. Here's a version adapted from Ring of Fire 3:
#var target-distance #var blast-direction #vector blast-velocity shoot: robot-distance target-distance! ;aim the shot robot-velocity velocity v- 2dup target-distance blaster-speed / vs* robot-position v+ position dist blaster-speed / vs* robot-position v+ position v- angle blast-direction! velocity blaster-speed blast-direction polar-to-rect v+ blast-velocity! ;look for friends 0 robot-sensor-sees-enemies! 1 robot-sensor-sees-friends! target-distance 2 / robot-sensor-focus-distance! blast-direction robot-sensor-focus-direction! fire-robot-sensor sync 1 robot-sensor-sees-enemies! 0 robot-sensor-sees-friends! 0 robot-sensor-focus-distance! ;anybody in the way? robot-found if do robot-position position v- 2dup ;vector to this robot blast-velocity robot-velocity v- unitize dot ;how far along the shot-path dup radius > swap target-distance < and ;is it between us and the target? rrot blast-velocity robot-velocity v- unitize cross ;distance from shot-path abs robot-radius .2 + < and ifr ;...and is it close to the path? next-robot while-loop then ;shoot blast-direction fire-blaster return
Obviously this takes a large processor and a multiresult sensor. The result is impressive: you can fire through the cracks in a crowd and rarely hit anyone.
Most gatherers waste a good deal of time when two cells are trying to eat the same food. Fool detects when it's been pushed off a food, and looks elsewhere. This is easy to implement.
Frog Celestial 2 introduced food hashing. Since then many sides have adopted variants of this technique. This page describes a simpler variant of the same idea used in Active 9. The world is divided into e.g. 250 vertical strips. For each strip the expiration date for the last claim to a food in that strip is stored. Shared memory starts at zero, which is less than the start time of 1 so no explicit initialization of shared memory is required. When a food is seen a gatherer checks shared memory to see if that food's strip has has an unexpired claim. If so the food is assumed to be already claimed and is ignored, if not the strip's expiration time is updated and the food thereby claimed. It's called food hashing because the assignment of food positions to strips can be thought of as a simple hash function. If the number of strips is large collisions don't happen much so it doesn't matter much how they're handled.
Here's the claim-food procedure from Active 9 that does the food hashing (some comments added):
#const FOOD_CLAIM_BASE 701 ;;the shared memory location of the hashtable #const NUM_FOOD_CLAIMS 250 ;;the number of strips #var food-hash ;;The shared memory location for the current food's strip claim-food: ;;no inputs, returns boolean whether the food is available food-position drop world-width / ;stack: between 0 and 1 NUM_FOOD_CLAIMS * floor ;stack: presumably between 0 inclusive and NUM_FOOD_CLAIMS exclusive FOOD_CLAIM_BASE + food-hash! ;stack empty time 400 + ;put on stack for later food-hash sync read time < ClaimExpired& ifg ;valid claim already not ;;drops and then pushes "0" since time+400 != 0. return ClaimExpired: ;stack: time+400 food-hash write 1 return
Here's an (incomplete) example of how to use the claim-food procedure:
#var have-food 0 ;;boolean indicating if we have a food #start do have-food DoneEating& nifg 30 periodic-food-sensor DoneEating& nifg food-found NoFoodFound& nifg do claim-food^ if 1 have-food! DoneEating& jump then next-food while-loop NoFoodFound: ;;No unclaimed food found. Put code handling that event here (if any). ;;Basic types don't need any code here. DoneEating: have-food if food-position seek-location food-position position radius in-range eaten not and if 0 have-food! then else ;;No food, try wandering or something (fill in) then ;;Put code here for constructor control etc. forever
You may want to try using some other method than a shared hashtable. Maybe use messages, and remember only the last few nearby claims. Segregated Eaters keeps track of 10x10 tiles rather than individual foods. This is even more effective, because cells don't waste time wandering past foods that are taken.
TODO: put passive dodging in this page, active dodging in another page?