noc-book-2/content/07_ca.html
2023-09-18 16:52:51 +00:00

773 lines
No EOL
72 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>
<div class="chapter-opening-quote">
<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.”</p>
<p>— Martin Gardner, <em>Scientific American </em>(October 1970)</p>
</blockquote>
</div>
<div class="chapter-opening-figure">
<figure>
<img src="images/07_ca/07_ca_1.png" alt="">
<figcaption></figcaption>
</figure>
<p><strong>TITLE</strong></p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>credit / url</p>
</div>
<p>In Chapter 5, I defined a <strong>complex system</strong> as a network of elements with short-range relationships, operating in parallel, that exhibit emergent behavior. I illustrated this definition by creating a flocking simulation and demonstrated how a complex system adds up to more than the sum of its parts. In this chapter, Im going to turn to developing other complex systems known as <strong>cellular automata</strong>.</p>
<p>In some respects, this shift may seem like a step backward. No longer will the individual elements of my systems be members of a physics world, driven by forces and vectors to move around the canvas. Instead, Ill build systems out of the simplest digital element possible: a single bit. This bit is called a <strong>cell</strong>, and its value (0 or 1) is called its <strong>state</strong>. Working with such simple elements will help illustrate the details behind how complex systems operate, and it will offer an opportunity to elaborate on some programming techniques that apply to code-based projects. Building cellular automata will also set the stage for the rest of the book, where Ill increasingly focus on systems and algorithms rather than vectors and motion—albeit systems and algorithms that I can and will apply to moving bodies.</p>
<h2 id="what-is-a-cellular-automaton">What Is a Cellular Automaton?</h2>
<p>A <strong>cellular automaton</strong> (<strong>cellular automata</strong> plural, or <strong>CA</strong> for short) is a model of a system of “cell” objects. A CAs "cell" does not refer to a biological cell (although you will see how CA can mimic life-like behavior and have applications in biology!). Instead, it represents a discrete unit in a grid, similar to a "cell" in a spreadsheet (e.g., Ex<em>cel</em>).</p>
<p>A cellular automaton has the following characteristics:</p>
<ul>
<li>The cells live on a <strong>grid</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>state</strong>, though a cells state can vary over time. The number of possible states 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>neighborhood</strong>. This can be defined in any number of ways, but its typically all the cells adjacent to that cell.</li>
</ul>
<p></p>
<p>Figure 7.1 illustrates these various CA characteristics.</p>
<figure>
<img src="images/07_ca/07_ca_2.png" alt="Figure 7.1: A 2D grid of cells, each with a state of “on” or “off.” A neighborhood is a subsection of the large grid, usually consisting of all the cells adjacent to a given cell (circled).">
<figcaption>Figure 7.1: A 2D grid of cells, each with a state of “on” or “off.” A neighborhood is a subsection of the large grid, usually consisting of all the cells adjacent to a given cell (circled).</figcaption>
</figure>
<p>The idea that an objects state can vary over time is an important development. So far in this book, the objects (movers, particles, vehicles, boids, bodies) have 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. Now, with cellular automata, youll see how an objects state can change based on a system of rules.</p>
<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. You read that right: robots that can build copies of themselves.</p>
<p>Von Neumanns original “cells” had 29 possible states, so perhaps the idea of self-replicating robots is bit too complex of a starting point. 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 whats probably the most famous cellular automaton, the “Game of Life,” which Ill discuss in detail later in the chapter.</p>
<p>Perhaps the most significant (and lengthy) scientific work studying cellular automata arrived in 2002: Stephen Wolframs 1,280-page <a href="https://www.wolframscience.com/nks/"><em>A New Kind of Science</em></a>. Available in its entirety for free online, Wolframs book discusses how CA arent simply neat tricks, but are relevant to the study of biology, chemistry, physics, and all branches of science. In a moment, Ill turn to building a simulation of Wolframs work, although Ill barely scratch the surface of the theories Wolfram outlines—my focus will be on the code implementation, not the philosophical implications. If the examples spark your curiosity, youll find plenty more to read about in Wolframs book as well as his ongoing research in this field entitled <a href="https://www.wolframphysics.org/"><em>An Approach to the Fundamental Theory of Physics</em></a>.</p>
<h2 id="elementary-cellular-automata">Elementary Cellular Automata</h2>
<p>Whats the simplest cellular automaton you can imagine? For Wolfram, an elementary CA has three key elements.</p>
<p>1) <strong>Grid</strong>. The simplest grid would be one-dimensional: a line of cells (Figure 7.2).</p>
<figure>
<img src="images/07_ca/07_ca_3.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>States</strong>. The simplest set of states (beyond having only one state) would be two states: 0 or 1 (Figure 7.3). Perhaps the initial states are set randomly.</p>
<figure>
<img src="images/07_ca/07_ca_4.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>Neighborhood</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 (Figure 7.4). Ill have to decide what I want to do with the cells on the left and right edges, since those only have one neighbor each, but this is something I can sort out later.</p>
<figure>
<img src="images/07_ca/07_ca_5.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>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 havent yet discussed perhaps the most important detail of how cellular automata work: change over time.</p>
<p>Im 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 <strong>generations</strong>. 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.</p>
<figure>
<img src="images/07_ca/07_ca_6.png" alt="Figure 7.5: The states for generation 1 are calculated using the states of the cells from generation 0.">
<figcaption>Figure 7.5: The states for generation 1 are calculated using the states of the cells from generation 0.</figcaption>
</figure>
<p>Lets say theres an individual cell in the CA called <span data-type="equation">\text{cell}</span>. The formula for calculating the cells state 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 (Figure 7.6).</p>
<figure>
<img src="images/07_ca/07_ca_7.png" alt="Figure 7.6 The state of a cell at generation 1 is a function of the previous generations neighborhood.">
<figcaption>Figure 7.6 The state of a cell at generation 1 is a function of the previous generations neighborhood.</figcaption>
</figure>
<p>There are many ways to compute a cells state from its neighbors states. 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 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 <strong>ruleset</strong>.</p>
<p>This approach might seem ridiculous at first—wouldnt there be way too many possibilities for it to be practical? Well, lets 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 theyre 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.</p>
<figure>
<img src="images/07_ca/07_ca_8.png" alt="Figure 7.7: Counting with 3 bits in binary, or the eight possible configurations of a three-cell neighborhood">
<figcaption>Figure 7.7: Counting with 3 bits in binary, or the eight possible configurations of a three-cell neighborhood</figcaption>
</figure>
<p>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.</p>
<figure>
<img src="images/07_ca/07_ca_9.png" alt="Figure 7.8: A ruleset shows the outcome for each possible configuration of three cells.">
<figcaption>Figure 7.8: A ruleset shows the outcome for each possible configuration of three cells.</figcaption>
</figure>
<p>Keep in mind that unlike the sum or averaging methods, the rulesets in elementary CA dont follow any arithmetic logic—theyre 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—its up to you to define the rule).</p>
<p>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, Ill use a one-dimensional CA of nine cells so that the middle is easy to pick out.</p>
<figure>
<img src="images/07_ca/07_ca_10.png" alt="Figure 7.9: Generation 0 in a Wolfram CA, with the center cell set to 1">
<figcaption>Figure 7.9: Generation 0 in a Wolfram CA, with the center cell set to 1</figcaption>
</figure>
<p>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.</p>
<figure>
<img src="images/07_ca/07_ca_11.png" alt="Figure 7.10: Determining a state for generation 1 using the CA rule set">
<figcaption>Figure 7.10: Determining a state for generation 1 using the CA rule set</figcaption>
</figure>
<p>Now for a slight change: instead of representing the cells states with 0s and 1s, Ill 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, Im 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.</p>
<figure>
<img src="images/07_ca/07_ca_12.png" alt="Figure 7.11: A white cell indicates 0, and a black cell indicates 1.">
<figcaption>Figure 7.11: A white cell indicates 0, and a black cell indicates 1.</figcaption>
</figure>
<p>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, Ill also start stacking the generations, with each new generation appearing below the previous one, as shown in Figure 7.12.</p>
<figure>
<img src="images/07_ca/07_ca_13.png" alt="Figure 7.12 Translating a grid of 0s and 1s to white and black squares.">
<figcaption>Figure 7.12 Translating a grid of 0s and 1s to white and black squares.</figcaption>
</figure>
<p>The low-resolution shape that emerges in Figure 7.12 is the <strong>Sierpiński triangle</strong>. Named after the Polish mathematician Wacław Sierpiński, its a famous example of a <strong>fractal</strong>. Ill examine fractals more closely in the next chapter, but briefly, theyre 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.</p>
<figure>
<img src="images/07_ca/07_ca_14.png" alt="Figure 7.13: Wolfram elementary CA, rule 90 ">
<figcaption>Figure 7.13: Wolfram elementary CA, rule 90 </figcaption>
</figure>
<p>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.</p>
<figure>
<img src="images/07_ca/07_ca_15.png" alt="Figure 7.14: Wolfram elementary CA, rule 90, at higher resolution">
<figcaption>Figure 7.14: Wolfram elementary CA, rule 90, at higher resolution</figcaption>
</figure>
<p>Take a moment to let the enormity of what youve 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.</p>
<p>Of course, this particular result didnt 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 doesnt 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.</p>
<h3 id="defining-rulesets">Defining Rulesets</h3>
<p>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 rulesets inputs, and they remain constant from ruleset to ruleset. Its 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.</p>
<figure>
<img src="images/07_ca/07_ca_16.png" alt="Figure 7.15 Representing the same ruleset (from Figure 7.8) with white and black squares">
<figcaption>Figure 7.15 Representing the same ruleset (from Figure 7.8) with white and black squares</figcaption>
</figure>
<p>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 Wolframs 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.</p>
<figure>
<img src="images/07_ca/07_ca_17.png" alt="Figure 7.16: How the Wolfram website represents a ruleset">
<figcaption>Figure 7.16: How the Wolfram website represents a ruleset</figcaption>
</figure>
<p>Ive said that each ruleset can essentially be boiled down to an 8-bit number, and how many combinations of eight 0s and 1s are there? Exactly <span data-type="equation">2^8</span>, or 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 in decimal, or 00000000 to 11111111 in binary.</p>
<p>The ruleset in Figure 7.16 could be called “Rule 01011010,” but Wolfram instead refers to it as “Rule 90.” Where does 90 come from? To make ruleset naming even more concise, Wolfram uses decimal (or “base 10”) representations rather than binary. 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 its named “Rule 90.”</p>
<p>Since there are 256 possible combinations of eight 0s and 1s, there are also 256 unique rulesets. Lets check out another one. How about rule 11011110, or more commonly, rule 222. Figure 7.17 shows how it looks.</p>
<figure>
<img src="images/07_ca/07_ca_18.png" alt="Figure 7.17: Wolfram elementary CA, rule 222 ">
<figcaption>Figure 7.17: Wolfram elementary CA, rule 222 </figcaption>
</figure>
<div class="half-width-right">
<figure>
<img src="images/07_ca/07_ca_19.jpg" alt="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 ">
<figcaption>Figure 7.18: A textile cone snail (<em>Conus textile</em>), Cod Hole, Great Barrier Reef, Australia, 7 August 2005. Photographer: Richard Ling richard@research.canon.com.au </figcaption>
</figure>
</div>
<p>The result is a recognizable shape, though it certainly isnt as exciting as the Sierpiński triangle. As I said earlier, most of the 256 elementary rulesets dont produce compelling outcomes. However, its 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. For example, see Figure 7.18, a snail shell resembling Wolframs rule 30. This demonstrates how valuable CAs can be in simulation and pattern generation.</p>
<p>Before I go too far down the road of characterizing the results of different rulesets, though, lets look at how to build a p5.js sketch that generates and visualizes a Wolfram elementary CA.</p>
<h3 id="programming-an-elementary-ca">Programming an Elementary CA</h3>
<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, 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:</p>
<pre class="codesplit" data-code-language="javascript">class Cell {
}</pre>
<p>However, this isnt the road I want to travel down right now. Later in this chapter, Ill 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? Why not describe a generation of a one-dimensional CA using an array?</p>
<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>This array corresponds to the row of cells shown in Figure 7.19.</p>
<figure>
<img src="images/07_ca/07_ca_20.png" alt="Figure 7.19: One generation of a 1D cellular automata">
<figcaption>Figure 7.19: One generation of a 1D cellular automata</figcaption>
</figure>
<p>To show that array, I check if each element is a 0 or a 1, choose a fill color accordingly, and draw a rectangle.</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>The array describes the cell states in the “current” generation. Now I need a mechanism to compute the next generations states. Heres the pseudocode describing what I want to achieve.</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 pseudocode may suggest writing 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 there are a few issues to resolve. For one, Im 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, so my work isnt done, but what Im aiming for here is modularity. I want a <code>for</code> loop that provides a basic framework for managing any CA, regardless of the specific ruleset. If I want to try different rulesets, I shouldnt have to touch that framework at all; I can just rewrite the <code>rules()</code> function to compute the new states differently.</p>
<p>So I still have the <code>rules()</code> function to write, but more important, Ive made one minor blunder and one major blunder in the <code>for</code> loop itself. Lets examine the code more closely.</p>
<p>First, 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>In fact, its not <em>quite</em> that easy. What have I done wrong? Think about how the code will execute. The first time through the loop, cell index <code>i</code> equals <code>0</code>. The code wants to look at cell 0s neighbors. Left is <code>i - 1</code> or <code>-1</code>. Oops! An array by definition doesnt have an element with an index of <code>-1</code>. It starts with <code>0</code>.</p>
<p>I alluded to this problem of edge cases earlier in the chapter and said I could worry about it later. Well, later is now. How should I handle the cell on the edge that doesnt have a neighbor to both its left and its right? Here are three possible solutions to this problem:</p>
<ol>
<li><strong>Edges remain constant.</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>Edges wrap around.</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 edge and vice versa. This can create the appearance of an infinite grid and is probably the most commonly used solution.</li>
<li><strong>Edges have different neighborhoods and rules.</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 it 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, and identifying it is absolutely fundamental to the techniques behind programming CA simulations. The bug is subtle and wont trigger an error; the CA just wont perform correctly. It all lies in this line of code:</p>
<pre class="codesplit" data-code-language="javascript"> cells[i] = newstate;</pre>
<p>This may seem perfectly innocent. After all, once Ive computed a new state value, I want to assign the cell its new state. But think about the next iteration of the <code>for</code> loop. Lets say the new state for cell 5 was just computed, and the loop is moving on to cell 6. What happens next?</p>
<ul>
<li><em>Cell 6, generation 0 = some state, 0 or 1.</em></li>
<li><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>.</li>
</ul>
<p>A cells new state is a function of the previous neighbor states, so in this case, the value of cell 5 at generation 0 is needed in order to calculate cell 6s new state at generation 1. Have I saved cell 5s value at generation 0? No, I have not. Remember, this line of code was just executed when <code>i</code> equaled<em> </em><code>5</code>:</p>
<pre class="codesplit" data-code-language="javascript"> cells[i] = newstate;</pre>
<p>Once this happens, cell 5s state at generation 0 is gone; <code>cells[5]</code> is now storing the value for generation 1. I cant overwrite the values in the array while Im processing the array, because I need those values to calculate the new values!</p>
<p>A solution to this problem is to have two arrays, one to store the current generations states and one for the next generations states. To save myself the step of re-initializing an array, Ill use JavaScripts <code>slice()</code> array method, which makes a copy of an array.</p>
<pre class="codesplit" data-code-language="javascript">//{!1 .bold} Create 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} Save the new state in the new array.
newcells[i] = newstate;
}</pre>
<p>Once the current generations array of values has been completely processed, the <code>cells</code> variable can be 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, but I still need to define <code>rules()</code>, 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).</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>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'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!</p>
<figure>
<img src="images/07_ca/07_ca_21.png" alt="Figure 7.20 A visual representation of a Wolfram ruleset with numeric encoding ">
<figcaption>Figure 7.20 A 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 I can say, for example:</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, so the new state should be equal to the first value in the <code>ruleset</code> array. Duplicating this strategy for all eight possibilities looks like this:</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 writing the <code>rules()</code> function this way because it describes line by line exactly whats happening for each neighborhood configuration. However, its not a great solution. After all, what if a CA has four possible states (0 through 3) instead of two? Suddenly there are 64 possible neighborhood configurations. And with 10 possible states, 1,000 configurations. And just imagine programming von Neumanns 29 possible states. Id be stuck typing out thousands upon thousands of <code>else if</code> statements!</p>
<p>Another solution, though not quite as transparent, 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 JavaScripts built-in <code>parseInt()</code> function:</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 2 in the second argument 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. 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 say the neighborhood being tested is 111. The resulting state should be 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 converts to the decimal number 7. But I dont want <code>ruleset[7]</code>; I want <code>ruleset[0]</code>. For this to work, I need to invert the index before looking up the state in the <code>ruleset</code> array:</p>
<pre class="codesplit" data-code-language="javascript"> // Invert the index so that 0 becomes 7, 1 becomes 6, and so on...
return ruleset[7 - index];
</pre>
<p>I now have everything needed to compute the generations for a Wolfram elementary CA. Heres how the code looks 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;
}
//{!5} 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[7 - index];
}</pre>
<p>This is great, but theres still one more missing piece: what good is a cellular automaton if you cant see it?</p>
<h3 id="drawing-an-elementary-ca">Drawing an Elementary CA</h3>
<p>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 thats black (for state 1) or white (for state 0), as in Figure 7.21. Before implementing this particular visualization, however, Id like to point out two things.</p>
<figure>
<img src="images/07_ca/07_ca_22.png" alt="Figure 7.21 Ruleset 90 visualized as a stack of generations">
<figcaption>Figure 7.21 Ruleset 90 visualized as a stack of generations</figcaption>
</figure>
<p>First, 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 youre 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.</p>
<p>Second, the fact that a one-dimensional CA is visualized with a two-dimensional image can be misleading. Its very important to remember that this is <em>not</em> a 2D CA. Im 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, Ill show you an actual 2D CA (the Game of Life), and Ill cover how to visualize such a system.</p>
<p>The good news is that drawing an elementary CA isnt particularly difficult. Ill begin by looking at how to render a single generation. Lets say each cell should be a 10 by 10 square.</p>
<pre class="codesplit" data-code-language="javascript">let w = 10;</pre>
<p>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 <code>cells</code> array in <code>setup()</code>.</p>
<pre class="codesplit" data-code-language="javascript">//{!1} How many cells fit across given a certain width
let cells = new Array(floor(width / w));</pre>
<p>Drawing the cells now involves iterating over the array and drawing a square based on the state of each cell.</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>There are two things off about this code. First, when multiplying the state by 255, cells with a state of 1 will be white and those with 0 will be black, which is the opposite of what I originally intended! While this is of course “ok” since the color representation is arbitrary, Ill correct this in the full example.</p>
<p>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, Ill also need to calculate a y-position based on the generation number. I can accomplish this by adding a <code>generation</code> 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"><img src="examples/07_ca/7_1_elementary_wolfram_ca/screenshot.png"></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 draw the cells 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++;
}
//{!5} 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[7 - index];
}</pre>
<p>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 isnt suitable for all cases (what if I want multicolored cells!), but it provides a performance boost in this simple case. Ill also note that if the size of each cell were 1 pixel, I wouldnt want to use p5.jss <code>square()</code> function, but rather access the pixel array directly.</p>
<p>This example does, however, have a different optimization problem. It continues to draw generation after generation, extending beyond the bottom of the canvas. The code on the books website includes a simple stopping condition, but you might come up with other approaches to address this issue (some mentioned in the exercises below).</p>
<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 cells in generation 0 with random states.</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="wolfram-classification">Wolfram Classification</h2>
<p>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.</p>
<p><strong>Class 1: Uniformity.</strong> Class 1 CAs end up, after some number of generations, with every cell constant. This isnt 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.</p>
<figure>
<img src="images/07_ca/07_ca_23.png" alt="Figure 7.22: Rule 222 ">
<figcaption>Figure 7.22: Rule 222 </figcaption>
</figure>
<p><strong>Class 2: Repetition.</strong> Like class 1 CAs, class 2 CAs remain stable, but the cell states arent constant. Instead, they oscillate in some repeating pattern of 0s and 1s. In rule 190 (Figure 7.23), each cell follows the sequence <code>11101110111011101110</code>.</p>
<figure>
<img src="images/07_ca/07_ca_24.png" alt="Figure 7.23: Rule 190 ">
<figcaption>Figure 7.23: Rule 190 </figcaption>
</figure>
<p><strong>Class 3: Random.</strong> 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 Wolframs 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.</p>
<figure>
<img src="images/07_ca/07_ca_25.png" alt="Figure 7.24: Rule 30 ">
<figcaption>Figure 7.24: Rule 30 </figcaption>
</figure>
<p><strong>Class 4: Complexity.</strong> 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. If a class 3 CA wowed you, then a class 4 like Rule 110 (Figure 7.25) should really blow your mind!</p>
<figure>
<img src="images/07_ca/07_ca_26.png" alt="Figure 7.25: Rule 110 ">
<figcaption>Figure 7.25: Rule 110 </figcaption>
</figure>
<p>In Chapter 5, I introduced the concept of a "complex system" and used the example of flocking to demonstrate how simple rules can result in emergent behaviors. Class 4 cellular automata remarkably exhibit the characteristics of complex systems and are the key to simulating phenomena such as forest fires, traffic patterns, and the spread of diseases. Research and applications of CA consistently emphasize the important of class 4 as the bridge between CA and nature.</p>
<h2 id="the-game-of-life">The Game of Life</h2>
<p>The next step to take is to move from a one-dimensional CA to a two-dimensional one: the Game of Life. This will introduce some additional complexity—each cell will have a bigger neighborhood—but with the complexity comes a wider 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, and so on), its still valuable to practice building the system with code for a few reasons.</p>
<p>For one, the Game of Life provides a good opportunity to practice skills with two-dimensional arrays, nested loops, and more. Perhaps more important, however, this cellular automatons core principles are tied directly to a core goal of this book: simulating the natural world with code. Though you may want to avoid using the Game of Life in a project without a great deal of thought or care, its 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>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 didnt use this terminology, it should have all those properties of a <em>complex system</em>.</p>
<h3 id="the-rules-of-the-game">The Rules of the Game</h3>
<p>Lets look at how the Game of Life works. It wont take up too much time or space, since I can build on everything from Wolframs elementary CA. First, instead of a line of cells, theres 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.”</p>
<p>Since the Game of Life is two-dimensional, each cells 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.</p>
<figure>
<img src="images/07_ca/07_ca_27.png" alt="Figure 7.26: A two-dimensional CA showing the neighborhood of 9 cells.">
<figcaption>Figure 7.26: A two-dimensional CA showing the neighborhood of 9 cells.</figcaption>
</figure>
<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: is the neighborhood overpopulated with life, surrounded by death, or just right? Here are the rules of life:</p>
<ol>
<li><strong>Death.</strong> If a cell is alive (state = 1), it will die (state becomes 0) under the following circumstances:
<ul>
<li><strong>Overpopulation.</strong> If the cell has four or more living neighbors, it dies.</li>
<li><strong>Loneliness.</strong> If the cell has one or fewer living neighbors, it dies.</li>
</ul>
</li>
<li><strong>Birth.</strong> If a cell is dead (state = 0), it will come to life (state becomes 1) if it has exactly three living neighbors (no more, no less).</li>
<li><strong>Stasis.</strong> In all other cases, the cells state doesnt change. There are two possible scenarios:
<ul>
<li><strong>Staying Alive.</strong> If a cell is alive and has exactly two or three live neighbors, it stays alive.</li>
<li><strong>Staying Dead.</strong> If a cell is dead and has anything other than three live neighbors, it stays dead.</li>
</ul>
</li>
</ol>
<p>Figure 7.27 shows a few examples of these rules. Focus on what happens to the center cell.</p>
<figure>
<img src="images/07_ca/07_ca_28.png" alt="Figure 7.27: Example scenarios for “death” and “birth” in the Game of Life">
<figcaption>Figure 7.27: Example scenarios for “death” and “birth” in the Game of Life</figcaption>
</figure>
<p>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.</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, the patterns shown in Figure 7.28 remain static and never change.</p>
<figure>
<img src="images/07_ca/07_ca_29.png" alt="Figure 7.28: Initial configurations of cells that remain stable">
<figcaption>Figure 7.28: Initial configurations of cells that remain stable</figcaption>
</figure>
<p>The patterns in Figure 7.29 oscillate back and forth between two states.</p>
<figure>
<img src="images/07_ca/07_ca_30.png" alt="Figure 7.29: Initial configurations of cells that oscillate between two states">
<figcaption>Figure 7.29: Initial configurations of cells that oscillate between two states</figcaption>
</figure>
<p>And the patterns in Figure 7.31 appear to move about the grid from generation to generation. The cells themselves dont actually move, but you see the illusion of motion in the result of adjacent cells turning on and off.</p>
<figure>
<img src="images/07_ca/07_ca_31.png" alt="Figure 7.30: Initial configurations of cells that appear to move">
<figcaption>Figure 7.30: Initial configurations of cells that appear to move</figcaption>
</figure>
<p>If youre 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 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>
<h3 id="the-implementation">The Implementation</h3>
<p>I already have a lot of what I need to implement the Game of Life in p5.js: mostly, I just need to extend the code from the Wolfram CA sketch to two dimensions. I previously used a one-dimensional array to store the list of cell states. Now 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>Just as before, I need an extra 2D array to receive the next generations states so I dont overwrite the current generations 2 array as Im processing it. Rather than write all the steps to create a 2D array in both <code>setup()</code> and <code>draw()</code>, however, 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>Now 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>
<div class="half-width-right">
<figure>
<img src="images/07_ca/07_ca_32.png" alt="Figure 7.31: The index values for the neighborhood of cells.">
<figcaption>Figure 7.31: The index values for the neighborhood of cells.</figcaption>
</figure>
</div>
<p>Next, I need to sort out how to actually calculate each cells new state. For that, 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.31, 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. 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>Much like with the Wolfram CA, I find myself writing out a bunch of <code>if</code> statements. This is another situation where, for teaching purposes, its useful and clear way to write the code this way, explicitly stating every step (each time a neighbor has a state of 1, the counter increases). Nevertheless, its a bit silly to say, “If the cell state equals 1, add 1e to a counter” when I could instead just say, “Add every 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 3 by 3 grid, I can introduce another nested loop to computer the sum more efficiently.</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 current cell itself doesnt 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 <code>0</code>, 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 I know the total number of live neighbors, I 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>Now I just need to draw the board. Ill draw a square for each spot: white for off, black for on.</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"><img src="examples/07_ca/7_2_game_of_life/screenshot.png"></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>
<p>In this example, Im introducing yet another method for drawing the squares based on a cells state. Remember, multiplying the cells state by 255 gives a white fill color for “on” and black for “off.” To invert this, I start with 255 and subtract cells state multiplied by 255: black for “on” and white for “off.”</p>
<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, either by hardcoding initial cell states or by drawing directly to the canvas. Use the simulation to explore some of the known Game of Life 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>The code in Example 7.2 is convenient, but it isnt 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="object-oriented-cells">Object-Oriented Cells</h2>
<p>Over the course of this book, Ive built examples of systems of <em>objects</em> that have properties and move about the canvas. 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 its state, a single 0 or 1. However, there are plenty of ways to further develop CA systems beyond the simple models discussed here, and often these may 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 each cell as an object, rather than as a single 0 or 1 in an array. In a Game of Life simulation, for example, Ill no longer want to initialize each cell like this:</p>
<pre class="codesplit" data-code-language="javascript"> board[i][j] = floor(random(2));
</pre>
<p>Instead, I want something like this:</p>
<pre class="codesplit" data-code-language="javascript"> board[i][j] = new Cell(floor(random(2));
</pre>
<p>Here, <code>Cell</code> is a new class that Ill 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 its 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 arrays 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-object-oriented-game-of-life">Example 7.3: Object-Oriented Game of Life</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"><img src="examples/07_ca/7_3_game_of_life_oop/screenshot.png"></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 my 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. It also might be beneficial to encapsulate this logic into a <code>calculateState()</code> method that takes the <code>board</code> as an argument. Ill leave that as an exercise for the reader.</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>
<p>By transforming the cells into objects, numerous possibilities emerge for enhancing the cells properties and behaviors. For example, what if each cell had a <code>lifespan</code> property that increments with each cycle and influences its color or shape over time? Or imagine if a cell had a <code>terrain</code> property that could be <code>land</code>, <code>water</code>, <code>mountain</code>, or <code>forest</code>. How could a two-dimensional CA integrate into a tile-based strategy game or other context?</p>
<h2 id="variations-on-traditional-ca">Variations on Traditional CA</h2>
<p>Now that Ive 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 a CA. Example answers to these exercises can be found on the book website.</p>
<p><strong>1) Non-rectangular grids</strong>. Theres no particular reason why you should limit yourself to having your cells in 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>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/_PMAwxPtZp" data-example-path="examples/07_ca/exercise_7_9_hexagon_ca"><img src="examples/07_ca/exercise_7_9_hexagon_ca/screenshot.png"></div>
<figcaption></figcaption>
</figure>
<p>As a hint, you can use polar to cartesian coordinate conversion to find the 6 vertices of a hexagon!</p>
<pre class="codesplit" data-code-language="javascript">function drawHexagon(x, y, r) {
push();
translate(x, y);
stroke(0);
beginShape();
for (let angle = 0; angle &#x3C; <span class="blank">TWO_PI</span>; angle += <span class="blank">PI / 3</span>) {
let xoff = <span class="blank">cos(angle) * r</span>;
let yoff = <span class="blank">sin(angle) * r</span>;
vertex(xoff, yoff);
}
endShape(CLOSE);
pop();
}</pre>
</div>
<p><strong>2) Probabilistic</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>
<ul>
<li>Overpopulation: If the cell has four or more living neighbors, it has an 80 percent chance of dying.</li>
<li>Loneliness: If the cell has one or fewer living neighbors, it has a 60 percent chance of dying.</li>
</ul>
<p>Or make up your own probabilistic rules!</p>
</div>
<p><strong>3) Continuous</strong>. This chapter has focused on examples with a finite number of discrete cell states—either 0 or 1. What if the cells state could be any 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>4) Image processing</strong>. I briefly touched on this earlier, but many image-processing algorithms operate on CA-like rules. For example, blurring an image involves 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 also be achieved with CA rules.</p>
<div data-type="exercise">
<h3 id="exercise-712">Exercise 7.12</h3>
<p>Create a CA in which each pixel is a cell and the pixels color is its state.</p>
</div>
<p><strong>5) Historical</strong>. In the object-oriented Game of Life example, I used two variables to keep track of a cells current and previous state. What if you use an array to keep track of a cells state history over a longer period? This relates to the idea of a “complex adaptive system,” one that has the ability to change its rules over time by learning from its history. (Stay tuned for more 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>6)</strong> <strong>Moving cells</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 farther from other boids?</p>
</div>
<p><strong>7) Nesting</strong>. As discussed in Chapter 5, a feature of complex systems is that they can be nested. 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. How could this be applied to CA?</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, and 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>