3. Functions (Draft)

If you lose your way tonight

That’s how you know the magic’s right

—“Doin’ it Right” by Daft Punk, Panda Bear

Over the last two chapters, you used equations to establish relationships between variables. We’re going to build on those ideas in this chapter as you acquire one of the most powerful tools in computation: abstraction.

3.1 Maps

Input/Output

Many of the systems we’ll study together take some kind of input and produce an output. Let’s consider a familiar example: a toaster. A toaster takes a slice of bread as its input, applies heat, and produces a toasted slice of bread as its output. Mathematicians like us call this sort of input/output process a map or function.

Figure 3.1 Transforming bread into toast

A bit more formally, a map applies a rule to assign elements from a set of inputs to a set of outputs. The set of inputs is called the map’s domain and the set of outputs is called its range or image.

Let’s see this input/output process in action with a concrete example. I’m going to flip a coin. You win if the coin lands on heads and you lose if it lands on tails.

\( H \mapsto Win \)

\( T \mapsto Lose \)

The \( \mapsto \) is a common shorthand for “maps to”. In this example, the domain is the set \( \{H, T\} \) and the range is the set \( \{Win, Lose\} \). Our simple game is based on a rule that assigns the result of a coin flip to an outcome. Inputs map to outputs. It’s a perfectly valid map despite its lack of numbers. All we need is a rule!

You could also represent the same map as a set of ordered pairs of inputs and outputs. I’ll organize the ordered pairs neatly in a table.

Table 3.1 A tabular representation of a coin flip

Coin Side

Game Result

Heads

Win

Tails

Lose

Now, I’ll define another rule, called \( d \), using notation that we’ll use throughout the rest of the book. There is nothing special about the name \( d \); any letter will do just fine. In the equation below, \( d \) is the doubling function; it takes a numerical input \( x \) and multiplies it by \( 2 \) to produce its output \( d(x) \).

\[ d(x)=2x \]

Figure 3.2 Parts of a function

In the diagram above, \( x \) is an element of the function’s domain. The rule \( d \) is applied to x to produce \( d(x) \), an element of the function’s range. \( x \) is the argument provided to \( d \) and \( d(x) \) is the resulting value.

Figure 3.3 Mapping values from the domain to the range

Now that we have a rule, let’s apply it to an argument of \( 3 \). We also call this process evaluating the function for the argument \( 3 \).

\( d(3)=23 \)

\( d(3)=6 \)

So, the value produced by evaluating \( d(3) \) is \( 6 \). The doubling function \( d \) is a specific example of a linear function. Linear functions multiply their argument by a number and then add another number to the value produced from multiplication. You can represent linear functions in the familiar form of a linear equation.

\[ f(x)=mx+b \]

\( f \) is the most common name for functions, but again, any letter will do. In the doubling function, \( m=2 \) and \( b=0 \).

\[ d(x)=2x+0 \]

Equations Interlude

Functions and equations share some notation as we just saw, but they are distinct concepts. The linear equation

\[ y=2x+5 \]

looks an awful lot like the linear function

\[ g(x)=2x+5 \]

However, keep in mind that a function describes a mapping from inputs to outputs. It is a one-way process, and we even denote it with the “maps to” arrow to point this out.

\[ x \mapsto g(x) \]

You can describe the same function in any way that conveys the rule. For example, you could create a table of arguments and their corresponding values as we did with the coin flip. Here is part of the table representing \( g \).

Table 3.2 A tabular representation of \( g \)

\( x \)

\( -1 \)

\( 0 \)

\( 1 \)

\( 2 \)

\( g(x) \)

\( 3 \)

\( 5 \)

\( 7 \)

\( 9 \)

In addition to equations and tables, there is also the graph of \( g \). You can generate a visual representation for a function by thinking of the values of \( g(x) \) as \( y \)-coordinates. Now the task simply requires drawing a point at each coordinate pair \( (x,g(x)) \).

Note that graph below uses a coordinate system more common in math class; namely, the \( y \)-axis points up. Moving between coordinate systems takes a bit of getting used to, but developing this mental dexterity now will serve you well when we introduce polar coordinates in the next chapter.

Figure 3.4 A graphical representation of \( g \)

OK, we’ve seen three ways to represent functions: a formula expressed as an equation, a table of input/output pairs, and a graph of inputs and outputs. One of these representations is built around an equation, while the others are not.

As we saw in the last chapter, equations can be rearranged and solved in a variety of ways that may have nothing to do with input or output. An equation is a statement of equality that specifies a relationship, nothing more. Functions and equations overlap a little, but they are distinct concepts.

Domain and Range

Functions can be as simple as doubling or as complex as you like. When given a rule, it’s often helpful to know when it applies and when it doesn’t. Let’s take a look at a new rule \( q \) I’ll call a quadratic function, which means it involves squaring a number.

\[ q(x)=x^{2} \]

The set of real numbers is closed under multiplication, and \( q \) just multiplies its argument by itself, so we could take any real number and use it to evaluate \( q \) to produce a value. That means the domain of \( q \) is the set of real numbers.

Domain: \( \mathbb{R} \)

We can build up a little intuition for how this function behaves by evaluating it for a small subset of the real numbers.

Table 3.4 A tabular representation of \( q \)

\( x \)

\( -2 \)

\( -1 \)

\( 0 \)

\( 1 \)

\( 2 \)

\( q(x) \)

\( 4 \)

\( 1 \)

\( 0 \)

\( 1 \)

\( 4 \)

Interesting. There seems to be a sort of symmetry in the table representing \( q \). I wonder what this looks like when graphed.

Figure 3.5 A graphical representation of \( q \)

That U-shaped graph is known as a parabola. We’ll get to know it much better in the next chapter, but we can already observe some key details. Between the table and the graph, it’s clear that \( q \) never produces a value below \( 0 \). In other words, its range is the set of positive real numbers and \( 0 \).

Range: \( q(x) \ge 0 \)

Let’s look at one more rule together which I’ll call a square root function.

\[ s(x) = \sqrt{x} \]

\( s \) stakes its argument \( x \) and produces a value which, multiplied by itself, equals \( x \). Something strange happens when we start evaluating \( s \) for the same subset of the real numbers.

Table 3.5 A tabular representation of \( s \)

\( x \)

\( -2 \)

\( -1 \)

\( 0 \)

\( 1 \)

\( 2 \)

\( q(x) \)

ERROR

ERROR

\( 0 \)

\( 1 \)

\( 4 \)

Let’s examine what happens when we evaluate \( s(-1) \). The rule \( s \) tells us to compute the number which, multiplied by itself, equals \( -1 \). The first possibility that comes to mind is \( -1 \), but \( -1 \times -1=1 \), not \( -1 \) as required. And \( 1 \times 1 \) is also \( 1 \). We’re stuck!

There is no real number that we can multiply by itself to produce \( -1 \). Take a moment to prove that multiplying two numbers with the same sign always produces a positive number.

Bringing this back to the rule \( s \), we don’t have any sensible way to evaluate it for negative inputs. That means we need to restrict its domain to the real numbers greater than or equal to \( 0 \).

Domain: \( x \ge 0 \)

Now I’m curious to see what this function looks like when graphed.

Figure 3.6 A graphical representation of \( s \)

Just as we saw with \( q \), it’s clear that s never produces a value below \( 0 \). Its range is also the set of positive real numbers and \( 0 \).

