diff --git a/content/07_ca.html b/content/07_ca.html index 8b7c4739..8ab4a382 100644 --- a/content/07_ca.html +++ b/content/07_ca.html @@ -39,89 +39,86 @@

Elementary Cellular Automata

Figure 7.4: A neighborhood in one dimension is three cells. 
Figure 7.4: A neighborhood in one dimension is three cells. 
+

I have a line of cells, each with an initial state, and each with two neighbors. The exciting thing is, even with this simplest CA imaginable, the properties of complex systems can emerge. But I haven’t yet discussed perhaps the most important detail of how cellular automata work: change over time.

+

I’m not really talking about real-world time here, but rather about the CA developing across a series of discrete time steps, which could also be called a generations. In the case of a CA in p5.js, time will likely be tied to the frame count of the animation. The question, as depicted in Figure 7.5, is this: given the states of the cells at time equals 0 (or generation 0), how do I compute the states for all cells at generation 1? And then how do I get from generation 1 to generation 2? And so on and so forth.

- Figure 7.5: The edge cell only has a neighborhood of two.  -
Figure 7.5: The edge cell only has a neighborhood of two. 
-
-

I have a line of cells, each with an initial state, and each with two neighbors. The exciting thing is, even with this simplest CA imaginable, the properties of complex systems can emerge. But I haven’t yet discussed perhaps the most important detail of how cellular automata work: time.

-

I’m not really talking about real-world time here, but rather about the CA developing across a series of discrete time steps, which could also be called a generations. In the case of a CA in p5.js, time will likely be tied to the frame count of the animation. The question, as depicted in Figure 7.6, is this: given the states of the cells at time equals 0 (or generation 0), how do I compute the states for all cells at generation 1? And then how do I get from generation 1 to generation 2? And so on and so forth.

-
- Figure 7.6: The states for generation 1 are calculated using the states of the cells from generation 0. -
Figure 7.6: The states for generation 1 are calculated using the states of the cells from generation 0.
+ Figure 7.5: The states for generation 1 are calculated using the states of the cells from generation 0. +
Figure 7.5: The states for generation 1 are calculated using the states of the cells from generation 0.

Let’s say there’s an individual cell in the CA called \text{cell}. The formula for calculating the cell’s state at any given time t (\text{cell}_t) is as follows:

\text{cell}_t = f(\text{cell neighborhood}_{t-1})
-

In other words, a cell’s new state is a function of all the states in the cell’s neighborhood at the previous generation (time t-1). A new state value is calculated by looking at the previous generation’s neighbor states (Figure 7.7).

+

In other words, a cell’s new state is a function of all the states in the cell’s neighborhood at the previous generation (time t-1). A new state value is calculated by looking at the previous generation’s neighbor states (Figure 7.6).

- Figure 7.7 The state of a cell at generation 1 is a function of the previous generation’s neighborhood. -
Figure 7.7 The state of a cell at generation 1 is a function of the previous generation’s neighborhood.
+ Figure 7.6 The state of a cell at generation 1 is a function of the previous generation’s neighborhood. +
Figure 7.6 The state of a cell at generation 1 is a function of the previous generation’s neighborhood.

There are many ways to compute a cell’s state from its neighbors’ states. Consider blurring an image. (Guess what? Image processing works with CA-like rules!) A pixel’s new state (its color) is the average of its neighbors’ colors. Similarly, a cell’s new state could be the sum of all of its neighbors’ states. However, in Wolfram’s elementary CA, the process takes a different approach: instead of mathematical operations, new states are determined by predefined rules that account for every possible configuration of a cell and its neighbors. These rules are known collectively as a ruleset.

-

This approach might seem ridiculous at first—wouldn’t there be way too many possibilities for it to be practical? Well, let’s give it a try. A neighborhood consists of three cells, each with a state of 0 or 1. How many possible ways can the states in a neighborhood be configured? A quick way to figure this out is to think of each neighborhood configuration as a binary number. Binary numbers use “base 2,” meaning they’re represented with only two possible digits (0 and 1). In this case, each neighborhood configuration corresponds to a 3-bit number, and how many values can you represent with 3 bits? Eight, from 0 (000) up to 7 (111). Figure 7.8 shows how.

+

This approach might seem ridiculous at first—wouldn’t there be way too many possibilities for it to be practical? Well, let’s give it a try. A neighborhood consists of three cells, each with a state of 0 or 1. How many possible ways can the states in a neighborhood be configured? A quick way to figure this out is to think of each neighborhood configuration as a binary number. Binary numbers use “base 2,” meaning they’re represented with only two possible digits (0 and 1). In this case, each neighborhood configuration corresponds to a 3-bit number, and how many values can you represent with 3 bits? Eight, from 0 (000) up to 7 (111). Figure 7.7 shows how.

- Figure 7.8: Counting with 3 bits in binary, or the eight possible configurations of a three-cell neighborhood -
Figure 7.8: Counting with 3 bits in binary, or the eight possible configurations of a three-cell neighborhood
+ Figure 7.7: Counting with 3 bits in binary, or the eight possible configurations of a three-cell neighborhood +
Figure 7.7: Counting with 3 bits in binary, or the eight possible configurations of a three-cell neighborhood
-

Once all the possible neighborhood configurations are defined, an outcome (new state value: 0 or 1) is specified for each configuration, as in Figure 7.9.

