<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>
<p>In this chapter, I’m 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>
<h2id="71-what-is-a-cellular-automaton">7.1 What Is a Cellular Automaton?</h2>
<p>First, let’s 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, I’ll 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. I’ve alluded to the possibility that these entities can change over time (for example, the weights of steering “desires” can vary), but I haven’t 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>
<li>The cells live on a <strong><em>grid</em></strong>. (I’ll 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>
<imgsrc="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>
<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. That’s 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 Neumann’s 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 Neumann’s 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 Wolfram’s 1,280-page <em>A New Kind of Science</em>. Available in its entirety for free online, Wolfram’s 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, you’ll find plenty more to read about in his book.</p>
<p>The examples in this chapter will begin with a simulation of Wolfram’s work. To understand Wolfram’s elementary CA, it’s best to begin with the question: “What is the simplest cellular automaton you can imagine?” What’s 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>Let’s build Wolfram’s elementary CA from scratch. Concepts first, then code. I’ll begin with the three key elements of a CA.</p>
<imgsrc="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>
<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>
<p>Let’s begin with a line of cells, each with an initial state (let’s say set at random), and each with two neighbors. I’ll 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>
<p>I haven’t yet discussed, however, what is perhaps the most important detail of how cellular automata work—<em>time</em>. I’m 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>
<p>Let’s say there is an individual cell in the CA called <spandata-type="equation">\text{cell}</span>. The formula for calculating the <spandata-type="equation">\text{cell state}</span> at any given time <spandata-type="equation">t</span> (<spandata-type="equation">\text{cell}_t</span>) is as follows:</p>
<p>In other words, a cell’s new state is a function of all the states in the cell’s neighborhood at the previous generation (time <spandata-type="equation">t-1</span>). A new state value is calculated by looking at the previous generation’s neighbor states.</p>
<p>Now, in the world of cellular automata, there are many ways to compute a cell’s state from a neighborhood of cells. Consider blurring an image. (Guess what? Image processing works with CA-like rules!) A pixel’s new state (its color) is the average of its neighbors’ colors. Similarly, a cell’s new state could be the sum of all of its neighbors’ states. However, in Wolfram’s elementary CA, the process 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—wouldn’t there be way too many possibilities for this to be practical? Let’s 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, you’ll notice that three cells define a 3-bit number, and how high can you count with 3 bits? Up to 8. Let’s have a look.</p>
<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, let’s use 1D CA of 9 cells so that the middle is easy to pick out!</p>
<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! Let’s move past just one generation to stacking, with each new generation appearing below the previous one.</p>
<p>The low-resolution shape depicted above is the “Sierpiński triangle.” Named after the Polish mathematician Wacław Sierpiński, it’s a fractal pattern that I’ll examine more closely in the next chapter. That’s 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. Let’s look at it again, only with each cell a single pixel wide so that the resolution is much higher.</p>
<p>This particular result didn’t 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>
<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 Wolfram’s website, you’ll see the convention of displayed any given rule as in Figure 7.16.</p>
<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. Let’s 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. Let’s try looking at the results of another ruleset, how about “Rule 11011110” or more commonly “Rule 222.”</p>
<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, it’s 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, let’s look at how to build a p5.js sketch that generates the Wolfram CA and visualizes it onscreen.</p>
<h2id="73-how-to-program-an-elementary-ca">7.3 How to Program an Elementary CA</h2>
<p>You may be thinking: “OK, I’ve got this cell thing. And the cell thing has some properties, like a state, what generation it’s on, who its neighbors are, 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>
<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, it’s easier to work with a more elementary data structure. After all, what is an elementary CA but a list of 0s and 1s? Certainly, a generation of a one-dimensional CA could be described using an array:</p>
<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. Let’s think about the pseudocode of what I am hoping to do here.</p>
<p>I’m fairly close to getting this right, but I’ve made one minor blunder and one major blunder in the above code. Let’s examine this code more closely.</p>
<p>Notice how easy it is to look at a cell’s 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>I’m also farming out the calculation of a new state value to some function called <code>rules()</code>. Obviously, I’m going to have to write this function, but the point I’m 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 don’t 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? Let’s talk through how the code will execute. First, look at cell index <code>i</code> when it equals 0. Now let’s look at 0’s 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 doesn’t have a neighbor to both its left and its right? Here are three possible solutions to this problem:</p>
<li><strong><em>Edges remain constant.</em></strong> This is perhaps the simplest solution. Don’t 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, it’s going to be a lot of extra lines of code for little benefit.</li>
<p>To make the code easiest to read and understand right now, I’ll 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>
<p>There’s one more problem to fix before this is done. It’s subtle and you won’t get an error; the CA just won’t 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>
<p>This seems like a perfectly innocent line. After all, I’ve computed the new state value and I’m assigning the cell its new state. But in the next iteration, there will be a massive bug. Let’s 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 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 #6’s new state at generation 1? A cell’s new state is a function of the previous neighbor states. Have I savedcell #5’s value at generation 0? Remember, this line of code was just executed for <em>i = 5</em>.</p>
<p>Once this happens, cell #5’s 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, I’ll use the JavaScript array function <code>slice()</code> which makes a copy of an array.</p>
<preclass="codesplit"data-code-language="javascript">//{!1 .bold} Another array to store the states for the next generation.
<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 generation’s values.</p>
<p>I’m 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>
<preclass="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 I’d like to start with a long-winded one that will hopefully provide a clear illustration of what is happening.</p>
<p>Let’s 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. Let’s bring back the visual representation of a ruleset and add back the numeric encoding.</p>
<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>
<p>I like the example written as above because it describes line by line exactly what is happening for each neighborhood configuration. However, it’s 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 don’t 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>
<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>
<p>The binary number “111” converted to a decimal number is 7. But I don’t 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>
<h2id="74-drawing-an-elementary-ca">7.4 Drawing an Elementary CA</h2>
<p>What’s 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>
<p>One, this visual interpretation of the data is completely literal. It’s useful for demonstrating the algorithms and results of Wolfram’s elementary CA, but it shouldn’t necessarily drive your own personal work. It’s rather unlikely that you 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. It’s 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 I’ll cover how to visualized such a system.</p>
<p>The good news is that drawing the CA is not particularly difficult. Let’s begin by looking at how to render a single generation. Let’s 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>
<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>
<preclass="codesplit"data-code-language="javascript">for (let i = 0; i < cells.length; i++) {
//{!2} By multiplying the cell state the result is 0 or 255
<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.) I’ll also note that if the size of each cell were 1 pixel I would not want to use p5.js’s <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, I’ll 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>
<p>Visualize the CA in a non-traditional way. Break all the rules you can; don’t feel tied to using squares on a perfect grid with black and white.</p>
<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, you’ll need to store a history of generations, always adding a new one and deleting the oldest one in each frame.</p>
<p>Before moving on to looking at CA in two dimensions, it’s worth taking a brief look at Wolfram’s 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>
<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>
<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>
<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 Wolfram’s 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>
<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>
<h2id="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 Conway’s 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 Conway’s goals as follows:</p>
<blockquotedata-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>
<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 didn’t use this terminology, it should have all those properties of a <em>complex system</em>.</p>
<p>Let’s look at how the Game of Life works. It won’t take up too much time or space, since I ca build on everything from the Wolfram Elementary CA.</p>
<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>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>
<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>
<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>
<p>And there are also patterns that from generation to generation appear to move about the grid. (It’s important to note that the cells themselves aren’t actually moving, rather you see the illusion of motion in the result as the cells turn on and off.)</p>
<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 CA’s initial state and watch it run at varying speeds. Two examples such examples are:</p>
<li><ahref="http://www.playfulinvention.com/emergence/">Exploring Emergence by Mitchel Resnick and Brian Silverman, Lifelong Kindergarten Group, MIT Media Laboratory</a></li>
<li><ahref="https://sklise.github.io/conways-game-of-life/">Conway’s Game of Life by Steven Klise</a></li>
<p>For the example I’ll build in the next section, I’ll focus on randomly initializing the states for each cell.</p>
<h2id="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, I’ll use a two-dimensional array.</p>
<p>And to compute the next generation, just as before, I need to make sure I don’t 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>it’s worth writing a function that returns a 2D array based on the number of columns and rows. I’ll also initialize each element of the array to <code>0</code> so that it isn’t filled with <code>undefined.</code></p>
<p>OK. Before I can sort out how to actually calculate the new state, I need to determine how to reference the cell’s neighbors. In the case of a 1D CA, this was simple: if a cell index was <code>i</code>, its neighbors were <code>i-1</code> and <code>i+1</code>. Here each cell doesn’t have a single index, but rather a column and row index: <code>i,j</code>. As shown in Figure 7.27, 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, I’ll have the total of live neighbors.</p>
<preclass="codesplit"data-code-language="javascript">let sum = 0;
<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, it’s 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>
<preclass="codesplit"data-code-language="javascript">let sum = 0;
//{!2} Using k and l as the counters since i and j are already used!
<p>Of course, I’ve 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>
<preclass="codesplit"data-code-language="javascript">// Whoops! Subtract the cell’s state!
<p>Finally, once we know the total number of live neighbors, we can decide what the cell’s new state should be according to the rules: birth, death, or stasis.</p>
<preclass="codesplit"data-code-language="javascript">// {.code-wide} If it is alive and has less than 2 live neighbors, it dies from loneliness.
<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>
<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, you’d 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 isn’t the current array. Implement this particular solution.</p>
<p>Over the course of this book, I’ve built examples of systems of <em>objects</em> with properties that move about the canvas. And in this chapter, although I’ve been talking about a “cell” as if it were an object, I haven’t 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, let’s just recreate the Game of Life simulation. Only instead of:</p>
<preclass="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>
<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>
<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>
<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 neighbor’s <code>previous</code> states are counted and the cell’s new <code>state</code> property is updated.</p>
<h2id="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, it’s 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, I’ll 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>. There’s 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>
<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 cell’s state was a floating point number between 0 and 1?</p>
<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>
<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>
<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 cell’s 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>
<p>Visualize the Game of Life by coloring each cell according to how long it’s been alive or dead. Can you also use the cell’s history to inform the rules?</p>
<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>
<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>
<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>
<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 ecosystem’s 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>