Range: \( s(x) \ge 0 \)

We just demonstrated that the set of real numbers isn’t closed under taking square roots. This problem will be our jumping off point for generating fractals in the next chapter.

Exercise 3.1   Find the domain and range of \( a(x)=2x+5 \)

Exercise 3.2   Find the domain and range of \( b(x)=(x-3)^{2}+4 \)

Exercise 3.3   Find the domain and range of \( c(x)=\sqrt{x+1}-7 \)

Inverses

In the first function we studied together, a coin flip resulted in one of two possible outcomes. If you told a friend that you had just won the coin flip game, it would be easy for them to work backwards and figure out what had happened; the coin must have landed on heads.

Working your way from an output back to an input is known as inverting a function. Some rules, like our coin flip game, are easy to invert.

\( H \mapsfrom Win \)

\( T \mapsfrom Lose \)

But other rules aren’t so clear. Let’s play another game of chance, this time with a pair of dice.

Figure 3.7 A six-sided die

Each face of a die has a value from \( 1 \) to \( 6 \) corresponding to the number of segments on the face. When you roll a die, its value is the face pointing up. You win the game if you roll a pair of dice with values that sum to an even number, and you lose if they sum to an odd number.

\( Even \mapsto Win \)

\( Odd \mapsto Lose \)

The game is afoot! How many ways are there for you to win?

We can represent each dice roll using an ordered pair of numbers like \( (1,3) \) or with a picture. I’ll keep the analysis manageable by considering dice rolls of \( (1,3) \) and \( (3,1) \) as the same roll.

Figure 3.8 The set of winning combinations of dice

There are so many ways for you to win! If you did win, and you shared the news of your win with a friend, they would have no idea whether you rolled \( (1,1) \) or \( (4,6) \) or some other combination of dice that sum to an even number. There is no clear path from an output back to a unique input.

\( (1,1) \mapsfrom Win \)

\( \vdots \)

\( (4,6) \mapsfrom Win \)

\( \vdots \)

\( (6,6) \mapsfrom Win \)

Unlike the coin flip, the dice role is not invertible. A function is only invertible if each element of the domain maps to a unique element of the range. This is also known as a bijective map.

Figure 3.9 A function and its inverse

The process of inverting a function depends on the representation you’ve chosen for it, but the idea is always the same: your inputs and outputs are reversed. Let’s take another look at the function \( g(x)=2x+5 \).

Table 3.6a A tabular representation of \( g \)

\( x \)

\( -1 \)

\( 0 \)

\( 1 \)

\( 2 \)

\( g(x) \)

\( 3 \)

\( 5 \)

\( 7 \)

\( 9 \)

The inputs in the left-hand column correspond to outputs in the right-hand column. Inverting this table simply requires swapping the two columns. The inverse of a function like \( g \) is typically written \( g^{-1} \) which is read “\( g \) inverse”.

Table 3.6b A tabular representation of \( g^{-1} \)

\( x \)

\( 3 \)

\( 5 \)

\( 7 \)

\( 9 \)

\( g^{-1}(x) \)

\( -1 \)

\( 0 \)

\( 1 \)

\( 2 \)

The visual result of inverting a function is striking for its symmetry. Inputs and outputs are swapped, so the corresponding \( x \) and \( y \)-coordinates are also swapped.

Figure 3.10 A graphical representation of \( g \) and \( g^{-1} \)

Inverting a function represented as an equation also involves swapping and reversing. Let’s continue with our old friend \( g(x)=2x+5 \). The mathematical operations that comprise equations follow a specific order, so it’s straightforward to run those operations in reverse.

I’m going to subvert standard notation a little bit and rewrite the function as follows:

\[ g=2x+5 \]

Now let’s swap the input \( x \) and output \( g \).

\[ x=2g+5 \]

That’s actually all there is to inverting a function represented as an equation. But now it’s not clear how an input \( x \) is supposed to produce an output; this rule isn’t easy to follow. We can clarify the process by solving the equation for \( g \).

\( x-5=2g+5-5 \)

\( x-5=2g \)

\( \frac{x-5}{2}=\frac{2g}{2} \)

\( \frac{x-5}{2}=g \)

The last step is to tidy up our notation. We just inverted \( g \), so this final equation represents \( g^{-1} \).

\( g^{-1}(x)=\frac{x-5}{2} \)

or

\( g^{-1}(x)=\frac{1}{2}x-2.5 \)

Inverting a function represented as an equation involves two steps. First, swap your input and output variables. Second, solve the equation for the output variable. The second step may require a fair amount of algebra, and it’s easy to get lost in a jungle of symbols. Keep in mind that inverting involves swapping and reversing.

Let’s take \( r(x)=(x-3)^{2}+4 \). First, rewrite for convenience.

\[ r=(x-3)^{2}+4 \]

Next, swap the input and output variables.

\[ x=(r-3)^{2}+4 \]

Following the order of operations, PEMDAS, that \( +4 \) would normally be the last operation applied. We’ll undo, or invert, that last operation first.

\( x-4=(r-3)^{2}+4-4 \)

\( x-4=(r-3)^{2} \)

And now we’ll continue in reverse, working our way in toward the output variable, \( r \), inverting operations along the way.

\[ \sqrt{x-4}=\sqrt{(r-3)^{2}} \]
\[ \pm \sqrt{x-4}=r-3 \]
\[ \pm \sqrt{x-4}+3=r-3+3 \]
\[ 3 \pm \sqrt{x-4}=r \]

Last but not least, let’s rewrite this equation using our notation for inverses.

\[ r^{-1}(x)= 3 \pm \sqrt{x-4} \]

That \( \pm \) operation reads “plus or minus”. If we evaluate \( r^{-1}(5) \), then the outputs are \( 2 \) and \( 4 \). Interesting. I wonder what that means in terms of the invertibility of \( r \). You’ll examine \( r \) and \( r^{-1} \) more closely in Exercise 3.5.

Functions are a one-way process and some functions can run in reverse just fine. Others cannot. Thinking back to our toaster metaphor, you can run bread through a toaster to make toast, but there isn’t an anti-toaster to revert the toast back to its pre-toasted existence.

Exercise 3.4   Determine whether the function \( h \), represented in tabular form below, is invertible.

\( x \)

\( -2 \)

\( -1 \)

\( 0 \)

\( 1 \)

\( 2 \)

\( h(x) \)

\( 4 \)

\( 1\)

\( 0 \)

\( 1 \)

\( 4 \)

Exercise 3.5   Determine whether the function \( r(x)=(x-3)^{2}+4 \) is invertible. If so, find the equation of its inverse \( r^{-1} \) and check your work against the example above. If not, explain why it isn’t invertible in plain English.

Exercise 3.6   Determine whether the function \( s(x)=\sqrt{x+1}-7 \) is invertible. If so, find the equation of its inverse \( s^{-1} \). If not, explain why it isn’t invertible in plain English.

3.2 JavaScript Functions

Functions are a big deal in mathematics–so big that their study led to the founding of computer science. It’s no coincidence that every major programming language has a way to define mathematical functions like the ones we just discussed. Let’s explore how functions work in JavaScript and begin defining our own.

Following Procedures

