noc-book-2/content/07_ca.html
2023-04-06 16:38:12 +00:00

731 lines
No EOL
65 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<section data-type="chapter">
<h1 id="chapter-7-cellular-automata">Chapter 7. Cellular Automata</h1>
<blockquote data-type="epigraph">
<p>“To play life you must have a fairly large checkerboard and a plentiful supply of flat counters of two colors. It is possible to work with pencil and graph paper but it is much easier, particularly for beginners, to use counters and a board.” —Martin Gardner, <em>Scientific American </em>(October 1970)</p>
</blockquote>
<p>In this chapter, Im going to take a break from talking about vectors and motion exclusively. In fact, the rest of the book will mostly focus on systems and algorithms (albeit ones that I can and will apply to moving bodies). In the chapter 5, the first example of a complex system was introduced: flocking. I briefly stated the core principles behind complex systems: more than the sum of its parts, a complex system is a system of elements, operating in parallel, with short-range relationships that as a whole exhibit emergent behavior. This entire chapter is going to be dedicated to building another complex system simulation. I am, however, going to take some steps backward and simplify the elements of the system. No longer will the individual elements be members of a physics world; instead I will build a system out of the simplest digital element possible, a single bit. This bit is called a cell and its value (0 or 1) is called its state. Working with such simple elements will help you to understand more of the details behind how complex systems work, and will offer an opportunity to elaborate on some programming techniques that apply to code-based projects.</p>
<h2 id="71-what-is-a-cellular-automaton">7.1 What Is a Cellular Automaton?</h2>
<p>First, lets get one thing straight. The term <strong><em>cellular automata</em></strong> is plural. The code examples here will simulate just one—a <strong><em>cellular automaton</em></strong>, singular. To keep things short and sweet, Ill refer to cellular automata as “CA.”</p>
<p>In Chapters 1 through 6, the objects (mover, particle, vehicle, boid) generally existed in only one “state.” They might have moved with sophisticated behaviors and physics, but ultimately they remained the same type of object over the course of their digital lifetime. Ive alluded to the possibility that these entities can change over time (for example, the weights of steering “desires” can vary), but I havent fully put this into practice. In this context, cellular automata make a great first step in building a system of many objects that have varying states over time.</p>
<p>A cellular automaton is a model of a system of “cell” objects with the following characteristics.</p>
<ul>
<li>The cells live on a <strong><em>grid</em></strong>. (Ill include examples in both one and two dimensions in this chapter, though a cellular automaton can exist in any finite number of dimensions.)</li>
<li>Each cell has a <strong><em>state</em></strong>. The number of state possibilities is typically finite. The simplest example has the two possibilities of 1 and 0 (otherwise referred to as “on” and “off” or “alive” and “dead”).</li>
<li>Each cell has a <strong><em>neighborhood</em></strong>. This can be defined in any number of ways, but it is typically a list of adjacent cells.</li>
</ul>
<figure>
<img src="images/07_ca/07_ca_1.png" alt="Figure 7.1: A 2D grid of cells each with a state of “on” and “off”. A neighborhood is a subsection of the large grid. These examples will focus on how the algorithm will act on the circled cell in combination with its neighbors.">
<figcaption>Figure 7.1: A 2D grid of cells each with a state of “on” and “off”. A neighborhood is a subsection of the large grid. These examples will focus on how the algorithm will act on the circled cell in combination with its neighbors.</figcaption>
</figure>
<p>The development of cellular automata systems is typically attributed to Stanisław Ulam and John von Neumann, who were both researchers at the Los Alamos National Laboratory in New Mexico in the 1940s. Ulam was studying the growth of crystals and von Neumann was imagining a world of self-replicating robots. Thats right, robots that build copies of themselves.</p>
<p>The idea of self-replicating robots is bit too complex as a starting point. In fact, Von Neumanns cells had twenty-nine possible states. Let's instead imagine a row of dominoes, where each domino can be in one of two states: standing upright (1) or knocked down (0). Just like dominoes react to their neighboring dominoes, the behavior of each cell in a cellular automaton is influenced by the states of its neighboring cells.</p>
<p>This chapter will explore how even the most basic rules of something like dominoes can lead to a wide array of intricate patterns and behaviors, similar to natural processes like biological reproduction and evolution. Von Neumanns work in self-replication and CA is conceptually similar to what is probably the most famous cellular automaton: the “Game of Life,” which I will discuss in detail in section 7.6.</p>
<p>Perhaps the most significant scientific (and lengthy) work studying cellular automata arrived in 2002: Stephen Wolframs 1,280-page <em>A New Kind of Science</em>. Available in its entirety for free online, Wolframs book discusses how CA are not simply neat tricks, but are relevant to the study of biology, chemistry, physics, and all branches of science. This chapter will barely scratch the surface of the theories Wolfram outlines (we will focus on the code implementation) so if the examples provided spark your curiosity, youll find plenty more to read about in his book.</p>
<h2 id="72-elementary-cellular-automata">7.2 Elementary Cellular Automata</h2>
<p>The examples in this chapter will begin with a simulation of Wolframs work. To understand Wolframs elementary CA, its best to begin with the question: “What is the simplest cellular automaton you can imagine?” Whats exciting about this question and its answer is that even with the simplest CA imaginable, you will see the properties of complex systems at work.</p>
<p>Lets build Wolframs elementary CA from scratch. Concepts first, then code. Ill begin with the three key elements of a CA.</p>
<p>1) <strong><em>Grid</em></strong>. The simplest grid would be one-dimensional: a line of cells.</p>
<figure>
<img src="images/07_ca/07_ca_2.png" alt="Figure 7.2: A one-dimensional line of cells">
<figcaption>Figure 7.2: A one-dimensional line of cells</figcaption>
</figure>
<p>2) <strong><em>States</em></strong>. The simplest set of states (beyond having only one state) would be two states: 0 or 1.</p>
<figure>
<img src="images/07_ca/07_ca_3.png" alt="Figure 7.3: A one-dimensional line of cells marked with states 0 or 1. What familiar programming data structure that could represent this sequence?">
<figcaption>Figure 7.3: A one-dimensional line of cells marked with states 0 or 1. What familiar programming data structure that could represent this sequence?</figcaption>
</figure>
<p>3) <strong><em>Neighborhood</em></strong>. The simplest neighborhood in one dimension for any given cell would be the cell itself and its two adjacent neighbors: one to the left and one to the right.</p>
<figure>
<img src="images/07_ca/07_ca_4.png" alt="Figure 7.4: A neighborhood in one dimension is three cells. ">
<figcaption>Figure 7.4: A neighborhood in one dimension is three cells. </figcaption>
</figure>
<p>Lets begin with a line of cells, each with an initial state (lets say set at random), and each with two neighbors. Ill have to decide out what I want to do with the cells on the edges (since those have only one neighbor each), but this is something to be sorted out later.</p>
<figure>
<img src="images/07_ca/07_ca_5.png" alt="Figure 7.5: The edge cell only has a neighborhood of two. ">
<figcaption>Figure 7.5: The edge cell only has a neighborhood of two. </figcaption>
</figure>
<p>I havent yet discussed, however, what is perhaps the most important detail of how cellular automata work—<em>time</em>. Im not really talking about real-world time here, but rather about the CA living over a period of <em>time</em>, which could also be called a <strong><em>generation</em></strong> and, in this case, will likely refer to the <strong><em>frame count</em></strong> of an animation. The figures above show the CA at time equals 0 (or generation 0). The questions to ask are: <em>How do you compute the states for all cells at generation 1? And generation 2?</em> And so on and so forth.</p>
<figure>
<img src="images/07_ca/07_ca_6.png" alt="Figure 7.6: The states for generation 1 are calculated using the states of the cells from generation 0.">
<figcaption>Figure 7.6: The states for generation 1 are calculated using the states of the cells from generation 0.</figcaption>
</figure>
<p>Lets say there is an individual cell in the CA called <span data-type="equation">\text{cell}</span>. The formula for calculating the <span data-type="equation">\text{cell state}</span> at any given time <span data-type="equation">t</span> (<span data-type="equation">\text{cell}_t</span>) is as follows:</p>
<div data-type="equation">\text{cell}_t = f(\text{cell neighborhood}_{t-1})</div>
<p>In other words, a cells new state is a function of all the states in the cells neighborhood at the previous generation (time <span data-type="equation">t-1</span>). A new state value is calculated by looking at the previous generations neighbor states.</p>
<figure>
<img src="images/07_ca/07_ca_7.png" alt="Figure 7.7 The state of a cell at generation 1 is a function of the previous generations neighborhood.">
<figcaption>Figure 7.7 The state of a cell at generation 1 is a function of the previous generations neighborhood.</figcaption>
</figure>
<p>Now, in the world of cellular automata, there are many ways to compute a cells state from a neighborhood of cells. Consider blurring an image. (Guess what? Image processing works with CA-like rules!) A pixels new state (its color) is the average of its neighbors colors. Similarly, a cells new state could be the sum of all of its neighbors states. However, in Wolframs elementary CA, the process is takes a different approach—instead of mathematical operations, new states are determined by a predefined set of rules that account for every possible configuration of a cell and its neighbors. This might seem ridiculous at first—wouldnt there be way too many possibilities for this to be practical? Lets give it a try.</p>
<p>There are three cells, each with a state of 0 or 1. How many possible ways can the states be configured? If you love binary, youll notice that three cells define a 3-bit number, and how high can you count with 3 bits? Up to 8. Lets have a look.</p>
<figure>
<img src="images/07_ca/07_ca_8.png" alt="Figure 7.8: Counting up to 8 with 3 bits in binary.">
<figcaption>Figure 7.8: Counting up to 8 with 3 bits in binary.</figcaption>
</figure>
<p>Once all the possible neighborhood configurations are defined, an outcome (new state value: 0 or 1) is specified for each configuration.</p>
<figure>
<img src="images/07_ca/07_ca_9.png" alt="Figure 7.9: A ruleset shows the outcome for each possible configuration of 3 cells.">
<figcaption>Figure 7.9: A ruleset shows the outcome for each possible configuration of 3 cells.</figcaption>
</figure>
<p>Keep in mind that unlike the sum or averaging methods, the rule set in elementary CA doesn't follow any arithmetic logic. These predefined rule sets are flexible, with the binary inputs remaining constant (the first row in Figure 7.9) while the translation into new states (the bottom row of 0s or 1s) varies based on the specific rule applied.</p>
<p>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, lets use 1D CA of 9 cells so that the middle is easy to pick out!</p>
<figure>
<img src="images/07_ca/07_ca_10.png" alt="Figure 7.10: Generation 0 in a Wolfram CA with the center cell set to 1.">
<figcaption>Figure 7.10: Generation 0 in a Wolfram CA with the center cell set to 1.</figcaption>
</figure>
<p>Referring to the ruleset above, lets see how a given cell (Ill pick the center one) would change from generation 0 to generation 1.</p>
<figure>
<img src="images/07_ca/07_ca_11.png" alt="Figure 7.11: Demonstration how the state for generation 1 is set by the CA rule set.">
<figcaption>Figure 7.11: Demonstration how the state for generation 1 is set by the CA rule set.</figcaption>
</figure>
<p>Try applying the same logic to all of the cells above and fill in the empty cells.</p>
<figure>
<img src="images/07_ca/07_ca_12.png" alt="Figure 7.12: 0 translates to a white cell, 1 to a black cell.">
<figcaption>Figure 7.12: 0 translates to a white cell, 1 to a black cell.</figcaption>
</figure>
<p>In Figure 7.12, I'm transitioning from representing cells with 0s or 1s to using visual cues—white for 0 and black for 1. 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 to black.</p>
<p>Through converting the numerical representations into visual forms, the fascinating dynamics and patterns of cellular automata will come into view! Lets move past just one generation to stacking, with each new generation appearing below the previous one as shown in Figure 7.13.</p>
<figure>
<img src="images/07_ca/07_ca_13.png" alt="Figure 7.13 Translating a grid of 0s and 1s to white and black squares.">
<figcaption>Figure 7.13 Translating a grid of 0s and 1s to white and black squares.</figcaption>
</figure>
<p>The low-resolution shape depicted above is the “Sierpiński triangle.” Named after the Polish mathematician Wacław Sierpiński, its a fractal pattern that Ill examine more closely in the next chapter. Thats right: this incredibly simple system of 0s and 1s, with little neighborhoods of three cells, can generate a shape as sophisticated and detailed as the Sierpiński triangle. Lets look at it again, only with each cell a single pixel wide so that the resolution is much higher.</p>
<figure>
<img src="images/07_ca/07_ca_14.png" alt="Figure 7.14: Wolfram Elementary CA: Rule 90 ">
<figcaption>Figure 7.14: Wolfram Elementary CA: Rule 90 </figcaption>
</figure>
<figure>
<img src="images/07_ca/07_ca_15.png" alt="Figure 7.15: Wolfram Elementary CA: Rule 90 at higher resolution">
<figcaption>Figure 7.15: Wolfram Elementary CA: Rule 90 at higher resolution</figcaption>
</figure>
<p>This particular result didnt happen by accident. I picked this set of rules because of the pattern it generates. Take a look at Figure 7.8 one more time. Notice how there are eight possible neighborhood configurations. A “ruleset” is defined a list of 8 bits. Figure 7.9 showed that ruleset with 0s and 1s. Here is the same ruleset now visualized with white and black squares.</p>
<figure>
<img src="images/07_ca/07_ca_16.png" alt="Figure 7.16 Looking at same ruleset (from Figure 7.9) with white and black squares.">
<figcaption>Figure 7.16 Looking at same ruleset (from Figure 7.9) with white and black squares.</figcaption>
</figure>
<p>Notice again how there are 8 possible configurations of three cells, 8 outcomes that can vary. A ruleset is therefore defined as a sequence of eight 0s or 1s. If you visit Wolframs website, youll see the convention of displayed any given rule as in Figure 7.16.</p>
<figure>
<img src="images/07_ca/07_ca_17.png" alt="Figure 7.17: How the Wolfram website represents a ruleset">
<figcaption>Figure 7.17: How the Wolfram website represents a ruleset</figcaption>
</figure>
<p>Eight 0s and 1s means an 8-bit number. How many combinations of eight 0s and 1s are there? 256. You might remember this from when you first learned about RGB color in p5.js. When you write <code>background(r, g, b)</code>, each color component (red, green, and blue) is represented by an 8-bit number ranging from 0 to 255.</p>
<p>Now, let's explore how to convert the visual representation of white and black squares in a ruleset to binary and decimal numbers. Lets work with the binary number 01011010. Binary numbers use “base 2,” meaning they are represented with only two possible digits (0 and 1). Any binary number can be converted to decimal (or base 10, digits between 0-9). In the case of “01011010” the resulting base 10 value is 90. “Rule 01011010” is therefore more commonly known as “Rule 90.”</p>
<p>Since there are 256 possible combinations of eight 0s and 1s, there are also 256 unique rulesets. Lets try looking at the results of another ruleset, how about “Rule 11011110” or more commonly “Rule 222.”</p>
<figure>
<img src="images/07_ca/07_ca_18.png" alt="Figure 7.18: Wolfram CA: Rule 222 ">
<figcaption>Figure 7.18: Wolfram CA: Rule 222 </figcaption>
</figure>
<figure class="half-width-right">
<img src="images/07_ca/07_ca_19.jpg" alt="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 ">
<figcaption>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 </figcaption>
</figure>
<p>As you can now see, the simple act of creating a CA and defining a ruleset does not guarantee visually exciting results. Out of all 256 rulesets, only a handful produce compelling outcomes. However, its quite incredible that even one of these rulesets with only two possible states can produce the patterns seen every day in nature (see Figure 7.18), and it demonstrates how valuable these systems can be in simulation and pattern generation.</p>
<p>Before I go too far down the road of how Wolfram classifies the results of varying rulesets, lets look at how to build a p5.js sketch that generates the Wolfram CA and visualizes it onscreen.</p>
<h2 id="73-how-to-program-an-elementary-ca">7.3 How to Program an Elementary CA</h2>
<p>You may be thinking: “OK, Ive got this cell thing. And the cell thing has some properties, like a state, what generation its on, who its neighbors are, where it lives pixel-wise on the screen. And maybe it has some functions: it can display itself, it can generate its new state, etc.” This line of thinking is an excellent one and would likely lead you to write some code like this:</p>
<pre class="codesplit" data-code-language="javascript">class Cell {
}</pre>
<p>This line of thinking, however, is not the road I will first travel. Later in this chapter, I will discuss why an object-oriented approach could prove valuable in developing a CA simulation, but to begin, its easier to work with a more elementary data structure. After all, what is an elementary CA but a list of 0s and 1s? Certainly, a generation of a one-dimensional CA could be described using an array:</p>
<figure>
<img src="images/07_ca/07_ca_20.png" alt="Figure 7.20: One generation of a 1D cellular automata">
<figcaption>Figure 7.20: One generation of a 1D cellular automata</figcaption>
</figure>
<pre class="codesplit" data-code-language="javascript">let cells = [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0];</pre>
<p>To draw that array, I check the element is 0 or a 1 and create a fill color accordingly.</p>
<pre class="codesplit" data-code-language="javascript">//{!1} Loop through every cell.
for (let i = 0; i &#x3C; cells.length; i++) {
//{!5} Create a fill based on its state (0 or 1).
if (cells[i] == 0) {
fill(255);
} else {
fill(0);
}
stroke(0);
rect(i * 50, 0, 50, 50);
}</pre>
<p>Now that the array describes the cell states of a given generation (which will ultimately be considered the “current” generation), a mechanism to compute the next generation is needed. Lets think about the pseudocode of what I am hoping to do here.</p>
<p><strong>For every cell in the array:</strong></p>
<ul>
<li><strong><em>Take a look at the neighborhood states: left, middle, right.</em></strong></li>
<li><strong><em>Look up the new value for the cell state according to some ruleset.</em></strong></li>
<li><strong><em>Set the cells state to that new value.</em></strong></li>
</ul>
<p>This may lead you to write some code like this:</p>
<pre class="codesplit" data-code-language="javascript">// For every cell in the array...
for (let i = 0; i &#x3C; cells.length; i++) {
//{!3} ...take a look at the neighborhood.
let left = cells[i - 1];
let middle = cells[i];
let right = cells[i + 1];
//{!1} Look up the new value according to the rules.
let newstate = rules(left, middle, right);
//{!1} Set the cells state to the new value.
cells[i] = newstate;
}</pre>
<p>Im fairly close to getting this right, but Ive made one minor blunder and one major blunder in the above code. Lets examine this code more closely.</p>
<p>Notice how easy it is to look at a cells neighbors. Because an array is an ordered list of data, I can use the fact that the indices are numbered to know which cells are next to which cells. I know that cell number 15, for example, has cell 14 to its left and 16 to its right. More generally, I can say that for any cell <code>i</code>, its neighbors are <code>i - 1</code> and <code>i + 1</code>.</p>
<p>Im also farming out the calculation of a new state value to some function called <code>rules()</code>. Obviously, Im going to have to write this function, but the point Im making here is modularity. I have a basic framework for the CA in this function, and if I later want to change how the rules operate, I dont have to touch that framework; I can just rewrite the <code>rules()</code> function to compute the new states differently.</p>
<p>So what have I done wrong? Lets talk through how the code will execute. First, look at cell index <code>i</code> when it equals 0. Now lets look at 0s neighbors. Left is <code>i - 1</code> or <code>-1</code>. Middle is <code>i</code> or <code>0</code>. And right is <code>i + 1</code> or <code>1</code>. However, the array by definition does not have an element with the index <code>-1</code>. It starts with <code>0!</code> This is a problem I alluded to before: the edge cases.</p>
<p>How do we deal with the cell on the edge who doesnt have a neighbor to both its left and its right? Here are three possible solutions to this problem:</p>
<ol>
<li><strong><em>Edges remain constant.</em></strong> This is perhaps the simplest solution. Dont bother to evaluate the edges and always leave their state value constant (0 or 1).</li>
<li><strong><em>Edges wrap around.</em></strong> Think of the CA as a strip of paper and turn that strip of paper into a ring. The cell on the left edge is a neighbor of the cell on the right and vice versa. This can create the appearance of an infinite grid and is probably the most used solution.</li>
<li><strong><em>Edges have different neighborhoods and rules.</em></strong> If I wanted to, I could treat the edge cells differently and create rules for cells that have a neighborhood of two instead of three. You may want to do this in some circumstances, but in this case, its going to be a lot of extra lines of code for little benefit.</li>
</ol>
<p>To make the code easiest to read and understand right now, Ill go with option #1 and skip the edge cases, leaving the values constant. This can be accomplished by starting the loop one cell later and ending one cell earlier:</p>
<pre class="codesplit" data-code-language="javascript">//{.bold} A loop that ignores the first and last cell
for (let i = 1; i &#x3C; cells.length - 1; i++) {
let left = cells[i - 1];
let middle = cells[i];
let right = cells[i + 1];
let newstate = rules(left, middle, right);
cells[i] = newstate;
}</pre>
<p>Theres one more problem to fix before this is done. Its subtle and you wont get an error; the CA just wont perform correctly. However, identifying this problem is absolutely fundamental to the techniques behind programming CA simulations. It all lies in this line of code:</p>
<pre class="codesplit" data-code-language="javascript"> cells[i] = newstate;</pre>
<p>This seems like a perfectly innocent line. After all, Ive computed the new state value and Im assigning the cell its new state. But in the next iteration, there will be a massive bug. Lets say the new state for cell #5 was just computed. What happens next? The new state value for cell #6 is calculated.</p>
<p><em>Cell #6, generation 0 = some state, 0 or 1</em></p>
<p><em>Cell #6, generation 1 = a function of states for </em><strong><em>cell #5</em></strong><em>, cell #6, and cell #7 at *</em><strong><em>generation 0</em></strong><em>*</em></p>
<p>Notice how the value of cell #5 at generation 0 is needed in order to calculate cell #6s new state at generation 1? A cells new state is a function of the previous neighbor states. Have I savedcell #5s value at generation 0? Remember, this line of code was just executed for <em>i = 5</em>.</p>
<pre class="codesplit" data-code-language="javascript"> cells[i] = newstate;</pre>
<p>Once this happens, cell #5s state at generation 0 has been erased; cell index 5 is now storing the value for generation 1. I cannot overwrite the values in the array while I am processing the array, because I need those values to calculate the new values! A solution to this problem is to have two arrays, one to store the current generation states and one for the next generation states. To save myself the step of re-initializing an array, Ill use the JavaScript array function <code>slice()</code> which makes a copy of an array.</p>
<pre class="codesplit" data-code-language="javascript">//{!1 .bold} Another array to store the states for the next generation.
let newcells = cells.slice();
for (let i = 1; i &#x3C; cells.length - 1; i++) {
//{!3} Look at the states from the current array.
const left = cells[i - 1];
const middle = cells[i];
const right = cells[i + 1];
const newstate = rules(left, middle, right);
//{!1 .bold} Saving the new state in the new array
newcells[i] = newstate;
}</pre>
<p>Once the entire array of values is processed, the <code>cells</code> variable assigned the new array of states effectively throwing away the previous generations values.</p>
<pre class="codesplit" data-code-language="javascript">//{.bold} The new generation becomes the current generation.
cells = newcells;</pre>
<p>Im almost done. The above code is complete except for the <code>rules()</code> function that computes the new state value based on the neighborhood (left, middle, and right cells). I know that function needs to return an integer (0 or 1) as well as receive three arguments (for the three neighbors).</p>
<pre class="codesplit" data-code-language="javascript"> //{!1} Function signature: receives 3 ints and returns 1.
function rules (a, b, c) { return _______ }</pre>
<p>Now, there are many ways to write this function, but Id like to start with a long-winded one that will hopefully provide a clear illustration of what is happening.</p>
<p>Lets first establish how I will store the ruleset. The ruleset, if you remember from the previous section, is a series of 8 bits (0 or 1) that defines that outcome for every possible neighborhood configuration. Lets bring back the visual representation of a ruleset and add back the numeric encoding.</p>
<figure>
<img src="images/07_ca/07_ca_21.png" alt="Figure 7.21 Visual representation of a Wolfram ruleset with numeric encoding ">
<figcaption>Figure 7.21 Visual representation of a Wolfram ruleset with numeric encoding </figcaption>
</figure>
<p>I can store this ruleset in an array.</p>
<pre class="codesplit" data-code-language="javascript">let ruleset = [0, 1, 0, 1, 1, 0, 1, 0];</pre>
<p>And then say:</p>
<pre class="codesplit" data-code-language="javascript">if (a == 1 &#x26;&#x26; b == 1 &#x26;&#x26; c == 1) return ruleset[0];</pre>
<p>If left, middle, and right all have the state 1, that matches the configuration 111 and the new state should be equal to the first value in the ruleset array. Duplicating this strategy for all eight possibilities looks like:</p>
<pre class="codesplit" data-code-language="javascript"> function rules (a, b, c) {
if (a === 1 &#x26;&#x26; b === 1 &#x26;&#x26; c === 1) return ruleset[0];
else if (a === 1 &#x26;&#x26; b === 1 &#x26;&#x26; c === 0) return ruleset[1];
else if (a === 1 &#x26;&#x26; b === 0 &#x26;&#x26; c === 1) return ruleset[2];
else if (a === 1 &#x26;&#x26; b === 0 &#x26;&#x26; c === 0) return ruleset[3];
else if (a === 0 &#x26;&#x26; b === 1 &#x26;&#x26; c === 1) return ruleset[4];
else if (a === 0 &#x26;&#x26; b === 1 &#x26;&#x26; c === 0) return ruleset[5];
else if (a === 0 &#x26;&#x26; b === 0 &#x26;&#x26; c === 1) return ruleset[6];
else if (a === 0 &#x26;&#x26; b === 0 &#x26;&#x26; c === 0) return ruleset[7];
}</pre>
<p>I like the example written as above because it describes line by line exactly what is happening for each neighborhood configuration. However, its not a great solution. After all, what if a CA has 4 possible states (0-3)? Suddenly there are 64 possible neighborhood configurations. With 10 possible states, 1,000 configurations. Certainly I dont want to type in 1,000 <code>else if</code> statements!</p>
<p>Another solution, though perhaps more tricky to follow, is to convert the neighborhood configuration (a 3-bit number) into a regular integer and use that value as the index into the ruleset array. This can be done as follows using the built-in JavaScript function <code>parseInt()</code>:</p>
<pre class="codesplit" data-code-language="javascript"> function rules (a, b, c) {
// A quick way to concatenate three numbers into a string
let s = "" + a + b + c;
// The second argument 2 indicates that the number should be parsed as binary (base 2).
let index = parseInt(s, 2);
return ruleset[index];
}</pre>
<p>Theres one tiny problem with this solution, however. Lets consider rule 222:</p>
<pre class="codesplit" data-code-language="javascript">// Rule 222
let ruleset = [1, 1, 0, 1, 1, 1, 1, 0];</pre>
<p>And the neighborhood being tested is “111”. The resulting state is equal to ruleset index 0, based on how I first wrote the <code>rules()</code> function .</p>
<pre class="codesplit" data-code-language="javascript"> if (a === 1 &#x26;&#x26; b === 1 &#x26;&#x26; c === 1) return ruleset[0];</pre>
<p>The binary number “111” converted to a decimal number is 7. But I dont want <code>ruleset[7]</code>; I want <code>ruleset[0]</code>. For this to work, ruleset needs to be written with the bits in reverse order:</p>
<pre class="codesplit" data-code-language="javascript">//{!1} Rule 222 in “reverse” order
let ruleset = [0, 1, 1, 1, 1, 0, 1, 1];</pre>
<p>I now have everything needed to compute the generations for a Wolfram elementary CA. Lets take a moment to organize the code all together.</p>
<pre class="codesplit" data-code-language="javascript">//{!1} Array for the cells
let cells = [];
//{!1} Arbitrarily starting with rule 90
let ruleset = [0, 1, 0, 1, 1, 0, 1, 0];
function setup() {
//{!3} All cells start with state 0
for (let i = 0; i &#x3C; width; i++) {
cells[i] = 0;
}
// Except the center cell is set to state 1.
cells[floor(cells.length / 2)] = 1;
}
function draw() {
//{!7} Compute the next generation.
let nextgen = cells.slice();
for (let i = 1; i &#x3C; cells.length - 1; i++) {
let left = cells[i - 1];
let me = cells[i];
let right = cells[i + 1];
nextgen[i] = rules(left, me, right);
}
cells = nextgen;
}
//{!4} Look up a new state from the ruleset.
function rules(a, b, c) {
let s = "" + a + b + c;
let index = parseInt(s, 2);
return ruleset[index];
}</pre>
<h2 id="74-drawing-an-elementary-ca">7.4 Drawing an Elementary CA</h2>
<p>Whats missing? Presumably, the point here is to draw the cells. As you saw earlier, the standard technique for doing this is to stack the generations one on top of each other and draw a rectangle that is black (for state 1) or white (for state 0).</p>
<figure>
<img src="images/07_ca/07_ca_22.png" alt="Figure 7.22 Ruleset 90 visualized as a stack of generations">
<figcaption>Figure 7.22 Ruleset 90 visualized as a stack of generations</figcaption>
</figure>
<p>Before implementing this particular visualization, Id like to point out two things.</p>
<p>One, this visual interpretation of the data is completely literal. Its useful for demonstrating the algorithms and results of Wolframs elementary CA, but it shouldnt necessarily drive your own personal work. Its rather unlikely that you are building a project that needs precisely this algorithm with this visual style. So while learning to draw the CA in this way will help you understand and implement CA systems, this skill should exist only as a foundation.</p>
<p>Second, the fact that a one-dimensional CA is visualized with a two-dimensional image can be confusing. Its very important to remember that this is not a 2D CA. I am 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, you will see an actual 2D CA (the Game of Life) and Ill cover how to visualized such a system.</p>
<p>The good news is that drawing the CA is not particularly difficult. Lets begin by looking at how to render a single generation. Lets assume a canvas 640 pixels wide with cell being be a 10x10 square. We therefore have a CA with 64 cells. Of course, we can calculate this value dynamically.</p>
<pre class="codesplit" data-code-language="javascript">let w = 10;
//{!1} How many cells fit across given a certain width
let cells = new Array(floor(width / w));</pre>
<p>Assuming the cell initialization in <code>setup()</code> drawing the cells involves iterating over the array and drawing a white square when the state equals 1 and a black when the state equals 0.</p>
<pre class="codesplit" data-code-language="javascript">for (let i = 0; i &#x3C; cells.length; i++) {
//{!2} By multiplying the cell state the result is 0 or 255
fill(cells[i] * 255);
//{!1} Notice how the x-position is the cell index times the cell width.
// 0, 10, 20, 30, all the way to 640.
square(i * w, 0, w);
}</pre>
<p>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.) Ill also note that if the size of each cell were 1 pixel I would not want to use p5.jss <code>square()</code> function, but rather access the pixel array directly.</p>
<p>You might also notice that the y-position for each square is 0. If I want the generations to be drawn next to each other, with each row of cells marking a new generation, Ill also need to calculate a y-position based on number of generation iterations. I could accomplish this by adding a variable and incrementing it each time through <code>draw()</code>. With these additions, I can now look at the entire sketch.</p>
<div data-type="example">
<h3 id="example-71-wolfram-elementary-cellular-automata">Example 7.1: Wolfram elementary cellular automata</h3>
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/SaLy-OnPZ" data-example-path="examples/07_ca/7_1_elementary_wolfram_ca"></div>
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript">// Array of cells
let cells;
// Starting at generation 0
let generation = 0;
// Cell size
let w = 10;
//{!1} Rule 90
let ruleset = [0, 1, 0, 1, 1, 0, 1, 0];
function setup() {
createCanvas(640, 240);
background(255);
//{!5} An array of 0s and 1s
cells = new Array(floor(width / w));
for (let i = 0; i &#x3C; cells.length; i++) {
cells[i] = 0;
}
cells[floor(cells.length / 2)] = 1;
}
function draw() {
for (let i = 1; i &#x3C; cells.length - 1; i++) {
//{!1} Only drawing the cell's with a state of 1
if (cells[i] == 1) {
fill(0);
//{!1} Set the y-position according to the generation.
square(i * w, generation * w, w);
}
}
//{!7} Compute the next generation.
let nextgen = cells.slice();
for (let i = 1; i &#x3C; cells.length - 1; i++) {
let left = cells[i - 1];
let me = cells[i];
let right = cells[i + 1];
nextgen[i] = rules(left, me, right);
}
cells = nextgen;
//{!1} The next generation
generation++;
}
//{!4} Look up a new state from the ruleset.
function rules(a, b, c) {
let s = "" + a + b + c;
let index = parseInt(s, 2);
return ruleset[index];
}</pre>
<div data-type="exercise">
<h3 id="exercise-71">Exercise 7.1</h3>
<p>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.</p>
</div>
<div data-type="exercise">
<h3 id="exercise-72">Exercise 7.2</h3>
<p>Examine what patterns occur if you initialize the first generation with each cell having a random state.</p>
</div>
<div data-type="exercise">
<h3 id="exercise-73">Exercise 7.3</h3>
<p>Visualize the CA in a non-traditional way. Break all the rules you can; dont feel tied to using squares on a perfect grid with black and white.</p>
</div>
<div data-type="exercise">
<h3 id="exercise-74">Exercise 7.4</h3>
<p>Create a visualization of the CA that scrolls upwards as the generations increase so that you can view the generations to “infinity.” Hint: instead of keeping track of one generation at a time, youll need to store a history of generations, always adding a new one and deleting the oldest one in each frame.</p>
</div>
<h2 id="75-wolfram-classification">7.5 Wolfram Classification</h2>
<p>Before moving on to looking at CA in two dimensions, its worth taking a brief look at Wolframs classification for cellular automata. As we noted earlier, the vast majority of elementary CA rulesets produce uninspiring results, while some result in wondrously complex patterns like those found in nature. Wolfram has divided up the range of outcomes into four classes:</p>
<figure>
<img src="images/07_ca/07_ca_23.png" alt="Figure 7.23: Rule 222 ">
<figcaption>Figure 7.23: Rule 222 </figcaption>
</figure>
<p><strong><em>Class 1: Uniformity.</em></strong> Class 1 CAs end up, after some number of generations, with every cell constant. This is not terribly exciting to watch. Rule 222 (above) is a class 1 CA; if you run it for enough generations, every cell will eventually become and remain black.</p>
<figure>
<img src="images/07_ca/07_ca_24.png" alt="Figure 7.24: Rule 190 ">
<figcaption>Figure 7.24: Rule 190 </figcaption>
</figure>
<p><strong><em>Class 2: Repetition.</em></strong> Like class 1 CAs, class 2 CAs remain stable, but the cell states are not constant. Rather, they oscillate in some repeating pattern of 0s and 1s. In rule 190 (above), each cell follows the sequence <code>11101110111011101110</code>.</p>
<figure>
<img src="images/07_ca/07_ca_25.png" alt="Figure 7.25: Rule 30 ">
<figcaption>Figure 7.25: Rule 30 </figcaption>
</figure>
<p><strong><em>Class 3: Random.</em></strong> Class 3 CAs appear random and have no easily discernible pattern. In fact, rule 30 (above) is used as a random number generator in Wolframs Mathematica software. Again, this is a moment where we can feel amazed that such a simple system with simple rules can descend into a chaotic and random pattern.</p>
<figure>
<img src="images/07_ca/07_ca_26.png" alt="Figure 7.26: Rule 110 ">
<figcaption>Figure 7.26: Rule 110 </figcaption>
</figure>
<p><strong><em>Class 4: Complexity.</em></strong> Class 4 CAs can be thought of as a mix between class 2 and class 3. One 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 should really blow your mind.</p>
<div data-type="exercise">
<h3 id="exercise-75">Exercise 7.5</h3>
<p>Create a sketch that draws for every possible ruleset. Can you classify them?</p>
</div>
<h2 id="76-the-game-of-life">7.6 The Game of Life</h2>
<p>The next step to take is to move from a one-dimensional CA to a two-dimensional one. This will introduce some additional complexity; each cell will have a bigger neighborhood, but that will open up the door to a range of possible applications. After all, most of what happens in computer graphics lives in two dimensions, and this chapter will demonstrate how to apply CA thinking to a 2D p5.js canvas.</p>
<p>In 1970, Martin Gardner wrote an article in <em>Scientific American</em> that documented mathematician John Conways new “Game of Life,” describing it as “recreational” mathematics and suggesting that the reader get out a chessboard and some checkers and “play.” While the Game of Life has become something of a computational cliché (make note of the myriad projects that display the Game of Life on LEDs, screens, projection surfaces, etc.), it is still valuable to practice building the system with code. For one, it provides a good opportunity to practice skills with two-dimensional arrays, nested loops, and more. But perhaps more importantly, its core principles are tied directly to the core goals of this book—simulating the natural world with code. Though you may want to avoid duplicating it without a great deal of thought or care, the algorithm and its technical implementation will provide you with the inspiration and foundation to build simulations that exhibit the characteristics and behaviors of biological systems of reproduction.</p>
<p>Unlike von Neumann, who created an extraordinarily complex system of states and rules, Conway wanted to achieve a similar “lifelike” result with the simplest set of rules possible. Martin Gardner outlined Conways goals as follows:</p>
<blockquote data-type="epigraph">
<p>“1. There should be no initial pattern for which there is a simple proof that the population can grow without limit. 2. There should be initial patterns that apparently do grow without limit. 3. There should be simple initial patterns that grow and change for a considerable period of time before coming to an end in three possible ways: fading away completely (from overcrowding or becoming too sparse), settling into a stable configuration that remains unchanged thereafter, or entering an oscillating phase in which they repeat an endless cycle of two or more periods.”</p>
</blockquote>
<p>The above 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 didnt use this terminology, it should have all those properties of a <em>complex system</em>.</p>
<p>Lets look at how the Game of Life works. It wont take up too much time or space, since I ca build on everything from the Wolfram Elementary CA.</p>
<figure class="half-width-right">
<img src="images/07_ca/07_ca_27.png" alt="Figure 7.27: A two-dimensional CA showing the neighborhood of 9 cells.">
<figcaption>Figure 7.27: A two-dimensional CA showing the neighborhood of 9 cells.</figcaption>
</figure>
<p>First, instead of a line of cells, there is now a two-dimensional matrix of cells. As with the elementary CA, the possible states are 0 or 1. Only in this case, since the system is about “life," 0 means dead and 1 means alive.</p>
<p>The cells neighborhood has also expanded. If a neighbor is an adjacent cell, a neighborhood is now nine cells instead of three.</p>
<p>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. In other words, is the neighborhood overpopulated with life? Surrounded by death? Or just right? Here are the rules of life.</p>
<ol>
<li><strong><em>Death.</em></strong> If a cell is alive (state = 1) it will die (state becomes 0) under the following circumstances.
<ul>
<li><strong><em>Overpopulation:</em></strong> If the cell has four or more alive neighbors, it dies.</li>
<li><strong><em>Loneliness:</em></strong> If the cell has one or fewer alive neighbors, it dies.</li>
</ul>
</li>
<li><strong><em>Birth.</em></strong> If a cell is dead (state = 0) it will come to life (state becomes 1) if it has exactly three alive neighbors (no more, no less).</li>
<li><strong><em>Stasis.</em></strong> In all other cases, the cell state does not change. To be thorough, Ill describe those scenarios.
<ul>
<li><strong><em>Staying Alive:</em></strong> If a cell is alive and has exactly two or three live neighbors, it stays alive.</li>
<li><strong><em>Staying Dead:</em></strong> If a cell is dead and has anything other than three live neighbors, it stays dead.</li>
</ul>
</li>
</ol>
<p>Lets look at a few examples, focussing on the center cell.</p>
<figure>
<img src="images/07_ca/07_ca_28.png" alt="Figure 7.28: Example scenarios for “death” and “birth” in the Game of Life.">
<figcaption>Figure 7.28: Example scenarios for “death” and “birth” in the Game of Life.</figcaption>
</figure>
<p>With the elementary CA, I visualized the generations next to each other, 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). Nevertheless, the typical way the Game of Life is displayed is to treat each generation as a single frame in an animation. So 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.</p>
<p>One of the exciting aspects of the Game of Life is that there are known initial patterns that yield intriguing results. For example, some remain static and never change.</p>
<figure>
<img src="images/07_ca/07_ca_29.png" alt="Figure 7.29: Initial configurations of cells that remain stable.">
<figcaption>Figure 7.29: Initial configurations of cells that remain stable.</figcaption>
</figure>
<p>There are patterns that oscillate back and forth between two states.</p>
<figure>
<img src="images/07_ca/07_ca_30.png" alt="Figure 7.30: Initial configurations of cells that oscillate between two states.">
<figcaption>Figure 7.30: Initial configurations of cells that oscillate between two states.</figcaption>
</figure>
<p>And there are also patterns that from generation to generation appear to move about the grid. (Its important to note that the cells themselves arent actually moving, rather you see the illusion of motion in the result as the cells turn on and off.)</p>
<figure>
<img src="images/07_ca/07_ca_31.png" alt="Figure 7.31: Initial configurations of cells that appear to move.">
<figcaption>Figure 7.31: Initial configurations of cells that appear to move.</figcaption>
</figure>
<p>If you are interested in these patterns, there are several good “out of the box” Game of Life demonstrations online that allow you to configure the CAs initial state and watch it run at varying speeds. Two examples such examples are:</p>
<ul>
<li><a href="http://www.playfulinvention.com/emergence/">Exploring Emergence by Mitchel Resnick and Brian Silverman, Lifelong Kindergarten Group, MIT Media Laboratory</a></li>
<li><a href="https://sklise.github.io/conways-game-of-life/">Conways Game of Life by Steven Klise</a></li>
</ul>
<p>For the example Ill build in the next section, Ill focus on randomly initializing the states for each cell.</p>
<h2 id="77-programming-the-game-of-life">7.7 Programming the Game of Life</h2>
<p>Now I just need to extend the code from the Wolfram CA to two dimensions. I previously used a one-dimensional array to store the list of cell states before, and for the Game of Life, Ill use a two-dimensional array.</p>
<pre class="codesplit" data-code-language="javascript">let w = 8;
let columns = width / w;
let rows = height / w;
let board = new Array(columns);
for (let i = 0; i &#x3C; columns; i++) {
board[i] = new Array(rows);
}</pre>
<p>Ill begin by initializing each cell of the board with a random state: 0 or 1.</p>
<pre class="codesplit" data-code-language="javascript">for (let i = 0; i &#x3C; columns; i++) {
for (let j = 0; j &#x3C; rows; j++) {
//{!1} Initialize each cell with a 0 or 1.
board[i][j] = floor(random(2));
}
}</pre>
<p>And to compute the next generation, just as before, I need to make sure I dont overwrite the previous generation states. Rather than write all the steps to create a 2D array in both <code>setup()</code> and <code>draw()</code>its worth writing a function that returns a 2D array based on the number of columns and rows. Ill also initialize each element of the array to <code>0</code> so that it isnt filled with <code>undefined.</code></p>
<pre class="codesplit" data-code-language="javascript">function create2DArray(columns, rows) {
let arr = new Array(columns);
for (let i = 0; i &#x3C; columns; i++) {
arr[i] = new Array(rows);
for (let j = 0; j &#x3C; rows; j++) {
arr[i][j] = 0;
}
}
return arr;
}</pre>
<p>Then I can just call that function whenever a new 2D array is required.</p>
<pre class="codesplit" data-code-language="javascript">let next = create2DArray(columns, rows);
for (let i = 0; i &#x3C; columns; i++) {
for (let j = 0; j &#x3C; rows; j++) {
//{!1} Calculate the state for each cell.
next[x][y] = _______________?;
}
}</pre>
<figure class="half-width-right">
<img src="images/07_ca/07_ca_32.png" alt="Figure 7.32: The index values for the neighborhood of cells.">
<figcaption>Figure 7.32: The index values for the neighborhood of cells.</figcaption>
</figure>
<p>OK. Before I can sort out how to actually calculate the new state, I need to determine how to reference the cells neighbors. In the case of a 1D CA, this was simple: if a cell index was <code>i</code>, its neighbors were <code>i-1</code> and <code>i+1</code>. Here each cell doesnt have a single index, but rather a column and row index: <code>i,j</code>. As shown in Figure 7.32, the neighbors are <code>i-1,j-1</code> , <code>i,j-1</code>, <code>i+1,j-1</code>, <code>i-1,j</code>, <code>i+1,j</code>, <code>i-1,j+1</code>, <code>i,j+1</code>, and <code>i+1,j+1</code>.</p>
<p>The Game of Life rules operate by knowing how many neighbors are alive. So if I create a counter variable and increment it for each neighbor with a state of 1, Ill have the total of live neighbors.</p>
<pre class="codesplit" data-code-language="javascript">let sum = 0;
// Top row of neighbors
if (board[i - 1][j - 1] == 1) sum++;
if (board[i ][j - 1] == 1) sum++;
if (board[i + 1][j - 1] == 1) sum++;
// Middle row of neighbors (note i,j is skipped)
if (board[i - 1][j ] == 1) sum++;
if (board[i + 1][j ] == 1) sum++;
// Bottom row of neighbors
if (board[i - 1][j + 1] == 1) sum++;
if (board[i ][j + 1] == 1) sum++;
if (board[i + 1][j + 1] == 1) sum++;</pre>
<p>And again, just as with the Wolfram CA, I find myself in a situation where the above is a useful and clear way to write the code for teaching purposes, explicitly stating every step (each time a neighbor has a state of one, the counter increases). Nevertheless, its a bit silly to say, “If the cell state equals one, add one to a counter” when I could just as easily say, “Add the cell state to a counter.” After all, if the state can only be 0 or 1, the sum of all the neighbors states will yield the total number of live cells. Since the neighbors are arranged in a mini 3x3 grid, I can introduce another nested loop to computer the sum.</p>
<pre class="codesplit" data-code-language="javascript">let sum = 0;
//{!2} Using k and l as the counters since i and j are already used!
for (let k = -1; k &#x3C;= 1; k++) {
for (let l = -1; l &#x3C;= 1; l++) {
//{!1} Add up all the neighbors states.
sum += board[i + k][j + l];
}
}</pre>
<p>Of course, Ive made a significant mistake. In the Game of Life, the cell itself does not count as one of the neighbors. I could include a conditional to skip adding the state when both <code>k</code> and <code>l</code> equal 0, but another option is be to subtract the cell state once the loop is completed.</p>
<pre class="codesplit" data-code-language="javascript">// Whoops! Subtract the cells state!
neighbors -= board[i][j];</pre>
<p>Finally, once we know the total number of live neighbors, we can decide what the cells new state should be according to the rules: birth, death, or stasis.</p>
<pre class="codesplit" data-code-language="javascript">// {.code-wide} If it is alive and has less than 2 live neighbors, it dies from loneliness.
if (board[i][j] == 1 &#x26;&#x26; sum &#x3C; 2) {
next[i][j] = 0;
// {.code-wide} If it is alive and has more than 3 live neighbors, it dies from overpopulation.
} else if (board[x][y] == 1 &#x26;&#x26; sum > 3) {
next[i][j] = 0;
// {.code-wide} If it is dead and has exactly 3 live neighbors, it is born!
} else if (board[x][y] == 0 &#x26;&#x26; sum == 3) {
next[i][j] = 1;
// {.code-wide} In all other cases, its state remains the same.
} else {
next[i][j] = board[i][j];
}</pre>
<p>Putting this all together:</p>
<pre class="codesplit" data-code-language="javascript">// The next board
let next = create2DArray(columns, rows);
//{!2} Looping but skipping the edge cells
for (let i = 1; i &#x3C; columns - 1; i++) {
for (let j = 1; j &#x3C; rows - 1; j++) {
// Add up all the neighbor states to
// calculate the number of live neighbors.
let neighbors = 0;
for (let k = -1; k &#x3C;= 1; k++) {
for (let l = -1; l &#x3C;= 1; l++) {
neighbors += board[i + k][j + l];
}
}
// Correct by subtracting the cell state itself.
neighbors -= board[i][j];
//{!4} The rules of life!
if (board[i][j] == 1 &#x26;&#x26; neighbors &#x3C; 2) next[i][j] = 0;
else if (board[i][j] == 1 &#x26;&#x26; neighbors > 3) next[i][j] = 0;
else if (board[i][j] == 0 &#x26;&#x26; neighbors == 3) next[i][j] = 1;
else next[i][j] = board[i][j];
}
}
board = next;</pre>
<p>Finally, once the next generation is calculated, I can employ the same method used to draw the Wolfram CA—a square for each spot, white for off, black for on. The only new code here is drawing the board!</p>
<div data-type="example">
<h3 id="example-72-game-of-life">Example 7.2: Game of Life</h3>
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/vzLjFYwJc" data-example-path="examples/07_ca/7_2_game_of_life"></div>
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript"> for (let i = 0; i &#x3C; columns; i++) {
for (let j = 0; j &#x3C; rows; j++) {
//{!1} evaluates to 255 when state is 0 and 0 when state is 1
fill(255 - board[i][j] * 255);
stroke(0);
square(i * w, j * w, w);
}
}</pre>
<div data-type="exercise">
<h3 id="exercise-76">Exercise 7.6</h3>
<p>Create a Game of Life simulation that allows you to manually configure the grid by drawing or with specific known patterns.</p>
</div>
<div data-type="exercise">
<h3 id="exercise-77">Exercise 7.7</h3>
<p>Implement “wrap-around” for the Game of Life so that cells on the edges have neighbors on the opposite side of the grid.</p>
</div>
<div data-type="exercise">
<h3 id="exercise-78">Exercise 7.8</h3>
<p>While the above solution (Example 7.2) is convenient, it is not particularly memory-efficient. It creates a new 2D array for every frame of animation! This matters very little for a p5.js application, but if you were implementing the Game of Life on a microcontroller or mobile device, youd want to be more careful. One solution is to have only two arrays and constantly swap them, writing the next set of states into whichever one isnt the current array. Implement this particular solution.</p>
</div>
<h2 id="78-object-oriented-cells">7.8 Object-Oriented Cells</h2>
<p>Over the course of this book, Ive built examples of systems of <em>objects</em> with properties that move about the canvas. And in this chapter, although Ive been talking about a “cell” as if it were an object, I havent used the principles of object orientation in the code itself. This has worked because a cell is such an enormously simple object (its only property being a single 0 or 1). However, in a moment, I am going to suggest some ideas for further developing CA systems, many of which involve keeping track of multiple properties for each cell. For example, what if a cell needed to remember its history of states? Or what if you wanted to apply motion and physics to a CA and have the cells move about the canvas, dynamically changing their neighbors from frame to frame?</p>
<p>To accomplish any of these ideas (and more), it would be helpful to see how to treat a cell as an object with multiple properties, rather than as a single 0 or 1. To show this, lets just recreate the Game of Life simulation. Only instead of:</p>
<pre class="codesplit" data-code-language="javascript"> board[i][j] = floor(random(2));
</pre>
<p>Lets have:</p>
<pre class="codesplit" data-code-language="javascript"> board[i][j] = new Cell(floor(random(2));
</pre>
<p>where <code>Cell</code> is a new class I will write. What are the properties of a <code>Cell</code> object? In the Game of Life example, I might choose to create a cell that stores its position and size along with the state.</p>
<pre class="codesplit" data-code-language="javascript">class Cell {
constructor(state, x, y, w){
// What is the cells state?
this.state = state;
// position and size
this.x = x;
this.y = y;
this.w = w;
}
</pre>
<p>In the non-OOP version, I used a separate 2D array to keep track of the states for the current and next generation. By making a cell an object, however, each cell could keep track of both states by introducing a variable for the “previous” state.</p>
<pre class="codesplit" data-code-language="javascript"> // What was its previous state?
this.previous = this.state;</pre>
<p>Suddenly, with these additional properties, how the cell is visualized can incorporate more information about what the state is doing. For example, what if each cell were colored based on whether its state has changed from one frame to another?</p>
<div data-type="example">
<h3 id="example-73-game-of-life-oop">Example 7.3: Game of Life OOP</h3>
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/13KF3ysx8" data-example-path="examples/07_ca/7_3_game_of_life_oop"></div>
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript">
show() {
stroke(0);
//{!2} If the cell is born, color it blue!
if (this.previous === 0 &#x26;&#x26; this.state == 1) {
fill(0, 0, 255);
} else if (this.state == 1) {
fill(0);
//{!2} If the cell dies, color it red!
} else if (this.previous == 1 &#x26;&#x26; this.state === 0) {
fill(255, 0, 0);
} else {
fill(255);
}
square(this.x, this.y, this.w);
}</pre>
<p>Not much else about the code (at least for our purposes here) has to change. The neighbors can still be counted the same way; the difference is that the neighbors <code>previous</code> states are counted and the cells new <code>state</code> property is updated.</p>
<pre class="codesplit" data-code-language="javascript">
for (let x = 1; x &#x3C; columns - 1; x++) {
for (let y = 1; y &#x3C; rows - 1; y++) {
let neighbors = 0;
for (let i = -1; i &#x3C;= 1; i++) {
for (let j = -1; j &#x3C;= 1; j++) {
//{!1 .bold} Use the previous state when counting neighbors
neighbors += board[x + i][y + j].previous;
}
}
neighbors -= board[x][y].previous;
//{!7} Set the cell's new state based on the neighbor count
if (board[x][y].state == 1 &#x26;&#x26; neighbors &#x3C; 2) {
board[x][y].state = 0;
} else if (board[x][y].state == 1 &#x26;&#x26; neighbors > 3) {
board[x][y].state = 0;
} else if (board[x][y].state == 0 &#x26;&#x26; neighbors == 3) {
board[x][y].state = 1;
}
}
}
}</pre>
<h2 id="79-variations-of-traditional-ca">7.9 Variations of Traditional CA</h2>
<p>Now that I have covered the basic concepts, algorithms, and programming strategies behind the most famous 1D and 2D cellular automata, its time to think about how you might take this foundation of code and build on it, developing creative applications of CAs in your own work. In this section, Ill talk through some ideas for expanding the features of the CA examples. Example answers to these exercises can be found on the book website.</p>
<p><strong><em>1) Non-rectangular Grids</em></strong>. Theres no particular reason why you should limit yourself to having your cells on a rectangular grid. What happens if you design a CA with another type of shape?</p>
<div data-type="exercise">
<h3 id="exercise-79">Exercise 7.9</h3>
<p>Create a CA using a grid of hexagons (as below), each with six neighbors.</p>
<figure>
<img src="images/07_ca/07_ca_33.png" alt="">
<figcaption></figcaption>
</figure>
</div>
<p><strong><em>2) Probabilistic</em></strong>. The rules of a CA dont necessarily have to define an exact outcome.</p>
<div data-type="exercise">
<h3 id="exercise-710">Exercise 7.10</h3>
<p>Rewrite the Game of Life rules as follows:</p>
<p>Overpopulation: If the cell has four or more alive neighbors, it has a 80% chance of dying. Loneliness: If the cell has one or fewer alive neighbors, it has a 60% chance of dying. Or make up your own!</p>
</div>
<p><strong><em>3) Continuous</em></strong>. This chapter has focused on at examples where number of possible cell states are finite (1 or 0) But what if the cells state was a floating point number between 0 and 1?</p>
<div data-type="exercise">
<h3 id="exercise-711">Exercise 7.11</h3>
<p>Adapt Wolfram elementary CA to have the state be a float. You could define rules such as, “If the state is greater than 0.5” or “…less than 0.2.”</p>
</div>
<p><strong><em>4) Image Processing</em></strong>. I briefly touched on this earlier, but many image-processing algorithms operate on CA-like rules. Blurring an image is creating a new pixel out of the average of a neighborhood of pixels. Simulations of ink dispersing on paper or water rippling over an image can be achieved with CA rules.</p>
<div data-type="exercise">
<h3 id="exercise-712">Exercise 7.12</h3>
<p>Create a CA in which a pixel is a cell and a color is its state.</p>
</div>
<p><strong><em>5) Historical</em></strong>. In the Game of Life object-oriented example, I used two variables to keep track of its state: current and previous. What if you use an array to keep track of a cells longer history? This relates to the idea of a “complex adaptive system,” one that has the ability to adapt and change its rules over time by learning from its history. (Stay tuned for more examples on this concept in Chapters 9 and 10.)</p>
<div data-type="exercise">
<h3 id="exercise-713">Exercise 7.13</h3>
<p>Visualize the Game of Life by coloring each cell according to how long its been alive or dead. Can you also use the cells history to inform the rules?</p>
</div>
<p><strong><em>6) Moving cells</em></strong>. In these basic examples, cells have a fixed position on a grid, but you could build a CA with cells that have no fixed position and instead move about the canvas.</p>
<div data-type="exercise">
<h3 id="exercise-714">Exercise 7.14</h3>
<p>Use CA rules in a flocking system. What if each boid had a state (that perhaps informs its steering behaviors) and its neighborhood changed from frame to frame as it moved closer to or further from other boids?</p>
</div>
<p><strong><em>7) Nesting</em></strong>. Another feature of complex systems is that they can be nested. Our world tends to work this way: a city is a complex system of people, a person is a complex system of organs, an organ is a complex system of cells, and so on and so forth.</p>
<div data-type="exercise">
<h3 id="exercise-715">Exercise 7.15</h3>
<p>Design a CA in which each cell itself is a smaller CA.</p>
</div>
<div data-type="project">
<h3 id="the-ecosystem-project-6">The Ecosystem Project</h3>
<p>Step 7 Exercise:</p>
<p>Incorporate cellular automata into your ecosystem. Some possibilities:</p>
<ul>
<li>Give each creature a state. How can that state drive their behavior? Taking inspiration from CA, how can that state change over time according to its neighbors states?</li>
<li>Consider the ecosystems world to be a CA. The creatures move from tile to tile. Each tile has a state—is it land? water? food?</li>
<li>Use a CA to generate a pattern for the design of a creature in your ecosystem.</li>
</ul>
</div>
</section>