Random Walker using React, p5.js and TypeScript

This article will go through on how to make a random walker using react, p5.js and typescript.

Random Walker using React, p5.js and TypeScript

Before we move on to the creation of random walker using react and p5 js, let's understand about random walk.

Random Walks:

Random walk is a path defined as a series of random steps. Imagine you are standing in the middle of a balance beam. For every few seconds, you flip a coin. If it is heads, you take a step forward, if it is tails, take a step backward. This is nothing but a random walk. See below table for e.g.

Flip 1 Flip 2 Result
Heads Heads Step forward
Heads Tails Step right
Tails Heads Step left
Tails Tails Step backward

Random walks can be used to model phenomena that occur in real world, from the movements of molecues in a gas to the behaviour of a gambler spending a day at the casino.

Setting up the Random Walker project:

We will use create-react-app template of react-boilerplate. Make sure you have Node.js installed in your system.

Here are the steps to bootstrap the react project.

  • Create CRA app with custom template. 

Open the node.js command prompt and hit the below command. We gave the project name as react-p5-random-walk. It will take a few minutes to set up the project.

npx create-react-app --template cra-template-rb react-p5-random-walk

Once the installation is completed you can move into directory react-p5-random-walk and start the app.

cd react-pt-random-walk
npm start
  • By default it will create a sample application. To clean it up, stop the running server by pressing Ctrl+C and issue the below command in terminal.
npm run cleanAndSetup

Once the set up is done, you will now have a good project structure to start with. Feel free to explore the project structure created with cra template. You will have redux state management, but we won't use it in this post. See package.json to see what all dependencies are installed with cra template.

As we will use p5.js, we need to install it. The package that we need is react-p5. Install it by using the below command on terminal/command prompt in your project.

npm install react-p5

This package let's us integrate our p5 sketches into React project. 

Random Walker folder structure:

Our react project contains src directory. If you dive inside, you will find app folder which further has components and containers folders. Let's not disturb them for now. So, create a folder called features inside app directory. Every app will have features, we create only random walker which is only a single feature, it is good to organize our app into features. So, inside features folder create a folder called random-walker. Our random-walker may have components and containers. So create the folders components and containers inside random-walker folder. I'm using below folder structure for this project (not every folder is included).

->| features/
---->|random-walker/
----->| components/
------->| RandomWalker/
----------| index.tsx
----->| containers/
------->| RandomWalkerPage/
----------| index.tsx
----->| model/
--------| Walker.ts

We are using Typescript for this project, so that's why the extensions of files are .tsx and .ts.

Creating Random Walker:

Ok, now our folder structure is ready, open index.tsx file from components/RandomWalker. Create Random Walker component as shown below. We use React functional based component.

// index.tsx (components/RandomWalker)
import React from 'react';
import Sketch from 'react-p5';
import p5Types from 'p5';

const RandomWalker: React.FC = () => {

  const setup = (p5: p5Types, canvasParentRef: Element) => {
    p5.createCanvas(window.innerWidth, window.innerHeight).parent(
      canvasParentRef,
    );
    p5.background(255);
  };

  const draw = (p5: p5Types) => {

  };

  return <Sketch setup={setup} draw={draw} />;
};

export default RandomWalker;

Every p5.js sketch will have a setup and draw functions, the setup() function runs only once, whereas the draw() function runs continuously. The setup() function takes p5Types and Element as parameters. p5Types will have all the methods that are needed for our sketch. We create canvas using createCanvas method of p5Types which we imported from p5. We have set the background for our canvas to white. Finally, we return the Sketch component with setup and draw as props.

Open index.tsx from containers/RandomWalkerPage. Create RandomWalkerPage component by writing the below code. This component simply renders our RandomWalker component.

// index.tsx (containers/RandomWalkerPage)
import React from 'react';
import RandomWalker from '../../components/RandomWalker';

const RandomWalkerPage: React.FC = () => {
  return (
    <div>
      <h2>Random Walker</h2>
      <RandomWalker />
    </div>
  );
};

export default RandomWalkerPage;

We have not yet created our actual Random Walker that walks randomly. So, open up Walker.ts file from model folder. Create an interface called IWalker inside it. In this interface, we will declare two functions called step() and display().

import p5Types from 'p5';

interface IWalker {
  step();
  display(p5: p5Types);
}

The above two functions are implemented by our Walker class. So, create Walker class below the interface as shown below.

// imports....

interface IWalker {
  // ...methods/functions
}