+

Once all the possible neighborhood configurations are defined, an outcome (new state value: 0 or 1) is specified for each configuration. In Wolfram's original notation and other common references, these configurations are written in descending order, Figure 7.8 follows this convention, starting with 111 and counting down to 000.

- Figure 7.9: A ruleset shows the outcome for each possible configuration of three cells. -
Figure 7.9: A ruleset shows the outcome for each possible configuration of three cells.
+ Figure 7.8: A ruleset shows the outcome for each possible configuration of three cells. +
Figure 7.8: A ruleset shows the outcome for each possible configuration of three cells.

Keep in mind that unlike the sum or averaging methods, the rulesets in elementary CA don’t follow any arithmetic logic—they’re just arbitrary mappings of inputs to outputs. The input is the current configuration of the neighborhood (one of eight possibilities), and the output is the next state of the middle cell in the neighborhood (0 or 1—it’s up to you to define the rule).

Once you have a ruleset, you can set the cellular automaton in motion. The standard Wolfram model is to start generation 0 with all cells having a state of 0 except for the middle cell, which should have a state of 1. You can do this with any size (length) grid, but for clarity, I’ll use a one-dimensional CA of nine cells so that the middle is easy to pick out.

- Figure 7.10: Generation 0 in a Wolfram CA, with the center cell set to 1 -
Figure 7.10: Generation 0 in a Wolfram CA, with the center cell set to 1
+ Figure 7.9: Generation 0 in a Wolfram CA, with the center cell set to 1 +
Figure 7.9: Generation 0 in a Wolfram CA, with the center cell set to 1
-

Based on the ruleset in Figure 7.9, how do the cells change from generation 0 to generation 1? In Figure 7.11, I’ve shown how the center cell, with a neighborhood of 010, switches from a 1 to a 0. Try applying the ruleset to the remaining cells to fill in the rest of the generation 1 states.

+

Based on the ruleset in Figure 7.8, how do the cells change from generation 0 to generation 1? Figure 7.10 shows how the center cell, with a neighborhood of 010, switches from a 1 to a 0. Try applying the ruleset to the remaining cells to fill in the rest of the generation 1 states.

- Figure 7.11: Determining a state for generation 1 using the CA rule set -
Figure 7.11: Determining a state for generation 1 using the CA rule set
+ Figure 7.10: Determining a state for generation 1 using the CA rule set +
Figure 7.10: Determining a state for generation 1 using the CA rule set
-

Now for a slight change: instead of representing the cells’ states with 0s and 1s, I’ll indicate them with visual cues—white for 0 and black for 1 (see FIgure 7.12). Although this might seem counterintuitive, as 0 usually signifies black in computer graphics, I’m using this convention because the examples in this book have a white background, so “turning on” a cell corresponds to switching its color from white to black.

+

Now for a slight change: instead of representing the cells’ states with 0s and 1s, I’ll indicate them with visual cues—white for 0 and black for 1 (see Figure 7.11). Although this might seem counterintuitive, as 0 usually signifies black in computer graphics, I’m using this convention because the examples in this book have a white background, so “turning on” a cell corresponds to switching its color from white to black.

- Figure 7.12: A white cell indicates 0, and a black cell indicates 1. -
Figure 7.12: A white cell indicates 0, and a black cell indicates 1.
+ Figure 7.11: A white cell indicates 0, and a black cell indicates 1. +
Figure 7.11: A white cell indicates 0, and a black cell indicates 1.
-

With this switch from numerical representations into visual forms, the fascinating dynamics and patterns of cellular automata will come into view! To see them even more clearly, instead of drawing one generation at a time, I’ll also start stacking the generations, with each new generation appearing below the previous one, as shown in Figure 7.13.

+

With this switch from numerical representations into visual forms, the fascinating dynamics and patterns of cellular automata will come into view! To see them even more clearly, instead of drawing one generation at a time, I’ll also start stacking the generations, with each new generation appearing below the previous one, as shown in Figure 7.12.

- Figure 7.13 Translating a grid of 0s and 1s to white and black squares. -
Figure 7.13 Translating a grid of 0s and 1s to white and black squares.
+ Figure 7.12 Translating a grid of 0s and 1s to white and black squares. +
Figure 7.12 Translating a grid of 0s and 1s to white and black squares.
-

The low-resolution shape that emerges in Figure 7.13 is the Sierpiński triangle. Named after the Polish mathematician Wacław Sierpiński, it’s a famous example of a fractal. I’ll examine fractals more closely in the next chapter, but briefly, they’re patterns where the same shapes repeat themselves at different scales. To give you a better sense of this, Figure 7.14 shows the CA over several more generations, and with a wider grid size.

+

The low-resolution shape that emerges in Figure 7.12 is the Sierpiński triangle. Named after the Polish mathematician Wacław Sierpiński, it’s a famous example of a fractal. I’ll examine fractals more closely in the next chapter, but briefly, they’re patterns where the same shapes repeat themselves at different scales. To give you a better sense of this, Figure 7.13 shows the CA over several more generations, and with a wider grid size.

- Figure 7.14: Wolfram elementary CA, rule 90  -
Figure 7.14: Wolfram elementary CA, rule 90 
+ Figure 7.13: Wolfram elementary CA, rule 90  +
Figure 7.13: Wolfram elementary CA, rule 90 
-

