Click here to Skip to main content
Click here to Skip to main content

Generative Art - Autonomy: Cellular Automata

, 30 Jun 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
A chapter excerpt from Generative Art
image002.jpg Generative Art
A practical guide using Processing

Matt Pearson

The most familiar single example of an autonomous agent is you. The cards in your wallet are writing your economic autobiography, your tweets and SMSs are writing an ASCII diary as part of a social map, and the phone in your pocket is drawing a GPS picture of your daily psycho-geography. This article, based on chapter 7 of Generative Art, familiarizes you with the concept of autonomy and the emergent complexity of this breed of object through one of the early parlor games of computer science: cellular automata.

Compliments of manning.com for The Code Project viewers is a 40% discount on Generative Art at manning.com only. Use promo code code40project and receive 40% off the eBook and pBook. All pBook purchases include FREE eFormats (PDF, ePub and Kindle) as soon as available.

You may also be interested in…

The most familiar single example of an autonomous agent, to you at least, is the one staring at this page. You, dear reader, with your behavior on any particular day defined by a complex mix of biology, psychology, and the comfortableness of your footwear, are an autonomous object, creating interesting patterns in the data you create. Most everything you do these days leaves a data trail: every purchase you make, every point you earn on your store card, every link you click, and every journey you take. The cards in your wallet are writing your economic autobiography, your tweets and SMSs are writing an ASCII diary as part of a social map, and the phone in your pocket is drawing a GPS picture of your daily psycho-geography.

To familiarize you with the concept of autonomy and the emergent complexity of this breed of object, we’ll play one of the early parlor games of computer science: cellular automata.

In the 1970s, the field of computer science was obsessed with cellular automata (CA). Super-computers were employed to churn through iterations of John Conway’s Game of Life over periods of weeks. Today, that kind of computing power is available in our mobile phones, so we can easily simulate cellular automata in a lightweight Processing sketch.

A 2D CA is a grid of cells (see figure 1), each of which has only two states: on and off, black or white, alive or dead. Each cell has limited local knowledge, only able to see its eight immediate neighbors. In a series of cycles, each cell decides its next state based on the current states of its surrounding cells.

Figure 1 A cellular automata grid

Setting up the Framework

The best way to demonstrate is by example. The following listing creates a grid of cells with on/off states and a reference to their immediate neighbors.

Listing 1 CA framework

Cell[][] _cellArray;
int _cellSize = 10;
int _numX, _numY;
 
void setup() {
 size(500, 300);
 _numX = floor(width/_cellSize);
 _numY = floor(height/_cellSize);
 restart();
}
 
void restart() {
 _cellArray = new Cell[_numX][_numY];   #A
 for (int x = 0; x<_numX; x++) {    #A
  for (int y = 0; y<_numY; y++) {#A
   Cell newCell = new Cell(x, y);  #A
   _cellArray[x][y] = newCell;  #A
  }  #A
 }  #A
 
 for (int x = 0; x < _numX; x++) {   #B
  for (int y = 0; y < _numY; y++) {  #B
 
   int above = y-1;  #C
   int below = y+1;  #C
   int left = x-1;  #C
   int right = x+1;  #C
 
   if (above < 0) { above = _numY-1; }  #D
   if (below == _numY) { below = 0; }  #D
   if (left < 0) { left = _numX-1; }  #D
   if (right == _numX) { right = 0; }  #D
 
   _cellArray[x][y].addNeighbour(_cellArray[left][above]);   #E
   _cellArray[x][y].addNeighbour(_cellArray[left][y]);   #E
   _cellArray[x][y].addNeighbour(_cellArray[left][below]);   #E
   _cellArray[x][y].addNeighbour(_cellArray[x][below]);   #E
   _cellArray[x][y].addNeighbour(_cellArray[right][below]);   #E
   _cellArray[x][y].addNeighbour(_cellArray[right][y]);   #E

  _cellArray[x][y].addNeighbour(_cellArray[right][above]);   #E
   _cellArray[x][y].addNeighbour(_cellArray[x][above]);   #E
  }
 }
}
 
void draw() {
 background(200);
 
 for (int x = 0; x < _numX; x++) {   #F
  for (int y = 0; y < _numY; y++) {   #F
   _cellArray[x][y].calcNextState();   #F
  }   #F
 }   #F
 
 translate(_cellSize/2, _cellSize/2);
 
 for (int x = 0; x < _numX; x++) {   #G
  for (int y = 0; y < _numY; y++) {   #G
   _cellArray[x][y].drawMe();   #G
  }   #G
 }  #G 
}   #G
 
void mousePressed() {
 restart();
}
 
//================================= object
 
