Cellular Automata

Here’s the example I showed at the projector, which is also the starting code you’ll find in the link above:

    neighborhood: `
     oo
      x
    `,

    rule: (cell) => {
      return cell.sumNeighbors() == 1
    },

    everyStep: (automaton) => {
      automaton.setCell(0, 0, 1)
    },

Ask: What does each part of this code do?

  • What is the neighborhood? What do the x and os mean?
  • What is the rule?
  • Why everyStep? (What happens if you remove it?)

Conduct some experiments! Tinker with other things in the code too, like width and height and cellSize and colorScheme.

There’s another way to write this. The % operator (“remainder” operator, sometimes also incorrectly called the “modulo” operator) makes positive numbers “wrap around” when they reach some limit. For example, n % 4 means “as n increases, when n reaches 4 then it goes back to 0:”

n n % 4
0 0
1 1
2 2
3 3
4 0
5 1
6 2
7 3
8 0
9 1

We thus get the same results if we replace this:

cell.sumNeighbors() == 1

..with this:

cell.sumNeighbors() % 2

Try it! That will set you up better for further experiments where the sum of the neighbors can be great than 2.

Sticking with the same code as above, try this neighborhood instead:

     oo
     ox

Now try making the numbers wrap around at 3 instead of 3:

    rule: (cell) => {
      return cell.sumNeighbors() % 3
    },

Try some even higher numbers!

In the starter code, you’ll see this line that sets the upper left cell’s value to 1:

automaton.setCell(0, 0, 1)

Change it to set the center cell instead:

automaton.setCenterCell(1)

…and then try this neighborhood:

      o
     oxo
      o

…or this one:

     ooo
     oxo
     ooo

Make up your own oddly shaped neighborhoods!

You can also get interesting results by changing everyStep to firstStep. (Warning: variations on this can create flashing / strobing patterns. If you are photosensitive, use caution.)

Here is an automaton that makes each cell take on the average value of its neighbors:

    neighborhood: `
      o
     oxo
      o
    `,

    rule: (cell) => {
      return Math.round(
        (cell.sumNeighbors() + cell.value()) / (cell.neighborhoodSize + 1)
      )
    },

    firstStep: (automaton) => {
      automaton.fillRandom(50)
    },

Note the firstStep: we start by filling the whole gride with random numbers less than 3. (There is no everyStep now, just firstStep!)

Try larger arguments to fillRandom.

Try this neighborhood instead:

        ooo
        oxo
        ooo

Try passing a number in the thousands or tens of thousands to fillRandom. Now try changing Math.round (which rounds to the nearest integer) into Math.floor (which always rounds down) and notice the difference!

Let’s make each cell take on the value of one of its neighbors, selected at random:

    neighborhood: `
        ooo
        oxo
        ooo
    `,

    rule: (cell) => {
      return cell.randomNeighborValue()
    },

    firstStep: (automaton) => {
      automaton.fillRandom(3)
    },

What if we make the cell choose two neighbors, and make it agree with them if they both have the same value?

    rule: (cell) => {
      let choice0 = cell.randomNeighborValue()
      let choice1 = cell.randomNeighborValue()
      if (choice0 == choice1) {
        return choice0
      } else {
        return cell.value()
      }
    },

Social scientists sometimes use more complex versions of this to simulate social pressure and the propagation of opinions.

Here’s a terrible, wildly inaccurate simulation of mineral growth:

    neighborhood: `
        ooo
        oxo
        ooo
    `,

    rule: (cell) => {
      if (cell.value() == 2) {
        return 2;
      }
      let randNeighbor = cell.randomNeighborValue()
      if (cell.value() == 1 && cell.sumNeighbors() < 5 && randNeighbor == 2) {
        return 2;
      }
      if (randNeighbor != 2) {
        return randNeighbor
      }
      return cell.value()
    },

    firstStep: (automaton) => {
      automaton.fillRandom(2.1)
    },

In this code, && means “and.” This code:

cell.value() == 1 && cell.sumNeighbors() < 5 && randNeighbor == 2

…means “cell value is 1 and the sum of the neighbors is less than 5 and the random neighbor’s value is 2.””

Try changing the neighborhood! And try changing 5 to other numbers.

In the game of “rock, paper, scissors,” two players simultaneously choose one of those three choices in the name of the game. Each choice wins against one of the others and loses against one of the others: paper wraps rock, rock blunts scissors, scissors cut paper.

When humans play this game, they play with hands signs. In this cellular automaton version, there are three cell states / colors that correspond to “rock,” “paper,” and “scissors.” Each cell plays the game against all of its neighbors. If it loses more of those games than it wins, then its value changes. Try it:

    neighborhood: `
         o
        oxo
         o
    `,

    rule: (cell) => {
      let wins = cell.countNeighborsWithValue(wrapAround(cell.value() + 1, 3))
      let losses = cell.countNeighborsWithValue(wrapAround(cell.value() - 1, 3))
      if (losses > wins) {
        return wrapAround(cell.value() - 1, 3);
      } else {
        return cell.value()
      }
    },

    firstStep: (automaton) => {
      automaton.fillRandom(3)
    },

You’ll see waves traveling across the grid: waves of scissors cutting paper, chased by waves of rock blunting scissors, chased by waves of paper wrapping rock.

Try changing the argument to fillRandom to a larger number. The rule code here expects numbers in the range 0…2, and allowing those larger numbers breaks it in an interesting way!

This is probably the most famous cellular automaton of all time:

    neighborhood: `
        ooo
        oxo
        ooo
    `,

    rule: (cell) => {
      let sum = cell.sumNeighbors()
      if (sum == 3 || (sum == 2 && cell.value() == 1)) {
        return 1
      } else {
        return 0
      }
    },

    firstStep: (automaton) => {
      automaton.fillRandom(1.5)
    },

What does the rule mean? Can you figure it out?

That simple rule generates incredibly complex behaviors. You can go deep into this one! Here is more info.