The DSL
The when and do fields take a tiny expression language. It is small on purpose, so a rule reads in one line. Need more? Use a CodeBlock (next chapter).
The full reference is one click away in the rule editor. The ? DSL button lists every function and name by category. This chapter explains the shape of the language.
Operators
+ - * / % arithmetic
== != < <= > >= comparison (=== and !== work too, same meaning)
&& || ! boolean
? : ternary
= += -= *= /= assignment (only inside do)
; statement separator
Numeric helpers
random(min, max) float in [min, max), seeded RNG
random_int(min, max) integer in [min, max] inclusive
random_normal(mean, sd) Gaussian sample
chance(p) true with probability p
sqrt(x) abs(x) floor(x) round(x)
min(...) max(...) smallest / largest argument
Math.PI Math.atan2(y, x) ...
Use the seeded helpers (random, chance) where you can. With them, the same seed gives the same run every time. Math.random() works, but then runs no longer repeat.
Movement (agent rules)
move_forward(d) walk d patches along the current heading
turn(deg) rotate heading by deg degrees (negative = left)
turn_random() pick a uniform random heading
face(target) turn to face an agent or patch
set_position(x, y) teleport; wrapping rules apply
distance_to(target) distance to an agent or patch
Patches and agents nearby
patch() the patch under the current agent
patch_ahead(d) the patch d steps ahead in the current heading
patches_in(r) patches within radius r
neighbors() 8 surrounding patches (patch rules) /
nearby agents (agent rules)
neighbors4() 4 cardinal neighbour patches (Von Neumann)
agents_here() every agent currently on this patch (patch rules)
all_patches() every patch in the world (observer)
all_agents() every alive agent (observer)
agents_of_type('foo') every alive agent of that type
nearest('foo') the closest agent of type 'foo', or null
Agent lifecycle
create_agents('foo', n) spawn n agents at random positions (observer)
create_agents_at('foo', n,x,y) spawn n agents at exact (x, y) (observer)
spawn('foo') one new agent at the current spot (agent rule)
spawn_at('foo', x, y) one new agent at (x, y) (agent rule)
sprout('foo') one new agent at this patch (patch rule)
hatch(count?, init?) clone the current agent; run init(child) on each
die() remove self at end of tick
You don't have to write create_agents in a setup rule. Every agent type has an initial_count. If no setup rule creates agents, Setup creates that many at random positions.
Diffusion (patch properties)
Two helpers spread a number property of the patches to their neighbours each tick. Use them for chemicals, pheromone trails, heat and the like. They work for the whole model, so they go in a go_rule:
diffuse('pheromone', 0.5) // 8-neighbour Moore stencil
diffuse4('heat', 0.2) // 4-neighbour Von Neumann stencil
The second argument is the share of each patch's value that spreads out each tick.
Links (networks)
Agents can be joined by links that have no direction. Together they form a network on top of the population. Create and inspect them with:
link(a, b) // observer or agent rules
unlink(a, b)
linked(a, b) // observer: bool
my_links() // agent: links touching this agent
link_neighbors() // agent: every agent I'm linked to
all_links() // observer: every link in the world
Aggregations
count, sum, mean, min and max work on arrays. count takes any array: agents, patches, anything. The others need numbers, so use .map to pick out a property first:
count(agents_of_type('sheep')) // 47
count(agents_of_type('sheep').filter(a => a.energy > 5)) // 33
mean(agents_of_type('sheep').map(a => a.energy)) // 12.4
sum(all_patches().map(p => p.grass)) // 1280
A common slip is mean(agents_of_type('sheep').energy). It reads .energy on the array, which has none. Always .map first.
Lambdas
Anonymous functions are allowed wherever an array method (.filter, .map, .sort, .reduce) or the ask helper needs a callback. Three shapes work:
x => x.energy > 0 single arg
(a, b) => a.energy - b.energy multi arg, e.g. sort comparator
() => spawn('child') zero arg, for ask(arr, () => ...)
ask(collection, fn) calls fn for each item. An error in one item does not stop the others.
me and globals
In agent or patch rules, me is the current agent or patch. A bare name works when nothing else has it. energy = energy - 1 is the same as me.properties.energy = me.properties.energy - 1. Use me. when a global or a function has the same name.
Sliders, switches and choosers on the Interface tab are bare names in rules. A slider named sheep_repro is just sheep_repro in an expression.
When a rule fails
A when or do can fail: a typo, an unknown name, a bad value. Then the rule is skipped for that agent or patch on that tick. One bad rule never stops the model. The message is kept in RuntimeState.rule_errors. A red ! error badge shows it next to the rule in the Code tab. The badge goes as soon as the rule runs cleanly.