mirror of
https://github.com/rileyjshaw/terra.git
synced 2024-11-21 04:54:23 +00:00
508 lines
21 KiB
HTML
508 lines
21 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>terra.js</title>
|
|
<link rel="stylesheet" href="main.css">
|
|
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
|
|
</head>
|
|
<body>
|
|
<div class="fullPage splash">
|
|
<div class="vcent"></div>
|
|
<header id="header">
|
|
<h1>terra.js <span>beta</span></h1>
|
|
<p>A JavaScript library for simple biological simulations and cellular automata.</p>
|
|
<a data-scroll href="#main" id="scroller1" class="downArrow">⬇</a>
|
|
</header>
|
|
</div>
|
|
|
|
<nav>
|
|
<ul>
|
|
<li><a data-scroll href="#usage">Usage</a></li>
|
|
<li><a data-scroll href="#examples">Examples</a></li>
|
|
<li><a data-scroll href="#creatures">Creatures</a></li>
|
|
<li><a data-scroll href="#terrarium">Terrarium</a></li>
|
|
<li><a href="https://github.com/rileyjshaw/terra">GitHub</a></li>
|
|
</ul>
|
|
</nav>
|
|
|
|
<div id="main" class="main">
|
|
<p>terra is a <strong>super customizable</strong> library for creating and analyzing biological simulations. It's open-source and licensed under MIT.</p>
|
|
|
|
<h2 id="usage">Usage</h2>
|
|
|
|
<h3>Including terra</h3>
|
|
<p>Getting started is as easy as including the script!</p>
|
|
<pre><code class="language-markup"><script src="//cdn.jsdelivr.net/terra/latest/mainfile"></script></code></pre>
|
|
<p>terra can also be used as a module with most popular module systems: <a class="question" target="_blank" href="https://github.com/umdjs/umd">?</a></p>
|
|
<pre><code class="language-javascript">// 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;</code></pre>
|
|
|
|
<p>If you manage dependencies with <a href="http://bower.io/">Bower</a>, you're in luck!</p>
|
|
<pre><code class="language-bash">bower install terra</code></pre>
|
|
|
|
<h3>Creating creatures</h3>
|
|
<p>Let's create a simple creature using the registerCreature method. Each creature requires a type.</p>
|
|
<pre><code class="language-javascript">terra.registerCreature({
|
|
type: 'firstCreature'
|
|
});</code></pre>
|
|
<p>This creature is valid, but it's pretty boring. To make a more interesting creature, let's override some of the default <a data-scroll href="#creatures">attributes and methods</a>.</p>
|
|
<pre><code class="language-javascript">terra.registerCreature({
|
|
type: 'secondCreature',
|
|
color: [120, 0, 240],
|
|
sustainability: 6,
|
|
reproduceLv: 1
|
|
});</code></pre>
|
|
<p>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.</p>
|
|
|
|
<h3>Creating the environment</h3>
|
|
<p>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 <code class="language-javascript">simplePlant</code> creature, but don't worry too much about how that works just yet.</p>
|
|
<pre><code class="language-javascript">// create a simple plant creature
|
|
factory.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]]);</code></pre>
|
|
|
|
<h3>Running a simulation</h3>
|
|
|
|
<p>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.</p>
|
|
<pre id="ex1End"><code class="language-javascript">ex1.animate(300);</code></pre>
|
|
|
|
<p>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 <a data-scroll href="#customOptions">creatures are entirely customizable</a>.</p>
|
|
|
|
<h2 id="examples">Examples</h2>
|
|
|
|
<h3 id="gol">Conway's Game of Life <a class="question" target="_blank" href="http://en.wikipedia.org/wiki/Conway's_Game_of_Life">?</a></h3>
|
|
<pre><code class="language-javascript">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();</code></pre>
|
|
|
|
<h3 id="cyclic">Cyclic Cellular Automaton <a class="question" target="_blank" href="http://en.wikipedia.org/wiki/Cyclic_cellular_automaton">?</a></h3>
|
|
<pre><code class="language-javascript">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();</code></pre>
|
|
|
|
<h3 id="brutesAndBullies">Brutes and Bullies</h3>
|
|
<pre><code class="language-javascript">// 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();</code></pre>
|
|
|
|
<h3 id="rule146">Rule 146</h3>
|
|
<pre><code class="language-javascript">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();</code></pre>
|
|
|
|
<p>A few more awesome examples:</p>
|
|
<ul>
|
|
<li><a href="http://crawfy48.github.io/Ising-model/ising.html">Ising model simulation of 2D ferromagnetic material</a> by <a href="https://github.com/crawfy48/Ising-model">Łukasz Mioduszewski</a></li>
|
|
<li><a href="http://labofoz.com/first-try/terrajs">Forest canopies on a tidally locked planet</a> by <a href="http://labofoz.com/blog/first-try-terrajs">Oz Ramos</a></li>
|
|
</ul>
|
|
|
|
<p>If you come up with a cool demo, <a href="mailto:i@rileyjshaw.com">let me know</a>! I'll add it to this list and credit you.</p>
|
|
|
|
|
|
<h2 id="creatures">Creatures</h2>
|
|
<p>Creatures are registered with <pre><code class="language-javascript">terra.registerCreature(options, init)</code></pre> or <pre><code class="language-javascript">terra.registerCA(options, init)</code></pre> for cellular automata.</p>
|
|
|
|
<p>The following methods and attributes can be passed in an object as the first argument:</p>
|
|
<h3 id="creatureOptions">Required</h3>
|
|
<ul class="defaults">
|
|
<li id="type">
|
|
<h4><span class="token keyword">string</span> type</h4>
|
|
<p>Creature type, to be used later in <a data-scroll href="#terrariumMethods" class="token function">makeGrid( )</a> or <a data-scroll href="#terrariumMethods" class="token function">makeGridWithDistribution( )</a>.</p>
|
|
</li>
|
|
</ul>
|
|
|
|
<h3>Optional</h3>
|
|
<ul class="defaults">
|
|
<li id="actionRadius">
|
|
<h4><span class="token keyword">int</span> actionRadius</h4>
|
|
<p>A creature's vision and movement range for each step.</p>
|
|
<ul>
|
|
<li>Default: 1</li>
|
|
</ul>
|
|
</li>
|
|
<li id="character">
|
|
<h4><span class="token keyword">char</span> character</h4>
|
|
<p>ASCII character used to visually represent a creature.</p>
|
|
<ul>
|
|
<li>Default: undefined (fills cell)</li>
|
|
</ul>
|
|
</li>
|
|
<li id="color">
|
|
<h4><span class="token keyword">int [3]</span> color</h4>
|
|
<p>RGB components of a creature's display color.</p>
|
|
<ul>
|
|
<li>Range: [0, 255]</li>
|
|
<li>Default: random</li>
|
|
</ul>
|
|
</li>
|
|
<li id="colorFn">
|
|
<h4><span class="token keyword">function</span> colorFn</h4>
|
|
<p>How a creature's color is determined at each step.</p>
|
|
<ul>
|
|
<li>Returns: <span class="mono"><span class="token keyword">string</span></span> of comma-separated RGBA components.</li>
|
|
</ul>
|
|
</li>
|
|
<li id="efficiency">
|
|
<h4><span class="token keyword">int</span> efficiency</h4>
|
|
<p>Conversion ratio of food to energy. Food energy × efficiency = gained energy.</p>
|
|
<ul>
|
|
<li>Default: 0.7</li>
|
|
</ul>
|
|
</li>
|
|
<li id="initialEnergy">
|
|
<h4><span class="token keyword">float</span> initialEnergy</h4>
|
|
<p>Energy level that a creature has at the start of its life.
|
|
<ul>
|
|
<li>Range: (0, <a data-scroll href="#maxEnergy">maxEnergy</a>]</li>
|
|
<li>Default: 50</li>
|
|
</ul>
|
|
</li>
|
|
<li id="isDead">
|
|
<h4><span class="token keyword">function</span> isDead</h4>
|
|
<p>Determines whether a creature should be removed at the beginning of a step.</p>
|
|
<ul>
|
|
<li>Returns: <span class="mono"><span class="token keyword">boolean</span></span></li>
|
|
<li>Default: Return true for creatures with energy <= 0. Return false always for cellular automata.</li>
|
|
</ul>
|
|
</li>
|
|
<li id="maxEnergy">
|
|
<h4><span class="token keyword">float</span> maxEnergy</h4>
|
|
<p>Maximum energy that a creature can have; excess energy is discarded.</p>
|
|
<ul>
|
|
<li>Default: 100</li>
|
|
<li>Minimum: 0</li>
|
|
</ul>
|
|
</li>
|
|
<li id="move">
|
|
<h4><span class="token keyword">function</span> move</h4>
|
|
<p>How a creature moves.</p>
|
|
<ul>
|
|
<li>Parameters: <span class="mono"><span class="token keyword">{coords, creature} []</span> neighbors</span></li>
|
|
<li>Default: Look for edible creatures; if none are found, look for open spaces to move to; if none are found, wait.</li>
|
|
<li>Returns: <span class="mono">{x, y, creature, successFn} || false</span></li>
|
|
</ul>
|
|
</li>
|
|
<li id="moveLv">
|
|
<h4><span class="token keyword">float</span> moveLv</h4>
|
|
<p></p>
|
|
<ul>
|
|
<p>Percentage of a creature's max energy below which it will stop moving (used in the default <a data-scroll href="#process">process</a> method).</p>
|
|
<li>Default: 0</li>
|
|
<li>Range: [0, 1]</li>
|
|
</ul>
|
|
</li>
|
|
<li id="process">
|
|
<h4><span class="token keyword">function</span> process</h4>
|
|
<p>Main entry point for behavior; called for each creature on each iteration.</p>
|
|
<ul>
|
|
<li>Parameters: <span class="mono"><span class="token keyword">{coords, creature} []</span> neighbors, <span class="token keyword">int</span> x, <span class="token keyword">int</span> y</span></li>
|
|
<li>Default: Creatures reproduce if energy is sufficient, otherwise move if energy is sufficient, otherwise wait. Cellular automata do nothing by default.</li>
|
|
<li>Returns:
|
|
<ul>
|
|
<li><span class="mono">true</span>: indicates that a change has occurred in the creature; if no creatures return a truthy value, the simulation terminates</li>
|
|
<li><span class="mono">false</span></li>
|
|
<li><span class="mono">{x, y, creature, observed}</span>: a creature in a new position; observed acts like <span class="mono">true</span> and <span class="mono">false</span> above and allows watching for specific conditions</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li id="reproduce">
|
|
<h4><span class="token keyword">function</span> reproduce</h4>
|
|
<p>How a creature reproduces.</p>
|
|
<ul>
|
|
<li>Parameters: <span class="mono"><span class="token keyword">{coords, creature} []</span> neighbors</span></li>
|
|
<li>Default: Look for neighboring open space; if any exists, randomly place a new creature and lose energy equal to the child's <a data-scroll href="#initialEnergy">initialEnergy</a>.</li>
|
|
<li>Returns: <span class="mono">{x, y, creature, successFn, failureFn} || false</span></li>
|
|
</ul>
|
|
</li>
|
|
<li id="reproduceLv">
|
|
<h4><span class="token keyword">float</span> reproduceLv</h4>
|
|
<p>Percentage of a creature's max energy above which it will reproduce (used in the default <a data-scroll href="#process">process</a> method).</p>
|
|
<ul>
|
|
<li>Default: 0.7</li>
|
|
<li>Range: [0, 1]</li>
|
|
</ul>
|
|
</li>
|
|
<li id="size">
|
|
<h4><span class="token keyword">int</span> size</h4>
|
|
<p>A creature's size; by default, creatures can only eat creatures smaller than them.</p>
|
|
<ul>
|
|
<li>Default: 50</li>
|
|
</ul>
|
|
</li>
|
|
<li id="sustainability">
|
|
<h4><span class="token keyword">int</span> sustainability</h4>
|
|
<p>Number of visible food sources needed before a creature will eat.</p>
|
|
<ul>
|
|
<li>Default: 2</li>
|
|
<li>Range: (0, 16 × <a data-scroll href="#actionRadius">actionRadius</a> - 8]</li>
|
|
</ul>
|
|
</li>
|
|
<li id="wait">
|
|
<h4><span class="token keyword">function</span> wait</h4>
|
|
<p>What happens when a creature waits.</p>
|
|
<ul>
|
|
<li>Default: Creatures lose 5 energy. No effect for cellular automata.</li>
|
|
</ul>
|
|
</li>
|
|
<li id="customOptions">
|
|
<h4><span class="token keyword">*</span> *</h4>
|
|
<p>The best part about creatures is that you can add <em>whatever you want</em> to them! In addition to overriding any of the above properties, creatures will accept any methods and properties you throw at 'em.</p>
|
|
</li>
|
|
</ul>
|
|
|
|
<p>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 <a data-scroll href="#cyclic">Cyclic Cellular Automaton</a> example above we see the following init function:</p>
|
|
<pre><code class="language-javascript">function () {
|
|
this.state = Math.floor(Math.random() * 16);
|
|
});</code></pre>
|
|
<p>Whenever a new creature is created of type 'cyclic', it will be randomly assigned a state of 0 to 15.</p>
|
|
|
|
<h2 id="terrarium">Terrarium</h2>
|
|
<p>Terrariums are where the action happens. They're initialized with the following constructor:</p>
|
|
<pre><code class="language-javascript">//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')
|
|
});</code></pre>
|
|
<h3>Required</h3>
|
|
<ul class="defaults">
|
|
<li id="width">
|
|
<h4><span class="token keyword">int</span> width</h4>
|
|
<p>Number of cells in the x-direction.</p>
|
|
</li>
|
|
<li id="height">
|
|
<h4><span class="token keyword">int</span> height</h4>
|
|
<p>Number of cells in the y-direction.</p>
|
|
</li>
|
|
</ul>
|
|
<h3>Optional</h3>
|
|
<ul class="defaults">
|
|
<li id="id">
|
|
<h4><span class="token keyword">string</span> id</h4>
|
|
<p>id assigned to the generated canvas.</p>
|
|
</li>
|
|
<li id="cellSize">
|
|
<h4><span class="token keyword">int</span> cellSize</h4>
|
|
<p>Pixel width of each cell.</p>
|
|
<ul>
|
|
<li>Default: 10</li>
|
|
</ul>
|
|
</li>
|
|
<li id="insertAfter">
|
|
<h4><span class="token keyword">string</span> insertAfter</h4>
|
|
<p>id of the element to insert the canvas after.</p>
|
|
<ul>
|
|
<li>Default: canvas is appended to <code class="language-markup">document.body</code></li>
|
|
</ul>
|
|
</li>
|
|
<li id="periodic">
|
|
<h4><span class="token keyword">boolean</span> periodic</h4>
|
|
<p>Determines if boundaries wrap around; a creature at the top of a periodic map would see the bottom cells as though they were adjacent.</p>
|
|
<ul>
|
|
<li>Default: false</li>
|
|
</ul>
|
|
</li>
|
|
<li id="neighborhood">
|
|
<h4><span class="token keyword">string</span> neighborhood</h4>
|
|
<p>Defines neighborhood type as either <a href="http://en.wikipedia.org/wiki/Moore_neighborhood"><code class="language-javascript">moore</code></a> or <a href="http://en.wikipedia.org/wiki/Von_Neumann_neighborhood"><code class="language-javascript">vonNeumann</code></a>.</p>
|
|
<ul>
|
|
<li>Default: moore</li>
|
|
</ul>
|
|
</li>
|
|
<li id="trails">
|
|
<h4><span class="token keyword">float</span> trails</h4>
|
|
<p>Allows for "trails", which visualize system history. A value of 1 shows all past state; trails fade faster as we approach 0.</p>
|
|
<ul>
|
|
<li>Range: [0, 1]</li>
|
|
<li>Default: canvas is appended to <code class="language-markup">document.body</code></li>
|
|
<li>Dependencies: "background" option is required if trails is set.</li>
|
|
</ul>
|
|
</li>
|
|
<li id="background">
|
|
<h4><span class="token keyword">int [3]</span> background</h4>
|
|
<p>RGB components of the canvas' background.</p>
|
|
<ul>
|
|
<li>Range: [0, 255]</li>
|
|
<li>Default: transparent</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
|
|
<p id="terrariumMethods">Once initialized, terrariums have a few exposed methods. Using our terrarium <code class="language-javascript">t</code> that we just created:</p>
|
|
<pre><code class="language-javascript">//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();</code></pre>
|
|
|
|
<p>Still want more? Check out the source on <a href="https://github.com/rileyjshaw/terra">GitHub</a>!</p>
|
|
</div>
|
|
|
|
<footer>Created with ❤ by <a href="http://rileyjshaw.com">rileyjshaw</a>. Inspired by <a href="http://eloquentjavascript.net/">Marijn Haverbeke</a> and <a href="https://www.wolframscience.com/">Stephen Wolfram</a>.</footer>
|
|
<script src="terra.demo.min.js"></script>
|
|
</body>
|
|
</html>
|