diff --git a/content/00_randomness.html b/content/00_randomness.html index cf80db50..ed81ce93 100644 --- a/content/00_randomness.html +++ b/content/00_randomness.html @@ -605,7 +605,7 @@

Two-Dimensional Noise

Noise Detail

The p5.js noise reference explains that noise is calculated over several octaves. Calling the noiseDetail() function changes both the number of octaves and their importance relative to one another. This, in turn, changes the quality of the noise values produced.

-

If you wanted to color every pixel of a canvas randomly using the random() function, you would need a nested loop to cycle through the rows and columns of pixels and pick a random brightness for each. Note that in p5, the pixels are arranged in an array with four spots for each: red, green, blue, and alpha. For details, see the pixel array video in the “Pixels" track on the Coding Train website.

+

If you wanted to color every pixel of a canvas randomly using the random() function, you would need a nested loop to cycle through the rows and columns of pixels and pick a random brightness for each. Note that in p5, the pixels are arranged in an array with four spots for each: red, green, blue, and alpha. For details, see the pixel array video in the “Pixels" track on the Coding Train website.

loadPixels();
 for (let x = 0; x < width; x++) {
   for (let y = 0; y < height; y++) {
diff --git a/content/02_forces.html b/content/02_forces.html
index d50dc93f..7688b5a6 100644
--- a/content/02_forces.html
+++ b/content/02_forces.html
@@ -998,7 +998,7 @@ 

Example 2.8: Two-Body Attraction

Example 2.8 could be improved by refactoring the code to include constructor arguments that assign the body velocities. For now, however, this approach serves as a quick way to experiment with patterns based on various initial positions and velocities.

Exercise 2.14

-

The paper “Classification of Symmetry Groups for Planar n-Body Choreographies” by James Montaldi and Katrina Steckles (2013) explores choreographic solutions to the n-body problem (defined as periodic motions of bodies following one another at regular intervals). Educator and artist Dan Gries created an interactive demonstration of these choreographies. Try adding a third (or more!) body to Example 2.8 and experiment with setting initial positions and velocities. What choreographies can you achieve?

+

The paper “Classification of Symmetry Groups for Planar n-Body Choreographies” by James Montaldi and Katrina Steckles (2013) explores choreographic solutions to the n-body problem (defined as periodic motions of bodies following one another at regular intervals). Educator and artist Dan Gries created an interactive demonstration of these choreographies. Try adding a third (or more!) body to Example 2.8 and experiment with setting initial positions and velocities. What choreographies can you achieve?

I’m now ready to move on to an example with n bodies by incorporating an array:

// Start with an empty array.
diff --git a/content/03_oscillation.html b/content/03_oscillation.html
index 9568bb6e..1a007649 100644
--- a/content/03_oscillation.html
+++ b/content/03_oscillation.html
@@ -888,21 +888,21 @@ 

Exercise 3.14

Create a system of multiple bobs and spring connections. How about connecting a bob to another bob with no fixed anchor?

The Pendulum

-

You might have noticed that in Example 3.10’s spring code, I never once used sine or cosine. Before you write off all this trigonometry stuff as a tangent, however, allow me to show an example of how it all fits together. Imagine a bob hanging from an anchor connected by a spring with a fully rigid connection that can neither be compressed nor extended. This idealized scenario describes a pendulum and provides an excellent opportunity to practice combining all that you’ve learned about forces and trigonometry.

Figure 3.18: A pendulum with a pivot, arm, and bob
Figure 3.18: A pendulum with a pivot, arm, and bob
+

You might have noticed that in Example 3.10’s spring code, I never once used sine or cosine. Before you write off all this trigonometry stuff as a tangent, however, allow me to show an example of how it all fits together. Imagine a bob hanging from an anchor connected by a spring with a fully rigid connection that can neither be compressed nor extended. This idealized scenario describes a pendulum and provides an excellent opportunity to practice combining all that you’ve learned about forces and trigonometry.

A pendulum is a bob suspended by an arm from a pivot (previously called the anchor in the spring). When the pendulum is at rest, it hangs straight down, as in Figure 3.18. If you lift up the pendulum at an angle from its resting state and then release it, however, it starts to swing back and forth, tracing the shape of an arc. A real-world pendulum would live in a 3D space, but I’m going to look at a simpler scenario: a pendulum in the 2D space of a p5.js canvas. Figure 3.19 shows the pendulum in a nonresting position and adds the forces at play: gravity and tension.

+

When the pendulum swings, its arm and bob are essentially rotating around the fixed point of the pivot. If no arm connected the bob and the pivot, the bob would simply fall to the ground under the influence of gravity. Obviously, that isn’t what happens. Instead, the fixed length of the arm creates the second force—tension. However, I’m not going to work with this scenario according to these forces, at least not in the way I approached the spring scenario.

Figure 3.19: A pendulum showing \theta as angle relative to its resting position
Figure 3.19: A pendulum showing \theta as angle relative to its resting position
-

When the pendulum swings, its arm and bob are essentially rotating around the fixed point of the pivot. If no arm connected the bob and the pivot, the bob would simply fall to the ground under the influence of gravity. Obviously, that isn’t what happens. Instead, the fixed length of the arm creates the second force—tension. However, I’m not going to work with this scenario according to these forces, at least not in the way I approached the spring scenario.

Instead of using linear acceleration and velocity, I’m going to describe the motion of the pendulum in terms of angular acceleration and angular velocity, which refer to the change of the arm’s angle \theta relative to the pendulum’s resting position. I should first warn you, especially if you’re a seasoned physicist, that I’m going to conveniently ignore several important concepts here: conservation of energy, momentum, centripetal force, and more. This isn’t intended to be a comprehensive description of pendulum physics. My goal is to offer you an opportunity to practice your new skills in trigonometry and further explore the relationship between forces and angles through a concrete example.

To calculate the pendulum’s angular acceleration, I’m going to use Newton’s second law of motion but with a little trigonometric twist. Take a look back at Figure 3.19 and tilt your head so that the pendulum’s arm becomes the vertical axis. The force of gravity suddenly points askew, a little to the left—it’s at an angle with respect to your tilted head. If this is starting to hurt your neck, don’t worry. I’ll redraw the tilted figure and relabel the forces F_g for gravity and T for tension (Figure 3.20, left).

@@ -977,7 +977,7 @@

The Pendulum

Note that the acceleration calculation now includes a multiplication by –1. When the pendulum is to the right of its resting position, the angle is positive, and so the sine of the angle is also positive. However, gravity should pull the bob back toward the resting position. Conversely, when the pendulum is to the left of its resting position, the angle is negative, and so its sine is negative too. In this case, the pulling force should be positive. Multiplying by –1 is necessary in both scenarios.

-

Next, I need a show() method to draw the pendulum on the canvas. But where exactly should I draw it? How do I calculate the x- and y-coordinates (Cartesian!) for both the pendulum’s pivot point (let’s call it pivot) and bob position (let’s call it bob)? This may be getting a little tiresome, but the answer, yet again, is trigonometry, as shown in Figure 3.22.

+

Next, I need a show() method to draw the pendulum on the canvas. But where exactly should I draw it? How do I calculate the x- and y-coordinates (Cartesian!) for both the pendulum’s pivot point (let’s call it pivot) and bob position (let’s call it bob)? This may be getting a little tiresome, but the answer, yet again, is trigonometry, as shown in Figure 3.19.

First, I’ll need to add a this.pivot property to the constructor to specify where to draw the pendulum on the canvas:

this.pivot = createVector(100, 10);

I know the bob should be a set distance away from the pivot, as determined by the arm length. That’s my variable r, which I’ll set now:

diff --git a/content/04_particles.html b/content/04_particles.html index c900a139..e4a223ac 100644 --- a/content/04_particles.html +++ b/content/04_particles.html @@ -451,7 +451,7 @@

A System of Emitters

You click the mouse and generate a particle system at the mouse’s position (Figure 4.3).

-
+
Figure 4.3 Adding one particle system

You keep clicking the mouse. Each time, another particle system springs up where you clicked (Figure 4.4).

diff --git a/content/05_steering.html b/content/05_steering.html index 59ff931c..cf7506b0 100644 --- a/content/05_steering.html +++ b/content/05_steering.html @@ -235,13 +235,13 @@

The Arrive Behavior

  • I want to go as fast as possible toward the target.
  • and so on . . .
  • -

    The vehicle is so gosh darn excited about getting to the target that it doesn’t bother to make any intelligent decisions about its speed. No matter the distance to the target, it always wants to go as fast as possible. When the vehicle is very close, it will therefore end up overshooting the target (see Figure 5.6, top).

    Figure 5.6: The top vehicle has a desired velocity at maximum speed and will overshoot the target. The bottom vehicle illustrates scaling the desired velocity according to the distance from the target. (While I encourage you to continue thinking about the vehicle as a cute, bug-like creature, from this point it’s drawn as a triangle to keep things simple.)
    Figure 5.6: The top vehicle has a desired velocity at maximum speed and will overshoot the target. The bottom vehicle illustrates scaling the desired velocity according to the distance from the target. (While I encourage you to continue thinking about the vehicle as a cute, bug-like creature, from this point it’s drawn as a triangle to keep things simple.)
    +

    The vehicle is so gosh darn excited about getting to the target that it doesn’t bother to make any intelligent decisions about its speed. No matter the distance to the target, it always wants to go as fast as possible. When the vehicle is very close, it will therefore end up overshooting the target (see Figure 5.6, top).

    In some cases, this is the desired behavior. (Consider a puppy going after its favorite toy: it’s not slowing down, no matter how close it gets!) However, in many other cases (a car pulling into a parking spot, a bee landing on a flower), the vehicle’s thought process needs to consider its speed relative to the distance from its target (see Figure 5.6, bottom). For example:

    • I’m very far away. I want to go as fast as possible toward the target.
    • diff --git a/content/08_fractals.html b/content/08_fractals.html index d9df4f92..4a5d70f7 100644 --- a/content/08_fractals.html +++ b/content/08_fractals.html @@ -75,7 +75,7 @@

      Recursion

      Implementing Recursive Functions

      In a moment, I’ll write a sketch that recursively implements the Cantor set. But first, what does it mean to have recursion in code? It all boils down to calling a function from inside a function. This, in and of itself, isn’t anything new. After all, you probably call functions from inside other functions all the time. For example:

      function someFunction() {
      -  //{!1} Call the function background() in the definition of someFunction().
      +  //{!1} Call the function background() in the definition of someFunction().
         background(0);
       }

      Here’s the key difference with recursion: what would happen if you called the function you’re defining within that function itself? Can someFunction() call someFunction()?

      @@ -161,7 +161,7 @@

      Example 8.2: Recursive Circles Twice noFill(); circle(x, y, radius * 2); if (radius > 4) { - //{!2} drawCircles() calls itself twice. For every circle, a smaller circle is drawn to the left and the right. + //{!2} drawCircles() calls itself twice. For every circle, a smaller circle is drawn to the left and the right. drawCircles(x + radius / 2, y, radius / 2); drawCircles(x - radius / 2, y, radius / 2); } @@ -179,7 +179,7 @@

      Example 8.3: Recursive Circles noFill(); circle(x, y, radius * 2); if (radius > 16) { - //{!4} drawCircles() calls itself four times. + //{!4} drawCircles() calls itself four times. drawCircles(x + radius / 2, y, radius / 2); drawCircles(x - radius / 2, y, radius / 2); drawCircles(x, y + radius / 2, radius / 2); @@ -278,16 +278,16 @@

      The Monster Curve

      To accomplish the goal of treating each segment as an individual object, I must first decide what this object should be in the first place. What data should it store? What functions should it have? The Koch curve is a series of connected lines, and so I’ll think of each segment as a KochLine. Each KochLine object has a start point (a) and an end point (b). These points are represented as p5.Vector objects, and the line is drawn using the line() function:

      class KochLine {
       
      -  //{!2} A line between two points: a and b
      +  //{!2} A line between two points: a and b
         constructor(a, b) {
      -    // a and b are p5.Vector objects.
      +    // a and b are p5.Vector objects.
           this.start = a.copy();
           this.end = b.copy();
         }
       
         show() {
           stroke(0);
      -    //{!1} Draw the line from a to b.
      +    //{!1} Draw the line from a to b.
           line(this.start.x, this.start.y, this.end.x, this.end.y);
         }
       }
      @@ -301,7 +301,7 @@

      The Monster Curve

      // Right side of the canvas let end = createVector(width, 200); - //{!1} The first KochLine object + //{!1} The first KochLine object segments.push(new KochLine(start, end)); }

    Then in draw(), all KochLine objects (just one for now) can be rendered with a for...of loop:

    @@ -348,7 +348,7 @@

    The Monster Curve

    function generate() {
       let next = [];
       for (let segment of segments) {
    -    //{!5} A KochLine needs a method that returns all five points computed according to the Koch rules.
    +    //{!5} A KochLine needs a method that returns all five points computed according to the Koch rules.
         let [a, b, c, d, e] = segment.kochPoints();
     
         next.push(new KochLine(a, b));
    @@ -365,7 +365,7 @@ 

    The Monster Curve

    Now I just need to write a new kochPoints() method in the KochLine class that returns an array of p5.Vector objects representing the points a through e in Figure 8.15. I’ll knock off a and e first, which are the easiest: they’re just copies of the start and end points of the original line:

      kochPoints() {
    -    //{!1} Note the use of copy(). As discussed in Chapter 5, it’s best to avoid making copies whenever
    +    //{!1} Note the use of copy(). As discussed in Chapter 5, it’s best to avoid making copies whenever
         // possible, but here a new object is needed in case the segments need to move
         // independently of each other.
         let a = this.start.copy();
    @@ -386,7 +386,7 @@ 

    The Monster Curve

    //{!1} Add that vector to the beginning of the line to find the new point. let b = p5.Vector.add(a, v); - // d is just another one-third of the way past b! + // d is just another one-third of the way past b! let d = p5.Vector.add(b, v);
    @@ -399,7 +399,7 @@

    The Monster Curve

        //{!1} Rotate by –PI/3 radians (negative angle so it rotates “up”).
         v.rotate(-PI / 3);    
    -    //{!1} Move along from b by v to get to point c.
    +    //{!1} Move along from b by v to get to point c.
         let c = p5.Vector.add(b, v);

    Finally, after calculating the five points, I can return them all together in an array. This will match the code for destructuring the array into five separate variables, as previously outlined:

    @@ -542,7 +542,7 @@

    Exercise 8.6

    if (len > 2) { push(); rotate(angle); - //{!1} Subsequent calls to branch() include the length argument. + //{!1} Subsequent calls to branch() include the length argument. branch(len); pop(); @@ -568,7 +568,7 @@

    Example 8.6: A Recursive Tree

    function draw() { background(255); - // Map the angle to range from 0 to 90° (HALF_PI) according to mouseX. + // Map the angle to range from 0 to 90° (HALF_PI) according to mouseX. angle = map(mouseX, 0, width, 0, HALF_PI); // Start the tree from the bottom of the canvas. @@ -646,7 +646,7 @@

    L-systems

    // The length of a string is stored in its length property. for (let i = 0; i < message.length; i++) { - //{!1} Individual characters can be accessed by an index, just like an array! I'm using charAt(i) instead of [i]. + //{!1} Individual characters can be accessed by an index, just like an array! I'm using charAt(i) instead of `[i]`. let character = message.charAt(i); }

    An L-system has three main components:

    diff --git a/content/09_ga.html b/content/09_ga.html index aae117e0..e1f987dd 100644 --- a/content/09_ga.html +++ b/content/09_ga.html @@ -88,7 +88,7 @@

    Step 1: Creating a Population

    - + @@ -117,7 +117,7 @@

    Step 1: Creating a Population

    GenotypeGenotype Phenotype
    - + @@ -153,7 +153,7 @@

    Step 2: Selection

    Same GenotypeSame Genotype Different Phenotype (Line Length)
    - + @@ -179,7 +179,7 @@

    Step 2: Selection

    DNADNA Fitness
    - + @@ -212,7 +212,7 @@

    Step 2: Selection

    ElementElement Fitness
    - + @@ -262,7 +262,7 @@

    Step 2: Selection

    ElementElement Fitness Normalized Fitness Expressed as a Percentage
    - + @@ -430,11 +430,11 @@

    Step 2: Selection

    let matingPool = []; for (let phrase of population) { - //{!1} n is equal to fitness times 100. + //{!1} n is equal to fitness times 100. // 100 is an arbitrary way to scale the percentage of fitness to a larger integer value. let n = floor(phrase.fitness * 100); for (let j = 0; j < n; j++) { - //{!1} Add each member of the population to the mating pool n times. + //{!1} Add each member of the population to the mating pool n times. matingPool.push(phrase); } } @@ -485,7 +485,7 @@

    Exercise 9.3

    ElementElement DNA
    - + @@ -508,7 +508,7 @@

    Exercise 9.3

    ElementElement Probability
    - + @@ -546,8 +546,8 @@

    Step 3: Reproduction (Crosso child.mutate();

    Of course, the crossover() and mutate() methods don’t magically exist in the DNA class; I have to write them. The way I’ve called crossover() indicates that it should receive an instance of DNA as an argument (parentB) and return a new instance of DNA, the child:

    crossover(partner) {
    -  // The child is a new instance of DNA.
    -  // (Note that the genes are generated randomly in the DNA constructor,
    +  // The child is a new instance of DNA.
    +  // (Note that the genes are generated randomly in the DNA constructor,
       // but the crossover method will override the array.)
       let child = new DNA(this.genes.length);
     
    @@ -629,7 +629,7 @@ 

    Example 9.1: Gene let matingPool = []; for (let phrase of population) { - //{!4} Add each member n times according to its fitness score. + //{!4} Add each member n times according to its fitness score. let n = floor(phrase.fitness * 100); for (let j = 0; j < n; j++) { matingPool.push(phrase); @@ -646,12 +646,12 @@

    Example 9.1: Gene child.mutate(mutationRate); //{!1} Note that you are overwriting the population with the new - // children. When draw() loops, you will perform all the same + // children. When draw() loops, you will perform all the same // steps with the new population of children. population[i] = child; } - // Step 4: Repeat — go back to the beginning of draw()! + // Step 4: Repeat — go back to the beginning of `draw()`! }

    The sketch.js file precisely mirrors the steps of the GA. However, most of the functionality called upon is encapsulated in the DNA class:

    @@ -735,8 +735,8 @@ 

    Key 1: The Global Variables

    ElementElement Rank Probability
    - - + + @@ -773,8 +773,8 @@

    Key 1: The Global Variables

    Population SizeMutation RatePhrasePhrase Number of Generations Until the Phrase Is Solved Total Time (in Seconds) Until the Phrase Is Solved
    - - + + @@ -814,7 +814,7 @@

    Key 2: The Fitness Function

    - + @@ -844,7 +844,7 @@

    Key 2: The Fitness Function

    Population SizeMutation RatePhrasePhrase Number of Generations Until the Phrase Is Solved Total Time (in Seconds) Until the Phrase Is Solved
    PhraseCharacters CorrectCharacters Correct Fitness
    - + @@ -865,7 +865,7 @@

    Key 2: The Fitness Function

    Correct CharactersCorrect Characters Fitness
    - + @@ -938,7 +938,7 @@

    Key 3: The Genotype and Phenotype

    class Vehicle {
       constructor() {
    -    //{!1} A DNA object embedded into the Vehicle class
    +    //{!1} A DNA object embedded into the Vehicle class
         this.dna = new DNA(4);
         //{!4} Use the genes to set variables.
         this.maxspeed = dna.genes[0];
    @@ -1079,7 +1079,7 @@ 

    Developing the Rockets

    this.genes = []; // How strong can the thrusters be? this.maxForce = 0.1; - // Notice that the length of genes is equal to a global lifeSpan variable. + // Notice that the length of genes is equal to a global lifeSpan variable. for (let i = 0; i < lifeSpan; i++) { this.genes[i] = p5.Vector.random2D(); //{!1} Scale the vectors randomly, but no stronger than the maximum force. @@ -1109,7 +1109,7 @@

    Developing the Rockets

    this.dna = dna; // A rocket has fitness. this.fitness = 0; - //{!1} A counter for the dna genes array + //{!1} A counter for the DNA genes array this.geneCounter = 0; this.position = createVector(x, y); this.velocity = createVector(); @@ -1184,7 +1184,7 @@

    Managing the Population

      live() {
         for (let rocket of this.population) {
    -      //{!1} The run() method takes care of the simulation, updates the rocket’s
    +      //{!1} The run() method takes care of the simulation, updates the rocket’s
           // position, and draws it to the canvas.
           rocket.run();
         }
    @@ -1236,11 +1236,11 @@ 

    Example 9.2: Smart Rockets

    background(255); // The revised GA if (lifeCounter < lifeSpan) { - // Step 2: The rockets live their lives until lifeCounter reaches lifeSpan. + // Step 2: The rockets live their lives until lifeCounter reaches lifeSpan. population.live(); lifeCounter++; } else { - // When lifeSpan is reached, reset lifeCounter and evolve the next + // When lifeSpan is reached, reset lifeCounter and evolve the next // generation (steps 3 and 4, selection and reproduction). lifeCounter = 0; population.fitness(); @@ -1276,7 +1276,7 @@

    Making Improvements

    ); }

    If I create an array of Obstacle objects, I can then have each rocket check to see whether it has collided with each obstacle. If a collision occurs, the rocket can set the Boolean flag hitObstacle to true. To achieve this, I need to add a method to the Rocket class:

    -
      // This new method lives in the Rocket class and checks whether a rocket has
    +
      // This new method lives in the Rocket class and checks whether a rocket has
       // hit an obstacle.
       checkObstacles(obstacles) {
         for (let obstacle of obstacles) {
    @@ -1411,7 +1411,7 @@ 

    Interactive Selection

    // The DNA values are assigned to flower properties // such as petal color, petal size, and number of petals. let genes = this.dna.genes; - // I’ll set the RGB range to from 0 to 1 with colorMode() and use map() as needed elsewhere for drawing the flower. + // I’ll set the RGB range to from 0 to 1 with colorMode() and use map() as needed elsewhere for drawing the flower. let petalColor = color(genes[0], genes[1], genes[2], genes[3]); let petalSize = map(genes[4], 0, 1, 4, 24); let petalCount = floor(map(genes[5], 0, 1, 2, 16)); @@ -1545,7 +1545,7 @@

    Ecosystem Simulation

      update() {
         // Death is always looming.
         this.health -= 0.2;
    -    /* All the rest of update() */
    + /* All the rest of update() */

    If health drops below 0, the bloop dies:

      // A method to test whether the bloop is alive or dead.
    @@ -1630,9 +1630,9 @@ 

    Selection and Reproduction

    Note that I’ve lowered the probability of reproduction from 1 percent to 0.05 percent. This change makes a significant difference; with a high reproduction probability, the system will rapidly become overpopulated. Too low a probability, and everything will likely die out quickly.

    Writing the copy() method into the DNA class is easy with the JavaScript array method slice(), a standard JavaScript method that makes a new array by copying elements from an existing array:

    class DNA {
    -  //{!1} This copy() method replaces crossover().
    +  //{!1} This copy() method replaces crossover().
       copy() {
    -    // Create new DNA (with random genes).
    +    // Create new DNA (with random genes).
         let newDNA = new DNA();
         //{!1} Overwrite the random genes with a copy this DNA’s genes.
         newDNA.genes = this.genes.slice();
    @@ -1662,7 +1662,7 @@ 

    Example 9.5: An Evolving Ecosystem

    World class manages the // population of bloops and all the food. constructor(populationSize) { // Create the population. diff --git a/content/10_nn.html b/content/10_nn.html index 843acd57..0c375922 100644 --- a/content/10_nn.html +++ b/content/10_nn.html @@ -73,7 +73,7 @@

    The Perceptron

    Correct CharactersCorrect Characters Fitness
    - + @@ -95,7 +95,7 @@

    Step 1: Weight the Inputs

    InputPhrase Value
    - + @@ -114,8 +114,8 @@

    Step 1: Weight the Inputs

    WeightPhrase Value
    - - + + @@ -193,8 +193,8 @@

    Simple Pattern Recognitio

    InputWeightPhrasePhrase Input \boldsymbol{\times} Weight
    - - + + @@ -281,8 +281,8 @@

    The Perceptron Code

    Input ValueWeightPhrasePhrase Result
    - - + +
    DesiredGuessPhrasePhrase Error