And Figure 7.15 shows the CA again, this time with cells that are just a single pixel wide so the resolution is much higher.

+

And Figure 7.14 shows the CA again, this time with cells that are just a single pixel wide so the resolution is much higher.

- Figure 7.15: Wolfram elementary CA, rule 90, at higher resolution -
Figure 7.15: Wolfram elementary CA, rule 90, at higher resolution
+ Figure 7.14: Wolfram elementary CA, rule 90, at higher resolution +
Figure 7.14: Wolfram elementary CA, rule 90, at higher resolution

Take a moment to let the enormity of what you’ve just seen sink in. Using an incredibly simple system of 0s and 1s, with little neighborhoods of three cells, I was able to generate a shape as sophisticated and detailed as the Sierpiński triangle. This is the beauty of complex systems.

-

Of course, this particular result didn’t happen by accident. I picked the set of rules in Figure 7.9 because I knew the pattern it would generate. The mere act of defining a ruleset doesn’t guarantee visually exciting results. In fact, for a one-dimensional CA where each cell can have two possible states, there are exactly 256 possible rulesets to choose from, and only a handful of them are on par with the Sierpiński triangle. How do I know there are 256 possible rulesets? It comes down to a little more binary math.

+

Of course, this particular result didn’t happen by accident. I picked the set of rules in Figure 7.8 because I knew the pattern it would generate. The mere act of defining a ruleset doesn’t guarantee visually exciting results. In fact, for a one-dimensional CA where each cell can have two possible states, there are exactly 256 possible rulesets to choose from, and only a handful of them are on par with the Sierpiński triangle. How do I know there are 256 possible rulesets? It comes down to a little more binary math.

Defining Rulesets

-

Take a look back at Figure 7.8 and notice again how there are eight possible neighborhood configurations, from 000 to 111. These are a ruleset’s inputs, and they remain constant from ruleset to ruleset. It’s only the outputs that vary from one ruleset to another—the individual 0 or 1 paired with each neighborhood configuration. Figure 7.9 represented a ruleset entirely with 0s and 1s. Now Figure 7.16 shows the same ruleset visualized with white and black squares.

+

Take a look back at Figure 7.7 and notice again how there are eight possible neighborhood configurations, from 000 to 111. These are a ruleset’s inputs, and they remain constant from ruleset to ruleset. It’s only the outputs that vary from one ruleset to another—the individual 0 or 1 paired with each neighborhood configuration. Figure 7.9 represented a ruleset entirely with 0s and 1s. Now Figure 7.16 shows the same ruleset visualized with white and black squares.

- Figure 7.16 Representing the same ruleset (from Figure 7.9) with white and black squares -
Figure 7.16 Representing the same ruleset (from Figure 7.9) with white and black squares
+ Figure 7.15 Representing the same ruleset (from Figure 7.8) with white and black squares +
Figure 7.15 Representing the same ruleset (from Figure 7.8) with white and black squares
-

Since the eight possible inputs are the same no matter what, a potential shorthand for indicating a ruleset is to specify just the outputs, writing them as a sequence of eight 0s or 1s—in other words, an 8-bit binary number. For example, the ruleset in Figure 7.16 could be written as 01011010. The 0 on the right corresponds to input configuration 000, the 1 next to it corresponds to input 001, and so on. On Wolfram’s website, CA rules are illustrated using a combination of this binary shorthand an Figure 7.16’s black-and-white square representation, yielding depictions like Figure 7.17.

+

Since the eight possible inputs are the same no matter what, a potential shorthand for indicating a ruleset is to specify just the outputs, writing them as a sequence of eight 0s or 1s—in other words, an 8-bit binary number. For example, the ruleset in Figure 7.15 could be written as 01011010. The 0 on the right corresponds to input configuration 000, the 1 next to it corresponds to input 001, and so on. On Wolfram’s website, CA rules are illustrated using a combination of this binary shorthand and the black-and-white square representation, yielding depictions like Figure 7.16.

- Figure 7.17: How the Wolfram website represents a ruleset -
Figure 7.17: How the Wolfram website represents a ruleset
+ Figure 7.16: How the Wolfram website represents a ruleset +
Figure 7.16: How the Wolfram website represents a ruleset
-

A ruleset can be reduced to an 8-bit number. And how many combinations of eight 0s and 1s are there? Exactly 2^8, or 256. You might remember this from when you first learned about RGB color in p5.js. When you write background(r, g, b), each color component (red, green, and blue) is represented by an 8-bit number ranging from 0 to 255 in decimal, or 00000000 to 11111111 in binary. And speaking of decimal, an even shorter shorthand for a ruleset is to convert its 8-bit binary number to decimal (or “base 10”). “Rule 01011010” is therefore more commonly known as “rule 90.”

+

Figure 7.16 is identified by Wolfram as “Rule 90.” Where does 90 come from? Each ruleset is essentially an 8-bit number. How many combinations of eight 0s and 1s are there? Exactly 2^8, or 256. You might remember this from when you first learned about RGB color in p5.js. When you write background(r, g, b), each color component (red, green, and blue) is represented by an 8-bit number ranging from 0 to 255 in decimal, or 00000000 to 11111111 in binary.

+

To make ruleset naming even more concise, Wolfram uses decimal (or “base 10”) representations. To name a rule, you convert its 8-bit binary number to its decimal counterpart. The binary number “01011010” translates to the decimal number 90, and therefore it’s named “Rule 90.”