In code, functions bundle together a sequence of instructions, or statements, and give them a name. Organizing our code with functions enables us to work at a higher level of generality, or abstraction, than we could otherwise. Doing so can make it easier to plan our programs and to analyze them, whether to improve performance or to fix errors that inevitably crop up. They also enable programmers to share their solutions to problems so that others can build upon them.

You can define functions to do almost anything imaginable, from solving important math problems to creating delightful multimedia experiences. As tiny computers are increasingly embedded into a wider range of products, the ability to control computers is approaching the sort of witchcraft and wizardry normally reserved for fantasy novels.

The sketches you’ve written so far all defined two functions: setup() and draw(). And nearly all of the code you’ve written has been part of the body of one of these two functions.

The function keyword begins a function header, which is the first line of code in a function’s definition. p5 looks for your definitions of setup() and draw() to figure out how to proceed once you press the play button. Much goes on behind the scenes to present a simple set of variables and functions that comprise p5’s application programming interface (API). Here is the subset of the p5 API you’ve used thus far along with links to the documentation for each variable or function.

Environment

width
height

2D Primitives

line()
point()

Structure

setup()
draw()
noLoop()

Loading & Displaying

text()

We know that your statements are executed in order from top to bottom; this is the typical flow of execution in a computer program. When you call a function such as background() or point(), your program jumps over to the definition of the function and begins executing statements in the function’s body in order from top to bottom. Once finished, your program jumps back out of the program and picks up where it left off.

function setup() {
  createCanvas(200, 200)
}

function draw() {
  background(220) // jumped over to the definition of background
  // ... and we're back
  point(100, 100)
  // ...
}
// add sim

Let’s revisit our first simulation of a constellation and tidy it up a bit using functions. I think we can break the simulation into two core pieces: drawing outer space and drawing stars. The process of taking a problem and breaking into simpler pieces is known as decomposition.

Example 3.1 Decomposing a constellation

When you press the play button for this sketch, p5 calls your setup() function once, which calls createCanvas() once. Then, p5 calls your draw() function repeatedly, which calls drawSpace() and drawConstellation() each time it executes. They in turn call other drawing functions before they finish executing and it’s time to draw() the next frame to the canvas.

// add sim

It is much easier to keep track of what’s going on in your programs when you write code with descriptive names like drawConstellation(). We’ll use the JavaScript community’s convention of naming new functions and variables with multiple words separated by capitalization as opposed to spaces. In this naming convention, called “camelCase”, the first word is all lowercase and subsequent words begin with a capital letter.

Exercise 3.7   Rewrite one of your previous sketches and organize it using new functions you define. It may not be obvious at first how to delineate between different parts of your code, and there are probably multiple ways to do so elegantly.

Exercise 3.8   Rewrite one of your previous sketches and organize it using new functions you define. It may not be obvious at first how to delineate between different parts of your code, and there are probably multiple ways to do so elegantly.

Input/Output

The functions we’ve defined thus far do a nice job of bundling instructions together and organizing code, but they’re a bit limited. You can customize a function by adding a required input, or parameter, to its header. Let’s add a parameter to the drawConstellation() function.

function drawConstellation(starColor) {
  stroke(starColor)
  strokeWeight(3)
  point(20, 120)
  point(55, 130)
  point(90, 127)
  point(125, 115)
  point(150, 135)
  point(150, 85)
  point(175, 100)
}

Now, you can specify what color you want to draw your constellation by providing an argument when you call the function.

Arguments are assigned to parameters, and you can have more than one! For example, you could write a version of drawConstellation() that lets you specify the color and size of stars that are drawn.

function drawConstellation(starColor, starSize) {
  stroke(starColor)
  strokeWeight(starSize)
  point(20, 120)
  point(55, 130)
  point(90, 127)
  point(125, 115)
  point(150, 135)
  point(150, 85)
  point(175, 100)
}

Now you can customize the size and color of stars when you call drawConstellation().

Parameters are part of a function’s definition and only exist within a function’s body. The region of a program where a variable exists is called its scope. In our drawConstellation() example, the rest of the sketch has no idea what starSize means. Any variables you declare inside of a function are also invisible to the outside world; their scope is the function body. Let’s revisit one of our abstract paintings from the last chapter and tidy up the code a little.

Example 3.2 Decomposing an abstract painting

The variables x and y only exist inside the body of the paintCanvas() function. They are examples of local variables, meaning they only exist locally inside of the function where they were created.

By contrast, variables you declare at the top of your sketch are visible throughout your code and are known as global variables. The meteor sketch was our first encounter with variables in the global scope.

Example 3.3 Revisiting global variables

It’s convenient for us to define the variables x and y in the global scope since we’re referring to the same meteor when we draw it and when we update its position.

Global variables can be helpful when used sparingly, but they can be hard to keep track of as your programs grow in size. It’s often better practice to produce, or return, values on the fly and assign them to local variables. Let’s see this in action with the collision example from the last chapter.

Example 3.4 Introducing mathematical functions

The millis() function on line 6 is like a stopwatch built into p5; it returns the number of milliseconds that have passed since a sketch started its execution. We can use it to increase the value of x locally instead of globally.

The JavaScript functions s() and d() are equivalent to the mathematical functions \( s(x)=0.5x+50 \) and \( d(x)=-0.5x+150 \), respectively. When you call either function with an argument, they assign the argument to the parameter x and use x to compute a return value.

