Game of Life as a React Component using Canvas
Published: 2020-05-23 12:42:56

While redesigning my [portfolio site](https://www.benwinchester.dev)  I thought it would be cool to integrate the classic programming simulation, Game of Life by John Conway.  It turns out using `canvas` with React is a bit of a challenge for beginners, and required putting together multiple online resources I found.  Here's how to do it.

## Conway's Game of Life

The first step is pretty straight forward in regards to programming a canvas to display the Game of Life.  For this I followed [Adrian Henry's tutorial](https://www.youtube.com/watch?v=deXzu0Eregs).  I won't rehash the walkthrough here, but I encourage you to watch him and clone the final code from [here](https://github.com/HungryTurtleCode/gameoflife/blob/master/index.js).

This is what we will convert into a header element.
{% codepen https://codepen.io/bmw2621/pen/vYNPGBE %}

## Let's just drop it into the Component render method!

That isn't possible.

The VanillaJS code requires the canvas element to already be mounted on the DOM so it can be referenced in the first line of the code.  Whenever we want something to occur after the element has been mounted, we have to use the `useEffect` hook (when using functional components). So lets try placing the entire original script inside component's `useEffect` hook...

{% codepen https://codepen.io/bmw2621/pen/BaobjgL %}

## Remove logic from the `useEffect`

This turned out to be very challenging, because we will have to refactor our functions to take dependent variables.  Lets start with moving the `buildGrid()` function out of the component completely, and add the `COLS` and `ROWS` as dependent parameters.  Move the now `buildGrid(COLS,ROWS)` function above the `GameOfLife` component.

The same can be done with the `nextGen(grid)` function, but because it references elements on the DOM, it must remain in the component, so we can move it out of the `useEffect` hook, and add the requisite `COLS` and `ROWS` parameters like so: `nextGen(grid, COLS, ROWS)`

{% codepen https://codepen.io/bmw2621/pen/xxwBVEN %}

Noting has changed on the view yet but we have cleaned up out `useEffect` hook to make room for the next step: making the canvas and pixel resolution responsive.

## Make it fill the width of the screen, and make it responsive

For this section, my primary source was [Pete Corey's tutorial](http://www.petecorey.com/blog/2019/08/19/animating-a-canvas-with-react-hooks/).

First thing we need to introduce is the `useRef` hook to allow us to interact with the canvas. And we will create the `getPixelRatio` function above the component definition:

```
const getPixelRatio = context => {
    var backingStore =
    context.backingStorePixelRatio ||
    context.webkitBackingStorePixelRatio ||
    context.mozBackingStorePixelRatio ||
    context.msBackingStorePixelRatio ||
    context.oBackingStorePixelRatio ||
    context.backingStorePixelRatio ||
    1;

    return (window.devicePixelRatio || 1) / backingStore;
};
```

The `getPixelRatio` function will return the aspect ratio of the viewport to avoid stretching the pixels and help us keep square colonies in our Game of Life simulation.

Now we can replace
```
canvas.width = 800;
canvas.height = 800;

```
with
```
let ratio = getPixelRatio(ctx);
    let width = getComputedStyle(canvas)
        .getPropertyValue('width')
        .slice(0, -2);
    let height = getComputedStyle(canvas)
        .getPropertyValue('height')
        .slice(0, -2);

    canvas.width = width * ratio;
    canvas.height = height * ratio;
    canvas.style.width = `100%`;
    canvas.style.height = `100%`;
```

> Because of the restricted size of the embedded codepen, I changed the value of resolution to 1 to give a larger field of colonies.

{% codepen https://codepen.io/bmw2621/pen/RwWdaLG %}

## Slow it down!

If you are familiar with Conway's Game of Life, you're aware that a stable state is reach fairly quickly, so lets slow down the rate at which the animation refreshes so it lasts longer.  An additional benefit, is it slows down the rate in which images are flashing on the screen for accessibility purposes. To accomplish this, we will need to wrap the `requestAnimationFrame(update)` call inside the `update` function in a `setTimeout` function and give it a 2 second refresh rate, like so: `setTimeout(() => requestAnimationFram(update), 2000)`.

{% codepen https://codepen.io/bmw2621/pen/ZEbPWow %}

## Give it a little style

Now lets add a little color.  This must be done in the javascript where the canvas is being drawn.  I will add variables to hold the hex values for the `fieldColor` and `colonyColor` and change the "black" and "white" options, which are contingent on the grid values, to reference these variables.

Now I want to add a slant to the bottom on the header.  Typically, we would use CSS transform's skew function, but that would ruin the grid nature of the colonies.  So I applied a simple clip-path to the CSS.

{% codepen https://codepen.io/bmw2621/pen/NWGJNZB %}

You can see the full application on [my portfolio page](https://www.benwinchester.dev) or view [the repo](https://www.github.com/bmw2621/portfolio) on github.