Since there are 256 possible combinations of eight 0s and 1s, there are also 256 unique rulesets. Let’s check out another one. How about rule 11011110, or more commonly, rule 222. Figure 7.18 shows how it looks.

- Figure 7.18: Wolfram elementary CA, rule 222  -
Figure 7.18: Wolfram elementary CA, rule 222 
+ Figure 7.17: Wolfram elementary CA, rule 222  +
Figure 7.17: Wolfram elementary CA, rule 222 
- Figure 7.19: A textile cone snail (Conus textile), Cod Hole, Great Barrier Reef, Australia, 7 August 2005. Photographer: Richard Ling richard@research.canon.com.au  -
Figure 7.19: A textile cone snail (Conus textile), Cod Hole, Great Barrier Reef, Australia, 7 August 2005. Photographer: Richard Ling richard@research.canon.com.au 
+ Figure 7.18: A textile cone snail (Conus textile), Cod Hole, Great Barrier Reef, Australia, 7 August 2005. Photographer: Richard Ling richard@research.canon.com.au  +
Figure 7.18: A textile cone snail (Conus textile), Cod Hole, Great Barrier Reef, Australia, 7 August 2005. Photographer: Richard Ling richard@research.canon.com.au 
-

The result is a recognizable shape, though it certainly isn’t as exciting as the Sierpiński triangle. As I saidd earlier, most of the 256 elementary rulesets dodn’t produce compelling outcomes. However, it’s still quite incredible that even just a few of these rulesets—simple systems of cells with only two possible states—can produce fascinating patterns seen every day in nature (see Figure 7.19, a snail shell resembling Wolfram’s rule 30). This demonstrates how valuable CAs can be in simulation and pattern generation.

+

The result is a recognizable shape, though it certainly isn’t as exciting as the Sierpiński triangle. As I saidd earlier, most of the 256 elementary rulesets dodn’t produce compelling outcomes. However, it’s still quite incredible that even just a few of these rulesets—simple systems of cells with only two possible states—can produce fascinating patterns seen every day in nature (see Figure 7.18, a snail shell resembling Wolfram’s rule 30). This demonstrates how valuable CAs can be in simulation and pattern generation.

Before I go too far down the road of characterizing the results of different rulesets, though, let’s look at how to build a p5.js sketch that generates and visualizes a Wolfram elementary CA.

Programming an Elementary CA

You may be thinking: “OK, I’ve got this cell thing. And the cell thing has some properties, like a state, what generation it’s on, who its neighbors are, and where it lives pixel-wise on the screen. And maybe it has some functions, like to display itself and determine its new state.” This line of thinking is an excellent one and would likely lead you to write some code like this:

@@ -130,10 +127,10 @@

Programming an Elementary CA

}

However, this isn’t the road I want to travel down right now. Later in this chapter, I’ll discuss why an object-oriented approach could prove valuable in developing a CA simulation, but to begin, it’s easier to work with a more elementary data structure. After all, what is an elementary CA but a list of 0s and 1s? Why not describe a generation of a one-dimensional CA using an array?

let cells = [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0];
-

This array corresponds to the row of cells shown in Figure 7.20.

+

This array corresponds to the row of cells shown in Figure 7.19.

- Figure 7.20: One generation of a 1D cellular automata -
Figure 7.20: One generation of a 1D cellular automata
+ Figure 7.19: One generation of a 1D cellular automata +
Figure 7.19: One generation of a 1D cellular automata

To show that array, I check if each element is a 0 or a 1, choose a fill color accordingly, and draw a rectangle.

//{!1} Loop through every cell.
@@ -217,10 +214,10 @@ 

Programming an Elementary CA

I’m almost done, but I still need to define rules(), the function that computes the new state value based on the neighborhood (left, middle, and right cells). I know the function needs to return an integer (0 or 1), as well as receive three arguments (for the three neighbors).

  //{!1} Function signature: receives 3 ints and returns 1.
   function rules (a, b, c) { return _______ }
-

There are many ways to write this function, but I’d like to start with a long-winded one that will hopefully provide a clear illustration of what’s happening. First, I need to establish how I’ll store the ruleset. Recall that a ruleset is a series of 8 bits (0 or 1) defining the outcome for every possible neighborhood configuration. If you need to refresh your memory, Figure 7.21 shows a visual representation of the Sierpiński triangle ruleset.

+

There are many ways to write this function, but I’d like to start with a long-winded one that will hopefully provide a clear illustration of what's happening. How shall I store the ruleset? Remember that a ruleset is a series of 8 bits (0 or 1) that define the outcome for every possible neighborhood configuration. If you need a refresher, Figure 7.20 shows the Wolfram notation for the Sierpiński triangle ruleset, along with the corresponding 0s and 1s listed in order. This should give you a hint as to the data structure I have in mind!

- Figure 7.21 A visual representation of a Wolfram ruleset with numeric encoding  -
Figure 7.21 A visual representation of a Wolfram ruleset with numeric encoding 
+ Figure 7.20 A visual representation of a Wolfram ruleset with numeric encoding  +
Figure 7.20 A visual representation of a Wolfram ruleset with numeric encoding 

I can store this ruleset in an array.

let ruleset = [0, 1, 0, 1, 1, 0, 1, 0];
@@ -292,10 +289,10 @@