On line 7 of the previous example, it may seem strange to call s() with an argument x when `x. also happens to be the name of the function’s parameter Aligning variable and parameter names in this way can be helpful to clarify your intent, but it isn’t required.

Also on line 7 above, note that the value returned from s(x) is assigned the y parameter of drawShuttle(). Using the return value from one function as the argument for another is known as composition; we’ll discuss the idea in more detail in the next section. For now, let’s draw some more images.


John Henry Thompson

 

John Henry Thompson is an inventor and teacher. John Henry combines technology with art to help people express themselves and learn about the world. He led the development of several applications for artists along with Lingo, a programming language for building such applications.


Graphing with p5

Let’s continue drawing with functions by visualizing them as a set of points. Keep in mind that the \( y \)-axis points downward in computer graphics by default.

Example 3.5a Drawing a set of points

I can see the basic shape of the graph, but what gives with all of that empty space? Let’s draw the points a bit closer together by decreasing the value of our increment dx.

Example 3.5b Bonus points

Much better. But what if we changed the shape of the graph a bit by changing the value of a?

Example 3.5c Back to square one

Oof, the points on the graph are once again separated by empty space. We can continue adjusting the value of dx each time we draw a graph, but I think this is a nice opportunity to present another one of p5’s drawing capabilities: shapes. More precisely, we can connect a set of points, called vertices, using line segments to create a polygon. The following sketch draws the simplest polygon, a triangle.

Example 3.6a A triangle

This object isn’t quite a polygon, however, because the vertices don’t enclose a region of space. You can connect the final and initial vertices by adding the CLOSE argument to endShape().

Example 3.6b An enclosed triangle

p5 fills the interior of shapes with the color white by default. You can set the fill color for shapes by calling the fill() function like so.

Example 3.6c A colorful triangle

Back to our graph, let’s try to make our code more flexible by swapping out individual points for vertices connected by tiny line segments. That way, we are guaranteed to cover any gaps introduced by changing the graph’s shape. We aren’t concerned with the area enclosed by the graph just yet, so we won’t add the CLOSE argument to endGraph().

Example 3.7 Graphing with line segments

The variable width keeps track of the width of the drawing canvas; height keeps track of its height.

You could make the sketchbook function a little more like a graphing calculator by removing the default fill from the shape you just created. To do so, add a call to noFill() at the beginning of your graph() function.

function graph() {
  noFill()
  beginShape()
  // add vertices...
}

It’s worth taking a moment to present another loop syntax that can help to clarify our code. In the previous example we wrote the following:

let dx = 1
let x = 0
while (x < width) {
  vertex(x, f(x))
  x += dx
}

Let’s focus on the basic structure of the loop by removing the variable dx and the call to vertex():

let x = 0
while (x < width) {
  // do stuff
  x += 1
}

This steady, sequential increment has shown up repeatedly in our sketches. It turns out that the pattern can also be expressed using a for loop.

for (let x = 0; x < width; x += 1) {
  // do stuff
}

The loop header now completely describes the iteration. Start from \( 0 \), stop at \( 200 \), and count by \( 1 \)’s. When you know that your loop is meant to repeat a specific number of times, for loops can help to clarify that intent.

By way of contrast, while loops are especially useful when you aren’t sure how many iterations a process may need to complete.

while (stillLearning) {
  learn()
}

OK, let’s put for loops to work drawing graphs.

Example 3.8 Drawing a graph with a for loop

Exercise 3.9   Simulate a ball flying through the air along a parabolic path. Use a mathematical model like \( f(x)=a(x-h)^{2}+k \) to describe the ball’s motion. Try setting a to a small negative number and choose a point \( (h,k) \) as the peak of its flight. Decompose your simulation into a few functions including one that returns a value. The following code snippet may be a helpful template for describing your ball’s path.

function f(x) {
  let a = 0
  let h = 0
  let k = 0
  return a * (x - h) ** 2 + k
}

3.3 Combination and Composition

We’ve seen that mathematical functions are useful for modeling all sorts of phenomena, and that they are easy to express in a way that computers can execute. In this section, we’ll use financial modeling to explore a few ways that functions can work together.

Combination

Adding Functions

Given a pair of functions, it’s simple to combine their outputs using an arithmetic operation. Let’s say you invest in a pair of companies by buying shares of their stock and becoming a part-owner. The stock price of the first company, Acme Corporation, is currently \( \$ 100 \) and increases \( 7 \% \) annually, which we can express with the function \( A(t) = 100 \cdot 1.07^{t} \). In this equation, \( A(t) \) is the price of one share \( t \) years after you bought the share for \( \$ 100 \). The second company, Whitewhale Consolidated Interests, also has a stock price modeled using an exponential function, in this case \( W(t)=80 \cdot 1.09^{t} \), which gives the price of one share \( t \) years after you bought it for \( \$ 80 \).

The value of your set of stocks, called a portfolio, at some time in the future is the sum of the prices of your Acme and Whitewhale shares at that future time. We can express this relationship algebraically as \( P(t)=A(t)+W(t) \) where \( P(t) \) is the value of your portfolio. So, how much would your portfolio be worth in twenty years?

Individual Stocks

\( A(t) = 100 \cdot 1.07^{t} \)

\( W(t) = 80 \cdot 1.09^{t} \)

Portfolio

\( P(t) = A(t) + W(t) \)

Projection

\( P(20) = A(20) + W(20) \)

\( P(20) = ( 100 \cdot 1.07^{20} ) + ( 80 \cdot 1.09^{20}) \)

\( P(20) = 386.96 + 448.35 \)

\( P(20) = 835.51 \)

Given you originally purchased the stocks for \( \$ 180 \), you made a profit of \( \$ 835.31 - \$ 180 = \$ 655.31 \). Not too shabby!

Multiplying Functions

Let’s shift our focus to another example of combining functions, this time based on budgeting. I love working from cafés because I run into friends and meet new people while enjoying a change of scenery. Coffees add up, though, so I try to limit my café spending to \( \$ 24 \) each week. An economist might model the situation using an equation like the following.

\[ B = P \cdot Q \]

My café budget must equal the price of each cup of coffee, \( P \), multiplied by the quantity of those coffees, \( Q \), that I purchase. If we assume that each coffee costs \( \$ 4 \) after tax and tip, then we can easily solve for the number of coffees that I can order each week if I am to stay within my budget.

\( 24 = 4 \cdot Q \)

\( \frac{24}{4} = Q \)

\( 6 = Q \)

Now let’s imagine the café is steadily raising coffee prices to reflect the increased prices of all of the goods and services upon which it depends. This economic scenario is known as inflation. Each year, the overall price for each cup of coffee will increase by \( 2 \% \). Now, price is a function of time \( P(t) \). If I continued buying 6 cups of coffee each week, my budget would steadily increase as follows.

\( P(t) = 4 \cdot 1.02^{t} \)

\( Q(t) = 6 \)

\( B(t) = P(t) \cdot Q(t) \)

My weekly café budget, \( B(t) \), is now the product of two functions, \( P(t) \) and \( Q(t) \). Let’s check in to see how my budget is doing ten years from now.

\( B(10) = P(10) \cdot Q(10) \)

\( B(10) = (4 \cdot 1.02^{10}) \cdot 6 \)

\( B(10) = 29.25 \)

If I continued with business as usual, I would be more than \( 20 \% \) over my original budget! I may decide that this budget increase is fine or that I need to change my behavior in order to achieve my original financial goal. Either way, a little analysis can help me to plan.

Composition

Mathematical functions are input/output processes, and we can link them together like machines on an assembly line. Using the output of one function as the input to another is known as composition. Let’s build up this mathematical machinery with a minimal example before applying the concept in the exercises.

Figure 3.11 The composition of two functions \( f \) and \( g \)

// add figure

First, let’s define these functions as the following.

\( f(x) = x^{2}+5 \)

\( g(x)=3x-4 \)

Next, we’ll evaluate \( f(7) \) to produce the value \( f(7) = (7)^{2} + 5 = 54 \).

Finally, let’s take that \( 54 \) and pass it as the input to \( g \), producing \( g(54) = 3(54) - 4 = 158 \).

We mathematicians typically express compositions using the notation \( g(f(x)) \) or \( (g \circ f)(x) \), both read “\( g \) of \( f \) of \( x \)“. In the previous example, we evaluated the composition \( g(f(7)) \) in two steps. You could also simplify the composition algebraically to start. The output of the function \( f \) has become the input to the function \( g \), so you can replace \( g \)’s argument \( x \) with \( f(x) \).

\( f(x) = x^{2} + 5 \)

\( g(x) = 3x - 4 \)

\( g(f(x)) = 3 \cdot f(x) - 4 \)

Now, it’s easy to substitute \( f(x) \) as follows.

\( g(f(x)) = 3 (x^{2}+5) - 4 \)

\( g(f(x)) = 3x^{2} + 15 - 4 \)

\( g(f(x)) = 3x^{2} + 11 \)

Evaluating \( g(f(x)) \) is just a matter of substituting a number for \( x \) into this final expression and computing the value.

\( g(f(7)) = 3(7)^{2} + 11 \)

\( g(f(7)) = 3(49) + 11 \)

\( g(f(7)) = 158 \)

Composing functions in code maps neatly to the mathematical notation we just covered.

Example 3.5a Composing two functions

String Interlude

JavaScript’s data type for text is called a string. You can create strings by putting alphanumeric characters and even emoji between pairs of single '' or double "" quotes. Let’s display a string on the canvas by passing one as the first argument to p5’s text() function.

Example 3.5b Displaying strings

You could also combine, or concatenate, strings with other strings using the + operator.

Example 3.6a Concatenating strings with strings

Let’s end our brief introduction to strings by concatenating the left-hand side of our equation, a string, with the right-hand side, a floating point number.

Example 3.6b Concatenating strings with numbers

Now that we can visualize our algebraic work, let’s return to composition. Composition, as with any new mathematical technique, requires careful consideration to use correctly. Let’s compose the function \( g(x) = 3x-4 \) with the function \( h(x) = \frac{1}{x} \).

\( g(x) = 3x - 4 \)

\( h(x) = \frac{1}{x} \)

\( h(g(x)) = \frac{1}{g(x)} = \frac{1}{3x - 4} \)

Now, let’s try evaluating \( h(g(\frac{4}{3})) \).

\( h(g(\frac{4}{3})) = \frac{1}{3(\frac{4}{3}) - 4} \)

\( h(g(\frac{4}{3})) = \frac{1}{4 - 4} \)

\( h(g(\frac{4}{3})) = \frac{1}{0} \)

Uh oh. Division by zero is undefined, so the denominator of \( h(x) \) can’t be \( 0 \). Since \( g(\frac{4}{3}) = 0 \), we need to restrict the domain of the composition \( h(g(x)) \) to all real numbers except \( \frac{4}{3} \).

\[ D : \{ n \: | \: n \in \mathbb{R} \: and \: n \ne \frac{4}{3} \} \]

Exercise 3.10   Compose the functions \( a(x) = \sqrt{x} + 2 \) and \( b(x) = 7x + 11 \) both ways. That is, find the algebraic expressions for \( a(b(x)) \) and \( b(a(x)) \) and note their domain and range. Does the order of composition matter? Explain why or why not in plain English.

Inverses Revisited

Let’s conclude this section by considering a special case of composition: a function and its inverse. I’ll define the function \( q \) as \( q(x) = 3x + 5 \) and its inverse \( q^{-1}(x) = \frac{x-5}{3} \). Frist, let’s evaluate the composition \( q(q^{-1}(10)) \) by starting with the inner function.

\( q^{-1}(10) = \frac{10 - 5}{3} = \frac{5}{3} \)

\( q( \frac{5}{3}) = 3(\frac{5}{3}) + 5 = 5 + 5 = 10 \)

Interesting. Our input of \( 10 \) produced an output of \( 10 \). I wonder if that’s just a coincidence. Let’s try another input value, this time something negative.

\( q^{-1}(-4) = \frac{-4 - 5}{3} = \frac{-9}{3} = -3 \)

\( q(-3) = 3(-3) + 5 = -9 + 5 = -4 \)

OK, two for two. How about we see what happens when we compose these inverses algebraically?

\( q(q^{-1}(x)) = 3 \cdot q^{-1}(x) + 5 \)

\( q(q^{-1}(x)) = 3( \frac{x-5}{3}) + 5 \)

\( q(q^{-1}(x)) = x - 5 + 5 \)

\( q(q^{-1}(x)) = x \)

How about that? Composing a function and its inverse results in… nothing happening at all. The output value is just the input! If you think about this result, it has some intuitive appeal. Inverting a function requires applying each operation in reverse. If you run a process forward, then immediately reverse each step, you wind up right where you started.

OK, I think you’re ready to apply composition as part of a quick financial analysis.

Exercise 3.11   Imagine opening a small business that specializes in data visualization. Let’s say you charge \( \$ 30 \) per hour to help local organizations to communicate their work by making interactive charts and simulations.

Write a short program that calculates how much tax you would owe at the end of a year based on the number of hours that you work. For simplicity, assume all of your income is taxed at \( 15 \% \).

Hint: write two functions and compose them.

Exercise 3.12   In this exercise, you will write another program that more accurately reflects income taxes in the United States. Use the marginal tax rates in Table 3.7 to compute the amount of income tax you owe. This system is a little complex, so let’s walk through a slightly simplified example together. If you worked \( 2,000 \) hours and earned \( \$ 60,000 \), then your income would be taxed as follows.

\( \$ 9,950 \) at \( 10 \% \)

\( \$ 40,525 - \$ 9,951 = \$ 30,574 \) at \( 12 \% \)

\( \$ 60,000 - \$ 40,526 = \$ 19,474 \) at \( 22 \% \)

 

All together, you would owe \( \$ 9,950 \times 0.10 + \$ 30,574 \times 0.12 + \$19,474 \times 0.22 = \$ 8,948.16 \). From a taxable income of \( \$ 60,000 \), your effective tax rate would be the following:

\[ \frac{ \$ 8,948.16 }{ \$ 60,000 } = 0.149 = 14.9 \% \]

Hint: write two functions and use composition again, this time accounting for different tax brackets. If-statements and inequalities are your friends here.

Table 3.7 United States single filer tax rates for 2021 Courtesy irs.gov

Income ($)

Rate (%)

\( \$ 0 \) to \( \$ 9,950 \)

\( 10 \% \)

\( \$ 9,951 \) to \( \$ 40,525 \)

\( 12 \% \)

\( \$ 40,526 \) to \( \$ 86,375 \)

\( 22 \% \)

\( \$ 86,375 \) to \( \$ 164,925 \)

\( 24 \% \)

\( \$ 164,926 \) to \( \$ 209,425 \)

\( 32 \% \)

\( \$ 209,426 \) to \( \$ 523,600 \)

\( 35 \% \)

\( > \$ 523,600 \)

\( 37 \% \)

3.4 Vectors

Now that you have a handle on mapping numerical inputs to numerical outputs, I’d like to introduce a new mathematical object that has a starring role in applications from finance to physics to artificial intelligence: the vector. We’ll get acquainted with vectors by using them to simulate motion in this section.

p5.Vector

A vector is a powerful abstraction that takes different forms depending upon the application. We will explore several of these applications together in this book series. For starters, let’s focus on Euclidean vectors, which you can think of as arrows pointing in space.

Figure 3.12 A Euclidean vector \( \vec{AB} \)

The vector \( \vec{AB} \) starts from its initial point \( A \) and ends at its terminal point \( B \). Mathematicians use an arrow \( \vec{} \) to denote a vector quantity and distinguish it from numbers, points, or any other mathematical objects. We call \( A \) and \( B \) the vector’s tail and tip, respectively. You could draw this vector anywhere in two-dimensional space, but how about we pin the tail on the origin to start?

Figure 3.13 A vector from the origin

The vector \( \vec{OP} \) now tells us where the point \( P \) is relative to the origin \( O \). Let’s consider the specific case of a point \( P \) located at coordinates \( (150,50) \).

We could write the point’s position vector \( \vec{p} \) using either of the following notation schemes:

Row vector:   \( \vec{p} = (150,50) \)

Column vector:   \( \vec{p} = \begin{bmatrix} 150 \\ 50 \end{bmatrix} \)

I will typically write vector names with single, lowercase letters such as \( \vec{p} \) in this book for brevity. Row vectors are easier to fit on a line of text, so I will default to that notation. We will work with column vectors extensively in Chapter 8.

Figure \( \pi \) A position vector

\( \vec{p} \) is an arrow that extends \( 150 \) units to the right and \( 50 \) units down. Its horizontal extent, called its \( x \)-component, is \( 150 \) and its vertical extent, called its \( y \)-component, is \( 50 \). Taken together, we can see that vectors have a length, or magnitude, and point in some direction. We will put these intuitive ideas on firmer footing in the next chapter.

Our vector \( \vec{p} \)’s components \( (150,50) \) are just real numbers, also known as scalars. As such, each component supports all of the arithmetic we covered in Chapter 1. We’ll see these arithmetic abilities in action shortly.

Let’s begin our journey into programming with vectors by steadily enhancing our meteor sketch. Fortunately for us, p5 includes vectors out-of-the-box.

Example 3.7 Hello, p5.Vector

Line 8 of the sketch above creates a new p5.Vector object by calling the createVector() function. Objects are containers for data, such as our vector’s \( x \) and \( y \)-components, and functions intended to operate on that data. The two arguments passed to createVector() set the new vector’s \( x \) and \( y \)-components.

On line 12, we access the position vector’s \( x \) and \( y \)-components using . known as the dot operator. The dot operator is our entry point to accessing an object’s data and calling its functions.

Computer programmers use the Unified Modeling Language (UML) to visualize the design of software systems. The following UML diagram describes the blueprint for our position object, called its class.

Figure 3.15 UML class diagram of p5.Vector

// add figure

p5.Vector objects created using this blueprint contain additional data and functions, but we’ll stick with this subset of the class for now as we lay some foundations in vector arithmetic. It’s worth noting that functions bundled with a class are also known as methods. The distinction isn’t particularly meaningful for us, so I will continue calling them functions.

Vector Addition

All we need to get started with vectors is addition. Recall how we represented addition with natural numbers by snapping blocks together.

Figure 3.16 Block addition

Visually, we just placed one stack of blocks on top of the other and counted the number of blocks in the combined stack. Vector addition works similarly, but instead of stacking blocks, we place arrows next to each other “tip to tail”.

Figure 3.17 Vector addition

// add figure

Let’s walk through this operation together. First, imagine taking a stroll starting from \( \vec{a} \)’s tail and continuing until you reach its tip. Once there, continue from \( \vec{b} \)’s tail until you reach its tip. That’s all there is to vector addition!

Now that you’ve seen vector addition, let’s perform the operation by hand to get a feel for the mechanics. When we add two vectors, we add their corresponding components: \( x \)’s with \( x \)’s and \( y \)’s with \( y \)‘s.

\( \color{#785ef0}{\vec{a}} = (\color{#785ef0}{1}, \color{#785ef0}{2}) \)

\( \color{#fe6100}{\vec{b}} = (\color{#fe6100}{3}, \color{#fe6100}{1}) \)

\( \color{#785ef0}{\vec{a}} + \color{#fe6100}{\vec{b}} = (\color{#785ef0}{1} + \color{#fe6100}{3}, \color{#785ef0}{2} + \color{#fe6100}{1}) \)

\( \color{#785ef0}{\vec{a}} + \color{#fe6100}{\vec{b}} = (\color{#ffb000}{4}, \color{#ffb000}{3}) \)

\( \vec{a} + \vec{b} \) is the resultant vector produced by adding \( \vec{a} \) and \( \vec{b} \) and it describes a more direct path: given the same arrangement of \( \vec{a} \) and \( \vec{b} \), imagine flying in a straight line from \( \vec{a} \)’s tail to \( \vec{b} \)’s tip. Your initial and terminal points are the same, which means that the \( x \) and \( y \)-components are the same. Let’s give the resultant vector a name \( \vec{c} \) and state \( \vec{c} = \vec{a} + \vec{b} \).

Figure 3.18 Vector addition

Two vectors are equal when their corresponding \( x \) and \( y \)-components are equal. Using our previous result from vector addition, we can state the following:

\( \vec{a} + \vec{b} = (4,6) \)

\( \vec{c} = (4,6) \)

\( \vec{d} = (4,6) \)

\( \vec{a} + \vec{b} = \vec{c} = \vec{d} \)

Exercise 3.13   Prove whether or not vector addition is commutative. That is, does \( \vec{a} + \vec{b} = \vec{b} + \vec{a} \)? Use a concrete example to guide your intuition, then prove your result in general for a pair of vectors such as the following:

\( \vec{a} = (a_x, a_y) \)

\( \vec{b} = (b_x, b_y) \)

The subscripts \( _x \) and \( _y \) denote a vector’s \( x \) and \( y \)-components, respectively.

Exercise \( \pi \)   Prove whether or not vector addition is associative. In other words, does \( (\vec{a} + \vec{b}) + \vec{c} = \vec{a} + (\vec{b} + \vec{c}) \)?

Vector Motion

Now that you have vector addition under your belt, let’s apply this operation to set your meteor in motion. Velocity is a change in position, and velocity vectors enable us to specify how the \( x \) and \( y \)-components of a position vector should change. If you know where you are, and you know how you intend to move, then you can figure out where you will end up.

Figure 3.19 Vector motion

Let’s see position and velocity vectors working together to move our meteor.

Example 3.8 Meteor Motion

This sketch introduces a few new ideas. First, notice that I declared the variables position and velocity on lines \( 1 \) and \( 2 \), but I didn’t assign them values until lines \( 6 \) and \( 7 \). p5 can’t create p5.Vector objects until your sketch begins running, so any calls you make to createVector() must occur within the body of the setup() or draw() functions. We only needed to create the vectors once in this sketch, so I called createVector() in the body of setup().

Next, let’s have a look at line \( 13 \). I used the dot operator . to call the add() function which belongs to the position vector. Behind the scenes, calling position.add(velocity) updates position.x and position.y by adding velocity.x and velocity.y, respectively. You could achieve the same result manually:

position.x = position.x + velocity.x
position.y = position.y + velocity.y

Now, let’s compare this pair of statements to our call to the add() function:

position.add(velocity)

Much better. Objects enable us to write code that is often clearer, more concise, and more expressive than we could without them. We’ll play with p5.Oscillator objects in the next chapter, and you will define classes of your own in Chapter 5. In the meantime, let’s move forward to multiplication.

Scalar Multiplication

The next stop on our introduction to vector arithmetic is scalar multiplication. Given a vector \( \vec{v} = (1,2) \) you can multiply both components by a scalar value such as \( 5 \).

\( 5\vec{v} = (5 \cdot 1, 5 \cdot 2) \)

\( 5\vec{v} = (5, 10) \)

In code, you can multiply a vector by a scalar like so: velocity.mult(5).

Exercise 3.15   Use a system of linear inequalities to create regions of the canvas that scale the meteor’s velocity up or down. For example, consider the following code snippet:

if (position.x > 95 && position.x < 105) {
  velocity.mult(1.001)
}

Next up, how about we use vector multiplication to turn our meteor into a celestial ping-pong ball?

Example 3.9 Celestial ping-pong

When we call velocity.mult(-1) on line 14, the velocity vector’s components are both multiplied by \( -1 \), so the meteor begins traveling in the opposite direction.

Exercise 3.16   Grab something to write with and draw a picture of the initial velocity vector from Example 3.9 and label its components. Then, draw the vector produced after multiplying velocity by \( -1 \) and label the transformed vector.

Exercise 3.17   Given \( \vec{a} = (3,5) \) and \( \vec{b} = (1,2) \) draw the following:

First, draw a picture of \( \vec{a} + (-\vec{b}) \) and the resultant vector from this operation.

Next, draw \( \vec{a} \) and \( \vec{b} \) with their tails pinned to the origin. Draw the vector pointing from \( \vec{b} \)‘s tip to \( \vec{a} \)‘s tip. What do you notice about this vector and the resultant vector you drew a moment ago?

Paintball

OK, we’ve found our footing with vector arithmetic, so let’s have some fun using this new toolkit. Turning a meteor into a ping-pong ball got me thinking about arcade games, and I have a classic game in mind for the next section. Let’s work towards that goal by making our bounces a bit more flexible.

In Example 3.9, we reversed the ball’s velocity vector when the value of its position vector met a logical condition. My question is this: how can we bounce off of the walls of the canvas in a more realistic fashion? Let’s see what happens if we simply widen the left and right boundaries.

Example 3.10 Widening the walls

Now the ball flies past the top and bottom of the screen before bouncing. How about we set another condition to handle those boundaries?

Example 3.11 Raising the roof

This result is more realistic but the bounce still doesn’t seem very natural. When the ball hits the bottom edge of the canvas, we definitely need to reverse the \( y \)-component of its velocity. But why does it begin traveling to the left? When we detect a collision with the top or bottom edges of the canvas, let’s limit our scalar multiplication to the velocity vector’s \( y \)-component and leave its \( x \)-component unchanged.

Example 3.12 Isolating components

Now we’re almost in business!

Exercise 3.18   Edit Example 3.12 so that the ball bounces correctly off of the left and right edges of the canvas.

OK, our ball bounces around the canvas just fine now, so let’s play with that effect. First, I’m going to move the call to background() into the setup() function so that we can see the entire path traveled by the ball. Then I’ll set the stroke based on the ball’s position.

Example 3.13 Paintball

Exercise 3.19   Revisit your abstract painting from Chapter 2. Instead of looping over each pixel on the canvas, paint the image using the paintball from Example 3.13.

3.5 TRON

We’ll conclude this chapter with a look at Tron, a classic 1982 science fiction film that imagines a digital world shared by different lifeforms. The team behind Tron made many technical breakthroughs while working on the film, including Perlin noise, a computer-based technique used to generate textures that appear natural. Perlin noise stands out among the team’s accomplishments for its broad application in artwork generated with computers. In this section, we will apply Perlin noise to paint trails of light in our interpretation of a video game from the film.

map() and noise()

It’s time to journey into the Tron computer system. In the film, human Users are digitized and uploaded into a computer where they interact with Programs they have written. Within the vast digital space of the Tron system, the Grid is envisioned as a general-purpose research platform where Users and Programs interact. Let’s commence our research by writing our first Program.

System: A simulation environment in the Tron computer system where Programs ride vehicles called “light cycles”.

\( \mapsto \)Model: The Grid is an empty, two-dimensional surface that is \( 200 \) pixels wide and \( 200 \) pixels high. A light cycle is a point of light that travels in this plane.

Data: The first Program’s position and velocity vectors are p5.Vector objects. The initial values of these vectors are \( \vec{p} = (100,100) \) and \( \vec{v} = (1,2) \).

Algorithm: First, update position by adding velocity. Then check to see if the velocity vector needs to change due to a collision with the edge of the Grid. Finally, draw a point at the Program’s position.

Let’s extend the code in Example 3.13 to leave a unique mark as the trailblazing first Program travels about the Grid. Recall that mixing different amounts of red, green, and blue (RGB) light is similar to mixing different pigments of paint. We can map the Program’s position to its color by calling the map() function as follows.

Example \( \pi \) The First Program

Red and green light combine to make yellow light, and we can generate fiery colors by combining different RGB values. The steady color transition visible along our Program’s trail is called a color gradient. Let’s examine how we achieved this gradient effect.

Figure 3.20a Mapping position to red values

// add figure

Figure 3.20b Mapping position to green values

// add figure

On line 31 of Example \( \pi \), the map() function maps position.x from the interval \( [0,200] \), which covers all possible \( x \)-coordinates within the Grid, to the interval \( [150,255] \), which covers the upper portion of possible red values. Line 32 performs a similar task for green values.

Now that you’ve seen map() in action, we’ll use it as we explore another system for mapping numerical values to color values called the hue, saturation, and brightness (HSB) color system. If you’ve seen a color wheel before, then you are already familiar with the HSB color system.

Figure 3.21 HSB Color Courtesy p5.js

// add figure

Hue describes the color type (e.g., orange or blue) – \( [0, 360] \)

Saturation describes the vibrancy (e.g., full color or washed out) – \( [0, 100] \)

Brightness describes brightness… :) – \( [0, 100] \)

In p5, you can call colorMode(HSB) to use HSB values when you call background(), stroke(), and fill(). Use the tool below to explore how different values for hue, saturation, and brightness combine to make colors.

For starters, let’s change our Program’s hue as it travels from left to right.

Example 3.15 HSB Program

Exercise 3.20   Use the map() function to change the Program’s saturation and/or brightness as it moves. You can base your color mappings on position, velocity, or a combination of the two.

We now have multiple ways to generate color based on gameplay, but they’re all utterly predictable. The Program’s position maps to its color value using a few arithmetic operations, so the colors in any region of the Grid are always the same. Let’s mix things up a bit by introducing randomness.


Ken Perlin

 

Ken Perlin is a Professor of Computer Science at NYU. While working on the original Tron film, Ken invented an algorithm to generate realistic textures using random numbers. This breakthrough earned him an Academy Award for Technical Achievement in 1997.


p5 provides an implementation of Perlin noise that we can use by calling the noise() function. To start, let’s graph noise() to get a sense of the function’s behavior. The function’s documentation states that it returns values in the interval \( [0,1] \) which will be difficult to see. This is a good opportunity to use map() to scale those outputs and make better use of our canvas.

Example 3.16 Graphing Noise

The graph certainly looks noisy! Let’s analyze it for a moment to see what we can learn about the noise() function.

The first thing I notice is that the graph always appears as a static image. A quick inspection of lines 14 and 15 reveals that the graph’s \( y \)-values are continually recomputed with noise() while the sketch runs. The implication here isn’t immediately obvious, so let’s hone in on a specific case. The first point on the graph is always based on a call to noise(0). If noise(0) were to return different values while the sketch is running, then the graph would start moving.

Given the same argument, noise() must always return the same value while the sketch is running.

The next thing that I notice only occurs when I run Example 3.16 multiple times. The graph changes each time the sketch runs! Given the same set of input values, noise() returns different output values during each successive run.

In sum, noise() generates random sequences of numbers that are consistent while a sketch is running and vary in between successive runs.

These results may seem more like curiosities than tools to start, so let’s steadily reveal the full picture. Let’s draw a pair of noise graphs, our original graph from Example 3.16 and a version that is scaled horizontally.

Example 3.17 Tuning Noise

On line 22 of this sketch, we scaled all of the \( x \)-values by a factor of \( 0.01 \) before passing them to noise(). The new graph’s inputs to noise() are in the interval \( [0,2] \) instead of \( [0,200] \). Figure 3.22 visualizes this familiar scaling.

Figure 3.22 Scaling inputs to noise()

// add figure

OK, we scaled the inputs to noise(), so what effect does this transformation have on the outputs? The \( y \)-values in the second graph vary more slowly than those in the first graph because the inputs to noise() are packed more closely together. The closer the inputs, the closer the outputs. This transformation is equivalent to zooming into the original graph on the interval \( [0,2] \) which you can try out for yourself in Figure 3.23.

Figure 3.23 Zooming into the original graph

Scaling the inputs to noise() in this way enables us to tune the outputs to achieve different results. Let’s apply noise() to give our Program unique hues as it traverses the Grid.

Example 3.18 Noisy Program

Exercise 3.21   Use the map() and noise() functions to change the Program’s velocity from within your update() function.

Now that you’ve set your digital world in motion, let’s join in the fun as the first Users.

Keyboard Events

The Grid is up and running, but there’s a problem: we can’t play! We can incorporate User interaction by defining a special function called keyPressed(). Any code placed in the body of keyPressed() will execute when p5 detects that you’ve pressed a key on your keyboard.

function keyPressed() {
  // only runs when a key is pressed
}

p5 keeps track of the most recent key that was pressed in the variable key. Note that you will need to click on the drawing canvas before p5 can detect your key presses.

You can check for individual key presses by adding an if statement to the body of keyPressed() as in the following example:

Example 3.19 Hello, keys

Let’s use the same approach to set the User’s velocity vector so that it points directly up, left, down, or right when they press the 'w', 'a', 's', or 'd' key, respectively.

Figure 3.24 Mapping keys to velocities

// add figure

Example 3.20a Hello, User

The p5.Vector class provides a set() function that enables us to set a vector’s components with a single statement such as velocity.set(1, 0). Let’s use set() to clean up our keyPressed() function a bit.

Example 3.20b Setting vector components

The keys 'w', 'a', 's', or 'd' are often used to control velocity in computer games, but our keyboards do have those nice arrow keys just sitting there. You can detect presses on these special keys by swapping the key variable for keyCode and checking for a few key codes.

Figure 3.24 Mapping key codes to velocities

// add figure

Example 3.21 keyCode Control

OK, I think we’re ready to enter the next phase of our research: game design. Bouncing off of the walls is fun, but we now have all of the tools needed to build a real video game. Let’s create a minimal version of Light Cycles outlined below.

System: A simulation environment in the Tron computer system where Users ride vehicles called light cycles.

\( \mapsto \)Model: The Grid is an empty, two-dimensional surface that is \( 200 \) pixels wide and \( 200 \) pixels high. A light cycle is a point of light that travels up, left, down, or right.

Data: The User’s position and velocity vectors are p5.Vector objects. The initial values of these vectors are \( \vec{p} = (100,100) \) and \( \vec{v} = (1,0) \).

Algorithm: First, update velocity by checking for key presses. Then update position by adding velocity. After that, check to see if the User’s light cycle is destroyed due to a collision with the edge of the Grid. Finally, draw a point at the User’s position.

Instead of scaling velocity.x or velocity.y when the User collides with the wall, we will set the value of the boolean variable gameOver to true. When the game ends, we can pause the sketch by calling noLoop(). All together now:

Example 3.22 Game Over

The textAlign() function on line 19 makes it easier to center the GAME OVER message in the middle of the Grid. This is a big step forward for our game, but we still have a ways to go.

In the real Light Cycles game, Users can also destroy their vehicles if they collide with the trail of light they emit, known as a jetwall. We need a way to keep track of every position a User has visited on the Grid so that we can check for collisions. It’s time to introduce another object that can store all of these position values.

Arrays Interlude

An array is a list of data that provides functions for reading, writing, searching, sorting, and many other useful tasks. We will get better acquainted with these objects in the next chapter. For now, let’s focus on the following functionality:

Figure 3.25 UML class diagram of Array

// add figure

The data contained in an array are zero or more individual elements of any type. The push() function adds a new element e to the end of the array. Arrays aren’t picky about the data types they contain, so our UML class diagram labels the newly-added element e’s type as any.

let alphabet = ['α', 'β', 'γ', 'δ', 'ε']

You can also add elements to the end of an array by calling its push() function like so:

alphabet.push('ζ')

Let’s do a quick before-and-after comparison.

Example 3.23 Displaying string arrays

OK, we can create arrays and add elements to them. Now let’s turn to the task of working with individual array elements by rebuilding a familiar example. Recall that we can use for loops to repeat statements as in the following code snippet:

for (let x = 50; x < 200; x += 50) {
  let y = 0.5 * x + 50
  point(x, y)
}

If we instead had an array of \( x \)-coordinates, we could quickly iterate over its elements using a for…of statement as in the following code snippet:

let xcoords = [50, 100, 150]
for (let x of xcoords) {
  let y = 0.5 * x + 50
  point(x, y) 
}

This new loop syntax starts from the beginning of the xcoords array and assigns each element to the variable x one at a time. Let’s see for…of in action alongside a traditional for loop.

Example 3.24 Constellation generator

Exercise 3.22   Revisit your meteor sketch from Chapter 1 and turn it into a meteor shower using vectors, noise, and arrays.

Exercise 3.23   Remix Example 3.17 to graph the outputs of noise(noiseScale * x) and noise(noiseScale * (x + 100)). What do you notice about the graphs?

Light Cycles \( \alpha \)

Now that we can store p5.Vector objects in an array, we can keep track of a User’s jetwall as they roam the Grid. The most recent iteration of our Light Cycles game is missing three key components that we can incorporate quickly.

First, we need an empty array to keep track of each position the User visits.

let jetWall = []

Next, we need to add each position to the jetWall array. The position variable is constantly changing, so we’ll call its copy() function to take a snapshot.

let gridCell = position.copy()
jetWall.push(gridCell)

And finally, we need to iterate over the jetWall array to determine whether or not the User has collided with a previous position. We can call the equals() function to compare position to each gridCell in the jetWall.

for (let gridCell of jetWall) {
  if (position.equals(gridCell)) {
    gameOver = true
  }
}

OK, let’s integrate all of these components into the next iteration of the game.

System: A simulation environment in the Tron computer system where Users ride vehicles called light cycles.

\( \mapsto \)Model: The Grid is an empty, two-dimensional surface that is \( 200 \) pixels wide and \( 200 \) pixels high. A light cycle is a point of light that travels up, left, down, or right.

Data: The User’s position and velocity vectors are p5.Vector objects. The initial values of these vectors are \( \vec{p} = (100,100) \) and \( \vec{v} = (1,0) \), respectively.

Algorithm: First, update velocity by checking for key presses. Then update position by adding velocity. After that, check to see if the User’s light cycle is destroyed due to a collision with the edge of the Grid or a jetwall. Finally, draw a point at the User’s position.

Example 3.25 Jetwalls

Exercise 3.23   Display the User’s score at the end of the game. You can access the length of an array using the dot operator as in jetWall.length.

Exercise 3.24   Add a second User and display the combined score at the end of each game.

It’s been quite a ride, but you now have a solid foundation with functions, the building blocks that enable us to design, construct, and reason about complex systems piece by piece. The functions we’ll meet in the next chapter are guaranteed to set your world spinning.



Tron, Light Cycles, and other terms referenced from the film are © Walt Disney Studios. Their use in this chapter is educational and thus considered fair use.


Reference