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
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.
#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
constructor-type nif 2 type-population 3 type-population < 2 3 ifev constructor-type! then
nif is short for
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
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.
There are two main uses for forcefields: getting food and getting rid of enemies. Look at Untouchable or Shepherds for examples of both.
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.
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 has a fancy solution: it keeps a hashtable in shared memory, showing which foods have been claimed already:
#comment Food hashing Keep a hashtable of position -> (time, x-position) showing who claims this food. Ignore foods other people have claimed recently. (x-position is used to confirm this is the right food.) Collisions are ignored; at worst they lead to pushing matches. Food hashing was first used in YAR, a not-yet-released side by Warren. #code #const hashtable-size 100 #const hashtable-base 1 #const claim-time 240 food-hash: ;(-- hash-index) food-position + dup floor - epsilon / floor hashtable-size mod hashtable-base + return claim-food: ;(-- claimed?) food-energy 750 > if 1 return then do food-velocity or nif ;preload with (time t-addr x-pos x-addr time-threshold t-addr x-pos x-addr) time food-hash dup hashtable-size + food-position drop swap time claim-time - 2over over hashtable-size + sync read = if read > if write write 1 return else 2drop 2drop then else 2drop write write 1 return then then next-food while-loop 0 return ;Then when you see food, call claim-food before trying to eat: food-found if claim-food energy 20 < or eat& ifg then ;Eat anyway if we're short on energy.
This is very effective, but complex. In order to update the hashtable atomically, it preloads some data on the stack.
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.Grobots by Devon Schudy and Warren Schudy