class Cell {
 float x, y;
 boolean state;    #H
 boolean nextState;
 Cell[] neighbors;
 
 Cell(float ex, float why) {
  x = ex * _cellSize;
  y = why * _cellSize;
  if (random(2) > 1) {   #I
   nextState = true;   #I
  } else {   #I
   nextState = false;   #I
  }   #I
  state = nextState;
  neighbors = new Cell[0];
 }
 
 void addNeighbor(Cell cell) {
  neighbors = (Cell[])append(neighbors, cell);
 }


 void calcNextState() {
  // to come
 }
 
 void drawMe() {
  state = nextState;
  stroke(0);
  if (state == true) {
   fill(0);
  } else {
   fill(255);
  }
  ellipse(x, y, _cellSize, _cellSize);
 }
 
}
#A Creates grid of cells
#B Tells each object its neighbors
#C Gets locations to each side
#D Wraps locations at edges
#E Passes refs to surrounding locs
#F Calculates next states first
#G Draws all cells
#H On or off
#I Randomizes initial state

It’s a lengthy starting point, but nothing you haven’t encountered before. At the bottom of the script you define an object, a Cell, which has x and y positions and a state, either on or off. It also has a holder for its next state, nextState, the state it will enter the next time its drawMe method is called.

In setup, you calculate the number of rows and columns based on the width and height and the size you want the cells; then, you call the restart function to fill a 2D array with this grid.

After the grid is created, there is a second pass through the array to tell each of the cells who its neighbors are (above, below, left, and right of them).

The draw loop then does a double pass through the array. The first pass triggers each cell to calculate its next state, and the second pass triggers each to make the transition and draw itself.

Note that this needs to be done in two stages so each cell has a static set of neighbors on which to base its next state calculation.

Now that you have the grid of cells (as shown in figure 1), you can begin giving the cells their simple behaviors. The best-known CA is John Conway’s Game of Life, so that is the one we’ll start with.

Multidimensional Arrays

A one-dimensional array is a data structure for storing a list of elements. You define one as follows:

int[] numberArray = {1, 2, 3};

But, if you want to store more than a list, if you have a matrix of elements to keep track of, you can add an extra dimension to the array. A 2D array is initialized like so:

int[][] twoDimArray = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };

Essentially, you’re defining an array of arrays. In listing 1, you can see an example of creating and iterating through a 2D array.

If you want more dimensions, you can have them. You’re free to add as many dimensions as you can get your head around:

int[][][] threeDimArray;

int[][][][] fourDimArray;

...

Figure 2 Conway’s Game of Life after 100 iterations

The Game of Life

The popularity of Conway’s Game of Life (GOL) is mostly due to the relevance it has found outside the field of mathematics. Biologists, economists, sociologists, and neuroscientists, amongst others, have discussed at length the parallels between the behavior of CA obeying GOL rules and the results of studies in their respective fields. It’s essentially a simple computational form of artificial life, a mathematical petri dish teeming with autonomous agents.

The rules for GOL are as follows:

  • Rule 1: If a live (black) cell has two or three neighbors, it continues to live. Otherwise it dies, of either loneliness or overcrowding.
  • Rule 2: If a dead cell has exactly three neighbors, a miracle occurs: it comes back to life.

You write this in code as follows. Complete the calcNextState function in accordance with the following listing.

Figure 3 Triangle height and rotation depend on the age of a cell. Triangle width is the neighbor count.

Figure 4 Circle size is determined by neighbor count. Lines and their rotations are determined by cell age.

Listing 2 Calculating the next state using GOL rules

void calcNextState() {
  int liveCount = 0;
  for (int i=0; i < neighbours.length; i++) {
   if (neighbors[i].state == true) {
     liveCount++;
   }
  }
 
    if (state == true) {
    if ((liveCount == 2) || (liveCount == 3)) {
      nextState = true;
    } else {
      nextState = false;
    }
  } else {
    if (liveCount == 3) {
      nextState = true;
    } else {
      nextState = false;
    }
  }
}

This code first counts the number of neighbors alive and then applies GOL rules. If you run it for a few seconds, you begin to see what looks like a microscope view of abstract organisms (see figure 2). Some parts bubble and then fade out; others achieve a looping stability. There has been much study of this game, identifying the mathematical “life” that forms: gliders, toads, boats, blinkers, beacons, and so on. The GOL Wikipedia page has many animated examples of the complex shapes and patterns that arise from these simple rules: see http://en.wikipedia.org/wiki/Conway’s_Game_of_Life#Examples_of_patterns.

