Grobots Wiki
Advertisement

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.

Basics[]

Growing[]

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.

Shooting[]

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.

Eating[]

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.

Wandering[]

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 fighting[]

#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

General advice[]

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.

Common behaviors[]

Type selection[]

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.

Shot-chasing[]

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.

Swarming[]

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.

Running away[]

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.

Forcefields[]

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.

Hiding[]

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[]

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.

Advanced tricks[]

Avoiding friendly fire[]

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.

Food hashing[]

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.

Dodging[]

TODO: put passive dodging in this page, active dodging in another page?

Advertisement