Programming an Elementary CA

}

This is great, but there’s still one more missing piece: what good is a cellular automaton if you can’t see it?

Drawing an Elementary CA

-

The standard technique for drawing an elementary CA is to stack the generations one on top of the other, and to draw each cell as a square that’s black (for state 1) or white (for state 0), as in Figure 7.22. Before implementing this particular visualization, however, I’d like to point out two things.

+

The standard technique for drawing an elementary CA is to stack the generations one on top of the other, and to draw each cell as a square that’s black (for state 1) or white (for state 0), as in Figure 7.21. Before implementing this particular visualization, however, I’d like to point out two things.

- Figure 7.22 Ruleset 90 visualized as a stack of generations -
Figure 7.22 Ruleset 90 visualized as a stack of generations
+ Figure 7.21 Ruleset 90 visualized as a stack of generations +
Figure 7.21 Ruleset 90 visualized as a stack of generations

First, this visual interpretation of the data is completely literal. It’s useful for demonstrating the algorithms and results of Wolfram’s elementary CA, but it shouldn’t necessarily drive your own personal work. It’s rather unlikely that you’re building a project that needs precisely this algorithm with this visual style. So while learning to draw a CA in this way will help you understand and implement CA systems, this skill should exist only as a foundation.

Second, the fact that a one-dimensional CA is visualized with a two-dimensional image can be misleading. It’s very important to remember that this is not a 2D CA. I’m simply choosing to show a history of all the generations stacked vertically. This technique creates a two-dimensional image out of many instances of one-dimensional data, but the system itself is one-dimensional. Later, I’ll show you an actual 2D CA (the Game of Life), and I’ll cover how to visualize such a system.

@@ -304,7 +301,7 @@

Drawing an Elementary CA

Assuming the canvas is 640 pixels wide, that means the CA will have 64 cells. Of course, I can calculate this value dynamically when I initialize the cells array in setup().

//{!1} How many cells fit across given a certain width
 let cells = new Array(floor(width / w));
-

Drawing the cells now involves iterating over the array and drawing a white square when the state equals 1 or a black when the state equals 0.

+

Drawing the cells now involves iterating over the array and drawing a square based on the state of each cell.

for (let i = 0; i < cells.length; i++) {
   //{!2} By multiplying the cell state the result is 0 or 255
   fill(cells[i] * 255);
@@ -312,8 +309,8 @@ 

Drawing an Elementary CA

// 0, 10, 20, 30, all the way to 640. square(i * w, 0, w); }
-

In truth, I could simplify the code by having a black background and only drawing when there is a white cell (saving the work of drawing many squares), but in most cases this solution is good enough (and necessary for other more sophisticated designs with varying colors.) I’ll also note that if the size of each cell were 1 pixel I would not want to use p5.js’s square() function, but rather access the pixel array directly.

-

Right now the y-position for each square is hardcoded to 0. If I want the generations to be stacked on top of each other, with each row of cells marking a new generation, I’ll also need to calculate a y-position based on the generation number. I could accomplish this by adding a generation variable and incrementing it each time through draw(). With these additions, I can now look at the entire sketch.

+

There are two things off about this code. First, when multiplying the state by 255, cells with a state of 1 will be black and those with 0 will be white, which is the opposite of what I originally intended! While this is of course “ok” since the color representation is arbitrary, I’ll correct this in the full example.

+

The more pressing issue is that the y-position for each square is hardcoded to 0. If I want the generations to be stacked on top of each other, with each row of cells marking a new generation, I’ll also need to calculate a y-position based on the generation number. I can accomplish this by adding a generation variable and incrementing it each time through draw(). With these additions, I can now look at the entire sketch.

Example 7.1: Wolfram Elementary Cellular Automata

@@ -372,6 +369,7 @@

Example 7.1: Wolfram El let index = parseInt(s, 2); return ruleset[7 - index]; } +

You may have noticed an optimization I made in this example by simplifying the drawing. I included a white background and rendered only the black squares, which saves the work of drawing many squares. This solution isn’t suitable for all cases (what if I want multi-colored cells!), but it provides a performance boost in this simple case. I’ll also note that if the size of each cell were 1 pixel I would not want to use p5.js’s square() function, but rather access the pixel array directly.

Exercise 7.1

Expand Example 7.1 to have the following feature: when the CA reaches the bottom of the canvas, the CA starts over with a new, random ruleset.

@@ -390,25 +388,25 @@

Exercise 7.4

Wolfram Classification

Now that you have a sketch for visualizing an elementary CA, you can supply it whatever ruleset you want and see the results. What kind of outcomes can you expect? As I noted earlier, the vast majority of elementary CA rulesets produce visually uninspiring results, while some result in wondrously complex patterns like those found in nature. Wolfram himself has divided up the range of outcomes into four classes.

-

Class 1: Uniformity. Class 1 CAs end up, after some number of generations, with every cell constant. This isn’t terribly exciting to watch. Rule 222 (see Figure 7.23) is a class 1 CA; if you run it for enough generations, every cell will eventually become and remain black.

+

Class 1: Uniformity. Class 1 CAs end up, after some number of generations, with every cell constant. This isn’t terribly exciting to watch. Rule 222 (see Figure 7.22) is a class 1 CA; if you run it for enough generations, every cell will eventually become and remain black.

