Cellular automata is usually a model that consists of a grid and “cells” which have a finite number of states and behaviors. Typical states might include on, off, empty, populated, etc. The behaviors are usually pretty simple such as grow, die, give birth. These behaviors can be driven by time or based on relationships with neighboring cells. While the states and behaviors are fairly simple, the patterns that emerge across the grid can be complex and unpredictable. The image above is from Conway’s Game of Life, one of the earliest computational cellular automata models created by John Conway in 1974. Conway carefully chose four simple rules to govern his model:

  1. There should be no explosive growth.
  2. There should exist small initial patterns with chaotic, unpredictable outcomes.
  3. There should be potential for von Neumann universal constructors.
  4. The rules should be as simple as possible, whilst adhering to the above constraints.

The processing sketches below also use simple rules:

  1. A cell will grow for a certain amount of time.
  2. Once a cell reaches its peak growth it will give birth to neighboring cells if they are unoccupied.
  3. The amount of cells a cell gives birth to is variable with 4 or 8 being the most possible children depending on if diagonal neighbors are considred.
  4. After a cell gives birth it dies at a determined rate.

In the examples below a user activated cell is generated with a random color. That cell then gives that color to any children it creates, i a way giving the cells genetic material. This color transfer can also contain a rate of change, showing how clusters of cells change as they grow while still retaining generational or color information depending on that rate of change. The examples below show how randomness can dramatically effect the patterns of growth.

The example below on the left is shown in the diagrams above. This example contains no random change. The growth rate is constant and once a cell reaches it peak growth it will give birth to cells in its cardinal neighbors unless they are occupied.

The example above to the right uses the same code as the example to the left. The only difference is that the rate of growth for each cell is random and it picks up to four random neighboring cells to activate at its peak growth. The diagrams below show how picking random neighbors affects the pattern within one cycle.

The code below is for the processing sketch above that uses random growth rates and neighbor selection.

int W = 80;
int H = 80;

Cell [][] cells = new Cell[W][H];

int cellsize = 10;

void setup(){
  size(800,800);
  for(int i = 0; i < W; i++){
    for(int j = 0; j < H; j++){
      cells[i][j] = new Cell();
    }
  }
}


void draw(){
  background(0);
  noStroke();
  for(int i = 0; i < W; i++){
    for(int j = 0; j < H; j++){
      float R = cells[i][j].R;
      float G = cells[i][j].G;
      float B = cells[i][j].B;
      float lc = cells[i][j].life;
      fill(R, G, B, lc);
      rect(i*cellsize,j*cellsize,cellsize,cellsize);
      
      if(cells[i][j].grow == 1){
        if(cells[i][j].life < 255){
          cells[i][j].life = lc + cells[i][j].rate;
        }else{
          cells[i][j].grow = 0;
        }
      }
      
      if(cells[i][j].grow == 0){
        cells[i][j].life = lc - cells[i][j].rate/5.0;
      }

      if(lc > 150 && lc < 155 && cells[i][j].birth == 0 && cells[i][j].grow == 0 ){
        for(int g = 0; g < 4; g++){
          int nextx =  i + int(random(-2,2));
          println(nextx);
          if(nextx  == W){
            nextx  = 0;
          }
          if(nextx  < 0){
            nextx  = W-1;
          }
          int nexty =  j + int(random(-2,2));
          if(nexty == H){
            nexty = 0;
          }
          if(nexty < 0){
            nexty = H-1;
          }
          if(cells[nextx][nexty].life < 1){
            cells[nextx][nexty].life = 0;
            cells[nextx][nexty].R = cells[i][j].R + random(-6,6);
            cells[nextx][nexty].G = cells[i][j].G + random(-6,6);
            cells[nextx][nexty].B = cells[i][j].B + random(-6,6);
            cells[nextx][nexty].birth = 0; 
            cells[nextx][nexty].grow = 1;
            cells[nextx][nexty].rate = random(10)+1;
          }
          cells[i][j].birth++;
        }
      }
    }
  }
  
  
}

void mousePressed() {
    for (int i = 0; i < W; i++) {
        for (int j = 0; j < H; j++) {
          if(dist(i,j,(mouseX)/cellsize,(mouseY)/cellsize) < 1){
             cells[i][j].life = 0;
             cells[i][j].R = random(255);
             cells[i][j].G = random(255);
             cells[i][j].B = random(255);
             cells[i][j].grow = 1;
             cells[i][j].rate = random(10)+1;
             cells[i][j].birth = 0;
          }
        }
    }
}

class Cell{
  float R;
  float G;
  float B;
  float life;
  int birth;
  int grow = 0;
  float rate;
  
  // Contructor
  Cell() {
    R = 0;
    life = 0;
    R = 0;
    G = 0;
    B = 0;
    birth = 0;
  }
}

Cellular automata can also be modeled in a three-dimensional grid. The code below is for the processing sketch above.

import controlP5.*; 

ControlP5 cp5;

int W = 10;
int H = 10;
int D = 10;

Cell [][][] cells = new Cell[W][H][D];

int cellsize = 20;

int oldx = mouseX;
int oldy = mouseY;
float rotx = 0;
float roty = 0;
float zcam = 350;


