JavaScript tutorial: Using repeatable randomness with P5.js

With repeatable randomness, we can take advantage of happy accidents when generating art—and preserve the results

JavaScript tutorial: Using repeatable randomness with P5.js
Thinkstock

In the last few weeks, we’ve introduced P5.js and used it to generate a textured paper background and a relatively underwhelming (but fun!) representation of watercolor. In this post, we’ll discuss repeatable randomness and why you may want to use it (hint: repeatability!). Then we’ll dive in and update our code to draw on repeatable randomness to help us isolate and preserve the best visual effects we generate.

With the code we've written so far, our watercolor image changes, sometimes dramatically, every time the browser evaluates our JavaScript. This was by design. We used computer-generated randomness as a tool to create a variety of visual effects and further our artistic endeavor. These results are not all equally pleasing from a visual standpoint, though, and some may get us closer to the art we imagined than others.

Let’s refresh our browser, watching combinations of randomness that will likely never be produced again go by, until we generate an image that we find very appealing. Something about the positions and colors of our watercolor spots feels right, but perhaps we’d like to make those spots smaller and more opaque. If we change the values of the constants that define the range of the size of the spots or their opacity, however, we’ll have to refresh the page—and we’ll lose that perfect position and size combination.

Ideally, we’d be able to flip through our random results and, when we see something we really like, freeze all of those random values. Then we could modify other elements of the image to see how they’d turn out given our frozen set of random values. The way we’ll accomplish this is by using a seeded random number generator.

A seeded random number generator will take a “seed” — which is just some sort of random string, number, etc. — and return a sequence of numbers generated from that seed. Given the same seed, the random number generator will return the same sequence of numbers. Different seed, different numbers. By using a seed, we get a random result that is reproducible. We gain repeatable randomness.

We also take on a bit of a burden, by forcing ourselves to generate a seed to pass into our random number generator. The tradeoff is worth it, though, because now we have the ability to freeze all of our random values in place while we play with other variables. We can save the seeds of our favorite images and recreate them at any point in time.

repeatable randomness IDG

Notice that when we use the same seed, we get the same sequence of random numbers. Different seeds produce different sequences of random numbers.

Unfortunately, JavaScript’s Math.random() does not support seeds, so we’ll instead rely on a popular library called seedrandom, which provides a seedable random number generator. The subset of the API of seedrandom that we’ll use is really straightforward (the example above uses that API).

In essence we’ll use the seedrandom() function to create a random number generator function that we’ll call whenever we need a new random number. To streamline the process, I created an immediately executing anonymous function that optionally takes a value for a random seed. If a seed isn’t passed in, we’ll generate 12 random numbers, concatenate them, and use them as a seed. Every seed used will be logged out to the console, so if you like what you see, simply copy the seed from the console and pass it into the function.

const STARTER_SEED = undefined;
const random = ((seed) => {
  if (seed) {
    console.log(`Using user-defined seed: ${seed}`);
    return seedrandom(seed);
  }
  let seeds = [];
  let rnd = seedrandom();
  for(let i = 0; i < 12; i++) {
    seeds.push(rnd());
  }
  console.log(`Using seed: ${seeds.join('')}`);
  return seedrandom(seeds.join(''));
})(STARTER_SEED);
// call random() to get a random number

When you find a seed you like, simply set it to the value of STARTER_SEED. Then calls to random() will return the same numbers in the same order. This means that you can modify any part of your code that doesn’t change the number or sequence of calls to random() and still expect to see many of the same elements in the resulting image. Below is a progression where I found a seed I liked, then tweaked some of the ranges of randomness using the same seed:

p5 repeatable random experiments IDG

Top: the original starting point, at left, and adjustments to the circle opacity and outer stroke, right. Bottom: adjustments to the upper limit of the size of circles, at left, and halving both the x and y range of the circles and the initial radius value, right.

As you can see, repeatable randomness gives us the benefits of randomness that computers provide without giving up full artistic control of the process. As always, the code here is available on GitHub. Do you have any thoughts, questions, or favorite seeds? Post them here in the comments or on Twitter: @freethejazz.

Copyright © 2019 IDG Communications, Inc.