- Figure 7.23: Rule 222  -
Figure 7.23: Rule 222 
+ Figure 7.22: Rule 222  +
Figure 7.22: Rule 222 
-

Class 2: Repetition. Like class 1 CAs, class 2 CAs remain stable, but the cell states aren’t constant. Instead, they oscillate in some repeating pattern of 0s and 1s. In rule 190 (Figure 7.24), each cell follows the sequence 11101110111011101110.

+

Class 2: Repetition. Like class 1 CAs, class 2 CAs remain stable, but the cell states aren’t constant. Instead, they oscillate in some repeating pattern of 0s and 1s. In rule 190 (Figure 7.23), each cell follows the sequence 11101110111011101110.

- Figure 7.24: Rule 190  -
Figure 7.24: Rule 190 
+ Figure 7.23: Rule 190  +
Figure 7.23: Rule 190 
-

Class 3: Random. Class 3 CAs appear random and have no easily discernible pattern. In fact, rule 30 (Figure 7.25) is used as a random number generator in Wolfram’s Mathematica software. Again, this is a moment where you can feel amazed that such a simple system with simple rules can descend into a chaotic and random pattern.

+

Class 3: Random. Class 3 CAs appear random and have no easily discernible pattern. In fact, rule 30 (Figure 7.24) is used as a random number generator in Wolfram’s Mathematica software. Again, this is a moment where you can feel amazed that such a simple system with simple rules can descend into a chaotic and random pattern.

- Figure 7.25: Rule 30  -
Figure 7.25: Rule 30 
+ Figure 7.24: Rule 30  +
Figure 7.24: Rule 30 
-

Class 4: Complexity. Class 4 CAs can be thought of as a mix between class 2 and class 3. You can find repetitive, oscillating patterns inside the CA, but where and when these patterns appear is unpredictable and seemingly random. Class 4 CAs exhibit the properties of complex systems described earlier in this chapter and in Chapter 5. If a class 3 CA wowed you, then a class 4 like Rule 110 (Figure 7.26) should really blow your mind!

+

Class 4: Complexity. Class 4 CAs can be thought of as a mix between class 2 and class 3. You can find repetitive, oscillating patterns inside the CA, but where and when these patterns appear is unpredictable and seemingly random. Class 4 CAs exhibit the properties of complex systems described earlier in this chapter and in Chapter 5. If a class 3 CA wowed you, then a class 4 like Rule 110 (Figure 7.25) should really blow your mind!

- Figure 7.26: Rule 110  -
Figure 7.26: Rule 110 
+ Figure 7.25: Rule 110  +
Figure 7.25: Rule 110 

Exercise 7.5

@@ -425,11 +423,11 @@

The Game of Life

This might sound cryptic, but it essentially describes a Wolfram class 4 CA. The CA should be patterned but unpredictable over time, eventually settling into a uniform or oscillating state. In other words, though Conway didn’t use this terminology, it should have all those properties of a complex system.

The Rules of the Game

Let’s look at how the Game of Life works. It won’t take up too much time or space, since I can build on everything from Wolfram’s elementary CA. First, instead of a line of cells, there’s now a two-dimensional matrix of cells. As with the elementary CA, the possible states are 0 or 1. In this case, however, since the system is all about “life," 0 means “dead” and 1 means “alive.”

-
- Figure 7.27: A two-dimensional CA showing the neighborhood of 9 cells. -
Figure 7.27: A two-dimensional CA showing the neighborhood of 9 cells.
+

Since the Game of Life is two-dimensional, each cell’s neighborhood has now expanded. If a neighbor is an adjacent cell, a neighborhood is now nine cells instead of three, as shown in Figure 7.26.

+
+ Figure 7.26: A two-dimensional CA showing the neighborhood of 9 cells. +
Figure 7.26: A two-dimensional CA showing the neighborhood of 9 cells.
-

Since the Game of Life is two-dimensional, each cell’s neighborhood has now expanded. If a neighbor is an adjacent cell, a neighborhood is now nine cells instead of three, as shown in Figure 7.27.

With three cells, a 3-bit number had eight possible configurations. With nine cells, there are 9 bits, or 512 possible neighborhoods. In most cases, it would be impractical to define an outcome for every single possibility. The Game of Life gets around this problem by defining a set of rules according to general characteristics of the neighborhood: is the neighborhood overpopulated with life, surrounded by death, or just right? Here are the rules of life.

  1. Death. If a cell is alive (state = 1), it will die (state becomes 0) under the following circumstances: @@ -446,26 +444,26 @@

    The Rules of the Game

-

Figure 7.28 shows a few examples of these rules. Focus on what happens to the center cell.

+

Figure 7.27 shows a few examples of these rules. Focus on what happens to the center cell.

- Figure 7.28: Example scenarios for “death” and “birth” in the Game of Life -
Figure 7.28: Example scenarios for “death” and “birth” in the Game of Life
+ Figure 7.27: Example scenarios for “death” and “birth” in the Game of Life +
Figure 7.27: Example scenarios for “death” and “birth” in the Game of Life
-

With the elementary CA, I visualized many generations at once, stacked as rows in a 2D grid. With the Game of Life, however, the CA itself is in two dimensions. I could try to create an elaborate 3D visualization of the results and stack all the generations in a cube structure (and in fact, you might want to try this as an exercise), but a more typical way to visualize the Game of Life is to treat each generation as a single frame in an animation. This way, instead of viewing all the generations at once, you see them one at a time, and the result resembles rapidly growing bacteria in a Petri dish.