float birth_limit = 3;
float grow_rate = 3;

void setup(){
  size(1200,800,P3D);
  
  cp5 = new ControlP5(this);
  
   cp5.addSlider("birth_limit")
    .setPosition(50,50)
    .setRange(0,5)
    .setSize(240,20)
    .setValue(3)
    .setColorActive(color(150)) 
    .setColorBackground(color(50)) 
    .setColorForeground(color(100)) 
    .setNumberOfTickMarks(6)
  ;
  
  cp5.addSlider("grow_rate")
    .setPosition(50,90)
    .setRange(0,10)
    .setSize(240,20)
    .setValue(1)
    .setColorActive(color(150)) 
    .setColorBackground(color(50)) 
    .setColorForeground(color(100)) 
  ;
  
  
  for(int i = 0; i < W; i++){
    for(int j = 0; j < H; j++){
      for(int k = 0; k < D; k++){
        cells[i][j][k] = new Cell();
      }
    }
  }
  
  int i = int(random(W));
  int j = int(random(H));
  int k = int(random(D));
  cells[i][j][k].life = 0;
  cells[i][j][k].R = random(255);
  cells[i][j][k].G = random(255);
  cells[i][j][k].B = random(255);
  cells[i][j][k].grow = 1;
  cells[i][j][k].rate = random(grow_rate);
  cells[i][j][k].birth = 0;
  
}

void draw(){
  background(0);
  lights();
  pushMatrix();
  cam();
  noStroke();
  for(int i = 0; i < W; i++){
    for(int j = 0; j < H; j++){
      for(int k = 0; k < D; k++){
        float R = cells[i][j][k].R;
        float G = cells[i][j][k].G;
        float B = cells[i][j][k].B;
        float lc = cells[i][j][k].life;
        
        pushMatrix();
        fill(R,G,B);
        translate(i*cellsize - W*cellsize/2, j*cellsize-H*cellsize/2, k*cellsize-D*cellsize); 
        box(lc);
        popMatrix();
              
        if(cells[i][j][k].grow == 1){
          if(cells[i][j][k].life < cellsize){
            cells[i][j][k].life = lc + cells[i][j][k].rate;
          }else{
            cells[i][j][k].grow = 0;
          }
        }
        
        if(cells[i][j][k].grow == 0){
          if(cells[i][j][k].life > 1){
            cells[i][j][k].life = lc - cells[i][j][k].rate/4.0;
          }
          else{
            cells[i][j][k].life = 0;
          }
        }
  
        if(lc > 10 && lc < 15 && cells[i][j][k].birth == 0 && cells[i][j][k].grow == 0 ){
          for(int g = 0; g < birth_limit; g++){
            int nextx =  i + int(random(-2,2));
            if(nextx  == W){
              nextx  = 0;
            }
            if(nextx  < 0){
              nextx  = W-1;
            }
            int nexty =  j + int(random(-2,2));
            if(nexty == H){
              nexty = 0;
            }
            if(nexty < 0){
              nexty = H-1;
            }
            int nextz =  k + int(random(-2,2));
            if(nextz == D){
              nextz = 0;
            }
            if(nextz < 0){
              nextz = Z-1;
            }
            if(cells[nextx][nexty][nextz].life < 1){
              cells[nextx][nexty][nextz].life = 0;
              cells[nextx][nexty][nextz].R = cells[i][j][k].R + random(-6,6);
              cells[nextx][nexty][nextz].G = cells[i][j][k].G + random(-6,6);
              cells[nextx][nexty][nextz].B = cells[i][j][k].B + random(-6,6);
              cells[nextx][nexty][nextz].birth = 0; 
              cells[nextx][nexty][nextz].grow = 1;
              cells[nextx][nexty][nextz].rate = random(grow_rate)+.25;
            }
            cells[i][j][k].birth++;
          }
        }
      }
    }
  }
  popMatrix();
}

void cam() {
  int newx = mouseX;
  int newy = mouseY;
  
  translate(width/2, height/2,zcam);
  rotateY(rotx);
  rotateX(roty);
  if ((mousePressed == true) && (mouseY > 200)) {
    rotx = rotx + (oldx-newx)/50.0f;
    roty = roty + (oldy-newy)/50.0f;
  }
  oldx = newx;
  oldy = newy;
  translate(0,0,100);
}

public void mouseWheel(MouseEvent event) {
  float e = event.getCount();
  zcam = zcam - e*5;
}


void keyPressed() {
  if(key == ' ' ){
    int i = int(random(W));
    int j = int(random(H));
    int k = int(random(D));
    cells[i][j][k].life = 0;
    cells[i][j][k].R = random(255);
    cells[i][j][k].G = random(255);
    cells[i][j][k].B = random(255);
    cells[i][j][k].grow = 1;
    cells[i][j][k].rate = random(1) +0.25;
    cells[i][j][k].birth = 0;
  }
}

class Cell{
  float R;
  float G;
  float B;
  float life;
  int birth;
  int grow = 0;
  float rate;
  
  // Contructor
  Cell() {
    R = 0;
    life = 0;
    R = 0;
    G = 0;
    B = 0;
    birth = 0;
  }
}