terra is a super customizable library for creating and analyzing biological simulations. It's open-source and licensed under MIT.

Usage

Including terra

Getting started is as easy as including the script!

<script src="//cdn.jsdelivr.net/terra/latest/mainfile"></script>

terra can also be used as a module with most popular module systems: ?

// CommonJS var terra = require('./terra.min.js'); // ... // AMD define(['./terra.min.js'] , function (terra) { return function () { //... }; }); // ...and if you're not using a module system, it'll be in window.terra;

If you manage dependencies with Bower, you're in luck!

bower install terra

Creating creatures

Let's create a simple creature using the registerCreature method. Each creature requires a type.

terra.registerCreature({ type: 'firstCreature' });

This creature is valid, but it's pretty boring. To make a more interesting creature, let's override some of the default attributes and methods.

terra.registerCreature({ type: 'secondCreature', color: [120, 0, 240], sustainability: 6, reproduceLv: 1 });

We've just created a purple creature that only eats if 6 or more plants are around it. These creatures basically seek out an edge or corner and die there.

Creating the environment

To run a simulation, we'll need to create an environment. Let's make a 25x25 grid, populate 10% of the space with our lonely purple creature, and fill the rest with simple plants. We'll have to define a simplePlant creature, but don't worry too much about how that works just yet.

// create a simple plant creature terra.registerCreature({ type: 'simplePlant', color: [166, 226, 46], size: 10, reproduceLv: 0.8, wait: function() { this.energy += 3; }, move: false }); // initialize our environment var ex1 = new terra.Terrarium(25, 25, {id: 'ex1'}); ex1.grid = ex1.makeGridWithDistribution([['secondCreature', 10], ['simplePlant', 90]]);

Running a simulation

Terrariums have a few methods that allow you to interact with the simulation. Let's animate it and see how it does for the first 300 steps.

ex1.animate(300);

That's all there is to it! Though it's possible to generate complex behaviours by simply overriding default values, the real fun comes when you realize that creatures are entirely customizable.

Examples

Conway's Game of Life ?

var gameOfLife = new terra.Terrarium(25, 25, { trails: 0.9, periodic: true, background: [22, 22, 22] }); terra.registerCA({ type: 'GoL', colorFn: function () { return this.alive ? this.color + ',1' : '0,0,0,0'; }, process: function (neighbors, x, y) { var surrounding = neighbors.filter(function (spot) { return spot.creature.alive; }).length; this.alive = surrounding === 3 || surrounding === 2 && this.alive; return true; } }, function () { this.alive = Math.random() < 0.5; }); gameOfLife.grid = gameOfLife.makeGrid('GoL'); gameOfLife.animate();

Cyclic Cellular Automaton ?

var cyclic = new terra.Terrarium(100, 100); terra.registerCA({ type: 'cyclic', colors: ['255,0,0,1', '255,96,0,1', '255,191,0,1', '223,255,0,1', '128,255,0,1', '32,255,0,1', '0,255,64,1', '0,255,159,1', '0,255,255,1', '0,159,255,1', '0,64,255,1', '32,0,255,1', '127,0,255,1', '223,0,255,1', '255,0,191,1', '255,0,96,1'], colorFn: function () { return this.colors[this.state];}, process: function (neighbors, x, y) { var next = (this.state + 1) % 16; var changing = neighbors.some(function (spot) { return spot.creature.state === next; }); if (changing) this.state = next; return true; } }, function () { this.state = Math.floor(Math.random() * 16); }); cyclic.grid = cyclic.makeGrid('cyclic'); cyclic.animate();

Brutes and Bullies

// the demo running at the top of this page var bbTerrarium = new terra.Terrarium(25, 25); terra.registerCreature({ type: 'plant', color: [0, 120, 0], size: 10, initialEnergy: 5, maxEnergy: 20, wait: function() { // photosynthesis :) this.energy += 1; }, move: false, reproduceLv: 0.65 }); terra.registerCreature({ type: 'brute', color: [0, 255, 255], maxEnergy: 50, initialEnergy: 10, size: 20 }); terra.registerCreature({ type: 'bully', color: [241, 196, 15], initialEnergy: 20, reproduceLv: 0.6, sustainability: 3 }); bbTerrarium.grid = bbTerrarium.makeGridWithDistribution([['plant', 50], ['brute', 5], ['bully', 5]]); bbTerrarium.animate();

Rule 146