-

One of the exciting aspects of the Game of Life is that there are known initial patterns that yield intriguing results. For example, the patterns shown in Figure 7.29 some remain static and never change.

+

With the elementary CA, I visualized many generations at once, stacked as rows in a 2D grid. With the Game of Life, however, the CA itself is in two dimensions. I could try to create an elaborate 3D visualization of the results and stack all the generations in a cube structure (and in fact, you might want to try this as an exercise), but a more typical way to visualize the Game of Life is to treat each generation as a single frame in an animation. This way, instead of viewing all the generations at once, you see them one at a time, and the result resembles rapidly developing bacteria in a Petri dish.

+

One of the exciting aspects of the Game of Life is that there are known initial patterns that yield intriguing results. For example, the patterns shown in Figure 7.28 some remain static and never change.

- Figure 7.29: Initial configurations of cells that remain stable -
Figure 7.29: Initial configurations of cells that remain stable
+ Figure 7.28: Initial configurations of cells that remain stable +
Figure 7.28: Initial configurations of cells that remain stable
-

The patterns in Figure 7.30 oscillate back and forth between two states.

+

The patterns in Figure 7.29 oscillate back and forth between two states.

- Figure 7.30: Initial configurations of cells that oscillate between two states -
Figure 7.30: Initial configurations of cells that oscillate between two states
+ Figure 7.29: Initial configurations of cells that oscillate between two states +
Figure 7.29: Initial configurations of cells that oscillate between two states

And the patterns in Figure 7.31 appear to move about the grid from generation to generation. The cells themselves don’t actually move, but you see the illusion of motion in the result of adjacent cells turning on and off.

- Figure 7.31: Initial configurations of cells that appear to move -
Figure 7.31: Initial configurations of cells that appear to move
+ Figure 7.30: Initial configurations of cells that appear to move +
Figure 7.30: Initial configurations of cells that appear to move

If you’re interested in these patterns, there are several good “out of the box” Game of Life demonstrations online that allow you to configure the CA’s initial state and watch it run at varying speeds. Two examples are:

    @@ -510,10 +508,10 @@

    The Implementation

    } }
    - Figure 7.32: The index values for the neighborhood of cells. -
    Figure 7.32: The index values for the neighborhood of cells.
    + Figure 7.31: The index values for the neighborhood of cells. +
    Figure 7.31: The index values for the neighborhood of cells.
    -

    Next, I need to sort out how to actually calculate each cell’s new state. For that, I need to determine how to reference the cell’s neighbors. In the case of a 1D CA, this was simple: if a cell index was i, its neighbors were i-1 and i+1. Here, each cell doesn’t have a single index, but rather a column and row index: i,j. As shown in Figure 7.32, the neighbors are i-1,j-1 , i,j-1, i+1,j-1, i-1,j, i+1,j, i-1,j+1, i,j+1, and i+1,j+1.

    +

    Next, I need to sort out how to actually calculate each cell’s new state. For that, I need to determine how to reference the cell’s neighbors. In the case of a 1D CA, this was simple: if a cell index was i, its neighbors were i-1 and i+1. Here, each cell doesn’t have a single index, but rather a column and row index: i,j. As shown in Figure 7.31, the neighbors are i-1,j-1 , i,j-1, i+1,j-1, i-1,j, i+1,j, i-1,j+1, i,j+1, and i+1,j+1.

    The Game of Life rules operate by knowing how many neighbors are alive. If I create a counter variable and increment it for each neighbor with a state of 1, I’ll have the total of live neighbors.

    let sum = 0;
     
    @@ -691,7 +689,7 @@ 

    Variations on Traditional CA

    Exercise 7.9

    Create a CA using a grid of hexagons (as below), each with six neighbors.

    - +
