terra is a super customizable framework for creating and analyzing biological simulations. It's open-source and licenced under MIT.
Usage
Including terra
Getting started is as easy as including the script!
<script src="path/to/terra.min.js"></script>
terra can also be used as a module with most popular module systems. ?
Creating creatures
Let's create a simple creature using the creatureFactory. Each creature requires a type.
terra.creatureFactory.register({
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.creatureFactory.register({
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.
var ex1 = new terra.Terrarium(25, 25, 'ex1');
ex1.populate([['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 ?
terra.creatureFactory.register({
type: 'GoL',
colorFn: function () { return this.alive ? this.color + ',1' : '0,0,0,0'; },
wait: function () {},
isDead: function () { return false; },
queue: function (neighbors) {
var surrounding = _.filter(neighbors, function (spot) {
return spot.creature.alive;
}).length;
this.alive = surrounding === 3 || surrounding === 2 && this.alive;
return false;
}
}, function () {
this.alive = Math.random() < 0.5;
});
Cyclic Cellular Automaton ?
terra.creatureFactory.register({
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];},
wait: function () {},
isDead: function () { return false; },
queue: function (neighbors) {
var next = (this.state + 1) % 16;
var changing = _.some(neighbors, function (spot) {
return spot.creature.state === next;
});
if (changing) this.state = next;
}
}, function () {
this.state = Math.floor(Math.random() * 16);
});
If you come up with a cool example, let me know! I'll add it to this list and credit you.
Creatures
Creatures are registered with
terra.creatureFactory.register(options, init)
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 populate( ).
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
-
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
- Range: [0, 1]
Percentage of a creature's max energy below which it will stop moving (used in the default queue method).
-
function queue
Main entry point for behavior; called for each creature on each iteration.
- Parameters: {coords, creature} [] neighbors
- Default: Reproduce if energy is sufficient, otherwise move if energy is sufficient, otherwise wait.
- Returns: {x, y, creature} || false
-
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 queue 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: Creature loses 5 energy.
-
* *
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 terra.creatureFactory.register 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:
var t = new Terrarium(width, height, id, cellSize, insertAfter);
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
- Default: canvas is appended to
Once initialized, terrariums have a few exposed methods. Using our terrarium t
that we just created:
t.populate(creatures);
//populates the terrarium with a set distribution of creatures.
//<creatures> is an array of arrays of the form [string 'creatureName', int fillPercent]
t.grid = t.step(steps);
//t.step returns the next step of the simulation, or the grid after <steps> steps if specified.
//here, we're setting the terrarium's grid to the returned grid.
t.draw();
//updates the canvas to reflect the current grid
t.animate(steps, fn);
//starts animating the simulation. The simulation will stop after <steps> steps
//if specified, and call <fn> as a callback once the animation finishes.
t.stop();
//stops a currently running animation
Still want more? Check out the source on GitHub!