export class Walker implements IWalker {

	public step() {
	}

	public display(p5: p5Types) {
	}
}

The Walker only needs two pieces of data - a number for its x-location and one for its y-location.

export class Walker implements IWalker {
  x: number; // x-location
  y: number; // y-location

  //...
}

Every class must have a constructor, a special function that is called when the object is first created. So, here we will initialize the Walkers starting position as shown below.

After we create the starting position of our Walker, we want it to be displayed on screen. So, inside the display() funtion, we want it to display as a dot as shown below.

export class Walker implements IWalker {
  x: number; // x-location
  y: number; // y-location

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  public step() {
  }

  public display(p5: p5Types) {
    // to display as a dot
    p5.stroke(0);
    p5.point(this.x, this.y);
  }
}

Now we want our Walker to take a step. We can have nine possible steps in a window. But for simplicity, we will go with four steps: right, left, up and down. A step to the right can be simulated by incrementing x (x++); to the left by decrementing x (x--); forward by going down a pixel (y++); and backward by going up a pixel (y--). But how do we pick from these four choices? We can make use of random() function. Create a folder called utils in app directory. Inside utils, create a file named js-utils.ts and add the following code to it.

// js-utils.ts (app/utils)
export function randomIntFromInterval(min: number, max: number) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

Coming back to our Walker class, in step() function we will make use of the above randomIntFromInterval() to pick a random number between two given numbers. Based on the number generated we move in a different direction as shown below.

public step() {
  const choice: number = randomIntFromInterval(1, 4);

  if (choice === 1) {
    this.x++;
  } else if (choice === 2) {
    this.x--;
  } else if (choice === 3) {
    this.y++;
  } else {
    this.y--;
  }
}

So the final Walker class looks like this:

import { randomIntFromInterval } from '../../../utils/js-utils';
import p5Types from 'p5';

interface IWalker {
  step();
  display(p5: p5Types);
}

export class Walker implements IWalker {
  x: number; // x-location
  y: number; // y-location

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  public step() {
    const choice: number = randomIntFromInterval(1, 4);

    if (choice === 1) {
      this.x++;
    } else if (choice === 2) {
      this.x--;
    } else if (choice === 3) {
      this.y++;
    } else {
      this.y--;
    }
  }

  public display(p5: p5Types) {
    // to display as a dot
    p5.stroke(0);
    p5.point(this.x, this.y);
  }
}

Now, open index.tsx file from components/RandomWalker and create Walker object inside the setup() function. Give center of window as the starting position.

let walker;

const setup = (p5: p5Types, canvasParentRef: Element) => {
  p5.createCanvas(window.innerWidth, window.innerHeight).parent(
    canvasParentRef,
  );
  p5.background(255);
  // initialize the Walker object position at center of the window
  walker = new Walker(window.innerWidth / 2, window.innerHeight / 2);
};

Finally, during each cycle through draw(), we ask the Walker to take a step and and draw a dot.

const draw = (p5: p5Types) => {
  walker.step();
  walker.display(p5);
};

So the here is the complete code for RandomWalker component.

import React from 'react';
import Sketch from 'react-p5';
import p5Types from 'p5';
import { Walker } from '../../model/Walker';

const RandomWalker: React.FC = () => {
  let walker;

  const setup = (p5: p5Types, canvasParentRef: Element) => {
    p5.createCanvas(window.innerWidth, window.innerHeight).parent(
      canvasParentRef,
    );
    p5.background(255);
    // initialize the Walker object position at center of the window
    walker = new Walker(window.innerWidth / 2, window.innerHeight / 2);
  };

  const draw = (p5: p5Types) => {
    walker.step();
    walker.display(p5);
  };

  return <Sketch setup={setup} draw={draw} />;
};

export default RandomWalker;

Open index.tsx file from app/containers/HomePage and import our RandomWalkerPage component as shown below to render finally in our browser.

import React from 'react';
import { Helmet } from 'react-helmet-async';
import RandomWalkerPage from '../../features/random-walker/containers/RandomWalkerPage';

export function HomePage() {
  return (
    <>
      <Helmet>
        <title>Random Walk - React p5</title>
        <meta name="description" content="React p5" />
      </Helmet>
      <RandomWalkerPage />
    </>
  );
}

Start the app and open http://localhost:3000 to see the Random Walker. Since we only draw the background once in setup(), rather than clearing it continually each time through draw(), we see the trail of the random walk in our browser window.