Create the classic Snake game - React II
Tags: react, typescript, gamedev
Published: 2020-11-03 12:21:40

---
title: Create the classic Snake game - React II
published: true
date: 2020-10-14 00:00:00 UTC
tags: react, typescript, gamedev
canonical_url: https://www.mariokandut.com/create-classic-snake-game-in-react-with-typescript-part-2/
---

_This article was originally published on [mariokandut.com](https://www.mariokandut.com/create-classic-snake-game-in-react-with-typescript-part-2/)._

This is the second part of the tutorial [Create the classic Snake game - React](https://www.mariokandut.com/create-classic-snake-game-in-react-with-typescript-part-1/). The first part can be found on [dev.to](https://dev.to/iepur1lla/create-the-classic-snake-game-react-i-518b).

If you have followed the first part of the tutorial, you should have:

- Moving, controllable snake
- Border Collision detection
- Random generated apples
- Apple Collision Detection (snake eats apple)
- Snake grows when the apple has been eaten

Let's continue with the missing parts and features.

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#table-of-contents---part-2)Table of Contents - Part 2

1. [Fix collision detection for opposite direction keys](#1-fix-collision-detection-for-opposite-direction-keys)
2. [Styling Updates and Autofocus](#2-styling-updates-and-autofocus)
3. [Game Points](#3-game-points)
4. [Game End](#4-game-end)

### [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#1-fix-collision-detection-for-opposite-direction-keys)1. Fix collision detection for opposite direction keys

Currently, the collision detection return true if we press the opposite key of the current direction the snake is moving. For example: The key `ArrowRight` is pressed, so the snake moves right, and then the key `ArrowLeft` is pressed. This would trigger a collision, which is a wrong behaviour. We have to fix this.

An easy way to fix this, is to filter out keys which are in the opposite direction. Since we have a state for direction and coordinates for arrow keys, we can simply sum up the current direction and the arrow direction.

The sum of x-coordinates for ArrowLeft and ArrowRight equal 0 and return a falsy value, hence this can be filtered.

```
  ArrowLeft: { x: -1, y: 0 },
  ArrowRight: { x: 1, y: 0 },
```

Update the moveSnake with the following code:

```
const moveSnake = (event: React.KeyboardEvent) => {
  const { key } = event;
  // Check if key is arrow key
  if (
    key === 'ArrowUp' ||
    key === 'ArrowDown' ||
    key === 'ArrowRight' ||
    key === 'ArrowLeft'
  ) {
    // disable backwards key, this means no collision when going right, and then pressing ArrowLeft
    if (
      direction.x + directions[key].x &&
      direction.y + directions[key].y
    ) {
      setDirection(directions[key]);
    }
  }
};
```

### [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#2-styling-updates-and-autofocus)2. Styling Updates and Autofocus

The styling of the game needs some improvement, and we have to add an overlay, if we lost the game, and autofocus. The styling will be made in the `App.css`, there are plenty of other ways to do styling in a React application. What styling method do you prefer? Leave a comment.

The game wrapper should be autofocussed, after the start button is clicked. We have access to the `focus()` method, when we use the `useRef`hook.

Add the wrapperRef and a state for isPlaying:

```
// add wrapper ref and isPlaying flag for showing start button
const wrapperRef = useRef<HTMLDivElement>(null);
const [isPlaying, setIsPlaying] = useState<boolean>(false);
```

Now we have to update the startGame and endGame function:

```
// update startGame
const startGame = () => {
    setIsPlaying(true);
    setSnake(SNAKE_START);
    setApple(APPLE_START);
    setDirection(DIRECTION_START);
    setSpeed(SPEED);
    setGameOver(false);
    wrapperRef.current?.focus();
  };

// update endGame
const endGame = () => {
    setIsPlaying(false);
    setSpeed(null);
    setGameOver(true);
  };
```

Now we update the wrapper with some classNames and some condition for an overlay and the reference.

```
// Update div with classes and flag for showing buttons, conditional styles
return (
  <div className="wrapper">
    <div
      ref={wrapperRef}
      className="canvas"
      role="button"
      tabIndex={0}
      onKeyDown={(event: React.KeyboardEvent) => moveSnake(event)}
    >
      <canvas
        style={
          gameOver
            ? { border: '1px solid black', opacity: 0.5 }
            : { border: '1px solid black' }
        }
        ref={canvasRef}
        width={CANVAS_SIZE.x}
        height={CANVAS_SIZE.y}
      />
      {gameOver && <div className="game-over">Game Over</div>}
      {!isPlaying && (
        <button className="start" onClick={startGame}>
          Start Game
        </button>
      )}
    </div>
  </div>
);
```

Now we can update our styling.

```
.wrapper {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  height: 100vh;
}
.canvas {
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgb(151, 216, 148);
  position: relative;
}

.start {
  font-size: 1rem;
  position: absolute;
  border: 1px solid black;
  background: none;
  border-radius: 1rem;
  padding: 1rem;
  outline: none;
}

.start:hover {
  border: none;
  background: white;
}

.game-over {
  position: absolute;
  font-size: 5rem;
  margin-bottom: 10rem;
}
```

The fillStyle should be updated as well from `red` and `green` to `#1C1B17`, so we have this retro feeling/styling of the game.

We have now a working and styled version of the Classic Snake Game. Well, done. 😎

What's next?

- Adding points
- Game End

### [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#3-game-points)3. Game Points

Add state for points:

```
const [points, setPoints] = useState<number>(0);
```

Add setPoints to startGame to reset score:

```
setPoints(0);
```

Increase points if apple is eaten, add this to `checkAppleCollision`:

```
setPoints(points + 1);
```

Add points to game wrapper:

```
<p className="points">{points}</p>
```

Add some styling for the points:

```
.points {
  position: absolute;
  bottom: 0;
  right: 1rem;
  font-size: 2rem;
}
```

### [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#4-game-end)4. Game End

We have to define a condition, when somebody has finished the game, which is unlikely, though to be feature-complete. The game end, besides a collision, would be the reaching of the maximum of available points. With the current scaling, the calculation is 40x40 = 1600points.

So we just add a condition to check if the maxPoints are reached and update the state and show some message.

We add the state to track if the game hasFinished

```
const [hasFinishedGame, setHasFinishedGame] = useState<boolean>(
  false,
);
```

We add some condition to show the hasFinished message.

```
{
  hasFinishedGame && <p className="finished-game">Congratulations</p>;
}

.finished-game {
  position: absolute;
  top: 60px;
  font-size: 5rem;
  color: red;
  text-decoration: underline;
}
```

We add a variable for maxPoints and import it into `App.tsx`:

```
export const maxPoints = 1600;
```

We add the check if maxPoints have been reached:

```
const checkAppleCollision = (newSnake: ICoords[]) => {
  if (newSnake[0].x === apple.x && newSnake[0].y === apple.y) {
    let newApple = createRandomApple();
    while (checkCollision(newApple, newSnake)) {
      newApple = createRandomApple();
    }
    setPoints(points + 1);
    if (points === maxPoints) {
      setHasFinishedGame(true);
      endGame();
    }
    setApple(newApple);
    return true;
  }
  return false;
};
```

In case hasFinishedGame has been set to true and we start a new game, the value has to be resetted.

```
const startGame = () => {
    setHasFinishedGame(false);
    setPoints(0);
    setIsPlaying(true);
    setSnake(snake_start);
    setApple(apple_start);
    setDirection(direction_start);
    setSpeed(initial_speed);
    setGameOver(false);
    wrapperRef.current?.focus();
  };
```

That's it. We can always come back and add more features, like sound effects or saving the score in localStorage or ...

Yay, ✨✨ **Congratulations** ✨✨. Well, done.

Your Game should look like this. ![React Snake](https://www.mariokandut.com/58f51b2ab942deee7cf86f51b1ff1fb9/react-snake.gif)

Thanks for reading and if you have any **questions** , use the **comment** function or **send me a message** **[@mariokandut](https://www.twitter.com/mariokandut)**.

References (and Big thanks):[Maksim](https://maksimivanov.com/posts/react-console-snake-game-tutorial/) and [Weibenfalk](https://www.youtube.com/watch?v=OrpJdVP-hO4).