Getting started
A vivarium is a set of elements that interact with each other in a grid, changing into other elements over time. With this TypeScript library you can define your vivarium and see it evolve on your screen. A tool for creative coders and learners, based on cellular automata theory.
Install
Section titled “Install”Install Vivarium as a dependency. No other dependecies required.
npm i @wonderyard/vivariumpnpm add @wonderyard/vivariumyarn add @wonderyard/vivariumYou can start defining your vivarium like this:
import { vivarium } from "@wonderyard/vivarium";
const vi = vivarium();Elements
Section titled “Elements”An element is the basic bit of your simulation. It can be represented with a little square on your screen.
Here’s how to define one.
const alien = vi.element("alien", "green");We’re not drawing it on the screen yet. For now we are just defining what an alien is, so that our system is aware of its existence in the vivarium.
The second base concept of Vivarium is kinds. A kind is not an element, it doesn’t have a color and so it cannot be directly represented on a screen. However it can be inherited by elements to share common characteristics. It is not necessary to use kinds. Consider it an advanced concept that allows you to write less code and achieve more complex. To start, I’ll show you how to define one, which is similar to how we define elements.
const sentient = vi.kind("sentient");The heart of Vivarium is made of rules. A rule is a way to tell elements how to behave in our vivarium. Every step of the simulation, an element can become another element or stay the same. Let’s see a simple rule first:
const space = vi.element("space", "blue");const alien = vi.element("alien", "green");
space.to(alien); // all spaces become aliensThe method to can be read as “becomes” or “evolves to”. So with this last line, what we are saying is: when we will run the system, at every simulation step, every space element will become an alien element. It’s like aliens appearing out of nowhere, so on the screen we would see every blue cell in the grid becoming green as soon as we start the simulation. A room filled with aliens! (You might need to put your imagination to the test.)
Conditions
Section titled “Conditions”However in a system like that, nothing interesting would happen after the first step. All free spaces are occupied by aliens immediately and they will just sit there forever.
Let’s create a more interesting system, this time by adding a condition to the existing rule.
space.to(alien).count(alien, 1);By calling the method count we are stating that every time the simulation attempts to apply the rule space.to(alien) it must first count the number of whatever we pass as first parameter (alien in this case) in from the neighborhood of space and compare that with the second argument (1 in this case). If the condition is met, that is, the number of aliens in the neighborhood of space is exactly 1, then the rule is applied, otherwise it is skipped for the current step.
With this rule, we are filling the room bit by bit in an interesting pattern. It almost looks like alien tentacles! Eventually the simulation becomes stable and nothing else happens. Can we do any better?
Game of Life
Section titled “Game of Life”Probably the most famous cellular automata, Game of Life rules are not that far from what we created just before, but they show emergent behavior. There’s many ways to implement Game of Life rules. Here’s one version:
// A new alien is born near a family of 3space.to(alien).count(alien, 3);// Underpopulation: the alien leaves the space because it feels lonely therealien.to(space).count(alien, [0, 1]);// Overpopulation: too many aliens!alien.to(space).count(alien, [4, 5, 6, 7, 8]);Feel free to come up with your own implementation! Vivarium is designed to allow you to define the same thing in multiple ways, depending on the structure and conventions you want to use.
Here’s an equivalent list of rule declarations:
// Same as beforespace.to(alien).count(alien, 3);// Alien stays there when the number of aliens around it is just rightalien.to(alien).count(alien, [2, 3]);// Alien fell into a black hole :'(alien.to(space);Here we compressed the two alien.to(space) rules into one inverse rule, and we let it evolve to space otherwise, unconditionally. Try to reason on why this set of rules is equivalent to the previous one and notice two important things:
- Rules without conditions are still meaningful, so use them when they might simplify your overall rule syntax.
- Order of rules matters!
Order of rules
Section titled “Order of rules”Did I mention it already? Order of rules matters! Yes, because the system has to evaluate rules one by one. Whichever rule meets its conditions first, will be the one evolving the cell. No more rules are evaluated for that cell until the next simulation step. In other words, rules are declared in order of priority, high to low. The first rule passing the test is the winner. Every other rule is ignored. So it’s important that you design your vivarium with this concept in mind, or your rules won’t work as expected.