p5js week 05

Like every week, today we want to talk about toast! Some toast comes with fancy toppings, like this Nutella above. Still, it couldn't be good toast without toasting the bread underneath correctly.

In order to toast toast correctly, we need to understand some basic principles. It's the same in programming. This week, let's look at some important basic programming principles and apply them to make some useful tools:

  1. encapsulating
  2. generalizing
  3. procedures
  4. buildArray function
  5. arrow functions
  6. p5js: line, begin/endShape
  7. using buildArray and arrow functions to draw in p5js

We'll try to use these techniques to make a p5js drawing which has 100 or more pieces of Terrific Toast.

After we do that, let's start new drawings that are filled with either:

  1. hearts, stars, and smiley faces, 100+ of each
  2. three types of aliens/monsters, 100+ of each

OK, let's get started!

encapsulating making toast

(photo from https://commons.wikimedia.org/wiki/File:Making_toasts.jpg)

If someone tells you to make some toast, you know what to do:

  1. get two slices of bread (depending on the size of your toaster)
  2. put the bread in the toaster
  3. set the amount of toasting to do
  4. push down the lever of the toaster to start the toasting
  5. wait until the toaster is finished
  6. remove the cooked toast from the toaster
  7. put it on a plate

Bon appetit!

Imagine if every time you wanted someone to make toast you had to tell them each and every step? It would be really inconvenient! Luckily, we can just tell someone “hey, please make me some toast” and they can do it because they know that 'make toast' means to do all of those steps.

That's a big part of what language and programming are all about. We wrap up some complicated situation and give it one word. In programming, we call that encapsulation (https://en.wikipedia.org/wiki/Encapsulation_(computer_programming) ).

generalizing about toast

(photo from https://commons.wikimedia.org/wiki/File:Toast-2.jpg)

We also generalize. That means to think of what different instances of something have in common. How about toast? We can imagine some kinds:

  1. light toast
  2. dark toast
  3. toast with white bread
  4. toast with whole wheat bread
  5. toast without crust
  6. sliced toast
  7. and so on…

Even though these are all different kinds of toast, they're all still toast! All of them are bread, and all of the bread goes into and comes out of the toaster! That's what we mean by generalization.

procedure for toast

(photo from https://www.maxpixel.net/Food-Toast-Bread-Toaster-2617854)

There's another point here that is important for programming: if you change the order of the steps, you don't get toast! Think about what happens if:

  1. you push the lever down to start the toaster before you get the bread
  2. you wait for the toaster before you push the lever down
  3. you put the bread on the plate before you toast it

Each one is a failure to make toast. You or the person who you are making toast for is going to be really disappointed. Having the right steps in the right order means having the right procedure. That's what programmers mean when they talk about procedural programming: doing things in the computer in the right order to get the result that we want.

JavaScript

(photo from https://www.flickr.com/photos/lonelybob/39658050634/)

Let's learn some JavaScript that will help us to practice encapsulation, generalization, and thinking about procedures.

  1. buildArray
  2. arrow functions

Let's try all of this in the console.

Remember last time when we used the pick function as part of some steps to make an array of colors for our drawing?

https://renickbell.net/doku.php?id=p5js-week-04#using_pick_to_choose_random_colors_for_our_toast

That is something that we might want to do many times. In programming, whenever there are some steps that we think we might need to repeat more than once, we want to make a function. That's like the discussion about encapsulation of a procedure for toast above. Let's try to take those steps about making a color array and encapsulate it into a colorArray function that we can use anytime.

First, look at the steps that we did last time. I'll leave out some things to focus on the stuff about color. Here's the code:

function randomInteger (min, max) {
  return Math.floor(min + ((max-min) * Math.random()))
}

function pick (inputArray) {
  return inputArray[randomInteger(0,inputArray.length)]
}

let myColors = ['black','pink','magenta','yellow','darkgray'];

let pickedColors  = [];

for (let i = 0; i < 3; i++) {
  pickedColors.push(pick(myColors))
}

So what did we do?

  1. defined the pick function
  2. made an array of colors to choose from
  3. made an empty array
  4. used a for-loop to get colors from the color array using pick and put them into our empty array using push
  5. later in our code, we could use the colors from the new array

First, let's try to encapsulate this by putting it into a function.

Whenever we make a function, the first thing is to write down the basic form and give our function a name:

function someColors () {}

The input to our function should be how many colors we want and our first list of all of the possible colors. We often use n to represent the number of something. Let's use that here.

function someColors (n, possibleColors) {}

We made an empty array before, so let's put that into our function:

function someColors (n, possibleColors) {
  let pickedColors  = [];
}

Then we used a for-loop, so let's add that. Notice that in our older version, we picked three colors. In this case, we want n colors, so we change 3 in the for-loop to n.

function someColors (n, possibleColors) {
  let pickedColors  = [];
  for (let i = 0; i < n; i++) {
    pickedColors.push(pick(possibleColors))
  }
}

Finally, we need to get the array pickedColors from the function so that we can use it. For that, we return the array.

function someColors (n, possibleColors) {
  let pickedColors  = [];
  for (let i = 0; i < n; i++) {
    pickedColors.push(pick(possibleColors))
  }
  return pickedColors
}

We can call it like this:

someColors(3,['black','white','blue','red','yellow'])

Notice that sometimes you get the same color more than one time.

// Array(3) [ "red", "yellow", "red" ]

This time we made an array with colors. Another time we might make an array with sizes. Another time it might be shapes. It would be cool if we had a function that could help us to make arrays in every case. When you do something more than one time (especially if you do often), write a function instead of writing out a lot of complicated code each time. Don't repeat yourself. After you encapsulate it, you add some arguments to make it more useful. That's what generalization is.

It's just like what I said earlier, we want the computer and other programmers to understand that “make toast” means a certain set of steps. Here, we want the computer and other programmers to understand a particular way of making an array. We encapsulate the process of making an array and then we generalize to make it more useful in more cases.

In this way, programmers use the computer to help themselves be more efficient (sometimes people say it's lazy, but let's look at the positive side!).

Let's make a function that can make arrays of things for us. Here's the procedure we want to make:

  1. tell the function how many items we want in our array
  2. tell the function what items we want in our array using another function
  3. make an empty array
  4. make the items and put them in the array
  5. return the array

Looking at that, we need two arguments: n for number and a function that we'll use to fill our array.

For-loops always give us an i variable that is increasing that we can use in our code. We want to pass that variable to our fill function, so remember that the fill function can have one argument: i.

Otherwise, this is going to look very similar to our someColor function above. We'll change some of the names so that it is more generic, and therefore it won't confuse our users.

function buildArray (n, fillFunction) {
  let outputArray = [];
  for (let i = 0; i < n; i++) {
    outputArray.push(fillFunction(i))
  }
  return outputArray
}

(photo from https://img-9gag-fun.9cache.com/photo/a5W7BMr_700bwp.webp)

It's important to remember that if you declare a variable inside of a for-loop, it will be reset each time the for-loop runs. This code won't work, because it will empty the array on each iteration of the for-loop.

// WARNING! THIS CODE DOESN'T WORK CORRECTLY!
function buildArray (n, fillFunction) {
  for (let i = 0; i < n; i++) {
    let outputArray = [];
    outputArray.push(fillFunction(i))
  }
  return outputArray
}

Be sure you are setting up your procedure correctly!

By the way, also remember to be careful about n this for-loop. If you make a mistake about the variables in the first part of the for-loop, it might not stop which could crash your commputer.

(photo from https://pxhere.com/en/photo/646277)

Let's learn something else useful for a moment that is going to make using buildArray more useful.

We know how to write functions like this:

function printToast (someToast) {
  console.log(someToast)
}

printToast('toast with Nutella')

However, there's another way to write functions that can be shorter: an arrow function. An arrow function is a function which is made using the arrow operator instead of the function keyword. It looks like this:

let printToast2 = someToast => console.log(someToast)

We can call it the same way:

printToast2('toast with Nutella')

This symbol is the arrow function operator. On the left side, we put the argument. On the right side we write what we want to do. If it's simple like this (one argument, one step on the right side) then you don't need any parentheses, curly braces, or the return keyword. If you need more than one argument or need to do more than one step on the right side, it's a little more complicated. We'll practice that in the future, but for now, let's try using this simple version.

If you want to learn more about arrow functions right away, take a look at this page:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

We can use an arrow function inside buildArray like this:

buildArray(10, i=> i)

Since we can use i, we can do math with it, like this:

buildArray(10, i=> i*10)

You can also use the remainder operator, like this:

buildArray(12, i=> i%3)

That's cool, right?

Now let's try to use buildArray to make our someColors function:

function someColors (n, possibleColors) {
  return buildArray (n, i => pick(possibleColors))
}

That's also cool, right? Using buildArray and an arrow function, it became very easy to make someColors.

p5js

Now we can learn some new things in p5js before we apply the techniques which are described above to our drawings. Here's what we want to learn:

  1. line
  2. begin/end shape
  3. using buildArray to draw in p5js

Sometimes you just want to draw a line in p5js, not a shape. Naturally, there's a function that does that.

The arguments for the line function are coordinates of points. You put the x and y position of the starting point and the end point in, and p5js draws the line connecting the two points.

There's more about the line function here:

https://p5js.org/reference/#/p5/line

However, if you want to fill the space between several lines, you need a different set of functions which is described below.

Up to now, we have been drawing things using collections of shapes to make a more complicated shape. However, there is a set of functions we can use to draw irregular shapes (ones that aren't rects, ellipses, etc). That's the set that is shown in examples here using beginShape etc.:

https://p5js.org/reference/#/p5/beginShape

Here's the first example from that page. What is it doing?

First, you call the beginShape function. Then you give a list of vertices (vertices is the plural of vertex). A vertex is a point, and we describe it with arguments for x position and y position. When you have described all of the points, you call the endShape function. You can use as many vertices as you want. These shapes have four, but you can have three, five, or as many as you want.

If you use CLOSE as the argument to endShape, p5js will automatically connect the last point to the first point and fill the shape according to the current state of fill; that means how it is filled depends on the last time you called the fill function, just like for rect or ellipse. If you don't use CLOSE, it will not connect the last point to the first point with a line.

You can use as many vertices as you want, but you need at least three.

First, let's use the code above about beginShape and vertex to make a function to draw a shape:

Notice how the scaleFactor argument can be used to make the shape bigger or smaller. In the function call, try changing scaleFactor from 1 to 2, and then to 5. Also try changing the other arguments and see what happens.

Instead of adding the moveX and moveY arguments to the positions of the vertices, we could have used translate. You might want to rewrite this using translate to see if you understand.

Actually, there's a mistake in this code if we're trying to draw something like a grid of similar shapes. I wonder if you can find it. Let's try to use that code in a for-loop to draw funky random shaped toast.

Wait a minute, this looks more like a bunch of green onions than toast…

I think I'm making it worse…

… what's going on with my toast? m(

Terrific Toast

Can you fix the code above so that it shows some Terrific Toast instead of that crazy pink stuff?

Try to use these techniques to make a p5js drawing which has 100 or more pieces of Terrific Toast.

your NEW drawings with custom array builders and free shapes

Let's start new drawings. Make a canvas that is 1800 wide and 900 tall. I want you to fill it with either:

  1. hearts, stars, and smiley faces, with 100+ of each
  2. three types of aliens/monsters, with 100+ of each

Use buildArray, arrays, for-loops, pick, and other techniques that we have studied before to make sure that EACH item in your pictures is unique. We want to make drawings that would be impossible for someone to draw by hand with markers or even using traditional drawing software.

After you've been working on them for a while, share your screenshots in the group chat and put your code on your page on the wiki!

  • p5js-week-05.txt
  • Last modified: 8 months ago
  • by renick