It would be very easy for us to get sidetracked here because what you’ve written is essentially an AI program. Artificial intelligence is a field that, admittedly, has huge potential for the creation of generative art.[1] We’ll keep our focus relatively shallow and toy with the possibilities for each of these cells, with its local, blinkered behavior, to render itself to the screen in visually interesting ways.

A simple black or white dot is only a starting point. In the two examples in figures 3 and 4, I’ve used the number of live neighbors a cell has, and the number of frames it has remained alive, to determine the size, rotation, and color of a shape drawn at that cell’s location. You may like to try similar experiments.

GOL is only one of many potential rule sets. In addition to experimenting with visualizations, I’d encourage you to try inventing your own. In the following pages, I’ll demonstrate two more well-known patterns, and one invented behavior, each of which produces forms that have analogues in the natural world.

Vichniac Vote

The first pattern, the Vichniac Vote (after Gerard Vichniac who first experimented with it), is a lesson in conformity. Each cell is particularly susceptible to peer-group pressure and looks to its neighbors to observe the current trend. If the cell’s color is in the majority, it remains unchanged. If it’s in the minority, it changes. To ensure that there is an odd number of cells on which to make the decision, the cell includes its own current state in addition to its eight neighbors in making the calculation.

To see the vote in action, rewrite the calcNextState function using the code from the following listing. To create an artificial instability in the algorithm, you’ll swap the rules for four and five neighbors; otherwise, it settles quickly into a static pattern.

Listing 3 The Vichniac Vote Rule

void calcNextState() {
  int liveCount = 0;
  if (state) { liveCount++; }    #A
  for (int i=0; i < neighbors.length; i++) {    #A
   if (neighbors[i].state == true) {    #A
     liveCount++;    #A
   }    #A
  }    #A
 
  if (liveCount <= 4) {   #B
    nextState = false;   #B
  } else if (liveCount > 4) {   #B
    nextState = true;    #B
  }   #B
 
  if ((liveCount == 4) || (liveCount == 5)) {   #C
    nextState = !nextState;   #C
  }   #C
}   #C
#A Counts neighbors, including me
#B Am I in the majority?
#C Swaps rules for 4 and 5

The result is shown in figure 5. You can see how the clustering effect creates patterns similar to the hide of a cow, or the contours of a landscape seen from above.

Figure 5 The Vichniac Vote rule after 3 iterations, 11 iterations, 41 iterations, and 166 iterations

With the Vichniac Vote, the rules you apply are sociological—agents succumbing to the peer group pressure from their neighbors—but the results have an aesthetic more familiar from biology or geology. Might this imply that common computational principles underpin these disciplines?

Brian’s Brain

This is a three-state cellular automaton, meaning a cell can be in one more condition, apart from on or off. The states of a Brian’s Brain CA are firing, resting, and off. It’s designed to mimic the behavior of neurons in the brain, which fire and then rest before they can fire again. The rules are as follows:

  • If the state is firing, the next state is resting.
  • If the state is resting, the next state is off.
  • If the state is off, and exactly two neighbors are firing, the state becomes firing.

You’ll need to modify the cell class for this, as in the following code. Note that you have to change the type of state to int, so it can have more than two values. The integers 0, 1, and 2 indicate off, firing, and resting, respectively.

Listing 4 Cell object modified for the three-state Brian’s Brain behavior

class Cell {
 float x, y;
 int state;  #A
 int nextState;
 Cell[] neighbors;
 
 Cell(float ex, float why) {
  x = ex * _cellSize;
  y = why * _cellSize;
nextState = int(random(2));
  state = nextState;
  neighbors = new Cell[0];
 }
 
 void addNeighbor(Cell cell) {
  neighbors = (Cell[])append(neighbors, cell);
 }
 
 void calcNextState() {
  if (state == 0) {
int firingCount = 0;
   for (int i=0; i < neighbors.length; i++) {
    if (neighbors[i].state == 1) {   #B
     firingCount++;  #B
    }   #B
   }
   if (firingCount == 2) {   #C
    nextState = 1;   #C
   } else {
    nextState = state;    #D
   }
  } else if (state == 1) {   #E
   nextState = 2;   #E
  } else if (state == 2) {   #F
   nextState = 0;   #F
  }
}

void drawMe() {
 state = nextState;
 stroke(0);
 if (state == 1) {
  fill(0);    #G
 } else if (state == 2) {
  fill(150);   #H
 } else {
  fill(255);   #I
 }
  ellipse(x, y, _cellSize, _cellSize);
 }
}
#A State 1, 2, or 0
#B Counts firing Neighbors
#C If two neighbors are firing, fire too
#D Else, don’t change
#E If just fired, rest
#F If rested, turn off
#G Firing = black
#H Resting = grey
#I Off = white

