Skip to content

Condition

A rule without conditions is always accepted when evaluated. A condition determines whether a rule is accepted or not. Conditions are added by chaining methods after to.

There are three types of conditions: Count, Is, Chance.

The count condition checks how many cells of a given element (or kind) are present in the neighborhood. The rule is accepted only if the actual count is one of the specified values.

// Space becomes alien only if exactly 1 alien is in the neighborhood
space.to(alien).count(alien, 1);

You can pass multiple values. The condition passes if the count matches any of them:

// Alien survives if it has 2 or 3 alien neighbors
alien.to(alien).count(alien, 2, 3);

You can also pass an array of values:

alien.to(space).count(alien, [0, 1, 4, 5, 6, 7, 8]);

When called with no count values, all positive counts are matched (1 through 8 for square, 1 through 4 for cross). Zero is excluded, so this form means “at least one”:

// Matches if there is at least one alien neighbor
space.to(alien).count(alien);

You can also count by neighbor reference instead of a specific element. This counts how many neighbors match whatever element is at the referenced position:

// Count how many neighbors match the element above the current cell
space.to(alien).count(vi.neighbor.TOP, 3);

Helpers are convenience functions exposed through vi.helpers that make it easier to construct count arrays.

The between helper generates a range of whole numbers from the given start to finish (inclusive):

const { between } = vi.helpers;
space.to(alien).count(alien, between(1, 5));
// same as
space.to(alien).count(alien, [1, 2, 3, 4, 5]);

The not helper returns every valid count value except the ones specified. It is useful for expressing “anything but this value”:

const { not } = vi.helpers;
space.to(alien).count(alien, not(2));
// in a square neighborhood the line above is equivalent to:
space.to(alien).count(alien, [0, 1, 3, 4, 5, 6, 7, 8]);

You can also pass an array to exclude multiple values:

space.to(alien).count(alien, not([2, 3]));
// equivalent to:
space.to(alien).count(alien, [0, 1, 4, 5, 6, 7, 8]);

You can combine not with between:

space.to(alien).count(alien, not(between(3, 6)));
// equivalent to:
space.to(alien).count(alien, [0, 1, 2, 7, 8]);

These helpers generate arrays of even numbers (zero included) and odd numbers respectively:

const { even, odd } = vi.helpers;
space.to(alien).count(alien, even());
// same as
space.to(alien).count(alien, [0, 2, 4, 6, 8]);
space.to(alien).count(alien, odd());
// same as
space.to(alien).count(alien, [1, 3, 5, 7]);

In a "cross" neighborhood the ranges adjust accordingly ([0, 2, 4] for even, [1, 3] for odd).

The is condition checks whether a specific neighbor is a certain element, kind, or matches another neighbor position.

Compare a neighbor with a specific element:

const { neighbor } = vi;
// Space becomes alien if the cell above is an alien
space.to(alien).is(neighbor.TOP, alien);

Compare a neighbor with a kind (matches any element that extends the kind):

const { neighbor } = vi;
space.to(alien).is(neighbor.TOP, sentient);

Compare two neighbor positions (the rule passes if both positions hold the same element):

const { neighbor } = vi;
// Space becomes alien if the cell above and the cell below are the same element
space.to(alien).is(neighbor.TOP, neighbor.BOTTOM);

The chance condition introduces randomness. It accepts with a probability of part / whole:

// 1 in 1000 chance for the cell to become an alien each step
space.to(alien).chance(1, 1000);

If whole is omitted, it defaults to 100:

// 5% chance
space.to(alien).chance(5);

You can add more than one condition per rule. When that is the case, the rule is accepted only if every condition is met. To modify the strategy used for the acceptance of rules, you can use the accept method on the builder chain. This method stops the chain — you cannot add more conditions after determining the accept strategy.

There are 4 strategies available:

  • "all" (default): every condition has to be met in order for the rule to be accepted.
  • "any": at least one condition has to be met in order for the rule to be accepted.
  • "one": one and only one condition has to be met in order for the rule to be accepted.
  • "none": every condition has to be left unmet in order for the rule to be accepted.
space.to(alien).count(alien, 1).chance(1, 1000).accept("any");

"none" is also useful as a way to negate a single condition:

const { neighbor } = vi;
space.to(house).is(neighbor.BOTTOM, liquid).accept("none");

We build a house on top of another cell only if the cell below is not of liquid kind.