Skip to content

Commit

Permalink
Merge pull request #1053 from nature-of-code/dev/exercise-with-solution
Browse files Browse the repository at this point in the history
Feat: Exercise with solutions
  • Loading branch information
shiffman authored Nov 27, 2024
2 parents ccb3316 + a9bba05 commit 337d075
Show file tree
Hide file tree
Showing 38 changed files with 921 additions and 3 deletions.
52 changes: 52 additions & 0 deletions content/00_randomness.html
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ <h3 id="pseudorandom-numbers">Pseudorandom Numbers</h3>
<div data-type="exercise">
<h3 id="exercise-01">Exercise 0.1</h3>
<p>Create a random walker that has a greater tendency to move down and to the right. (A partial solution follows in the next section.)</p>
<div class="web-only solution">
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/S67KwJ2o5" data-example-path="examples/00_randomness/exercise_0_1_solution_skewed_random_walker"><img src="examples/00_randomness/exercise_0_1_solution_skewed_random_walker/screenshot.png"></div>
<figcaption></figcaption>
</figure>
</div>
</div>
<h2 id="probability-and-nonuniform-distributions">Probability and Nonuniform Distributions</h2>
<p>Uniform randomness often isn’t the most thoughtful solution to a design problem—in particular, the kind of problem that involves building an organic or natural-looking simulation. With a few tricks, however, the <code>random()</code> function can instead produce <strong>nonuniform distributions</strong> of random numbers, where some outcomes are more likely than others. This type of distribution can yield more interesting, seemingly natural results.</p>
Expand All @@ -221,6 +227,10 @@ <h2 id="probability-and-nonuniform-distributions">Probability and Nonuniform Dis
<div data-type="exercise">
<h3 id="exercise-02">Exercise 0.2</h3>
<p>What is the probability of drawing two aces in a row from a deck of 52 cards, if you reshuffle your first draw back into the deck before making your second draw? What would that probability be if you didn’t reshuffle after your first draw?</p>
<div class="web-only solution">
<p>The probability of drawing two aces in a row with reshuffling: 4/52 * 4/52 = 0.0059 or 0.6%.</p>
<p>The probability if you do not reshuffle: 4/52 * 3/51 = 0.0045 or 0.4%.</p>
</div>
</div>
<div class="avoid-break">
<p>You can use the <code>random()</code> function in a couple of ways to apply the concepts of probability in your code for a nonuniform distribution. One technique is to fill an array with numbers—some of which are repeated—and then choose random elements from that array and generate events based on those choices:</p>
Expand Down Expand Up @@ -291,6 +301,12 @@ <h3 id="example-03-a-walker-that-tends-to-move-to-the-right">Example 0.3: A Walk
<div data-type="exercise">
<h3 id="exercise-03">Exercise 0.3</h3>
<p>Create a random walker with dynamic probabilities. For example, can you give it a 50 percent chance of moving in the direction of the mouse? Remember, you can use <code>mouseX</code> and <code>mouseY</code> to get the current mouse position in p5.js!</p>
<div class="web-only solution">
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/S5dEezEDa" data-example-path="examples/00_randomness/exercise_0_3_solution_random_walk_towards_mouse"><img src="examples/00_randomness/exercise_0_3_solution_random_walk_towards_mouse/screenshot.png"></div>
<figcaption></figcaption>
</figure>
</div>
</div>
<h2 id="a-normal-distribution-of-random-numbers">A Normal Distribution of Random Numbers</h2>
<p>Another way to create a nonuniform distribution of random numbers is to use a <strong>normal distribution</strong>, where the numbers cluster around an average value. To see why this is useful, let’s go back to that population of simulated monkeys and assume your sketch generates a thousand <code>Monkey</code> objects, each with a random height value of 200 to 300 (as this is a world of monkeys that have heights of 200 to 300 pixels):</p>
Expand Down Expand Up @@ -378,10 +394,22 @@ <h3 id="example-04-a-gaussian-distribution">Example 0.4: A Gaussian Distribution
<div data-type="exercise">
<h3 id="exercise-04">Exercise 0.4</h3>
<p>Consider a simulation of paint splatter drawn as a collection of colored dots. Most of the paint clusters around a central position, but some dots splatter out toward the edges. Can you use a normal distribution of random numbers to generate the positions of the dots? Can you also use a normal distribution of random numbers to generate a color palette? Try creating a slider to adjust the standard deviation.</p>
<div class="web-only solution">
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/Lu_ivopTZ" data-example-path="examples/00_randomness/exercise_0_4_solution_paint_splatter"><img src="examples/00_randomness/exercise_0_4_solution_paint_splatter/screenshot.png"></div>
<figcaption></figcaption>
</figure>
</div>
</div>
<div data-type="exercise">
<h3 id="exercise-05">Exercise 0.5</h3>
<p>A Gaussian random walk is defined as one in which the step size (how far the object moves in a given direction) is generated with a normal distribution. Implement this variation of the <code>Walker</code> class.</p>
<div class="web-only solution">
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/d223_4XYQ" data-example-path="examples/00_randomness/exercise_0_5_solution_gaussian_random_walker"><img src="examples/00_randomness/exercise_0_5_solution_gaussian_random_walker/screenshot.png"></div>
<figcaption></figcaption>
</figure>
</div>
</div>
<h2 id="a-custom-distribution-of-random-numbers">A Custom Distribution of Random Numbers</h2>
<p>There will come a time in your life when you don’t want a uniform distribution of random values, or even a Gaussian one. Imagine for a moment that you’re a random walker in search of food. Moving randomly around a space seems like a reasonable strategy for finding something to eat. After all, you don’t know where the food is, so you might as well search randomly until you find it. However, there’s a problem. As you may have noticed while watching your <code>Walker</code> object in action, random walkers return to previously visited positions many times, a phenomenon known as <strong>oversampling</strong>. This could make your search for food fruitless, or at least inefficient.</p>
Expand Down Expand Up @@ -447,6 +475,12 @@ <h3 id="exercise-06">Exercise 0.6</h3>
this.x += stepx;
this.y += stepy;</pre>
<p>(In Chapter 1, I’ll show how to vary the step sizes more efficiently with vectors.)</p>
<div class="web-only solution">
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/R3qVUJdxe" data-example-path="examples/00_randomness/exercise_0_6_solution_quadratic_random_walker"><img src="examples/00_randomness/exercise_0_6_solution_quadratic_random_walker/screenshot.png"></div>
<figcaption></figcaption>
</figure>
</div>
</div>
<h2 id="a-smoother-approach-with-perlin-noise">A Smoother Approach with Perlin Noise</h2>
<p>A good random-number generator produces numbers that have no relationship to one another and show no discernible pattern. As I’ve hinted, however, while a little bit of randomness can be a good thing when programming organic, lifelike behaviors, uniform randomness as the single guiding principle isn’t necessarily natural. An algorithm known as <strong>Perlin noise</strong>, named for its inventor, Ken Perlin, takes this concept into account by producing a naturally ordered sequence of pseudorandom numbers, where each number in the sequence is quite close in value to the one before it. This creates a “smooth” transition between the random numbers and a more organic appearance than pure noise, making Perlin noise well suited for generating various effects with natural qualities, such as clouds, landscapes, and patterned textures like marble.</p>
Expand Down Expand Up @@ -582,6 +616,12 @@ <h3 id="example-06-a-perlin-noise-walker">Example 0.6: A Perlin Noise Walker</h3
<div data-type="exercise">
<h3 id="exercise-07">Exercise 0.7</h3>
<p>In the Perlin noise random walker, the result of the <code>noise()</code> function is mapped directly to the walker’s position. Create a random walker, but map the result of the <code>noise()</code> function to the walker’s step size instead.</p>
<div class="web-only solution">
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/Z_E2pPBqi" data-example-path="examples/00_randomness/exercise_0_7_solution_perlin_noise_walker"><img src="examples/00_randomness/exercise_0_7_solution_perlin_noise_walker/screenshot.png"></div>
<figcaption></figcaption>
</figure>
</div>
</div>
<h3 id="two-dimensional-noise">Two-Dimensional Noise</h3>
<p>Having explored the concept of noise values in one dimension, let’s consider how they can also exist in a two-dimensional (2D) space. With 1D noise, there’s a sequence of values in which any given value is similar to its neighbor. Imagine a piece of graph paper (or a spreadsheet!) with the values for 1D noise written across a single row, one value per cell. Because these values live in one dimension, each has only two neighbors: a value that comes before it (to the left) and one that comes after it (to the right), as shown on the left in Figure 0.8.</p>
Expand Down Expand Up @@ -649,10 +689,22 @@ <h3 id="noise-detail">Noise Detail</h3>
<div data-type="exercise">
<h3 id="exercise-08">Exercise 0.8</h3>
<p>Play with color, <code>noiseDetail()</code>, and the rate at which <code>xoff</code> and <code>yoff</code> are incremented to achieve different visual effects.</p>
<div class="web-only solution">
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/Xh6oQvxzg" data-example-path="examples/00_randomness/exercise_0_8_solution_2_d_noise_parameterized"><img src="examples/00_randomness/exercise_0_8_solution_2_d_noise_parameterized/screenshot.png"></div>
<figcaption></figcaption>
</figure>
</div>
</div>
<div data-type="exercise">
<h3 id="exercise-09">Exercise 0.9</h3>
<p>Add a third argument to noise that increments once per cycle through <code>draw()</code> to animate the 2D noise.</p>
<div class="web-only solution">
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/pLSWc7Ge5" data-example-path="examples/00_randomness/exercise_0_9_solution_2_d_noise_animated"><img src="examples/00_randomness/exercise_0_9_solution_2_d_noise_animated/screenshot.png"></div>
<figcaption></figcaption>
</figure>
</div>
</div>
<div data-type="exercise">
<h3 id="exercise-010">Exercise 0.10</h3>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<script src="sketch.js"></script>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// The Nature of Code, Exercise 0.1
// Solution by Rick Sidwell
// http://natureofcode.com

