diff --git a/content/02_forces.html b/content/02_forces.html index 9f6c6943..f6d09571 100644 --- a/content/02_forces.html +++ b/content/02_forces.html @@ -178,7 +178,7 @@
There’s another way you could write the applyForce()
function, using the static method div()
instead of copy()
. Rewrite applyForce()
using the static method. For help with this exercise, review static methods in Chapter 1.
applyForce(force) { - let f = _______._______(_______,_______); + let f = p5.Vector.div(force, this.mass); this.acceleration.add(f); }diff --git a/content/03_oscillation.html b/content/03_oscillation.html index 204b804d..0a3c66aa 100644 --- a/content/03_oscillation.html +++ b/content/03_oscillation.html @@ -817,7 +817,7 @@
-“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, Scientific American (October 1970)
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.
-First, let’s get one thing straight. The term cellular automata is plural. The code examples here will simulate just one—a cellular automaton, singular. To keep things short and sweet, I’ll refer to cellular automata as “CA.”
-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.
-A cellular automaton is a model of a system of “cell” objects with the following characteristics.
+In Chapter 5, I defined a complex system 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 ads up to more than the sum of its parts. In this chapter, I’m going to turn to developing other complex systems known as cellular automata.
+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, I’ll build systems 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 illustrate the details behind how complex systems work, 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 I’ll increasingly focus on systems an algorithms rather than vectors an motion—albeit systems an algorithms that I can and will apply to moving bodies.
+A cellular automaton (cellular automata plural, or CA for short) is a model of a system of “cell” objects with the following characteristics:
Figure 7.1 illustrates these various CA characteristics.
-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.
-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.
-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.
-Perhaps the most significant scientific (and lengthy) work studying cellular automata arrived in 2002: Stephen Wolfram’s 1,280-page A New Kind of Science. 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.
-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.
-Let’s build Wolfram’s elementary CA from scratch. Concepts first, then code. I’ll begin with the three key elements of a CA.
-1) Grid. The simplest grid would be one-dimensional: a line of cells.
+ +The idea that an object’s 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. 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. Now, with cellular automata, you’ll see how an object’s state can change based on a system of rules.
+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 readd that right: robots that can build copies of themselves.
+Von Neumann’s 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.
+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’s probably the most famous cellular automaton, the “Game of Life,” which I’ll discuss in detail later in the chapter.
+Perhaps the most significant (and lengthy) scientific work studying cellular automata arrived in 2002: Stephen Wolfram’s 1,280-page A New Kind of Science. Available in its entirety for free online, Wolfram’s book discusses how CA aren’t simply neat tricks, but are relevant to the study of biology, chemistry, physics, and all branches of science. In a moment, I’ll turn to building a simulation of Wolfram’s work, although I’ll 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, you’ll find plenty more to read about in Wolfram’s book.
+What’s the simplest cellular automaton you can imagine? For Wolfram, an elementary CA has three key elements.
+1) Grid. The simplest grid would be one-dimensional: a line of cells (Figure 7.2).
-2) States. The simplest set of states (beyond having only one state) would be two states: 0 or 1.
+2) States. 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.
-3) Neighborhood. 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.
+3) Neighborhood. 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). I’ll 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.
-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.
-I haven’t yet discussed, however, what is perhaps the most important detail of how cellular automata work—time. I’m not really talking about real-world time here, but rather about the CA living over a period of time, which could also be called a generation and, in this case, will likely refer to the frame count of an animation. The figures above show the CA at time equals 0 (or generation 0). The questions to ask are: How do you compute the states for all cells at generation 1? And generation 2? And so on and so forth.
+I have a line of cells, each with an initial state, and each with two neighbors. The exciting thing is, even with this simplest CA imaginable, the properties of complex systems can emerge. But I haven’t yet discussed perhaps the most important detail of how cellular automata work: time.
+I’m not really talking about real-world time here, but rather about the CA developing across a series of discrete time steps, which could also be called a generations. In the case of a CA in p5.js, time will likely be tied to the frame count of the animation. The question, as depicted in Figure 7.6, is this: given the states of the cells at time equals 0 (or generation 0), how do I compute the states for all cells at generation 1? And then how do I get from generation 1 to generation 2? And so on and so forth.
-Let’s say there is an individual cell in the CA called \text{cell}. The formula for calculating the \text{cell state} at any given time t (\text{cell}_t) is as follows:
+Let’s say there’s an individual cell in the CA called \text{cell}. The formula for calculating the cell’s state at any given time t (\text{cell}_t) is as follows:
In other words, a cell’s new state is a function of all the states in the cell’s neighborhood at the previous generation (time t-1). A new state value is calculated by looking at the previous generation’s neighbor states.
+In other words, a cell’s new state is a function of all the states in the cell’s neighborhood at the previous generation (time t-1). A new state value is calculated by looking at the previous generation’s neighbor states (Figure 7.7).
-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.
-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.
+There are many ways to compute a cell’s state from its neighbors’ states. Consider blurring an image. (Guess what? Image processing works with CA-like rules!) A pixel’s new state (its color) is the average of its neighbors’ colors. Similarly, a cell’s new state could be the sum of all of its neighbors’ states. However, in Wolfram’s elementary CA, the process takes a different approach: instead of mathematical operations, new states are determined by predefined rules that account for every possible configuration of a cell and its neighbors. These rules are known collectively as a ruleset.
+This approach might seem ridiculous at first—wouldn’t there be way too many possibilities for it to be practical? Well, let’s give it a try. A neighborhood consists of three cells, each with a state of 0 or 1. How many possible ways can the states in a neighborhood be configured? A quick way to figure this out is to think of each neighborhood configuration as a binary number. Binary numbers use “base 2,” meaning they’re represented with only two possible digits (0 and 1). In this case, each neighborhood configuration corresponds to a 3-bit number, and how many values can you represent with 3 bits? Eight, from 0 (000) up to 7 (111). Figure 7.8 shows how.
-Once all the possible neighborhood configurations are defined, an outcome (new state value: 0 or 1) is specified for each configuration.
+Once all the possible neighborhood configurations are defined, an outcome (new state value: 0 or 1) is specified for each configuration, as in Figure 7.9.
-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.
-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!
+Keep in mind that unlike the sum or averaging methods, the rulesets in elementary CA don’t follow any arithmetic logic—they’re just arbitrary mappings of inputs to outputs. The input is the current configuration of the neighborhood (one of eight possibilities), and the output is the next state of the middle cell in the neighborhood (0 or 1—it’s up to you to define the rule).
+Once you have a ruleset, you can set the cellular automaton in motion. The standard Wolfram model is to start generation 0 with all cells having a state of 0 except for the middle cell, which should have a state of 1. You can do this with any size (length) grid, but for clarity, I’ll use a one-dimensional CA of nine cells so that the middle is easy to pick out.
-Referring to the ruleset above, let’s see how a given cell (I’ll pick the center one) would change from generation 0 to generation 1.
+Based on the ruleset in Figure 7.9, how do the cells change from generation 0 to generation 1? In Figure 7.11, I’ve shown how the center cell, with a neighborhood of 010, switches from a 1 to a 0. Try applying the ruleset to the remaining cells to fill in the rest of the generation 1 states.
-Try applying the same logic to all of the cells above and fill in the empty cells.
+Now for a slight change: instead of representing the cells’ states with 0s and 1s, I’ll indicate them with visual cues—white for 0 and black for 1 (see FIgure 7.12). Although this might seem counterintuitive, as 0 usually signifies black in computer graphics, I’m using this convention because the examples in this book have a white background, so “turning on” a cell corresponds to switching its color from white to black.
-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.
-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 as shown in Figure 7.13.
+With this switch from numerical representations into visual forms, the fascinating dynamics and patterns of cellular automata will come into view! To see them even more clearly, instead of drawing one generation at a time, I’ll also start stacking the generations, with each new generation appearing below the previous one, as shown in Figure 7.13.
-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.
+The low-resolution shape that emerges in Figure 7.13 is the Sierpiński triangle. Named after the Polish mathematician Wacław Sierpiński, it’s a famous example of a fractal. I’ll examine fractals more closely in the next chapter, but briefly, they’re patterns where the same shapes repeat themselves at different scales. To give you a better sense of this, Figure 7.14 shows the CA over several more generations, and with a wider grid size.
+And Figure 7.15 shows the CA again, this time with cells that are just a single pixel wide so the resolution is much higher.
-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.
+Take a moment to let the enormity of what you’ve just seen sink in. Using an incredibly simple system of 0s and 1s, with little neighborhoods of three cells, I was able to generate a shape as sophisticated and detailed as the Sierpiński triangle. This is the beauty of complex systems.
+Of course, this particular result didn’t happen by accident. I picked the set of rules in Figure 7.9 because I knew the pattern it would generate. The mere act of defining a ruleset doesn’t guarantee visually exciting results. In fact, for a one-dimensional CA where each cell can have two possible states, there are exactly 256 possible rulesets to choose from, and only a handful of them are on par with the Sierpiński triangle. How do I know there are 256 possible rulesets? It comes down to a little more binary math.
+Take a look back at Figure 7.8 and notice again how there are eight possible neighborhood configurations, from 000 to 111. These are a ruleset’s inputs, and they remain constant from ruleset to ruleset. It’s only the outputs that vary from one ruleset to another—the individual 0 or 1 paired with each neighborhood configuration. Figure 7.9 represented a ruleset entirely with 0s and 1s. Now Figure 7.16 shows the same ruleset visualized with white and black squares.
-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.
+Since the eight possible inputs are the same no matter what, a potential shorthand for indicating a ruleset is to specify just the outputs, writing them as a sequence of eight 0s or 1s—in other words, an 8-bit binary number. For example, the ruleset in Figure 7.16 could be written as 01011010. The 0 on the right corresponds to input configuration 000, the 1 next to it corresponds to input 001, and so on. On Wolfram’s website, CA rules are illustrated using a combination of this binary shorthand an Figure 7.16’s black-and-white square representation, yielding depictions like Figure 7.17.
-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 background(r, g, b)
, each color component (red, green, and blue) is represented by an 8-bit number ranging from 0 to 255.
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.”
-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.”
+A ruleset can be reduced to an 8-bit number. And how many combinations of eight 0s and 1s are there? Exactly 2^8, or 256. You might remember this from when you first learned about RGB color in p5.js. When you write background(r, g, b)
, each color component (red, green, and blue) is represented by an 8-bit number ranging from 0 to 255 in decimal, or 00000000 to 11111111 in binary. And speaking of decimal, an even shorter shorthand for a ruleset is to convert its 8-bit binary number to decimal (or “base 10”). “Rule 01011010” is therefore more commonly known as “rule 90.”
Since there are 256 possible combinations of eight 0s and 1s, there are also 256 unique rulesets. Let’s check out another one. How about rule 11011110, or more commonly, rule 222. Figure 7.18 shows how it looks.
-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.
-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.
-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:
+The result is a recognizable shape, though it certainly isn’t as exciting as the Sierpiński triangle. As I saidd earlier, most of the 256 elementary rulesets dodn’t produce compelling outcomes. However, it’s still quite incredible that even just a few of these rulesets—simple systems of cells with only two possible states—can produce fascinating patterns seen every day in nature (see Figure 7.19, a snail shell resembling Wolfram’s rule 30). This demonstrates how valuable CAs can be in simulation and pattern generation.
+Before I go too far down the road of characterizing the results of different rulesets, though, let’s look at how to build a p5.js sketch that generates and visualizes a Wolfram elementary CA.
+You may be thinking: “OK, I’ve got this cell thing. And the cell thing has some properties, like a state, what generation it’s on, who its neighbors are, and where it lives pixel-wise on the screen. And maybe it has some functions, like to display itself and determine its new state.” This line of thinking is an excellent one and would likely lead you to write some code like this:
class Cell { }-
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:
+However, this isn’t the road I want to travel down right now. Later in this chapter, I’ll discuss why an object-oriented approach could prove valuable in developing a CA simulation, but to begin, it’s easier to work with a more elementary data structure. After all, what is an elementary CA but a list of 0s and 1s? Why not describe a generation of a one-dimensional CA using an array?
+let cells = [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0];+
This array corresponds to the row of cells shown in Figure 7.20.
-let cells = [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0];-
To draw that array, I check the element is 0 or a 1 and create a fill color accordingly.
+To show that array, I check if each element is a 0 or a 1, choose a fill color accordingly, and draw a rectangle.
//{!1} Loop through every cell. for (let i = 0; i < cells.length; i++) { - //{!5} Create a fill based on its state (0 or 1). + //{!5} Create a fill based on its state (0 or 1). if (cells[i] == 0) { - fill(255); - } else { - fill(0); - } + fill(255); + } else { + fill(0); + } stroke(0); rect(i * 50, 0, 50, 50); }-
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.
+The array describes the cell states in the “current” generation. Now I need a mechanism to compute the next generation’s states. Here’s the pseudocode describing what I want to achieve.
For every cell in the array:
This may lead you to write some code like this:
+This pseudocode may suggest writing some code like this:
// For every cell in the array... for (let i = 0; i < cells.length; i++) { @@ -166,17 +169,17 @@-7.3 How to Program an Elementary CA< //{!1} Set the cell’s state to the new value. cells[i] = newstate; }
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.
-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 i
, its neighbors are i - 1
and i + 1
.
I’m also farming out the calculation of a new state value to some function called rules()
. 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 rules()
function to compute the new states differently.
So what have I done wrong? Let’s talk through how the code will execute. First, look at cell index i
when it equals 0. Now let’s look at 0’s neighbors. Left is i - 1
or -1
. Middle is i
or 0
. And right is i + 1
or 1
. However, the array by definition does not have an element with the index -1
. It starts with 0!
This is a problem I alluded to before: the edge cases.
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:
+I’m fairly close to getting this right, but there are a few issues to resolve. For one, I’m farming out the calculation of a new state value to some function called rules()
. Obviously, I’m going to have to write this function, so my work isn’t done, but what I’m aiming for here is modularity. I want a for
loop that provides a basic framework for managing any CA, regardless of the specific ruleset. If I want to try different rulesets, I shouldn’t have to touch that framework at all; I can just rewrite the rules()
function to compute the new states differently.
So I still have the rules() function to write, but more important, I’ve made one minor blunder and one major blunder in the for
loop itself. Let’s examine the code more closely.
First, 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 i
, its neighbors are i - 1
and i + 1
.
In fact, it’s not quite that easy. What have I done wrong? Think about how the code will execute. The first time through the loop, cell index i
equals 0
. The code wants to look at cell 0’s neighbors. Left is i - 1
or -1
. Oops! An array by definition doesn’t have an element with an index of -1
. It starts with 0
.
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 doesn’t have a neighbor to both its left and its right? Here are three possible solutions to this problem:
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:
+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 it one cell earlier:
//{.bold} A loop that ignores the first and last cell for (let i = 1; i < cells.length - 1; i++) { let left = cells[i - 1]; @@ -185,14 +188,17 @@-7.3 How to Program an Elementary CA< let newstate = rules(left, middle, right); cells[i] = newstate; }
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:
+There’s 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 won’t trigger an error; the CA just won’t perform correctly. It all lies in this line of code:
cells[i] = newstate;-
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.
-Cell #6, generation 0 = some state, 0 or 1
-Cell #6, generation 1 = a function of states for cell #5, cell #6, and cell #7 at *generation 0*
-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 i = 5.
+This may seem perfectly innocent. After all, once I’ve computed a new state value, I want to assign the cell its new state. But think about the next iteration of the for
loop. Let’s say the new state for cell 5 was just computed, and the loop is moving on to cell 6. What happens next?
A cell’s 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 6’s new state at generation 1. Have I saved cell 5’s value at generation 0? No, I have not. Remember, this line of code was just executed when i
equaled 5
:
cells[i] = newstate;-
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 slice()
which makes a copy of an array.
Once this happens, cell 5’s state at generation 0 is gone; cells[5]
is now storing the value for generation 1. I can’t overwrite the values in the array while I’m 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 slice()
which makes a copy of an array.
//{!1 .bold} Another array to store the states for the next generation. let newcells = cells.slice(); diff --git a/content/examples/01_vectors/example_1_1_bouncing_ball_with_no_vectors/screenshot.png b/content/examples/01_vectors/example_1_1_bouncing_ball_with_no_vectors/screenshot.png index 2842c0f0..da698078 100644 Binary files a/content/examples/01_vectors/example_1_1_bouncing_ball_with_no_vectors/screenshot.png and b/content/examples/01_vectors/example_1_1_bouncing_ball_with_no_vectors/screenshot.png differ diff --git a/content/examples/01_vectors/example_1_2_bouncing_ball_with_vectors/screenshot.png b/content/examples/01_vectors/example_1_2_bouncing_ball_with_vectors/screenshot.png index cce0482a..9e77d59e 100644 Binary files a/content/examples/01_vectors/example_1_2_bouncing_ball_with_vectors/screenshot.png and b/content/examples/01_vectors/example_1_2_bouncing_ball_with_vectors/screenshot.png differ diff --git a/content/examples/01_vectors/example_1_8_motion_101_velocity_and_constant_acceleration/screenshot.png b/content/examples/01_vectors/example_1_8_motion_101_velocity_and_constant_acceleration/screenshot.png index 960f19af..390f5160 100644 Binary files a/content/examples/01_vectors/example_1_8_motion_101_velocity_and_constant_acceleration/screenshot.png and b/content/examples/01_vectors/example_1_8_motion_101_velocity_and_constant_acceleration/screenshot.png differ diff --git a/content/examples/01_vectors/example_1_8_motion_101_velocity_and_constant_acceleration/sketch.js b/content/examples/01_vectors/example_1_8_motion_101_velocity_and_constant_acceleration/sketch.js index 279f4627..d5f460d8 100644 --- a/content/examples/01_vectors/example_1_8_motion_101_velocity_and_constant_acceleration/sketch.js +++ b/content/examples/01_vectors/example_1_8_motion_101_velocity_and_constant_acceleration/sketch.js @@ -3,6 +3,7 @@ // http://natureofcode.com let mover; +let counter=0; function setup() { createCanvas(640, 240); @@ -10,9 +11,21 @@ function setup() { } function draw() { - background(255); - mover.update(); mover.checkEdges(); + + counter++; + if(counter%3===0){ + drawGraphic() + } +} + +function drawGraphic(){ + background(255,30); mover.show(); + +} + +function mousePressed(){ + save('screenshot.png') } diff --git a/content/examples/02_forces/example_2_5_fluid_resistance/screenshot.png b/content/examples/02_forces/example_2_5_fluid_resistance/screenshot.png index 3532b105..682239e8 100644 Binary files a/content/examples/02_forces/example_2_5_fluid_resistance/screenshot.png and b/content/examples/02_forces/example_2_5_fluid_resistance/screenshot.png differ