var elementary = new terra.Terrarium(150, 150); terra.registerCA({ type: 'elementary', alive: false, ruleset: [1, 0, 0, 1, 0, 0, 1, 0].reverse(), // rule 146 colorFn: function () { return this.alive ? this.color + ',1' : '0,0,0,0'; }, process: function (neighbors, x, y) { if (this.age === y) { var index = neighbors.filter(function (neighbor) { return neighbor.coords.y === y - 1; }).map(function (neighbor) { return neighbor.creature.alive ? 1 : 0; }); index = parseInt(index.join(''), 2); this.alive = isNaN(index) ? !x : this.ruleset[index]; } return true; } }); elementary.grid = elementary.makeGrid('elementary'); elementary.animate();

A few more awesome examples:

If you come up with a cool demo, let me know! I'll add it to this list and credit you.

Creatures

Creatures are registered with

terra.registerCreature(options, init)

terra.registerCA(options, init)

orfor cellular automata.

The following methods and attributes can be passed in an object as the first argument:

Required

string type Creature type, to be used later in makeGrid( ) or makeGridWithDistribution( ).

Optional

int actionRadius A creature's vision and movement range for each step. Default: 1

char character ASCII character used to visually represent a creature. Default: undefined (fills cell)

int [3] color RGB components of a creature's display color. Range: [0, 255] Default: random

function colorFn How a creature's color is determined at each step. Returns: string of comma-separated RGBA components.

int efficiency Conversion ratio of food to energy. Food energy × efficiency = gained energy. Default: 0.7

float initialEnergy Energy level that a creature has at the start of its life. Range: (0, maxEnergy] Default: 50

function isDead Determines whether a creature should be removed at the beginning of a step. Returns: boolean Default: Return true for creatures with energy <= 0. Return false always for cellular automata.

float maxEnergy Maximum energy that a creature can have; excess energy is discarded. Default: 100 Minimum: 0

function move How a creature moves. Parameters: {coords, creature} [] neighbors Default: Look for edible creatures; if none are found, look for open spaces to move to; if none are found, wait. Returns: {x, y, creature, successFn} || false

float moveLv Percentage of a creature's max energy below which it will stop moving (used in the default process method). Default: 0 Range: [0, 1]

function process Main entry point for behavior; called for each creature on each iteration. Parameters: {coords, creature} [] neighbors, int x, int y Default: Creatures reproduce if energy is sufficient, otherwise move if energy is sufficient, otherwise wait. Cellular automata do nothing by default. Returns: true : indicates that a change has occurred in the creature; if no creatures return a truthy value, the simulation terminates false {x, y, creature, observed} : a creature in a new position; observed acts like true and false above and allows watching for specific conditions

function reproduce How a creature reproduces. Parameters: {coords, creature} [] neighbors Default: Look for neighboring open space; if any exists, randomly place a new creature and lose energy equal to the child's initialEnergy. Returns: {x, y, creature, successFn, failureFn} || false

float reproduceLv Percentage of a creature's max energy above which it will reproduce (used in the default process method). Default: 0.7 Range: [0, 1]

int size A creature's size; by default, creatures can only eat creatures smaller than them. Default: 50

int sustainability Number of visible food sources needed before a creature will eat. Default: 2 Range: (0, 16 × actionRadius - 8]

function wait What happens when a creature waits. Default: Creatures lose 5 energy. No effect for cellular automata.

* * The best part about creatures is that you can add whatever you want to them! In addition to overriding any of the above properties, creatures will accept any methods and properties you throw at 'em.

The second argument to the terra.registerCreature and terra.registerCA is the init function. This function is run within a creature's constructor and allows you to set different attributes for individual creatures. For example, in the Cyclic Cellular Automaton example above we see the following init function:

function () { this.state = Math.floor(Math.random() * 16); });

Whenever a new creature is created of type 'cyclic', it will be randomly assigned a state of 0 to 15.

Terrarium

Terrariums are where the action happens. They're initialized with the following constructor:

//new terra.Terrarium(width, height, {options}); //example: create a 4x4 terrarium called #myTerrarium after element #bugStory var t = new terra.Terrarium(4, 4, { id: 'myTerrarium', cellSize: 15, insertAfter: document.getElementById('bugStory') });

Required

int width Number of cells in the x-direction.

int height Number of cells in the y-direction.

Optional

string id id assigned to the generated canvas.

int cellSize Pixel width of each cell. Default: 10

string insertAfter id of the element to insert the canvas after. Default: canvas is appended to document.body

boolean periodic Determines if boundaries wrap around; a creature at the top of a periodic map would see the bottom cells as though they were adjacent. Default: false

string neighborhood Defines neighborhood type as either moore or vonNeumann . Default: moore

float trails Allows for "trails", which visualize system history. A value of 1 shows all past state; trails fade faster as we approach 0. Range: [0, 1] Default: canvas is appended to document.body Dependencies: "background" option is required if trails is set.

int [3] background RGB components of the canvas' background. Range: [0, 255] Default: transparent



Once initialized, terrariums have a few exposed methods. Using our terrarium t that we just created:

//initial setup var a = 'a', b = 'b'; terra.registerCreature({type: a}); terra.registerCreature({type: b}); /* makeGrid(content) * * Returns a grid populated using a function, 2-d array, or uniform type */ // example: fill the terrarium with the 'b' creature type t.grid = t.makeGrid(b); // example: fill the terrarium with a checkerboard pattern t.grid = t.makeGrid(function (x, y) { return (x + y) % 2 ? a : b; }); // example: fill the terrarium's left half with 'a', right half with 'b' t.grid = t.makeGrid([ [a, a, b, b], [a, a, b, b], [a, a, b, b], [a, a, b, b] ]); /* makeGridWithDistribution(distribution) * * Returns a grid populated randomly with a set creature distribution, where distribution * is an array of arrays of the form [string 'creatureName', float fillPercent] */ // example: fill the terrarium randomly with approximately half 'a' and half 'b' t.grid = t.makeGridWithDistribution([[a, 50], [b, 50]]); /* step(steps) * * Returns the next step of the simulation, or the grid after <steps> steps if specified */ // example: advance the terrarium 10 steps in the future t.grid = t.step(10); /* draw() * * Updates the terrarium's canvas to reflect the current grid */ // example: display all the work we've done above t.draw(); /* animate(steps, fn) * animate(steps) * * Starts animating the simulation. The simulation will stop after <steps> steps * if specified, and call <fn> as a callback once the animation finishes. */ // example: animate the terrarium t.animate(); /* stop() * * stops a currently running simulation */ // example: stop the animation that we just started t.stop(); /* destroy() * * calls stop() *and* cleans up the DOM */ // example: remove the previously created canvas element t.destroy();

Still want more? Check out the source on GitHub!