let walker;

function setup() {
createCanvas(640, 240);
walker = new Walker();
background(255);
}

function draw() {
walker.step();
walker.show();
}

class Walker {
constructor() {
this.x = width / 2;
this.y = height / 2;
}

show() {
stroke(0);
point(this.x, this.y);
}

// Give the walker a greater tendency to move down and left by making
// the random range larger in that direction
step() {
let xstep = random(-2.75, 3);
let ystep = random(-2.75, 3);
this.x += xstep;
this.y += ystep;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
html, body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<script src="sketch.js"></script>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// The Nature of Code Exercise 0.3
// solution by Rick Sidwell
// http://natureofcode.com

let walker;

function setup() {
createCanvas(640, 240);
walker = new Walker();
background(255);
}

function draw() {
walker.step();
walker.show();
}

class Walker {
constructor() {
this.x = width / 2;
this.y = height / 2;
}

show() {
stroke(0);
point(this.x, this.y);
}

step() {
const r = random();
if (r < 0.5) {
if (r < 0.25) {
if (this.x < mouseX) {
this.x++;
} else {
this.x--;
}
} else {
if (this.y < mouseY) {
this.y++;
} else {
this.y--;
}
}
} else {
const choice = floor(random(4));
if (choice == 0) {
this.x++;
} else if (choice == 1) {
this.x--;
} else if (choice == 2) {
this.y++;
} else {
this.y--;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
html, body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.10.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.10.0/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />

</head>
<body>
<main>
</main>
<script src="sketch.js"></script>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// The Nature of Code, Exercise 0.4
// Solution by Rick Sidwell
// http://natureofcode.com

// Controls
let spreadSlider;
let sizeSlider;
let sizespSlider;
let baseHueSlider;
let huespSlider;
let alphaSlider;

function setup() {

createCanvas(640, 240);
colorMode(HSB);
background(97);
createControls(300);

}

function draw() {
translate(width / 2, height / 2);
scale(height / 2);

x = randomGaussian(0, spreadSlider.value());
y = randomGaussian(0, spreadSlider.value());
size = randomGaussian(sizeSlider.value() / height, sizespSlider.value());
if (size <= 0) {
size = 0.001;
}

let paintHue = randomGaussian(baseHueSlider.value(), huespSlider.value());
let paintSat = randomGaussian(80, 20);
let paintBright = randomGaussian(80, 20);
if (paintHue < 0) {
paintHue += 360;
} else if (paintHue >= 360) {
paintHue -= 360;
}
if (paintSat > 100) {
paintSat = 100;
}
if (paintBright > 100) {
paintBright = 100;
}

noStroke();
fill(paintHue, paintSat, paintBright, alphaSlider.value());
ellipse(x, y, size, size);
}

function createControls(ypos) {
let xpos = 0;

cpTitle = createP("Paint Splatter Simulation");
cpTitle.position(xpos, ypos -50);
cpTitle.style("font-size", "14pt");
cpTitle.style("font-weight", "bold");
xpos += 220;

clearButton = createButton("Clear");
clearButton.position(xpos, ypos-50);
clearButton.mousePressed(clearButtonClicked);


xpos=0;
spreadTitle = createP("Spread");
spreadTitle.position(xpos, ypos);
xpos += 50;

spreadSlider = createSlider(0, 0.75, 0.25, 0);
spreadSlider.position(xpos, ypos);
spreadSlider.size(80);
xpos += 100;

sizeTitle = createP("Size");
sizeTitle.position(xpos, ypos);
xpos += 35;

sizeSlider = createSlider(5, 50, 20, 0);
sizeSlider.position(xpos, ypos);
sizeSlider.size(80);
xpos += 100;

sizespTitle = createP("Size Spread");
sizespTitle.position(xpos, ypos);
xpos += 80;

sizespSlider = createSlider(0, 0.1, 0.01, 0);
sizespSlider.position(xpos, ypos);
sizespSlider.size(80);
xpos += 100;

xpos=0;
baseHueTitle = createP("Base Hue");
baseHueTitle.position(xpos, ypos+30);
xpos += 70;

baseHueSlider = createSlider(0, 360, 250, 0);
baseHueSlider.position(xpos, ypos+30);
baseHueSlider.size(80);
xpos += 100;

huespTitle = createP("Hue Spread");
huespTitle.position(xpos, ypos+30);
xpos += 80;

huespSlider = createSlider(0, 100, 15, 0);
huespSlider.position(xpos, ypos+30);
huespSlider.size(80);
xpos += 100;

alphaTitle = createP("Transparency");
alphaTitle.position(xpos, ypos+30);
xpos += 90;

alphaSlider = createSlider(0, 1, 0.75, 0);
alphaSlider.position(xpos, ypos+30);
alphaSlider.size(80);
xpos += 100;
}

function clearButtonClicked() {
background(97);
}
Loading

0 comments on commit 337d075

Please sign in to comment.