diff --git a/content/images/07_ca/07_ca_10.png b/content/images/07_ca/07_ca_10.png index fff97b21..e7c696ae 100644 Binary files a/content/images/07_ca/07_ca_10.png and b/content/images/07_ca/07_ca_10.png differ diff --git a/content/images/07_ca/07_ca_11.png b/content/images/07_ca/07_ca_11.png index e7c696ae..ab57a9de 100644 Binary files a/content/images/07_ca/07_ca_11.png and b/content/images/07_ca/07_ca_11.png differ diff --git a/content/images/07_ca/07_ca_12.png b/content/images/07_ca/07_ca_12.png index ab57a9de..31622b7a 100644 Binary files a/content/images/07_ca/07_ca_12.png and b/content/images/07_ca/07_ca_12.png differ diff --git a/content/images/07_ca/07_ca_13.png b/content/images/07_ca/07_ca_13.png index 31622b7a..e0ee4ea5 100644 Binary files a/content/images/07_ca/07_ca_13.png and b/content/images/07_ca/07_ca_13.png differ diff --git a/content/images/07_ca/07_ca_14.png b/content/images/07_ca/07_ca_14.png index e0ee4ea5..db5c377f 100644 Binary files a/content/images/07_ca/07_ca_14.png and b/content/images/07_ca/07_ca_14.png differ diff --git a/content/images/07_ca/07_ca_15.png b/content/images/07_ca/07_ca_15.png index db5c377f..602aeaa3 100644 Binary files a/content/images/07_ca/07_ca_15.png and b/content/images/07_ca/07_ca_15.png differ diff --git a/content/images/07_ca/07_ca_16.png b/content/images/07_ca/07_ca_16.png index 602aeaa3..8d684397 100644 Binary files a/content/images/07_ca/07_ca_16.png and b/content/images/07_ca/07_ca_16.png differ diff --git a/content/images/07_ca/07_ca_17.png b/content/images/07_ca/07_ca_17.png index 8d684397..6a3ccb02 100644 Binary files a/content/images/07_ca/07_ca_17.png and b/content/images/07_ca/07_ca_17.png differ diff --git a/content/images/07_ca/07_ca_20.png b/content/images/07_ca/07_ca_20.png index ae8dc521..d37b799c 100644 Binary files a/content/images/07_ca/07_ca_20.png and b/content/images/07_ca/07_ca_20.png differ diff --git a/content/images/07_ca/07_ca_21.png b/content/images/07_ca/07_ca_21.png index d37b799c..e0ee4ea5 100644 Binary files a/content/images/07_ca/07_ca_21.png and b/content/images/07_ca/07_ca_21.png differ diff --git a/content/images/07_ca/07_ca_22.png b/content/images/07_ca/07_ca_22.png index e0ee4ea5..e0aea8ff 100644 Binary files a/content/images/07_ca/07_ca_22.png and b/content/images/07_ca/07_ca_22.png differ diff --git a/content/images/07_ca/07_ca_23.png b/content/images/07_ca/07_ca_23.png index e0aea8ff..62d3557c 100644 Binary files a/content/images/07_ca/07_ca_23.png and b/content/images/07_ca/07_ca_23.png differ diff --git a/content/images/07_ca/07_ca_24.png b/content/images/07_ca/07_ca_24.png index 62d3557c..1f8d78d6 100644 Binary files a/content/images/07_ca/07_ca_24.png and b/content/images/07_ca/07_ca_24.png differ diff --git a/content/images/07_ca/07_ca_25.png b/content/images/07_ca/07_ca_25.png index 1f8d78d6..035a8694 100644 Binary files a/content/images/07_ca/07_ca_25.png and b/content/images/07_ca/07_ca_25.png differ diff --git a/content/images/07_ca/07_ca_26.png b/content/images/07_ca/07_ca_26.png index 035a8694..6151c6d5 100644 Binary files a/content/images/07_ca/07_ca_26.png and b/content/images/07_ca/07_ca_26.png differ diff --git a/content/images/07_ca/07_ca_27.png b/content/images/07_ca/07_ca_27.png index 6151c6d5..4b3e6ef3 100644 Binary files a/content/images/07_ca/07_ca_27.png and b/content/images/07_ca/07_ca_27.png differ diff --git a/content/images/07_ca/07_ca_28.png b/content/images/07_ca/07_ca_28.png index 4b3e6ef3..8d790d21 100644 Binary files a/content/images/07_ca/07_ca_28.png and b/content/images/07_ca/07_ca_28.png differ diff --git a/content/images/07_ca/07_ca_29.png b/content/images/07_ca/07_ca_29.png index 8d790d21..4a8a3e8b 100644 Binary files a/content/images/07_ca/07_ca_29.png and b/content/images/07_ca/07_ca_29.png differ diff --git a/content/images/07_ca/07_ca_30.png b/content/images/07_ca/07_ca_30.png index 4a8a3e8b..8a743d62 100644 Binary files a/content/images/07_ca/07_ca_30.png and b/content/images/07_ca/07_ca_30.png differ diff --git a/content/images/07_ca/07_ca_31.png b/content/images/07_ca/07_ca_31.png index 8a743d62..32d39cb1 100644 Binary files a/content/images/07_ca/07_ca_31.png and b/content/images/07_ca/07_ca_31.png differ diff --git a/content/images/07_ca/07_ca_32.png b/content/images/07_ca/07_ca_32.png index 32d39cb1..5dc9fff4 100644 Binary files a/content/images/07_ca/07_ca_32.png and b/content/images/07_ca/07_ca_32.png differ diff --git a/content/images/07_ca/07_ca_5.png b/content/images/07_ca/07_ca_5.png index acc8506a..5b59517b 100644 Binary files a/content/images/07_ca/07_ca_5.png and b/content/images/07_ca/07_ca_5.png differ diff --git a/content/images/07_ca/07_ca_6.png b/content/images/07_ca/07_ca_6.png index 5b59517b..e72a8704 100644 Binary files a/content/images/07_ca/07_ca_6.png and b/content/images/07_ca/07_ca_6.png differ diff --git a/content/images/07_ca/07_ca_7.png b/content/images/07_ca/07_ca_7.png index e72a8704..ae58c73c 100644 Binary files a/content/images/07_ca/07_ca_7.png and b/content/images/07_ca/07_ca_7.png differ diff --git a/content/images/07_ca/07_ca_8.png b/content/images/07_ca/07_ca_8.png index ae58c73c..eedaa351 100644 Binary files a/content/images/07_ca/07_ca_8.png and b/content/images/07_ca/07_ca_8.png differ diff --git a/content/images/07_ca/07_ca_9.png b/content/images/07_ca/07_ca_9.png index eedaa351..fff97b21 100644 Binary files a/content/images/07_ca/07_ca_9.png and b/content/images/07_ca/07_ca_9.png differ