The results look something like figure 6. Spaceships (the CA term, not mine) move across the plane in horizontal, vertical and sometimes diagonal directions.

Figure 6 The Brian’s Brain behavior after 19 iterations

Does this behavior reflect the way your thoughts form from the electronic bursts of firing synapses? Is what you’re seeing a mathematical model of consciousness itself? Perhaps; but for our purposes, for the time being at least, it’s just another emergent behavior that you can use as the basic for a generative visualization.

Just one more example before we move on. Not one of the famous rule sets, but a custom behavior instead. The patterns it produces should be quite familiar though.

This final pattern is an example of a CA with a continuous behavior. There is no reason a cell has to be limited to two or three distinct values: its state can vary across a range of values. In this example, you’ll use 255 values, the grayscale from white to black. This is a custom behavior, so it doesn’t have a name, but I’ve based it on a standard physics model—averaging—whereby a chaotic state settles into a steady one thorough the influence of its neighbors.

The rules are as follows:

  • If the average of the neighboring states is 255, the state becomes 0.
  • If the average of the neighboring states is 0, the state becomes 255.
  • Otherwise, new state = current state + neighborhood average – previous state value.
  • If the new state goes over 255, make it 255. If the new state goes under 0, make it 0.

If you were adhering to the physics model, you’d need only the third of these rules. I’ve added the other rules for instability, to stop it from settling. The code is in the next listing. The output is shown in figure 7.

Listing 5 Custom wave-like behavior

class Cell {
 float x, y;
 float state;
 float nextState;
 float lastState = 0;
 Cell[] neighbors;
 
 Cell(float ex, float why) {
  x = ex * _cellSize;
  y = why * _cellSize;
  nextState = ((x/500) + (y/300)) * 14;   #A
  state = nextState;
  neighbors = new Cell[0];
 }
 
 void addNeighbor(Cell cell) {
  neighbors = (Cell[])append(neighbors, cell);
 }
 
 void calcNextState() {
  float total = 0;  #B
  for (int i=0; i < neighbours.length; i++) {   #B
   total += neighbors[i].state;   #B
 }   #B
  float average = int(total/8);   #B
 
  if (average == 255) {
   nextState = 0;
  } else if (average == 0) {
   nextState = 255;
  } else {
   nextState = state + average;
   if (lastState > 0) { nextState -= lastState; }
   if (nextState > 255) { nextState = 255; }
   else if (nextState < 0) { nextState = 0; }
  }

  lastState = state;  #C
 }
 
 void drawMe() {
  state = nextState;
  stroke(0);
  fill(state);    #D
  ellipse(x, y, _cellSize, _cellSize);
 }
}
#A Creates initial gradient
#B Calculate neighborhood average
#C Stores previous state
#D Uses state value as fill color

 

Figure 7 The customized wave rule after 6, 26, 49, and 69 iterations

The most obvious comparison with this behavior is a liquid. Like rain on a shallow pool of water, it churns and varies.

The reason for this example is to emphasize how you can tweak not only the rules but also the framework itself. You don’t need a grid, you don’t need circles, and you don’t need a limited set of grayscale values: your system can be as subtle and sophisticated as you care to make it (see figure 8).

Figure 8 Single-pixel-sized cells produce more subtle (but more processor-intensive) patterns

Summary

The object-oriented approach to code is just the start. With this approach, you can begin realizing more sophisticated systems. Your objects can be autonomous agents, interacting with each other in an artificial environment, producing emergent complexity for you to visualize, an idea you explored through cellular automaton experiments.

Here are some other Manning titles you might be interested in:

image024.jpg

Secrets of the JavaScript Ninja
John Resig and Bear Bibeault

image025.jpg

Android in Action, Second Edition
W. Frank Ableson, Robi Sen, and Chris King

image026.jpg

iPhone and iPad in Action
Brandon Trebitowski, Christopher Allen, and Shannon Appelcline

[1] If this subject interests you, I suggest that you seek out Douglas Hofstadter’s Pulitzer Prize winning Gödel, Escher, Bach (1979) as an entertaining, not too scary, starting point.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Manning

United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 1 PinmemberRedDK5-Jul-11 5:45 
QuestionProcessing Language and article expansion Pinmember Randor 4-Jul-11 3:55 
AnswerRe: Processing Language and article expansion Pinmembermbedward4-Jul-11 17:17 
QuestionSound PinmemberTeashirt230-Jun-11 16:50 
I also would like experiment with sound as art in this setup. Imagine as cells move or stay alive or die or join with others they made certain pop sounds, fizzles. Recording that would result in amazing compositions I think.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.141015.1 | Last Updated 30 Jun 2011
Article Copyright 2011 by Manning
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid